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

Make translation, timezone and pronoun into real settings

Summary:
Ref T4103. These are currently stored on the user, for historic/performance reasons.

Since I want administrators to be able to set defaults for translations and timezones at a minimum and there's no longer a meaningful performance penalty for moving them off the user record, turn them into real preferences and then nuke the columns.

Test Plan:
- Set settings to unusual values.
- Ran migrations.
- Verified my unusual settings survived.
- Created a new user.
- Edited all settings with old and new UIs.
- Reconciled client/server timezone disagreement.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T4103

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

+422 -66
+59
resources/sql/autopatches/20160601.user.02.copyprefs.php
··· 1 + <?php 2 + 3 + // Move timezone, translation and pronoun from the user object to preferences 4 + // so they can be defaulted and edited like other settings. 5 + 6 + $table = new PhabricatorUser(); 7 + $conn_w = $table->establishConnection('w'); 8 + $table_name = $table->getTableName(); 9 + $prefs_table = new PhabricatorUserPreferences(); 10 + 11 + foreach (new LiskRawMigrationIterator($conn_w, $table_name) as $row) { 12 + $phid = $row['phid']; 13 + 14 + $pref_row = queryfx_one( 15 + $conn_w, 16 + 'SELECT preferences FROM %T WHERE userPHID = %s', 17 + $prefs_table->getTableName(), 18 + $phid); 19 + 20 + if ($pref_row) { 21 + try { 22 + $prefs = phutil_json_decode($pref_row['preferences']); 23 + } catch (Exception $ex) { 24 + $prefs = array(); 25 + } 26 + } else { 27 + $prefs = array(); 28 + } 29 + 30 + $zone = $row['timezoneIdentifier']; 31 + if (strlen($zone)) { 32 + $prefs[PhabricatorTimezoneSetting::SETTINGKEY] = $zone; 33 + } 34 + 35 + $pronoun = $row['sex']; 36 + if (strlen($pronoun)) { 37 + $prefs[PhabricatorPronounSetting::SETTINGKEY] = $pronoun; 38 + } 39 + 40 + $translation = $row['translation']; 41 + if (strlen($translation)) { 42 + $prefs[PhabricatorTranslationSetting::SETTINGKEY] = $translation; 43 + } 44 + 45 + if ($prefs) { 46 + queryfx( 47 + $conn_w, 48 + 'INSERT INTO %T (phid, userPHID, preferences, dateModified, dateCreated) 49 + VALUES (%s, %s, %s, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()) 50 + ON DUPLICATE KEY UPDATE preferences = VALUES(preferences)', 51 + $prefs_table->getTableName(), 52 + $prefs_table->generatePHID(), 53 + $phid, 54 + phutil_json_encode($prefs)); 55 + } 56 + } 57 + 58 + $prefs_key = PhabricatorUserPreferencesCacheType::KEY_PREFERENCES; 59 + PhabricatorUserCache::clearCacheForAllUsers($prefs_key);
+2
resources/sql/autopatches/20160601.user.03.removetime.sql
··· 1 + ALTER TABLE {$NAMESPACE}_user.user 2 + DROP COLUMN timezoneIdentifier;
+2
resources/sql/autopatches/20160601.user.04.removetranslation.sql
··· 1 + ALTER TABLE {$NAMESPACE}_user.user 2 + DROP COLUMN translation;
+2
resources/sql/autopatches/20160601.user.05.removesex.sql
··· 1 + ALTER TABLE {$NAMESPACE}_user.user 2 + DROP COLUMN sex;
+8
src/__phutil_library_map__.php
··· 2837 2837 'PhabricatorOlderInlinesSetting' => 'applications/settings/setting/PhabricatorOlderInlinesSetting.php', 2838 2838 'PhabricatorOneTimeTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorOneTimeTriggerClock.php', 2839 2839 'PhabricatorOpcodeCacheSpec' => 'applications/cache/spec/PhabricatorOpcodeCacheSpec.php', 2840 + 'PhabricatorOptionGroupSetting' => 'applications/settings/setting/PhabricatorOptionGroupSetting.php', 2840 2841 'PhabricatorOwnerPathQuery' => 'applications/owners/query/PhabricatorOwnerPathQuery.php', 2841 2842 'PhabricatorOwnersApplication' => 'applications/owners/application/PhabricatorOwnersApplication.php', 2842 2843 'PhabricatorOwnersArchiveController' => 'applications/owners/controller/PhabricatorOwnersArchiveController.php', ··· 3168 3169 'PhabricatorProjectsSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsSearchEngineAttachment.php', 3169 3170 'PhabricatorProjectsSearchEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsSearchEngineExtension.php', 3170 3171 'PhabricatorProjectsWatchersSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsWatchersSearchEngineAttachment.php', 3172 + 'PhabricatorPronounSetting' => 'applications/settings/setting/PhabricatorPronounSetting.php', 3171 3173 'PhabricatorProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorProtocolAdapter.php', 3172 3174 'PhabricatorPygmentSetupCheck' => 'applications/config/check/PhabricatorPygmentSetupCheck.php', 3173 3175 'PhabricatorQuery' => 'infrastructure/query/PhabricatorQuery.php', ··· 3535 3537 'PhabricatorTimeFormatSetting' => 'applications/settings/setting/PhabricatorTimeFormatSetting.php', 3536 3538 'PhabricatorTimeGuard' => 'infrastructure/time/PhabricatorTimeGuard.php', 3537 3539 'PhabricatorTimeTestCase' => 'infrastructure/time/__tests__/PhabricatorTimeTestCase.php', 3540 + 'PhabricatorTimezoneSetting' => 'applications/settings/setting/PhabricatorTimezoneSetting.php', 3538 3541 'PhabricatorTimezoneSetupCheck' => 'applications/config/check/PhabricatorTimezoneSetupCheck.php', 3539 3542 'PhabricatorTitleGlyphsSetting' => 'applications/settings/setting/PhabricatorTitleGlyphsSetting.php', 3540 3543 'PhabricatorToken' => 'applications/tokens/storage/PhabricatorToken.php', ··· 3565 3568 'PhabricatorTransactionsDestructionEngineExtension' => 'applications/transactions/engineextension/PhabricatorTransactionsDestructionEngineExtension.php', 3566 3569 'PhabricatorTransactionsFulltextEngineExtension' => 'applications/transactions/engineextension/PhabricatorTransactionsFulltextEngineExtension.php', 3567 3570 'PhabricatorTransformedFile' => 'applications/files/storage/PhabricatorTransformedFile.php', 3571 + 'PhabricatorTranslationSetting' => 'applications/settings/setting/PhabricatorTranslationSetting.php', 3568 3572 'PhabricatorTranslationsConfigOptions' => 'applications/config/option/PhabricatorTranslationsConfigOptions.php', 3569 3573 'PhabricatorTriggerAction' => 'infrastructure/daemon/workers/action/PhabricatorTriggerAction.php', 3570 3574 'PhabricatorTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorTriggerClock.php', ··· 7455 7459 'PhabricatorOlderInlinesSetting' => 'PhabricatorSelectSetting', 7456 7460 'PhabricatorOneTimeTriggerClock' => 'PhabricatorTriggerClock', 7457 7461 'PhabricatorOpcodeCacheSpec' => 'PhabricatorCacheSpec', 7462 + 'PhabricatorOptionGroupSetting' => 'PhabricatorSetting', 7458 7463 'PhabricatorOwnerPathQuery' => 'Phobject', 7459 7464 'PhabricatorOwnersApplication' => 'PhabricatorApplication', 7460 7465 'PhabricatorOwnersArchiveController' => 'PhabricatorOwnersController', ··· 7858 7863 'PhabricatorProjectsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', 7859 7864 'PhabricatorProjectsSearchEngineExtension' => 'PhabricatorSearchEngineExtension', 7860 7865 'PhabricatorProjectsWatchersSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', 7866 + 'PhabricatorPronounSetting' => 'PhabricatorSelectSetting', 7861 7867 'PhabricatorProtocolAdapter' => 'Phobject', 7862 7868 'PhabricatorPygmentSetupCheck' => 'PhabricatorSetupCheck', 7863 7869 'PhabricatorQuery' => 'Phobject', ··· 8294 8300 'PhabricatorTimeFormatSetting' => 'PhabricatorSelectSetting', 8295 8301 'PhabricatorTimeGuard' => 'Phobject', 8296 8302 'PhabricatorTimeTestCase' => 'PhabricatorTestCase', 8303 + 'PhabricatorTimezoneSetting' => 'PhabricatorOptionGroupSetting', 8297 8304 'PhabricatorTimezoneSetupCheck' => 'PhabricatorSetupCheck', 8298 8305 'PhabricatorTitleGlyphsSetting' => 'PhabricatorSelectSetting', 8299 8306 'PhabricatorToken' => array( ··· 8329 8336 'PhabricatorTransactionsDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', 8330 8337 'PhabricatorTransactionsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', 8331 8338 'PhabricatorTransformedFile' => 'PhabricatorFileDAO', 8339 + 'PhabricatorTranslationSetting' => 'PhabricatorOptionGroupSetting', 8332 8340 'PhabricatorTranslationsConfigOptions' => 'PhabricatorApplicationConfigOptions', 8333 8341 'PhabricatorTriggerAction' => 'Phobject', 8334 8342 'PhabricatorTriggerClock' => 'Phobject',
+2 -2
src/applications/calendar/__tests__/CalendarTimeUtilTestCase.php
··· 4 4 5 5 public function testTimestampsAtMidnight() { 6 6 $u = new PhabricatorUser(); 7 - $u->setTimezoneIdentifier('America/Los_Angeles'); 7 + $u->overrideTimezoneIdentifier('America/Los_Angeles'); 8 8 $days = $this->getAllDays(); 9 9 foreach ($days as $day) { 10 10 $data = CalendarTimeUtil::getCalendarWidgetTimestamps( ··· 19 19 20 20 public function testTimestampsStartDay() { 21 21 $u = new PhabricatorUser(); 22 - $u->setTimezoneIdentifier('America/Los_Angeles'); 22 + $u->overrideTimezoneIdentifier('America/Los_Angeles'); 23 23 $days = $this->getAllDays(); 24 24 foreach ($days as $day) { 25 25 $data = CalendarTimeUtil::getTimestamps(
-27
src/applications/metamta/storage/PhabricatorMetaMTAMail.php
··· 164 164 return $this->getParam('herald-force-recipients', array()); 165 165 } 166 166 167 - public function getTranslation(array $objects) { 168 - $default_translation = PhabricatorEnv::getEnvConfig('translation.provider'); 169 - $return = null; 170 - $recipients = array_merge( 171 - idx($this->parameters, 'to', array()), 172 - idx($this->parameters, 'cc', array())); 173 - foreach (array_select_keys($objects, $recipients) as $object) { 174 - $translation = null; 175 - if ($object instanceof PhabricatorUser) { 176 - $translation = $object->getTranslation(); 177 - } 178 - if (!$translation) { 179 - $translation = $default_translation; 180 - } 181 - if ($return && $translation != $return) { 182 - return $default_translation; 183 - } 184 - $return = $translation; 185 - } 186 - 187 - if (!$return) { 188 - $return = $default_translation; 189 - } 190 - 191 - return $return; 192 - } 193 - 194 167 public function addPHIDHeaders($name, array $phids) { 195 168 $phids = array_unique($phids); 196 169 foreach ($phids as $phid) {
+56 -16
src/applications/people/storage/PhabricatorUser.php
··· 5 5 * @task image-cache Profile Image Cache 6 6 * @task factors Multi-Factor Authentication 7 7 * @task handles Managing Handles 8 + * @task settings Settings 8 9 * @task cache User Cache 9 10 */ 10 11 final class PhabricatorUser ··· 26 27 27 28 protected $userName; 28 29 protected $realName; 29 - protected $sex; 30 - protected $translation; 31 30 protected $passwordSalt; 32 31 protected $passwordHash; 33 32 protected $profileImagePHID; 34 33 protected $profileImageCache; 35 34 protected $availabilityCache; 36 35 protected $availabilityCacheTTL; 37 - protected $timezoneIdentifier = ''; 38 36 39 37 protected $consoleEnabled = 0; 40 38 protected $consoleVisible = 0; ··· 68 66 private $authorities = array(); 69 67 private $handlePool; 70 68 private $csrfSalt; 69 + private $timezoneOverride; 71 70 72 71 protected function readField($field) { 73 72 switch ($field) { 74 - case 'timezoneIdentifier': 75 - // If the user hasn't set one, guess the server's time. 76 - return nonempty( 77 - $this->timezoneIdentifier, 78 - date_default_timezone_get()); 79 73 // Make sure these return booleans. 80 74 case 'isAdmin': 81 75 return (bool)$this->isAdmin; ··· 191 185 self::CONFIG_COLUMN_SCHEMA => array( 192 186 'userName' => 'sort64', 193 187 'realName' => 'text128', 194 - 'sex' => 'text4?', 195 - 'translation' => 'text64?', 196 188 'passwordSalt' => 'text32?', 197 189 'passwordHash' => 'text128?', 198 190 'profileImagePHID' => 'phid?', ··· 204 196 'isMailingList' => 'bool', 205 197 'isDisabled' => 'bool', 206 198 'isAdmin' => 'bool', 207 - 'timezoneIdentifier' => 'text255', 208 199 'isEmailVerified' => 'uint32', 209 200 'isApproved' => 'uint32', 210 201 'accountSecret' => 'bytes64', ··· 261 252 return $this; 262 253 } 263 254 264 - // To satisfy PhutilPerson. 265 - public function getSex() { 266 - return $this->sex; 267 - } 268 - 269 255 public function getMonogram() { 270 256 return '@'.$this->getUsername(); 271 257 } ··· 490 476 '(isPrimary = 1)'); 491 477 } 492 478 479 + 480 + /* -( Settings )----------------------------------------------------------- */ 481 + 482 + 493 483 public function getUserSetting($key) { 494 484 $settings_key = PhabricatorUserPreferencesCacheType::KEY_PREFERENCES; 495 485 $settings = $this->requireCacheData($settings_key); ··· 506 496 return null; 507 497 } 508 498 499 + 500 + /** 501 + * Test if a given setting is set to a particular value. 502 + * 503 + * @param const Setting key. 504 + * @param wild Value to compare. 505 + * @return bool True if the setting has the specified value. 506 + * @task settings 507 + */ 509 508 public function compareUserSetting($key, $value) { 510 509 $actual = $this->getUserSetting($key); 511 510 return ($actual == $value); 511 + } 512 + 513 + public function getTranslation() { 514 + return $this->getUserSetting(PhabricatorTranslationSetting::SETTINGKEY); 515 + } 516 + 517 + public function getTimezoneIdentifier() { 518 + if ($this->timezoneOverride) { 519 + return $this->timezoneOverride; 520 + } 521 + 522 + return $this->getUserSetting(PhabricatorTimezoneSetting::SETTINGKEY); 523 + } 524 + 525 + 526 + /** 527 + * Override the user's timezone identifier. 528 + * 529 + * This is primarily useful for unit tests. 530 + * 531 + * @param string New timezone identifier. 532 + * @return this 533 + * @task settings 534 + */ 535 + public function overrideTimezoneIdentifier($identifier) { 536 + $this->timezoneOverride = $identifier; 537 + return $this; 538 + } 539 + 540 + public function getSex() { 541 + return $this->getUserSetting(PhabricatorPronounSetting::SETTINGKEY); 512 542 } 513 543 514 544 public function loadPreferences() { ··· 1537 1567 $this->usableCacheData[$key] = $usable_value; 1538 1568 1539 1569 return $usable_value; 1570 + } 1571 + 1572 + 1573 + /** 1574 + * @task cache 1575 + */ 1576 + public function clearCacheData($key) { 1577 + unset($this->rawCacheData[$key]); 1578 + unset($this->usableCacheData[$key]); 1579 + return $this; 1540 1580 } 1541 1581 1542 1582 }
+6 -5
src/applications/settings/controller/PhabricatorSettingsTimezoneController.php
··· 31 31 $timezone = $request->getStr('timezone'); 32 32 33 33 $pref_ignore = PhabricatorUserPreferences::PREFERENCE_IGNORE_OFFSET; 34 + $pref_timezone = PhabricatorTimezoneSetting::SETTINGKEY; 34 35 35 36 $preferences = $viewer->loadPreferences(); 36 37 ··· 52 53 if (isset($options[$timezone])) { 53 54 $preferences 54 55 ->setPreference($pref_ignore, null) 56 + ->setPreference($pref_timezone, $timezone) 55 57 ->save(); 56 58 57 - $viewer 58 - ->setTimezoneIdentifier($timezone) 59 - ->save(); 59 + $viewer->clearCacheData( 60 + PhabricatorUserPreferencesCacheType::KEY_PREFERENCES); 60 61 } 61 62 } 62 63 ··· 115 116 $offset = $offset / 60; 116 117 117 118 if ($offset >= 0) { 118 - return pht('GMT-%d', $offset); 119 + return pht('UTC-%d', $offset); 119 120 } else { 120 - return pht('GMT+%d', -$offset); 121 + return pht('UTC+%d', -$offset); 121 122 } 122 123 } 123 124
+6
src/applications/settings/editor/PhabricatorSettingsEditEngine.php
··· 78 78 $viewer = $this->getViewer(); 79 79 $settings = PhabricatorSetting::getAllEnabledSettings($viewer); 80 80 81 + foreach ($settings as $key => $setting) { 82 + $setting = clone $setting; 83 + $setting->setViewer($viewer); 84 + $settings[$key] = $setting; 85 + } 86 + 81 87 $fields = array(); 82 88 foreach ($settings as $setting) { 83 89 foreach ($setting->newCustomEditFields($object) as $field) {
+13 -5
src/applications/settings/panel/PhabricatorAccountSettingsPanel.php
··· 23 23 $user = $this->getUser(); 24 24 $username = $user->getUsername(); 25 25 26 + $preferences = $user->loadPreferences(); 27 + 26 28 $errors = array(); 27 29 if ($request->isFormPost()) { 28 30 $sex = $request->getStr('sex'); 29 31 $sexes = array(PhutilPerson::SEX_MALE, PhutilPerson::SEX_FEMALE); 30 32 if (in_array($sex, $sexes)) { 31 - $user->setSex($sex); 33 + $new_value = $sex; 32 34 } else { 33 - $user->setSex(null); 35 + $new_value = null; 34 36 } 35 37 36 - // Checked in runtime. 37 - $user->setTranslation($request->getStr('translation')); 38 + $preferences->setPreference( 39 + PhabricatorPronounSetting::SETTINGKEY, 40 + $new_value); 41 + 42 + $preferences->setPreference( 43 + PhabricatorTranslationSetting::SETTINGKEY, 44 + $request->getStr('translation')); 38 45 39 46 if (!$errors) { 40 - $user->save(); 47 + $preferences->save(); 48 + 41 49 return id(new AphrontRedirectResponse()) 42 50 ->setURI($this->getPanelURI('?saved=true')); 43 51 }
+4 -4
src/applications/settings/panel/PhabricatorDateTimeSettingsPanel.php
··· 18 18 $user = $request->getUser(); 19 19 $username = $user->getUsername(); 20 20 21 + $pref_timezone = PhabricatorTimezoneSetting::SETTINGKEY; 21 22 $pref_time = PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT; 22 23 $pref_date = PhabricatorUserPreferences::PREFERENCE_DATE_FORMAT; 23 24 $pref_week_start = PhabricatorUserPreferences::PREFERENCE_WEEK_START_DAY; ··· 27 28 $errors = array(); 28 29 if ($request->isFormPost()) { 29 30 $new_timezone = $request->getStr('timezone'); 30 - if (in_array($new_timezone, DateTimeZone::listIdentifiers(), true)) { 31 - $user->setTimezoneIdentifier($new_timezone); 32 - } else { 31 + if (!in_array($new_timezone, DateTimeZone::listIdentifiers(), true)) { 33 32 $errors[] = pht('The selected timezone is not a valid timezone.'); 34 33 } 35 34 36 35 $preferences 36 + ->setPreference($pref_timezone, $new_timezone) 37 37 ->setPreference( 38 38 $pref_time, 39 39 $request->getStr($pref_time)) ··· 47 47 48 48 if (!$errors) { 49 49 $preferences->save(); 50 - $user->save(); 50 + 51 51 return id(new AphrontRedirectResponse()) 52 52 ->setURI($this->getPanelURI('?saved=true')); 53 53 }
+73
src/applications/settings/setting/PhabricatorOptionGroupSetting.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorOptionGroupSetting 4 + extends PhabricatorSetting { 5 + 6 + abstract protected function getSelectOptionGroups(); 7 + 8 + final protected function getSelectOptionMap() { 9 + $groups = $this->getSelectOptionGroups(); 10 + 11 + $map = array(); 12 + foreach ($groups as $group) { 13 + $map += $group['options']; 14 + } 15 + 16 + return $map; 17 + } 18 + 19 + final protected function newCustomEditField($object) { 20 + $setting_key = $this->getSettingKey(); 21 + $default_value = $object->getDefaultValue($setting_key); 22 + 23 + $options = $this->getSelectOptionGroups(); 24 + 25 + $map = $this->getSelectOptionMap(); 26 + if (isset($map[$default_value])) { 27 + $default_label = pht('Default (%s)', $map[$default_value]); 28 + } else { 29 + $default_label = pht('Default (Unknown, "%s")', $default_value); 30 + } 31 + 32 + $head_key = head_key($options); 33 + $options[$head_key]['options'] = array( 34 + '' => $default_label, 35 + ) + $options[$head_key]['options']; 36 + 37 + $flat_options = array(); 38 + foreach ($options as $group) { 39 + $flat_options[$group['label']] = $group['options']; 40 + } 41 + 42 + return $this->newEditField($object, new PhabricatorSelectEditField()) 43 + ->setOptions($flat_options); 44 + } 45 + 46 + final public function validateTransactionValue($value) { 47 + if (!strlen($value)) { 48 + return; 49 + } 50 + 51 + $map = $this->getSelectOptionMap(); 52 + 53 + if (!isset($map[$value])) { 54 + throw new Exception( 55 + pht( 56 + 'Value "%s" is not valid for setting "%s": valid values are %s.', 57 + $value, 58 + $this->getSettingName(), 59 + implode(', ', array_keys($map)))); 60 + } 61 + 62 + return; 63 + } 64 + 65 + public function getTransactionNewValue($value) { 66 + if (!strlen($value)) { 67 + return null; 68 + } 69 + 70 + return (string)$value; 71 + } 72 + 73 + }
+35
src/applications/settings/setting/PhabricatorPronounSetting.php
··· 1 + <?php 2 + 3 + final class PhabricatorPronounSetting 4 + extends PhabricatorSelectSetting { 5 + 6 + const SETTINGKEY = 'pronoun'; 7 + 8 + public function getSettingName() { 9 + return pht('Pronoun'); 10 + } 11 + 12 + protected function getControlInstructions() { 13 + return pht('Choose the pronoun you prefer.'); 14 + } 15 + 16 + public function getSettingDefaultValue() { 17 + return PhutilPerson::SEX_UNKNOWN; 18 + } 19 + 20 + protected function getSelectOptions() { 21 + $viewer = $this->getViewer(); 22 + $username = $viewer->getUsername(); 23 + 24 + $label_unknown = pht('%s updated their profile', $username); 25 + $label_her = pht('%s updated her profile', $username); 26 + $label_his = pht('%s updated his profile', $username); 27 + 28 + return array( 29 + PhutilPerson::SEX_UNKNOWN => $label_unknown, 30 + PhutilPerson::SEX_MALE => $label_his, 31 + PhutilPerson::SEX_FEMALE => $label_her, 32 + ); 33 + } 34 + 35 + }
+5 -3
src/applications/settings/setting/PhabricatorSelectSetting.php
··· 17 17 $default_label = pht('Default (Unknown, "%s")', $default_value); 18 18 } 19 19 20 - $options = array( 21 - '' => $default_label, 22 - ) + $options; 20 + if (empty($options[''])) { 21 + $options = array( 22 + '' => $default_label, 23 + ) + $options; 24 + } 23 25 24 26 return $this->newEditField($object, new PhabricatorSelectEditField()) 25 27 ->setOptions($options);
+53
src/applications/settings/setting/PhabricatorTimezoneSetting.php
··· 1 + <?php 2 + 3 + final class PhabricatorTimezoneSetting 4 + extends PhabricatorOptionGroupSetting { 5 + 6 + const SETTINGKEY = 'timezone'; 7 + 8 + public function getSettingName() { 9 + return pht('Timezone'); 10 + } 11 + 12 + public function getSettingDefaultValue() { 13 + return date_default_timezone_get(); 14 + } 15 + 16 + protected function getSelectOptionGroups() { 17 + $timezones = DateTimeZone::listIdentifiers(); 18 + $now = new DateTime('@'.PhabricatorTime::getNow()); 19 + 20 + $groups = array(); 21 + foreach ($timezones as $timezone) { 22 + $zone = new DateTimeZone($timezone); 23 + $offset = -($zone->getOffset($now) / (60 * 60)); 24 + $groups[$offset][] = $timezone; 25 + } 26 + 27 + krsort($groups); 28 + 29 + $option_groups = array( 30 + array( 31 + 'label' => pht('Default'), 32 + 'options' => array(), 33 + ), 34 + ); 35 + 36 + foreach ($groups as $offset => $group) { 37 + if ($offset >= 0) { 38 + $label = pht('UTC-%d', $offset); 39 + } else { 40 + $label = pht('UTC+%d', -$offset); 41 + } 42 + 43 + sort($group); 44 + $option_groups[] = array( 45 + 'label' => $label, 46 + 'options' => array_fuse($group), 47 + ); 48 + } 49 + 50 + return $option_groups; 51 + } 52 + 53 + }
+92
src/applications/settings/setting/PhabricatorTranslationSetting.php
··· 1 + <?php 2 + 3 + final class PhabricatorTranslationSetting 4 + extends PhabricatorOptionGroupSetting { 5 + 6 + const SETTINGKEY = 'translation'; 7 + 8 + public function getSettingName() { 9 + return pht('Translation'); 10 + } 11 + 12 + public function getSettingDefaultValue() { 13 + return 'en_US'; 14 + } 15 + 16 + protected function getSelectOptionGroups() { 17 + $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); 18 + $locales = PhutilLocale::loadAllLocales(); 19 + 20 + $group_labels = array( 21 + 'normal' => pht('Translations'), 22 + 'limited' => pht('Limited Translations'), 23 + 'silly' => pht('Silly Translations'), 24 + 'test' => pht('Developer/Test Translations'), 25 + ); 26 + 27 + $groups = array_fill_keys(array_keys($group_labels), array()); 28 + 29 + $translations = array(); 30 + foreach ($locales as $locale) { 31 + $code = $locale->getLocaleCode(); 32 + 33 + // Get the locale's localized name if it's available. For example, 34 + // "Deutsch" instead of "German". This helps users who do not speak the 35 + // current language to find the correct setting. 36 + $raw_scope = PhabricatorEnv::beginScopedLocale($code); 37 + $name = $locale->getLocaleName(); 38 + unset($raw_scope); 39 + 40 + if ($locale->isSillyLocale()) { 41 + if ($is_serious) { 42 + // Omit silly locales on serious business installs. 43 + continue; 44 + } 45 + $groups['silly'][$code] = $name; 46 + continue; 47 + } 48 + 49 + if ($locale->isTestLocale()) { 50 + $groups['test'][$code] = $name; 51 + continue; 52 + } 53 + 54 + $strings = PhutilTranslation::getTranslationMapForLocale($code); 55 + $size = count($strings); 56 + 57 + // If a translation is English, assume it can fall back to the default 58 + // strings and don't caveat its completeness. 59 + $is_english = (substr($code, 0, 3) == 'en_'); 60 + 61 + // Arbitrarily pick some number of available strings to promote a 62 + // translation out of the "limited" group. The major goal is just to 63 + // keep locales with very few strings out of the main group, so users 64 + // aren't surprised if a locale has no upstream translations available. 65 + if ($size > 512 || $is_english) { 66 + $type = 'normal'; 67 + } else { 68 + $type = 'limited'; 69 + } 70 + 71 + $groups[$type][$code] = $name; 72 + } 73 + 74 + $results = array(); 75 + foreach ($groups as $key => $group) { 76 + $label = $group_labels[$key]; 77 + if (!$group) { 78 + continue; 79 + } 80 + 81 + asort($group); 82 + 83 + $results[] = array( 84 + 'label' => $label, 85 + 'options' => $group, 86 + ); 87 + } 88 + 89 + return $results; 90 + } 91 + 92 + }
+2 -2
src/infrastructure/time/__tests__/PhabricatorTimeTestCase.php
··· 15 15 16 16 public function testParseLocalTime() { 17 17 $u = new PhabricatorUser(); 18 - $u->setTimezoneIdentifier('UTC'); 18 + $u->overrideTimezoneIdentifier('UTC'); 19 19 20 20 $v = new PhabricatorUser(); 21 - $v->setTimezoneIdentifier('America/Los_Angeles'); 21 + $v->overrideTimezoneIdentifier('America/Los_Angeles'); 22 22 23 23 $t = 1370202281; // 2013-06-02 12:44:41 -0700 24 24 $time = PhabricatorTime::pushTime($t, 'America/Los_Angeles');
+2 -2
src/view/__tests__/PhabricatorLocalTimeTestCase.php
··· 4 4 5 5 public function testLocalTimeFormatting() { 6 6 $user = new PhabricatorUser(); 7 - $user->setTimezoneIdentifier('America/Los_Angeles'); 7 + $user->overrideTimezoneIdentifier('America/Los_Angeles'); 8 8 9 9 $utc = new PhabricatorUser(); 10 - $utc->setTimezoneIdentifier('UTC'); 10 + $utc->overrideTimezoneIdentifier('UTC'); 11 11 12 12 $this->assertEqual( 13 13 'Jan 1 2000, 12:00 AM',