@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.

Start of a config web interface.

Summary:
This is somewhat clowny, particularly in how it handles JSON encode/decode, but
I've commented why I did things the way I did. The goal is to store minified JSON
but show pretty-printed JSON where possible, to the user editing it.

Test Plan:
* Went to /config/ and saw a list of keys from the `default` config.
* Clicked on one of them, submitted the default value successfully.
* Changed the value to invalid JSON and got a decent error.
* Changed the value to valid JSON and checked the DB to confirm it saved.
* Confirmed the DB values were minified.
* Confirmed the user-facing values were pretty-printed where they could be.
* Confirmed that PHIDs were getting assigned properly and that isDeleted
properly defaulted to false/0.

Reviewers: epriestley

Reviewed By: epriestley

CC: aran, Korvin

Maniphest Tasks: T2246

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

authored by

Ricky Elrod and committed by
epriestley
a7746200 1e2dfb5b

+352
+12
resources/sql/patches/20121226.config.sql
··· 1 + CREATE TABLE {$NAMESPACE}_config.config_entry ( 2 + `id` INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 3 + `phid` VARCHAR(64) NOT NULL COLLATE utf8_bin, 4 + `namespace` VARCHAR(64) BINARY NOT NULL COLLATE utf8_bin, 5 + `configKey` VARCHAR(64) BINARY NOT NULL COLLATE utf8_bin, 6 + `value` LONGTEXT NOT NULL, 7 + `isDeleted` BOOL NOT NULL, 8 + `dateCreated` INT UNSIGNED NOT NULL, 9 + `dateModified` INT UNSIGNED NOT NULL, 10 + UNIQUE KEY `key_phid` (`phid`), 11 + UNIQUE KEY `key_name` (`namespace`, `configKey`) 12 + ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+12
src/__phutil_library_map__.php
··· 580 580 'PhabricatorApplicationAuth' => 'applications/auth/application/PhabricatorApplicationAuth.php', 581 581 'PhabricatorApplicationCalendar' => 'applications/calendar/application/PhabricatorApplicationCalendar.php', 582 582 'PhabricatorApplicationConduit' => 'applications/conduit/application/PhabricatorApplicationConduit.php', 583 + 'PhabricatorApplicationConfig' => 'applications/config/application/PhabricatorApplicationConfig.php', 583 584 'PhabricatorApplicationCountdown' => 'applications/countdown/application/PhabricatorApplicationCountdown.php', 584 585 'PhabricatorApplicationDaemons' => 'applications/daemon/application/PhabricatorApplicationDaemons.php', 585 586 'PhabricatorApplicationDifferential' => 'applications/differential/application/PhabricatorApplicationDifferential.php', ··· 679 680 'PhabricatorConduitLogController' => 'applications/conduit/controller/PhabricatorConduitLogController.php', 680 681 'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/PhabricatorConduitMethodCallLog.php', 681 682 'PhabricatorConduitTokenController' => 'applications/conduit/controller/PhabricatorConduitTokenController.php', 683 + 'PhabricatorConfigController' => 'applications/config/controller/PhabricatorConfigController.php', 682 684 'PhabricatorConfigDictionarySource' => 'infrastructure/env/PhabricatorConfigDictionarySource.php', 685 + 'PhabricatorConfigEditController' => 'applications/config/controller/PhabricatorConfigEditController.php', 686 + 'PhabricatorConfigEntry' => 'applications/config/storage/PhabricatorConfigEntry.php', 687 + 'PhabricatorConfigEntryDAO' => 'applications/config/storage/PhabricatorConfigEntryDAO.php', 683 688 'PhabricatorConfigFileSource' => 'infrastructure/env/PhabricatorConfigFileSource.php', 689 + 'PhabricatorConfigListController' => 'applications/config/controller/PhabricatorConfigListController.php', 684 690 'PhabricatorConfigProxySource' => 'infrastructure/env/PhabricatorConfigProxySource.php', 685 691 'PhabricatorConfigSource' => 'infrastructure/env/PhabricatorConfigSource.php', 686 692 'PhabricatorConfigStackSource' => 'infrastructure/env/PhabricatorConfigStackSource.php', ··· 1873 1879 'PhabricatorApplicationAuth' => 'PhabricatorApplication', 1874 1880 'PhabricatorApplicationCalendar' => 'PhabricatorApplication', 1875 1881 'PhabricatorApplicationConduit' => 'PhabricatorApplication', 1882 + 'PhabricatorApplicationConfig' => 'PhabricatorApplication', 1876 1883 'PhabricatorApplicationCountdown' => 'PhabricatorApplication', 1877 1884 'PhabricatorApplicationDaemons' => 'PhabricatorApplication', 1878 1885 'PhabricatorApplicationDifferential' => 'PhabricatorApplication', ··· 1986 1993 'PhabricatorConduitLogController' => 'PhabricatorConduitController', 1987 1994 'PhabricatorConduitMethodCallLog' => 'PhabricatorConduitDAO', 1988 1995 'PhabricatorConduitTokenController' => 'PhabricatorConduitController', 1996 + 'PhabricatorConfigController' => 'PhabricatorController', 1989 1997 'PhabricatorConfigDictionarySource' => 'PhabricatorConfigSource', 1998 + 'PhabricatorConfigEditController' => 'PhabricatorConfigController', 1999 + 'PhabricatorConfigEntry' => 'PhabricatorConfigEntryDAO', 2000 + 'PhabricatorConfigEntryDAO' => 'PhabricatorLiskDAO', 1990 2001 'PhabricatorConfigFileSource' => 'PhabricatorConfigProxySource', 2002 + 'PhabricatorConfigListController' => 'PhabricatorConfigController', 1991 2003 'PhabricatorConfigProxySource' => 'PhabricatorConfigSource', 1992 2004 'PhabricatorConfigStackSource' => 'PhabricatorConfigSource', 1993 2005 'PhabricatorContentSourceView' => 'AphrontView',
+34
src/applications/config/application/PhabricatorApplicationConfig.php
··· 1 + <?php 2 + 3 + final class PhabricatorApplicationConfig extends PhabricatorApplication { 4 + 5 + public function getBaseURI() { 6 + return '/config/'; 7 + } 8 + 9 + public function getIconName() { 10 + return 'config'; 11 + } 12 + 13 + public function getTitleGlyph() { 14 + return "\xE2\x98\xBA"; 15 + } 16 + 17 + public function getApplicationGroup() { 18 + return self::GROUP_ADMIN; 19 + } 20 + 21 + public function shouldAppearInLaunchView() { 22 + return false; 23 + } 24 + 25 + public function getRoutes() { 26 + return array( 27 + '/config/' => array( 28 + '' => 'PhabricatorConfigListController', 29 + 'edit/(?P<key>[\w\.\-]+)/' => 'PhabricatorConfigEditController', 30 + ), 31 + ); 32 + } 33 + 34 + }
+27
src/applications/config/controller/PhabricatorConfigController.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorConfigController extends PhabricatorController { 4 + 5 + public function shouldRequireAdmin() { 6 + return true; 7 + } 8 + 9 + public function buildSideNavView($filter = null, $for_app = false) { 10 + $user = $this->getRequest()->getUser(); 11 + 12 + $nav = new AphrontSideNavFilterView(); 13 + $nav->setBaseURI(new PhutilURI($this->getApplicationURI('filter/'))); 14 + 15 + return $nav; 16 + } 17 + 18 + public function buildApplicationMenu() { 19 + return $this->buildSideNavView(null, true)->getMenu(); 20 + } 21 + 22 + public function buildApplicationCrumbs() { 23 + $crumbs = parent::buildApplicationCrumbs(); 24 + return $crumbs; 25 + } 26 + 27 + }
+124
src/applications/config/controller/PhabricatorConfigEditController.php
··· 1 + <?php 2 + 3 + final class PhabricatorConfigEditController 4 + extends PhabricatorConfigController { 5 + 6 + private $key; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->key = idx($data, 'key'); 10 + } 11 + 12 + public function processRequest() { 13 + $request = $this->getRequest(); 14 + $user = $request->getUser(); 15 + 16 + $config = id(new PhabricatorConfigFileSource('default')) 17 + ->getAllKeys(); 18 + if (!$this->key || !array_key_exists($this->key, $config)) { 19 + return new Aphront404Response(); 20 + } 21 + 22 + // Check if the config key is already stored in the database. 23 + // Grab the value if it is. 24 + $value = null; 25 + $config_entry = id(new PhabricatorConfigEntry()) 26 + ->loadOneWhere( 27 + 'configKey = %s AND namespace=%s', 28 + $this->key, 29 + 'default'); 30 + if ($config_entry) { 31 + $value = $config_entry->getValue(); 32 + } else { 33 + $config_entry = id(new PhabricatorConfigEntry()) 34 + ->setConfigKey($this->key); 35 + } 36 + 37 + $e_value = null; 38 + $errors = array(); 39 + if ($request->isFormPost()) { 40 + $new_value = $request->getStr('value'); 41 + if (strlen($new_value)) { 42 + $json = json_decode($new_value, true); 43 + if ($json === null && strtolower($value) != 'null') { 44 + $e_value = 'Invalid'; 45 + $errors[] = 'The given value must be valid JSON. This means, among '. 46 + 'other things, that you must wrap strings in double-quotes.'; 47 + $value = $new_value; 48 + } else { 49 + $value = $json; 50 + } 51 + } else { 52 + // TODO: When we do Transactions, make this just set isDeleted = 1 53 + $config_entry->delete(); 54 + } 55 + 56 + $config_entry->setValue($value); 57 + $config_entry->setNamespace('default'); 58 + 59 + if (!$errors) { 60 + $config_entry->save(); 61 + return id(new AphrontRedirectResponse()) 62 + ->setURI($config_entry->getURI()); 63 + } 64 + } 65 + 66 + $form = new AphrontFormView(); 67 + $form->setFlexible(true); 68 + 69 + $error_view = null; 70 + if ($errors) { 71 + $error_view = id(new AphrontErrorView()) 72 + ->setTitle('You broke everything!') 73 + ->setErrors($errors); 74 + } else { 75 + // Check not only that it's an array, but that it's an "unnatural" array 76 + // meaning that the keys aren't 0 -> size_of_array. 77 + if (is_array($value) && 78 + array_keys($value) != range(0, count($value) - 1)) { 79 + $value = id(new PhutilJSON())->encodeFormatted($value); 80 + } else { 81 + $value = json_encode($value); 82 + } 83 + } 84 + 85 + $form 86 + ->setUser($user) 87 + ->appendChild( 88 + id(new AphrontFormTextAreaControl()) 89 + ->setLabel('JSON Value') 90 + ->setError($e_value) 91 + ->setValue($value) 92 + ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) 93 + ->setCustomClass('PhabricatorMonospaced') 94 + ->setName('value')) 95 + ->appendChild( 96 + id(new AphrontFormSubmitControl()) 97 + ->addCancelButton($config_entry->getURI()) 98 + ->setValue(pht('Save Config Entry'))); 99 + 100 + $title = pht('Edit %s', $this->key); 101 + $short = pht('Edit'); 102 + 103 + $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView()); 104 + $crumbs->addCrumb( 105 + id(new PhabricatorCrumbView()) 106 + ->setName($this->key) 107 + ->setHref('/config/edit/'.$this->key)); 108 + $crumbs->addCrumb( 109 + id(new PhabricatorCrumbView())->setName($short)); 110 + 111 + return $this->buildApplicationPage( 112 + array( 113 + $crumbs, 114 + id(new PhabricatorHeaderView())->setHeader($title), 115 + $error_view, 116 + $form, 117 + ), 118 + array( 119 + 'title' => $title, 120 + 'device' => true, 121 + )); 122 + } 123 + 124 + }
+62
src/applications/config/controller/PhabricatorConfigListController.php
··· 1 + <?php 2 + 3 + final class PhabricatorConfigListController 4 + extends PhabricatorConfigController { 5 + 6 + public function processRequest() { 7 + $request = $this->getRequest(); 8 + $user = $request->getUser(); 9 + 10 + $nav = $this->buildSideNavView(); 11 + 12 + $pager = new AphrontCursorPagerView(); 13 + $pager->readFromRequest($request); 14 + 15 + $config = new PhabricatorConfigFileSource('default'); 16 + $list = $this->buildConfigList(array_keys($config->getAllKeys())); 17 + $list->setPager($pager); 18 + $list->setNoDataString( 19 + 'No data. Something probably went wrong in reading the default config.'); 20 + 21 + $header = id(new PhabricatorHeaderView()) 22 + ->setHeader(pht('Configuration')); 23 + 24 + $nav->appendChild( 25 + array( 26 + $header, 27 + $list, 28 + )); 29 + 30 + $crumbs = $this 31 + ->buildApplicationCrumbs($nav) 32 + ->addCrumb( 33 + id(new PhabricatorCrumbView()) 34 + ->setName(pht('Configuration')) 35 + ->setHref($this->getApplicationURI('filter/'))); 36 + 37 + $nav->setCrumbs($crumbs); 38 + 39 + return $this->buildApplicationPage( 40 + $nav, 41 + array( 42 + 'title' => pht('Configuration'), 43 + 'device' => true, 44 + ) 45 + ); 46 + } 47 + 48 + private function buildConfigList(array $keys) { 49 + $list = new PhabricatorObjectItemListView(); 50 + 51 + foreach ($keys as $key) { 52 + $item = id(new PhabricatorObjectItemView()) 53 + ->setHeader($key) 54 + ->setHref('/config/edit/'.$key) 55 + ->setObject($key); 56 + $list->addItem($item); 57 + } 58 + 59 + return $list; 60 + } 61 + 62 + }
+32
src/applications/config/storage/PhabricatorConfigEntry.php
··· 1 + <?php 2 + 3 + final class PhabricatorConfigEntry extends PhabricatorConfigEntryDAO { 4 + 5 + protected $id; 6 + protected $phid; 7 + protected $namespace; 8 + protected $configKey; 9 + protected $value; 10 + 11 + // TODO: Remove this default when implementing Transactions. 12 + protected $isDeleted = 0; 13 + 14 + public function getURI() { 15 + return '/config/edit/'.$this->configKey; 16 + } 17 + 18 + public function getConfiguration() { 19 + return array( 20 + self::CONFIG_AUX_PHID => true, 21 + self::CONFIG_SERIALIZATION => array( 22 + 'value' => self::SERIALIZATION_JSON, 23 + ), 24 + ) + parent::getConfiguration(); 25 + } 26 + 27 + public function generatePHID() { 28 + return PhabricatorPHID::generateNewPHID( 29 + PhabricatorPHIDConstants::PHID_TYPE_CONF); 30 + } 31 + 32 + }
+9
src/applications/config/storage/PhabricatorConfigEntryDAO.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorConfigEntryDAO extends PhabricatorLiskDAO { 4 + 5 + public function getApplicationName() { 6 + return 'config'; 7 + } 8 + 9 + }
+1
src/applications/phid/PhabricatorPHIDConstants.php
··· 30 30 const PHID_TYPE_ANSW = 'ANSW'; 31 31 const PHID_TYPE_MOCK = 'MOCK'; 32 32 const PHID_TYPE_MCRO = 'MCRO'; 33 + const PHID_TYPE_CONF = 'CONF'; 33 34 34 35 const PHID_TYPE_XACT = 'XACT'; 35 36 const PHID_TYPE_XCMT = 'XCMT';
+31
src/applications/phid/handle/PhabricatorObjectHandleData.php
··· 77 77 $objects[$task->getPHID()] = $task; 78 78 } 79 79 break; 80 + case PhabricatorPHIDConstants::PHID_TYPE_CONF: 81 + $config_dao = new PhabricatorConfigEntry(); 82 + $entries = $config_dao->loadAllWhere( 83 + 'phid IN (%Ls)', 84 + $phids); 85 + foreach ($entries as $entry) { 86 + $objects[$entry->getPHID()] = $entry; 87 + } 88 + break; 80 89 case PhabricatorPHIDConstants::PHID_TYPE_DREV: 81 90 $revision_dao = new DifferentialRevision(); 82 91 $revisions = $revision_dao->loadAllWhere( ··· 362 371 $closed = PhabricatorObjectHandleStatus::STATUS_CLOSED; 363 372 $handle->setStatus($closed); 364 373 } 374 + } 375 + $handles[$phid] = $handle; 376 + } 377 + break; 378 + case PhabricatorPHIDConstants::PHID_TYPE_CONF: 379 + $object = new PhabricatorConfigEntry(); 380 + 381 + $entries = $object->loadAllWhere('phid in (%Ls)', $phids); 382 + $entries = mpull($entries, null, 'getPHID'); 383 + 384 + foreach ($phids as $phid) { 385 + $handle = new PhabricatorObjectHandle(); 386 + $handle->setPHID($phid); 387 + $handle->setType($type); 388 + if (empty($entries[$phid])) { 389 + $handle->setName('Unknown Config Entry'); 390 + } else { 391 + $entry = $entry[$phid]; 392 + $handle->setName($entry->getKey()); 393 + $handle->setURI('/config/edit/'.$entry->getKey()); 394 + $handle->setFullName($entry->getKey()); 395 + $handle->setComplete(true); 365 396 } 366 397 $handles[$phid] = $handle; 367 398 }
+8
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 1064 1064 'type' => 'sql', 1065 1065 'name' => $this->getPatchPath('20121220.generalcache.sql'), 1066 1066 ), 1067 + 'db.config' => array( 1068 + 'type' => 'db', 1069 + 'name' => 'config', 1070 + ), 1071 + '20121226.config.sql' => array( 1072 + 'type' => 'sql', 1073 + 'name' => $this->getPatchPath('20121226.config.sql'), 1074 + ), 1067 1075 ); 1068 1076 } 1069 1077