Permalink
Browse files

Finished up implementation of frontend per-user timezones

  • Loading branch information...
kiwiz committed Jun 2, 2017
1 parent 6e2abdf commit c97d2e2f542170fbaddec21679645ec8d5ca1753
View
@@ -16,6 +16,7 @@
FOO\SiteFinder::setSite($newsite);
$cfg = new FOO\DBConfig();
$cfg['timezone'] = 'UTC';
$cfg['cookie_secret'] = FOO\Random::base64_bytes(FOO\Cookie::SECRET_LEN);
$cfg['cron_enabled'] = 1;
$cfg['worker_enabled'] = 1;
View
@@ -70,6 +70,17 @@ function ver_cmp($a, $b) {
}
}
if(ver_cmp($old_ver, '1.3.4') < 0) {
FOO\DB::query('ALTER TABLE `users` ADD COLUMN `timezone` VARCHAR(64) NOT NULL DEFAULT "UTC"');
foreach(FOO\SiteFinder::getAll() as $site) {
FOO\SiteFinder::setSite($site);
$config = new FOO\DBConfig;
$config['timezone'] = 'UTC';
}
FOO\SiteFinder::setSite(null);
}
/**
* Migration logic
*/
View
1 db.sql
@@ -162,6 +162,7 @@ CREATE TABLE `users` (
`real_name` VARCHAR(255) NOT NULL,
`password` VARCHAR(255) NOT NULL,
`email` VARCHAR(255) NOT NULL,
`timezone` VARCHAR(255) NOT NULL,
`admin` BOOLEAN NOT NULL, /* bool */
`settings` TEXT NOT NULL,
`api_key` VARCHAR(64) NOT NULL,
View
@@ -354,6 +354,7 @@ CREATE TABLE `users` (
`real_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`timezone` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`admin` tinyint(1) NOT NULL,
`settings` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`api_key` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
View
@@ -133,6 +133,9 @@ define(function(require) {
refresh: function() {
this.Data.Users = new UserCollection(Data.User.Models);
this.Data.User = this.Data.Users.get(Data.User.Me);
if(this.Data.User) {
this.setTimezone(this.Data.User.get('timezone'));
}
this.View.Header.unload();
this.View.Footer.unload();
this.View.Header.load();
@@ -149,6 +152,16 @@ define(function(require) {
if(_.isUndefined(pre)) pre = Data.AppName + ' / ';
document.title = pre + loc;
},
/**
* Set the timezone for the application.
* @param {string} str - Timezone string.
*/
setTimezone: function(tz) {
var valid = Moment.tz.names().indexOf(tz) >= 0;
console.log(valid, tz, Data.Timezone);
console.log(this.Data.User);
Moment.tz.setDefault(valid ? tz:Data.Timezone);
},
/**
* Add an alert to the top of the page.
* @param {string} str - The message to display.
View
@@ -8,39 +8,20 @@ define(function(require) {
Moment = require('moment'),
Data = require('data');
// Turn a timestamp into a datestring.
var formatDate = function(ts) {
if(ts === 0) {
return 'N/A';
}
var date = new Moment(new Date(parseInt(ts, 10) * 1000));
var timezone = '';
if (Data.User.Me) {
var results = $.grep(Data.User.Models, function(user) { return user.id == Data.User.Me; });
timezone = results[0].settings.timezone ? results[0].settings.timezone : '';
}
if (timezone === '' && Data.Timezone) {
timezone = Data.Timezone;
}
if (timezone === '') {
timezone = 'UTC';
}
if (timezone === 'LocalBrowserTime') {
date.tz(Moment.tz.guess());
} else {
date.tz(timezone);
}
return date.format("ddd, DD MMM YYYY HH:mm:ss z"); // Thu, 27 Apr 2017 21:42:31 GMT
var date = Moment.unix(ts);
return date.format("ddd, DD MMM YYYY HH:mm:ssZ");
};
// Turn a number into a timestring.
function formatTime(mins) {
var weeks = 0;
var days = 0;
var hours = 0;
mins = parseInt(mins, 10);
@@ -55,7 +36,14 @@ define(function(require) {
days = (hours / 24) | 0;
hours = (hours % 24);
}
if(days >= 7) {
weeks = (days / 7) | 0;
days = (days % 7);
}
var str = '';
if(weeks > 0) {
str += weeks + ' week' + (weeks == 1 ? '':'s') + ' ';
}
if(days > 0) {
str += days + ' day' + (days == 1 ? '':'s') + ' ';
}
@@ -8,6 +8,7 @@ define(function(require) {
Util = require('util'),
Moment = require('moment');
var AdminNavbarView = NavbarView.extend({
title: 'Admin',
});
@@ -26,12 +27,7 @@ define(function(require) {
url: Config.api_root + 'admin',
success: this.cbLoaded(function(resp) {
this.data = resp;
// prep tz data for display
this.data['timezones'] = _.map(Moment.tz.names(), function(tz){
return {timezone: tz, selected: (tz === this.data['timezone'])};
}, this);
this.data['timezones'].unshift({timezone: 'LocalBrowserTime', selected: ('LocalBrowserTime' === this.data['timezone']) });
this.data['timezones'] = Moment.tz.names();
this.render();
}),
@@ -34,21 +34,10 @@ define(function(require) {
this.App.setTitle('User: ' + (this.model.isNew() ? 'New':this.model.get('id')));
this.registerView(new UserNavbarView(this.App), true);
var user_tz = 'UTC';
if ('timezone' in this.model.get('settings')) {
user_tz = this.model.get('settings').timezone;
}
// prep tz data for display
var timezones = _.map(Moment.tz.names(), function(tz){
return {timezone: tz, selected: (tz === user_tz)};
}, this);
timezones.unshift({timezone: 'LocalBrowserTime', selected: ('LocalBrowserTime' === user_tz) });
var vars = this.model.toJSON();
_.extend(vars, {
new_user: this.model.isNew(),
timezones: timezones
timezones: Moment.tz.names(),
});
this.$el.append(this.template(vars));
@@ -80,10 +69,6 @@ define(function(require) {
var form = this.$('#user-form');
var data = Util.serializeForm(form);
data.settings = {};
data.settings.timezone = data.timezone;
delete data.timezone;
data.admin = !!parseInt(data.admin, 10);
return data;
@@ -108,7 +93,11 @@ define(function(require) {
// We don't want to ship the dupe password, so delete it.
delete data.password_;
this.saveModel(data);
this.saveModel(data).success(this.cbRendered(function() {
if(this.isSelf()) {
this.App.setTimezone(data.timezone);
}
}));
return false;
},
/**
@@ -124,7 +113,7 @@ define(function(require) {
* @param {string} url - The url to navigate to on success.
*/
destroyModel: function() {
if(this.model.get('id') == this.App.Data.User.get('id')) {
if(this.isSelf()) {
this.App.addMessage('Unable to delete this user');
} else {
ModelView.prototype.destroyModel.call(this, '/users');
@@ -17,9 +17,9 @@
<div role="tabpanel" class="tab-pane" id="core">
<div class="form-group col-xs-6 col-md-3">
<label for="timezone">Timezone Select <span class="glyphicon glyphicon-question-sign" data-toggle="tooltip" title="Timezone to display dates in."></span></label><br />
<select class="form-control tags select2-multiple" name="timezone">
<select class="form-control" name="timezone">
{{#each timezones}}
<option value="{{ this.timezone }}" {{#if this.selected }}selected{{/if}}>{{#if this.timezone }}{{ this.timezone }}{{ else }}Local Browser Timezone{{/if }}</option>
<option value="{{ this }}" {{#ifeq ../timezone this }}selected{{/ifeq}}>{{ this }}</option>
{{/each}}
</select>
</div>
@@ -34,9 +34,10 @@
</div>
<div class="col-md-6 col-xs-12 form-group">
<label for="timezone">Timezone</label><br />
<select class="form-control tags select2-multiple" name="timezone">
<select class="form-control" name="timezone">
<option></option>
{{#each timezones}}
<option value="{{ this.timezone }}" {{#if this.selected }}selected{{/if}}>{{ this.timezone }}</option>
<option value="{{ this }}" {{#ifeq ../timezone this }}selected{{/ifeq}}>{{ this }}</option>
{{/each}}
</select>
</div>
View
@@ -20,7 +20,7 @@
date_default_timezone_set("UTC");
define('BASE_DIR', realpath(__DIR__ . '/..'));
define('VERSION', '1.3.3');
define('VERSION', '1.3.4');
// Set up autoloader for our classes.
spl_autoload_register(function($class) {
View
@@ -11,8 +11,8 @@ class Admin_REST extends REST {
const T_BOOL = 0;
const T_INT = 1;
const T_EMAIL = 2;
const T_STR = 3;
const T_TZ = 4;
const T_TZ = 3;
const T_STR = 4;
public static $FIELDS = [
'cron_enabled' => self::T_BOOL,
@@ -59,10 +59,10 @@ public function POST(array $get, array $data) {
case self::T_EMAIL:
$ok = filter_var($val, FILTER_VALIDATE_EMAIL);
break;
case self::T_STR:
break;
case self::T_TZ:
# todo validate with json data from moment-timezone
$ok = in_array($val, timezone_identifiers_list());
break;
case self::T_STR:
break;
}
View
@@ -118,8 +118,8 @@ private function generateUsers() {
* @return string timezone for use with moment-timezone.
*/
private function getTimezone() {
$timezone = (new DBConfig())['timezone'];
return is_null($timezone) ? 'UTC' : $timezone;
$config = new DBConfig;
return Util::validateTimezone($config['timezone']);
}
/**
View
@@ -12,16 +12,16 @@ class Users_REST extends Models_REST {
protected static $MODEL = 'User';
protected static $CREATABLE = [
'name', 'real_name', 'password', 'email', 'admin', 'api_key'
'name', 'real_name', 'password', 'email', 'admin', 'api_key', 'timezone'
];
protected static $QUERYABLE = [
'name'
];
protected static $READABLE = [
'name', 'real_name', 'email', 'admin', 'settings', 'api_key'
'name', 'real_name', 'email', 'admin', 'settings', 'api_key', 'timezone'
];
protected static $UPDATEABLE = [
'name', 'real_name', 'password', 'email', 'admin', 'settings', 'api_key'
'name', 'real_name', 'password', 'email', 'admin', 'settings', 'api_key', 'timezone'
];
public function allowRead() {
View
@@ -62,9 +62,9 @@ class SiteFinder extends ModelFinder {
/**
* Set the currently active Site.
* @param Site $site The Site.
* @param Site|null $site The Site.
*/
public static function setSite(Site $site) {
public static function setSite(Site $site=null) {
self::$site = $site;
}
View
@@ -25,8 +25,9 @@ protected static function generateSchema() {
'password' => [static::T_STR, null, ''],
'email' => [static::T_STR, null, ''],
'admin' => [static::T_BOOL, null, false],
'timezone' => [static::T_STR, null, ''],
'settings' => [static::T_OBJ, null, []],
'api_key' => [static::T_STR, null, '']
'api_key' => [static::T_STR, null, ''],
];
}
@@ -44,6 +45,9 @@ public function validateData(array $data) {
if(strlen($data['password']) == 0) {
throw new ValidationException('Invalid password');
}
if($data['timezone'] != '' && !in_array($data['timezone'], timezone_identifiers_list())) {
throw new ValidationException('Invalid timezone');
}
}
protected function serialize(array $data) {
@@ -84,6 +88,19 @@ public function randomizeAPIKey() {
$this->obj['api_key'] = Random::base64_bytes(User::API_KEY_LEN);
return $this->obj['api_key'];
}
/**
* Get timezone for this user, if set or default to sitewide timezone otherwise.
* @return string Timezone string.
*/
public function getTimezone() {
$timezone = Util::validateTimezone($this->obj['timezone'], null);
if(is_null($timezone)) {
$config = new DBConfig;
$timezone = Util::validateTimezone($config['timezone']);
}
return $timezone;
}
}
/**
View
@@ -184,4 +184,13 @@ public static function parseDates($format, array $dates) {
return $ret;
}
/**
* Validate the timezone given. If invalid, default to UTC.
* @param string Timezone string.
* @return string Timezone string.
*/
public static function validateTimezone($timezone, $default='UTC') {
return in_array($timezone, timezone_identifiers_list()) ? $timezone:$default;
}
}

0 comments on commit c97d2e2

Please sign in to comment.