mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +00:00 
			
		
		
		
	Compare commits
	
		
			10 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					76809b87a6 | ||
| 
						 | 
					5baeb35ac8 | ||
| 
						 | 
					75fbce0532 | ||
| 
						 | 
					8ad7e08375 | ||
| 
						 | 
					bd01b1e2e4 | ||
| 
						 | 
					58a7f6085f | ||
| 
						 | 
					3367593b52 | ||
| 
						 | 
					1a92ec5d86 | ||
| 
						 | 
					7a8d685a71 | ||
| 
						 | 
					3c3a8747c3 | 
@@ -39,7 +39,8 @@ from typing import Any, Dict, List, Tuple, Optional, Sequence, Callable, Type, \
 | 
			
		||||
@zulip_login_required
 | 
			
		||||
def stats(request):
 | 
			
		||||
    # type: (HttpRequest) -> HttpResponse
 | 
			
		||||
    return render_to_response('analytics/stats.html')
 | 
			
		||||
    return render_to_response('analytics/stats.html',
 | 
			
		||||
                              context=dict(realm_name = request.user.realm.name))
 | 
			
		||||
 | 
			
		||||
@has_request_variables
 | 
			
		||||
def get_chart_data(request, user_profile, chart_name=REQ(),
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,16 @@ All notable changes to the Zulip server are documented in this file.
 | 
			
		||||
 | 
			
		||||
### Unreleased
 | 
			
		||||
 | 
			
		||||
### 1.5.2 -- 2017-06-01
 | 
			
		||||
 | 
			
		||||
- CVE-2017-0896: Restricting inviting new users to admins was broken.
 | 
			
		||||
- CVE-2015-8861: Insecure old version of handlebars templating engine.
 | 
			
		||||
 | 
			
		||||
### 1.5.1 -- 2017-02-07
 | 
			
		||||
 | 
			
		||||
- Fix exception trying to copy node_modules during upgrade process.
 | 
			
		||||
- Improved styling of /stats page to remove useless login/register links.
 | 
			
		||||
 | 
			
		||||
### 1.5.0 -- 2017-02-06
 | 
			
		||||
 | 
			
		||||
Highlights:
 | 
			
		||||
 
 | 
			
		||||
@@ -288,8 +288,7 @@ function render(template_name, args) {
 | 
			
		||||
    var all_html = '';
 | 
			
		||||
 | 
			
		||||
    html = render('bookend', args);
 | 
			
		||||
 | 
			
		||||
    assert.equal($(html).text().trim(), "subscribed to stream\n    \n        \n            \n            Unsubscribe");
 | 
			
		||||
    assert.equal($(html).text().trim(), "subscribed to stream\n    \n        \n            Unsubscribe");
 | 
			
		||||
 | 
			
		||||
    all_html += html;
 | 
			
		||||
 | 
			
		||||
@@ -300,7 +299,7 @@ function render(template_name, args) {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    html = render('bookend', args);
 | 
			
		||||
    assert.equal($(html).text().trim(), 'Not subscribed to stream\n    \n        \n            \n            Subscribe');
 | 
			
		||||
    assert.equal($(html).text().trim(), 'Not subscribed to stream\n    \n        \n            Subscribe');
 | 
			
		||||
 | 
			
		||||
    all_html += '<hr />';
 | 
			
		||||
    all_html += html;
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
  "description": "",
 | 
			
		||||
  "main": "",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "handlebars": "1.3.0",
 | 
			
		||||
    "handlebars": "4.0.6",
 | 
			
		||||
    "i18next": "3.0.0",
 | 
			
		||||
    "i18next-parser": "0.11.1",
 | 
			
		||||
    "i18next-xhr-backend": "0.5.4",
 | 
			
		||||
 
 | 
			
		||||
@@ -79,8 +79,6 @@ else:
 | 
			
		||||
    logging.info("Installing static assets...")
 | 
			
		||||
    subprocess.check_call(["cp", "-rT", os.path.join(deploy_path, 'prod-static/serve'),
 | 
			
		||||
                           '/home/zulip/prod-static'], preexec_fn=su_to_zulip)
 | 
			
		||||
    # Sets up npm cache
 | 
			
		||||
    setup_node_modules(npm_args=['--production'], copy_modules=True)
 | 
			
		||||
 | 
			
		||||
# Our next optimization is to check whether any migrations are needed
 | 
			
		||||
# before we start the critical section of the restart.  This saves
 | 
			
		||||
 
 | 
			
		||||
@@ -161,10 +161,10 @@ function hover(id) {
 | 
			
		||||
            $('#hover_bots_value').hide();
 | 
			
		||||
        }
 | 
			
		||||
        // var human_colors = data.points[0].data.x.map(function () {
 | 
			
		||||
        //     return '#1f77b4';
 | 
			
		||||
        //     return '#5f6ea0';
 | 
			
		||||
        // });
 | 
			
		||||
        // var bot_colors = data.points[0].data.x.map(function () {
 | 
			
		||||
        //     return '#ff7f00';
 | 
			
		||||
        //     return '#b7b867';
 | 
			
		||||
        // });
 | 
			
		||||
        // human_colors[data.points[0].pointNumber] = '#185a88';
 | 
			
		||||
        // bot_colors[data.points[0].pointNumber] = '#cc6600';
 | 
			
		||||
@@ -173,12 +173,12 @@ function hover(id) {
 | 
			
		||||
        // Plotly.restyle(id, update_human, 0);
 | 
			
		||||
        // Plotly.restyle(id, update_bot, 1);
 | 
			
		||||
        // var legendBoxes = document.getElementById(id).getElementsByClassName("legendbar");
 | 
			
		||||
        // Plotly.d3.select(legendBoxes[0]).style("fill", "#1f77b4");
 | 
			
		||||
        // Plotly.d3.select(legendBoxes[1]).style("fill", "#ff7f00");
 | 
			
		||||
        // Plotly.d3.select(legendBoxes[0]).style("fill", "#5f6ea0");
 | 
			
		||||
        // Plotly.d3.select(legendBoxes[1]).style("fill", "#b7b867");
 | 
			
		||||
    });
 | 
			
		||||
    // myPlot.on('plotly_unhover', function () {
 | 
			
		||||
    //     var update_human = {marker:{color: '#1f77b4'}};
 | 
			
		||||
    //     var update_bot = {marker:{color: '#ff7f00'}};
 | 
			
		||||
    //     var update_human = {marker:{color: '#5f6ea0'}};
 | 
			
		||||
    //     var update_bot = {marker:{color: '#b7b867'}};
 | 
			
		||||
    //     Plotly.restyle(id, update_human, 0);
 | 
			
		||||
    //     Plotly.restyle(id, update_bot, 1);
 | 
			
		||||
    // });
 | 
			
		||||
@@ -186,8 +186,8 @@ function hover(id) {
 | 
			
		||||
 | 
			
		||||
function fix_legend_colors() {
 | 
			
		||||
    var legendBoxes = document.getElementById('id_messages_sent_over_time').getElementsByClassName("legendbar");
 | 
			
		||||
    Plotly.d3.select(legendBoxes[0]).style("fill", "#1f77b4");
 | 
			
		||||
    Plotly.d3.select(legendBoxes[1]).style("fill", "#ff7f00");
 | 
			
		||||
    Plotly.d3.select(legendBoxes[0]).style("fill", "#5f6ea0");
 | 
			
		||||
    Plotly.d3.select(legendBoxes[1]).style("fill", "#b7b867");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function populate_messages_sent_over_time(data) {
 | 
			
		||||
@@ -242,13 +242,13 @@ function populate_messages_sent_over_time(data) {
 | 
			
		||||
    var date_formatter = function (date) {
 | 
			
		||||
        return format_date(date, true);
 | 
			
		||||
    };
 | 
			
		||||
    var hourly_traces = messages_sent_over_time_traces(start_dates, data.realm, 'bar', date_formatter, '#1f77b4', '#ff7f00');
 | 
			
		||||
    var hourly_traces = messages_sent_over_time_traces(start_dates, data.realm, 'bar', date_formatter, '#5f6ea0', '#b7b867');
 | 
			
		||||
 | 
			
		||||
    var info = aggregate_data('day');
 | 
			
		||||
    date_formatter = function (date) {
 | 
			
		||||
        return format_date(date, false);
 | 
			
		||||
    };
 | 
			
		||||
    var daily_traces = messages_sent_over_time_traces(info.dates, info.values, 'bar', date_formatter, '#1f77b4', '#ff7f00');
 | 
			
		||||
    var daily_traces = messages_sent_over_time_traces(info.dates, info.values, 'bar', date_formatter, '#5f6ea0', '#b7b867');
 | 
			
		||||
 | 
			
		||||
    info = aggregate_data('week');
 | 
			
		||||
    date_formatter = function (date) {
 | 
			
		||||
@@ -256,14 +256,14 @@ function populate_messages_sent_over_time(data) {
 | 
			
		||||
        return "Week of " + format_date(date, false);
 | 
			
		||||
    };
 | 
			
		||||
    var human_colors = info.dates.map(function () {
 | 
			
		||||
        return '#1f77b4';
 | 
			
		||||
        return '#5f6ea0';
 | 
			
		||||
    });
 | 
			
		||||
    var bot_colors = info.dates.map(function () {
 | 
			
		||||
        return '#ff7f00';
 | 
			
		||||
        return '#b7b867';
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    human_colors[info.dates.length-1] = '#66b0e5';
 | 
			
		||||
    bot_colors[info.dates.length-1] = '#ffa64d';
 | 
			
		||||
    human_colors[info.dates.length-1] = '#afb7d0';
 | 
			
		||||
    bot_colors[info.dates.length-1] = '#dbdcb3';
 | 
			
		||||
 | 
			
		||||
    var weekly_traces = messages_sent_over_time_traces(info.dates, info.values, 'bar', date_formatter, human_colors, bot_colors);
 | 
			
		||||
    var dates = data.end_times.map(function (timestamp) {
 | 
			
		||||
@@ -273,7 +273,7 @@ function populate_messages_sent_over_time(data) {
 | 
			
		||||
    date_formatter = function (date) {
 | 
			
		||||
        return format_date(date, true);
 | 
			
		||||
    };
 | 
			
		||||
    var cumulative_traces = messages_sent_over_time_traces(dates, values, 'scatter', date_formatter, '#1f77b4', '#ff7f00');
 | 
			
		||||
    var cumulative_traces = messages_sent_over_time_traces(dates, values, 'scatter', date_formatter, '#5f6ea0', '#b7b867');
 | 
			
		||||
 | 
			
		||||
    // Generate plot
 | 
			
		||||
    var layout = messages_sent_over_time_layout();
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,6 @@ svg {
 | 
			
		||||
 | 
			
		||||
.page-content {
 | 
			
		||||
    width: calc(100% - 300px - 8px);
 | 
			
		||||
    height: calc(100vh - 94px);
 | 
			
		||||
    overflow: auto;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    vertical-align: top;
 | 
			
		||||
 
 | 
			
		||||
@@ -12,10 +12,10 @@
 | 
			
		||||
{{/if}}
 | 
			
		||||
 | 
			
		||||
<div class="recipient_row" id="{{message_group_id}}">
 | 
			
		||||
  {{partial "recipient_row" "use_match_properties" ../../use_match_properties}}
 | 
			
		||||
  {{partial "recipient_row" "use_match_properties" ../use_match_properties}}
 | 
			
		||||
  {{#each message_containers}}
 | 
			
		||||
  {{#with this}}
 | 
			
		||||
    {{partial "single_message" "use_match_properties" ../../../../use_match_properties "table_name" ../../../../table_name}}
 | 
			
		||||
    {{partial "single_message" "use_match_properties" ../../use_match_properties "table_name" ../../table_name}}
 | 
			
		||||
  {{/with}}
 | 
			
		||||
  {{/each}}
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,554 +0,0 @@
 | 
			
		||||
/** @preserve
 | 
			
		||||
 Software from "Handlebars", an extension to the Mustache templating language, is
 | 
			
		||||
 Copyright (C) 2011 by Yehuda Katz and is provided under the following license:
 | 
			
		||||
 --
 | 
			
		||||
 Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 in the Software without restriction, including without limitation the rights
 | 
			
		||||
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
 The above copyright notice and this permission notice shall be included in
 | 
			
		||||
 all copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
			
		||||
 THE SOFTWARE.
 | 
			
		||||
 --
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 | 
			
		||||
 handlebars v1.3.0
 | 
			
		||||
 | 
			
		||||
Copyright (C) 2011 by Yehuda Katz
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
in the Software without restriction, including without limitation the rights
 | 
			
		||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
The above copyright notice and this permission notice shall be included in
 | 
			
		||||
all copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
			
		||||
THE SOFTWARE.
 | 
			
		||||
 | 
			
		||||
@license
 | 
			
		||||
*/
 | 
			
		||||
/* exported Handlebars */
 | 
			
		||||
var Handlebars = (function() {
 | 
			
		||||
// handlebars/safe-string.js
 | 
			
		||||
var __module3__ = (function() {
 | 
			
		||||
  "use strict";
 | 
			
		||||
  var __exports__;
 | 
			
		||||
  // Build out our basic SafeString type
 | 
			
		||||
  function SafeString(string) {
 | 
			
		||||
    this.string = string;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  SafeString.prototype.toString = function() {
 | 
			
		||||
    return "" + this.string;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  __exports__ = SafeString;
 | 
			
		||||
  return __exports__;
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
// handlebars/utils.js
 | 
			
		||||
var __module2__ = (function(__dependency1__) {
 | 
			
		||||
  "use strict";
 | 
			
		||||
  var __exports__ = {};
 | 
			
		||||
  /*jshint -W004 */
 | 
			
		||||
  var SafeString = __dependency1__;
 | 
			
		||||
 | 
			
		||||
  var escape = {
 | 
			
		||||
    "&": "&",
 | 
			
		||||
    "<": "<",
 | 
			
		||||
    ">": ">",
 | 
			
		||||
    '"': """,
 | 
			
		||||
    "'": "'",
 | 
			
		||||
    "`": "`"
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  var badChars = /[&<>"'`]/g;
 | 
			
		||||
  var possible = /[&<>"'`]/;
 | 
			
		||||
 | 
			
		||||
  function escapeChar(chr) {
 | 
			
		||||
    return escape[chr] || "&";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function extend(obj, value) {
 | 
			
		||||
    for(var key in value) {
 | 
			
		||||
      if(Object.prototype.hasOwnProperty.call(value, key)) {
 | 
			
		||||
        obj[key] = value[key];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  __exports__.extend = extend;var toString = Object.prototype.toString;
 | 
			
		||||
  __exports__.toString = toString;
 | 
			
		||||
  // Sourced from lodash
 | 
			
		||||
  // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
 | 
			
		||||
  var isFunction = function(value) {
 | 
			
		||||
    return typeof value === 'function';
 | 
			
		||||
  };
 | 
			
		||||
  // fallback for older versions of Chrome and Safari
 | 
			
		||||
  if (isFunction(/x/)) {
 | 
			
		||||
    isFunction = function(value) {
 | 
			
		||||
      return typeof value === 'function' && toString.call(value) === '[object Function]';
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  var isFunction;
 | 
			
		||||
  __exports__.isFunction = isFunction;
 | 
			
		||||
  var isArray = Array.isArray || function(value) {
 | 
			
		||||
    return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
 | 
			
		||||
  };
 | 
			
		||||
  __exports__.isArray = isArray;
 | 
			
		||||
 | 
			
		||||
  function escapeExpression(string) {
 | 
			
		||||
    // don't escape SafeStrings, since they're already safe
 | 
			
		||||
    if (string instanceof SafeString) {
 | 
			
		||||
      return string.toString();
 | 
			
		||||
    } else if (!string && string !== 0) {
 | 
			
		||||
      return "";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Force a string conversion as this will be done by the append regardless and
 | 
			
		||||
    // the regex test will do this transparently behind the scenes, causing issues if
 | 
			
		||||
    // an object's to string has escaped characters in it.
 | 
			
		||||
    string = "" + string;
 | 
			
		||||
 | 
			
		||||
    if(!possible.test(string)) { return string; }
 | 
			
		||||
    return string.replace(badChars, escapeChar);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  __exports__.escapeExpression = escapeExpression;function isEmpty(value) {
 | 
			
		||||
    if (!value && value !== 0) {
 | 
			
		||||
      return true;
 | 
			
		||||
    } else if (isArray(value) && value.length === 0) {
 | 
			
		||||
      return true;
 | 
			
		||||
    } else {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  __exports__.isEmpty = isEmpty;
 | 
			
		||||
  return __exports__;
 | 
			
		||||
})(__module3__);
 | 
			
		||||
 | 
			
		||||
// handlebars/exception.js
 | 
			
		||||
var __module4__ = (function() {
 | 
			
		||||
  "use strict";
 | 
			
		||||
  var __exports__;
 | 
			
		||||
 | 
			
		||||
  var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
 | 
			
		||||
 | 
			
		||||
  function Exception(message, node) {
 | 
			
		||||
    var line;
 | 
			
		||||
    if (node && node.firstLine) {
 | 
			
		||||
      line = node.firstLine;
 | 
			
		||||
 | 
			
		||||
      message += ' - ' + line + ':' + node.firstColumn;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var tmp = Error.prototype.constructor.call(this, message);
 | 
			
		||||
 | 
			
		||||
    // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
 | 
			
		||||
    for (var idx = 0; idx < errorProps.length; idx++) {
 | 
			
		||||
      this[errorProps[idx]] = tmp[errorProps[idx]];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (line) {
 | 
			
		||||
      this.lineNumber = line;
 | 
			
		||||
      this.column = node.firstColumn;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Exception.prototype = new Error();
 | 
			
		||||
 | 
			
		||||
  __exports__ = Exception;
 | 
			
		||||
  return __exports__;
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
// handlebars/base.js
 | 
			
		||||
var __module1__ = (function(__dependency1__, __dependency2__) {
 | 
			
		||||
  "use strict";
 | 
			
		||||
  var __exports__ = {};
 | 
			
		||||
  var Utils = __dependency1__;
 | 
			
		||||
  var Exception = __dependency2__;
 | 
			
		||||
 | 
			
		||||
  var VERSION = "1.3.0";
 | 
			
		||||
  __exports__.VERSION = VERSION;var COMPILER_REVISION = 4;
 | 
			
		||||
  __exports__.COMPILER_REVISION = COMPILER_REVISION;
 | 
			
		||||
  var REVISION_CHANGES = {
 | 
			
		||||
    1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
 | 
			
		||||
    2: '== 1.0.0-rc.3',
 | 
			
		||||
    3: '== 1.0.0-rc.4',
 | 
			
		||||
    4: '>= 1.0.0'
 | 
			
		||||
  };
 | 
			
		||||
  __exports__.REVISION_CHANGES = REVISION_CHANGES;
 | 
			
		||||
  var isArray = Utils.isArray,
 | 
			
		||||
      isFunction = Utils.isFunction,
 | 
			
		||||
      toString = Utils.toString,
 | 
			
		||||
      objectType = '[object Object]';
 | 
			
		||||
 | 
			
		||||
  function HandlebarsEnvironment(helpers, partials) {
 | 
			
		||||
    this.helpers = helpers || {};
 | 
			
		||||
    this.partials = partials || {};
 | 
			
		||||
 | 
			
		||||
    registerDefaultHelpers(this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  __exports__.HandlebarsEnvironment = HandlebarsEnvironment;HandlebarsEnvironment.prototype = {
 | 
			
		||||
    constructor: HandlebarsEnvironment,
 | 
			
		||||
 | 
			
		||||
    logger: logger,
 | 
			
		||||
    log: log,
 | 
			
		||||
 | 
			
		||||
    registerHelper: function(name, fn, inverse) {
 | 
			
		||||
      if (toString.call(name) === objectType) {
 | 
			
		||||
        if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); }
 | 
			
		||||
        Utils.extend(this.helpers, name);
 | 
			
		||||
      } else {
 | 
			
		||||
        if (inverse) { fn.not = inverse; }
 | 
			
		||||
        this.helpers[name] = fn;
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    registerPartial: function(name, str) {
 | 
			
		||||
      if (toString.call(name) === objectType) {
 | 
			
		||||
        Utils.extend(this.partials,  name);
 | 
			
		||||
      } else {
 | 
			
		||||
        this.partials[name] = str;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  function registerDefaultHelpers(instance) {
 | 
			
		||||
    instance.registerHelper('helperMissing', function(arg) {
 | 
			
		||||
      if(arguments.length === 2) {
 | 
			
		||||
        return undefined;
 | 
			
		||||
      } else {
 | 
			
		||||
        throw new Exception("Missing helper: '" + arg + "'");
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    instance.registerHelper('blockHelperMissing', function(context, options) {
 | 
			
		||||
      var inverse = options.inverse || function() {}, fn = options.fn;
 | 
			
		||||
 | 
			
		||||
      if (isFunction(context)) { context = context.call(this); }
 | 
			
		||||
 | 
			
		||||
      if(context === true) {
 | 
			
		||||
        return fn(this);
 | 
			
		||||
      } else if(context === false || context == null) {
 | 
			
		||||
        return inverse(this);
 | 
			
		||||
      } else if (isArray(context)) {
 | 
			
		||||
        if(context.length > 0) {
 | 
			
		||||
          return instance.helpers.each(context, options);
 | 
			
		||||
        } else {
 | 
			
		||||
          return inverse(this);
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        return fn(context);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    instance.registerHelper('each', function(context, options) {
 | 
			
		||||
      var fn = options.fn, inverse = options.inverse;
 | 
			
		||||
      var i = 0, ret = "", data;
 | 
			
		||||
 | 
			
		||||
      if (isFunction(context)) { context = context.call(this); }
 | 
			
		||||
 | 
			
		||||
      if (options.data) {
 | 
			
		||||
        data = createFrame(options.data);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if(context && typeof context === 'object') {
 | 
			
		||||
        if (isArray(context)) {
 | 
			
		||||
          for(var j = context.length; i<j; i++) {
 | 
			
		||||
            if (data) {
 | 
			
		||||
              data.index = i;
 | 
			
		||||
              data.first = (i === 0);
 | 
			
		||||
              data.last  = (i === (context.length-1));
 | 
			
		||||
            }
 | 
			
		||||
            ret = ret + fn(context[i], { data: data });
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          for(var key in context) {
 | 
			
		||||
            if(context.hasOwnProperty(key)) {
 | 
			
		||||
              if(data) {
 | 
			
		||||
                data.key = key;
 | 
			
		||||
                data.index = i;
 | 
			
		||||
                data.first = (i === 0);
 | 
			
		||||
              }
 | 
			
		||||
              ret = ret + fn(context[key], {data: data});
 | 
			
		||||
              i++;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if(i === 0){
 | 
			
		||||
        ret = inverse(this);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return ret;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    instance.registerHelper('if', function(conditional, options) {
 | 
			
		||||
      if (isFunction(conditional)) { conditional = conditional.call(this); }
 | 
			
		||||
 | 
			
		||||
      // Default behavior is to render the positive path if the value is truthy and not empty.
 | 
			
		||||
      // The `includeZero` option may be set to treat the condtional as purely not empty based on the
 | 
			
		||||
      // behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
 | 
			
		||||
      if ((!options.hash.includeZero && !conditional) || Utils.isEmpty(conditional)) {
 | 
			
		||||
        return options.inverse(this);
 | 
			
		||||
      } else {
 | 
			
		||||
        return options.fn(this);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    instance.registerHelper('unless', function(conditional, options) {
 | 
			
		||||
      return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    instance.registerHelper('with', function(context, options) {
 | 
			
		||||
      if (isFunction(context)) { context = context.call(this); }
 | 
			
		||||
 | 
			
		||||
      if (!Utils.isEmpty(context)) return options.fn(context);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    instance.registerHelper('log', function(context, options) {
 | 
			
		||||
      var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
 | 
			
		||||
      instance.log(level, context);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  var logger = {
 | 
			
		||||
    methodMap: { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' },
 | 
			
		||||
 | 
			
		||||
    // State enum
 | 
			
		||||
    DEBUG: 0,
 | 
			
		||||
    INFO: 1,
 | 
			
		||||
    WARN: 2,
 | 
			
		||||
    ERROR: 3,
 | 
			
		||||
    level: 3,
 | 
			
		||||
 | 
			
		||||
    // can be overridden in the host environment
 | 
			
		||||
    log: function(level, obj) {
 | 
			
		||||
      if (logger.level <= level) {
 | 
			
		||||
        var method = logger.methodMap[level];
 | 
			
		||||
        if (typeof console !== 'undefined' && console[method]) {
 | 
			
		||||
          console[method].call(console, obj);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
  __exports__.logger = logger;
 | 
			
		||||
  function log(level, obj) { logger.log(level, obj); }
 | 
			
		||||
 | 
			
		||||
  __exports__.log = log;var createFrame = function(object) {
 | 
			
		||||
    var obj = {};
 | 
			
		||||
    Utils.extend(obj, object);
 | 
			
		||||
    return obj;
 | 
			
		||||
  };
 | 
			
		||||
  __exports__.createFrame = createFrame;
 | 
			
		||||
  return __exports__;
 | 
			
		||||
})(__module2__, __module4__);
 | 
			
		||||
 | 
			
		||||
// handlebars/runtime.js
 | 
			
		||||
var __module5__ = (function(__dependency1__, __dependency2__, __dependency3__) {
 | 
			
		||||
  "use strict";
 | 
			
		||||
  var __exports__ = {};
 | 
			
		||||
  var Utils = __dependency1__;
 | 
			
		||||
  var Exception = __dependency2__;
 | 
			
		||||
  var COMPILER_REVISION = __dependency3__.COMPILER_REVISION;
 | 
			
		||||
  var REVISION_CHANGES = __dependency3__.REVISION_CHANGES;
 | 
			
		||||
 | 
			
		||||
  function checkRevision(compilerInfo) {
 | 
			
		||||
    var compilerRevision = compilerInfo && compilerInfo[0] || 1,
 | 
			
		||||
        currentRevision = COMPILER_REVISION;
 | 
			
		||||
 | 
			
		||||
    if (compilerRevision !== currentRevision) {
 | 
			
		||||
      if (compilerRevision < currentRevision) {
 | 
			
		||||
        var runtimeVersions = REVISION_CHANGES[currentRevision],
 | 
			
		||||
            compilerVersions = REVISION_CHANGES[compilerRevision];
 | 
			
		||||
        throw new Exception("Template was precompiled with an older version of Handlebars than the current runtime. "+
 | 
			
		||||
              "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").");
 | 
			
		||||
      } else {
 | 
			
		||||
        // Use the embedded version info since the runtime doesn't know about this revision yet
 | 
			
		||||
        throw new Exception("Template was precompiled with a newer version of Handlebars than the current runtime. "+
 | 
			
		||||
              "Please update your runtime to a newer version ("+compilerInfo[1]+").");
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  __exports__.checkRevision = checkRevision;// TODO: Remove this line and break up compilePartial
 | 
			
		||||
 | 
			
		||||
  function template(templateSpec, env) {
 | 
			
		||||
    if (!env) {
 | 
			
		||||
      throw new Exception("No environment passed to template");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Note: Using env.VM references rather than local var references throughout this section to allow
 | 
			
		||||
    // for external users to override these as psuedo-supported APIs.
 | 
			
		||||
    var invokePartialWrapper = function(partial, name, context, helpers, partials, data) {
 | 
			
		||||
      var result = env.VM.invokePartial.apply(this, arguments);
 | 
			
		||||
      if (result != null) { return result; }
 | 
			
		||||
 | 
			
		||||
      if (env.compile) {
 | 
			
		||||
        var options = { helpers: helpers, partials: partials, data: data };
 | 
			
		||||
        partials[name] = env.compile(partial, { data: data !== undefined }, env);
 | 
			
		||||
        return partials[name](context, options);
 | 
			
		||||
      } else {
 | 
			
		||||
        throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Just add water
 | 
			
		||||
    var container = {
 | 
			
		||||
      escapeExpression: Utils.escapeExpression,
 | 
			
		||||
      invokePartial: invokePartialWrapper,
 | 
			
		||||
      programs: [],
 | 
			
		||||
      program: function(i, fn, data) {
 | 
			
		||||
        var programWrapper = this.programs[i];
 | 
			
		||||
        if(data) {
 | 
			
		||||
          programWrapper = program(i, fn, data);
 | 
			
		||||
        } else if (!programWrapper) {
 | 
			
		||||
          programWrapper = this.programs[i] = program(i, fn);
 | 
			
		||||
        }
 | 
			
		||||
        return programWrapper;
 | 
			
		||||
      },
 | 
			
		||||
      merge: function(param, common) {
 | 
			
		||||
        var ret = param || common;
 | 
			
		||||
 | 
			
		||||
        if (param && common && (param !== common)) {
 | 
			
		||||
          ret = {};
 | 
			
		||||
          Utils.extend(ret, common);
 | 
			
		||||
          Utils.extend(ret, param);
 | 
			
		||||
        }
 | 
			
		||||
        return ret;
 | 
			
		||||
      },
 | 
			
		||||
      programWithDepth: env.VM.programWithDepth,
 | 
			
		||||
      noop: env.VM.noop,
 | 
			
		||||
      compilerInfo: null
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return function(context, options) {
 | 
			
		||||
      options = options || {};
 | 
			
		||||
      var namespace = options.partial ? options : env,
 | 
			
		||||
          helpers,
 | 
			
		||||
          partials;
 | 
			
		||||
 | 
			
		||||
      if (!options.partial) {
 | 
			
		||||
        helpers = options.helpers;
 | 
			
		||||
        partials = options.partials;
 | 
			
		||||
      }
 | 
			
		||||
      var result = templateSpec.call(
 | 
			
		||||
            container,
 | 
			
		||||
            namespace, context,
 | 
			
		||||
            helpers,
 | 
			
		||||
            partials,
 | 
			
		||||
            options.data);
 | 
			
		||||
 | 
			
		||||
      if (!options.partial) {
 | 
			
		||||
        env.VM.checkRevision(container.compilerInfo);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return result;
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  __exports__.template = template;function programWithDepth(i, fn, data /*, $depth */) {
 | 
			
		||||
    var args = Array.prototype.slice.call(arguments, 3);
 | 
			
		||||
 | 
			
		||||
    var prog = function(context, options) {
 | 
			
		||||
      options = options || {};
 | 
			
		||||
 | 
			
		||||
      return fn.apply(this, [context, options.data || data].concat(args));
 | 
			
		||||
    };
 | 
			
		||||
    prog.program = i;
 | 
			
		||||
    prog.depth = args.length;
 | 
			
		||||
    return prog;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  __exports__.programWithDepth = programWithDepth;function program(i, fn, data) {
 | 
			
		||||
    var prog = function(context, options) {
 | 
			
		||||
      options = options || {};
 | 
			
		||||
 | 
			
		||||
      return fn(context, options.data || data);
 | 
			
		||||
    };
 | 
			
		||||
    prog.program = i;
 | 
			
		||||
    prog.depth = 0;
 | 
			
		||||
    return prog;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  __exports__.program = program;function invokePartial(partial, name, context, helpers, partials, data) {
 | 
			
		||||
    var options = { partial: true, helpers: helpers, partials: partials, data: data };
 | 
			
		||||
 | 
			
		||||
    if(partial === undefined) {
 | 
			
		||||
      throw new Exception("The partial " + name + " could not be found");
 | 
			
		||||
    } else if(partial instanceof Function) {
 | 
			
		||||
      return partial(context, options);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  __exports__.invokePartial = invokePartial;function noop() { return ""; }
 | 
			
		||||
 | 
			
		||||
  __exports__.noop = noop;
 | 
			
		||||
  return __exports__;
 | 
			
		||||
})(__module2__, __module4__, __module1__);
 | 
			
		||||
 | 
			
		||||
// handlebars.runtime.js
 | 
			
		||||
var __module0__ = (function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__) {
 | 
			
		||||
  "use strict";
 | 
			
		||||
  var __exports__;
 | 
			
		||||
  /*globals Handlebars: true */
 | 
			
		||||
  var base = __dependency1__;
 | 
			
		||||
 | 
			
		||||
  // Each of these augment the Handlebars object. No need to setup here.
 | 
			
		||||
  // (This is done to easily share code between commonjs and browse envs)
 | 
			
		||||
  var SafeString = __dependency2__;
 | 
			
		||||
  var Exception = __dependency3__;
 | 
			
		||||
  var Utils = __dependency4__;
 | 
			
		||||
  var runtime = __dependency5__;
 | 
			
		||||
 | 
			
		||||
  // For compatibility and usage outside of module systems, make the Handlebars object a namespace
 | 
			
		||||
  var create = function() {
 | 
			
		||||
    var hb = new base.HandlebarsEnvironment();
 | 
			
		||||
 | 
			
		||||
    Utils.extend(hb, base);
 | 
			
		||||
    hb.SafeString = SafeString;
 | 
			
		||||
    hb.Exception = Exception;
 | 
			
		||||
    hb.Utils = Utils;
 | 
			
		||||
 | 
			
		||||
    hb.VM = runtime;
 | 
			
		||||
    hb.template = function(spec) {
 | 
			
		||||
      return runtime.template(spec, hb);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return hb;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  var Handlebars = create();
 | 
			
		||||
  Handlebars.create = create;
 | 
			
		||||
 | 
			
		||||
  __exports__ = Handlebars;
 | 
			
		||||
  return __exports__;
 | 
			
		||||
})(__module1__, __module3__, __module4__, __module2__, __module5__);
 | 
			
		||||
 | 
			
		||||
  return __module0__;
 | 
			
		||||
})();
 | 
			
		||||
@@ -1,11 +1,15 @@
 | 
			
		||||
{% extends "zerver/portico.html" %}
 | 
			
		||||
{% block portico_content %}
 | 
			
		||||
{% extends "zerver/base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block customhead %}
 | 
			
		||||
{% stylesheet 'portico' %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="app portico-page">
 | 
			
		||||
 | 
			
		||||
{{ minified_js('stats')|safe }}
 | 
			
		||||
{% stylesheet 'stats' %}
 | 
			
		||||
 | 
			
		||||
<html>
 | 
			
		||||
<body>
 | 
			
		||||
<div class="sidebar">
 | 
			
		||||
    <nav class="nav">
 | 
			
		||||
        <p class="nav-subtitle">Messages Sent</p>
 | 
			
		||||
@@ -18,7 +22,7 @@
 | 
			
		||||
</div>
 | 
			
		||||
<div class="page-content">
 | 
			
		||||
  <div id="id_stats_errors" class="alert alert-error"></div>
 | 
			
		||||
  <h1 class="analytics-page-header">Analytics</h1>
 | 
			
		||||
  <h1 class="analytics-page-header">Zulip Analytics for {{ realm_name }}</h1>
 | 
			
		||||
  <div class="center-container">
 | 
			
		||||
    <div class="center-block">
 | 
			
		||||
      <p class="graph-title" id="messages_timescale_anchor">Messages Sent Over Time</p>
 | 
			
		||||
@@ -79,7 +83,6 @@
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +1,2 @@
 | 
			
		||||
ZULIP_VERSION = "1.5.0"
 | 
			
		||||
ZULIP_VERSION = "1.5.2"
 | 
			
		||||
PROVISION_VERSION = '4.4'
 | 
			
		||||
 
 | 
			
		||||
@@ -407,6 +407,29 @@ class InviteUserTest(ZulipTestCase):
 | 
			
		||||
        self.assertTrue(find_key_by_email(email2))
 | 
			
		||||
        self.check_sent_emails([email, email2])
 | 
			
		||||
 | 
			
		||||
    def test_require_realm_admin(self):
 | 
			
		||||
        # type: () -> None
 | 
			
		||||
        """
 | 
			
		||||
        The invite_by_admins_only realm setting works properly.
 | 
			
		||||
        """
 | 
			
		||||
        realm = get_realm('zulip')
 | 
			
		||||
        realm.invite_by_admins_only = True
 | 
			
		||||
        realm.save()
 | 
			
		||||
 | 
			
		||||
        self.login("hamlet@zulip.com")
 | 
			
		||||
        email = "alice-test@zulip.com"
 | 
			
		||||
        email2 = "bob-test@zulip.com"
 | 
			
		||||
        invitee = "Alice Test <{}>, {}".format(email, email2)
 | 
			
		||||
        self.assert_json_error(self.invite(invitee, ["Denmark"]),
 | 
			
		||||
                               "Must be a realm administrator")
 | 
			
		||||
 | 
			
		||||
        # Now verify an administrator can do it
 | 
			
		||||
        self.login("iago@zulip.com")
 | 
			
		||||
        self.assert_json_success(self.invite(invitee, ["Denmark"]))
 | 
			
		||||
        self.assertTrue(find_key_by_email(email))
 | 
			
		||||
        self.assertTrue(find_key_by_email(email2))
 | 
			
		||||
        self.check_sent_emails([email, email2])
 | 
			
		||||
 | 
			
		||||
    def test_invite_user_signup_initial_history(self):
 | 
			
		||||
        # type: () -> None
 | 
			
		||||
        """
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,8 @@ import re
 | 
			
		||||
@has_request_variables
 | 
			
		||||
def json_invite_users(request, user_profile, invitee_emails_raw=REQ("invitee_emails")):
 | 
			
		||||
    # type: (HttpRequest, UserProfile, str) -> HttpResponse
 | 
			
		||||
    if user_profile.realm.invite_by_admins_only and not user_profile.is_realm_admin:
 | 
			
		||||
        return json_error(_("Must be a realm administrator"))
 | 
			
		||||
    if not invitee_emails_raw:
 | 
			
		||||
        return json_error(_("You must specify at least one email address."))
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -788,7 +788,7 @@ JS_SPECS = {
 | 
			
		||||
            'third/sockjs/sockjs-0.3.4.js',
 | 
			
		||||
            'node_modules/string.prototype.codepointat/codepointat.js',
 | 
			
		||||
            'node_modules/winchan/winchan.js',
 | 
			
		||||
            'third/handlebars/handlebars.runtime.js',
 | 
			
		||||
            'node_modules/handlebars/dist/handlebars.runtime.js',
 | 
			
		||||
            'third/marked/lib/marked.js',
 | 
			
		||||
            'generated/emoji/emoji_codes.js',
 | 
			
		||||
            'templates/compiled.js',
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user