From c0768bf32a5bdccb8b50ac67c922e8c57b678dc9 Mon Sep 17 00:00:00 2001
From: Benaka Moorthi <benaka.moorthi@gmail.com>
Date: Wed, 18 Sep 2013 00:13:20 -0400
Subject: [PATCH] Refs #4041, #3317, allow visualization ID to be whole class
 name, allow footer icons to be customized per report/visualization, made
 visitor log a new visualization and removed the datatable_template display
 property.

---
 core/DataTableVisualization.php               |   4 +-
 core/ViewDataTable.php                        |  26 +-
 core/ViewDataTable/Properties.php             |  41 ++-
 core/Visualization/Graph.php                  |   1 +
 plugins/CoreHome/javascripts/dataTable.js     |  14 +-
 plugins/CoreHome/templates/_dataTable.twig    |   2 +-
 .../Visualizations/Cloud.php                  |   1 +
 plugins/Live/Live.php                         |  28 +-
 plugins/Live/VisitorLog.php                   |  51 ++++
 plugins/Live/javascripts/visitorLog.js        |  77 ++++++
 plugins/Live/stylesheets/live.less            |   8 +-
 .../templates/_dataTableViz_visitorLog.twig   | 187 +++++++++++++
 plugins/Live/templates/getVisitorLog.twig     | 256 ------------------
 plugins/Referers/Referers.php                 |   1 +
 plugins/UserCountry/UserCountry.php           |   1 +
 plugins/UserSettings/UserSettings.php         |   3 +
 plugins/VisitTime/VisitTime.php               |   1 +
 plugins/VisitorInterest/VisitorInterest.php   |   6 +-
 18 files changed, 409 insertions(+), 299 deletions(-)
 create mode 100644 plugins/Live/VisitorLog.php
 create mode 100644 plugins/Live/javascripts/visitorLog.js
 create mode 100644 plugins/Live/templates/_dataTableViz_visitorLog.twig
 delete mode 100644 plugins/Live/templates/getVisitorLog.twig

diff --git a/core/DataTableVisualization.php b/core/DataTableVisualization.php
index 83a6021891..bfab67dfd6 100644
--- a/core/DataTableVisualization.php
+++ b/core/DataTableVisualization.php
@@ -37,7 +37,7 @@ abstract class DataTableVisualization
      * @param array $properties The view properties.
      * @return string The visualization HTML.
      */
-    public abstract function render($dataTable, $properties);
+    //public abstract function render($dataTable, $properties); temporarily commented out
 
     /**
      * Default implementation of getDefaultPropertyValues static function.
@@ -106,7 +106,7 @@ abstract class DataTableVisualization
         if (defined('static::ID')) {
             return static::ID;
         } else {
-            return Piwik::getUnnamespacedClassName($this);
+            return get_called_class();
         }
     }
 
diff --git a/core/ViewDataTable.php b/core/ViewDataTable.php
index 1acc5a5abb..7174d86594 100644
--- a/core/ViewDataTable.php
+++ b/core/ViewDataTable.php
@@ -128,7 +128,14 @@ class ViewDataTable
                                 $viewProperties = array(),
                                 $visualizationId = null)
     {
-        $visualizationClass = $visualizationId ? DataTableVisualization::getClassFromId($visualizationId) : null;
+        if (class_exists($visualizationId)
+            && is_subclass_of($visualizationId, "Piwik\\DataTableVisualization")
+        ) {
+            $visualizationClass = $visualizationId;
+        } else {
+            $visualizationClass = $visualizationId ? DataTableVisualization::getClassFromId($visualizationId) : null;
+        }
+
         $this->visualizationClass = $visualizationClass;
 
         list($currentControllerName, $currentControllerAction) = explode('.', $currentControllerAction);
@@ -764,17 +771,6 @@ class ViewDataTable
             $javascriptVariablesToSet['totalRows'] = $this->dataTable->getRowsCountBeforeLimitFilter();
         }
 
-        // we escape the values that will be displayed in the javascript footer of each datatable
-        // to make sure there is no malicious code injected (the value are already htmlspecialchar'ed as they
-        // are loaded with Common::getRequestVar()
-        foreach ($javascriptVariablesToSet as &$value) {
-            if (is_array($value)) {
-                $value = array_map('addslashes', $value);
-            } else {
-                $value = addslashes($value);
-            }
-        }
-
         $deleteFromJavascriptVariables = array(
             'filter_excludelowpop',
             'filter_excludelowpop_value',
@@ -1044,8 +1040,7 @@ class ViewDataTable
             $loadingError = array('message' => $e->getMessage());
         }
 
-        $template = $this->viewProperties['datatable_template'];
-        $view = new View($template);
+        $view = new View("@CoreHome/_dataTable");
 
         if (!empty($loadingError)) {
             $view->error = $loadingError;
@@ -1074,7 +1069,8 @@ class ViewDataTable
         $view->javascriptVariablesToSet = $this->getJavascriptVariablesToSet();
         $view->clientSidePropertiesToSet = $this->getClientSidePropertiesToSet();
         $view->properties = $this->viewProperties; // TODO: should be $this. need to move non-view properties from the class
-        $view->footerIcons = $this->getFooterIconsToShow();
+        $view->footerIcons = $this->viewProperties['footer_icons'] ?: $this->getFooterIconsToShow();
+        $view->isWidget = Common::getRequestVar('widget', 0, 'int');
 
         $this->view = $view;
     }
diff --git a/core/ViewDataTable/Properties.php b/core/ViewDataTable/Properties.php
index 4fca7d89e3..4c17672b7c 100644
--- a/core/ViewDataTable/Properties.php
+++ b/core/ViewDataTable/Properties.php
@@ -38,11 +38,34 @@ class Properties
     const DEFAULT_VIEW_TYPE = 'default_view_type';
 
     /**
-     * This property determines which Twig template to use when rendering a ViewDataTable.
-     *
-     * TODO: shouldn't have this property. should only use visualization classes.
-     */
-    const DATATABLE_TEMPLATE = 'datatable_template';
+     * Controls what footer icons are displayed on the bottom left of the DataTable view.
+     * The value of this property must be an array of footer icon groups. Footer icon groups
+     * have set of properties, including an array of arrays describing footer icons. See
+     * this example to get a clear idea of what is required:
+     * 
+     * array(
+     *     array( // footer icon group 1
+     *         'class' => 'footerIconGroup1CssClass',
+     *         'buttons' => array(
+     *             'id' => 'myid',
+     *             'title' => 'My Tooltip',
+     *             'icon' => 'path/to/my/icon.png'
+     *         )
+     *     ),
+     *     array( // footer icon group 2
+     *         'class' => 'footerIconGroup2CssClass',
+     *         'buttons' => array(...)
+     *     )
+     * )
+     * 
+     * By default, when a user clicks on a footer icon, Piwik will assume the 'id' is
+     * a viewDataTable ID and try to reload the DataTable w/ the new viewDataTable. You
+     * can provide your own footer icon behavior by adding an appropriate handler via
+     * DataTable.registerFooterIconHandler in your JavaScript.
+     * 
+     * Default value: // TODO
+     */
+    const FOOTER_ICONS = 'footer_icons';
 
     /**
      * Controls whether the buttons and UI controls around the visualization or shown or
@@ -412,7 +435,9 @@ class Properties
      * 
      * @see Piwik\DataTableVisualization::getClientSideProperties
      */
-    public static $clientSideProperties = array();
+    public static $clientSideProperties = array(
+        'show_limit_control'
+    );
 
     /**
      * The list of ViewDataTable properties that can be overriden by query parameters.
@@ -561,7 +586,7 @@ class Properties
     public static function getDefaultPropertyValues()
     {
         $result = array(
-            'datatable_template' => '@CoreHome/_dataTable',
+            'footer_icons' => false,
             'show_visualization_only' => false,
             'datatable_js_type' => 'DataTable',
             'show_goals' => false,
@@ -580,7 +605,7 @@ class Properties
             'show_flatten_table' => true,
             'show_offset_information' => true,
             'show_pagination_control' => true,
-            'show_limit_control' => false,
+            'show_limit_control' => true,
             'show_footer' => true,
             'show_footer_icons' => true,
             'show_related_reports' => true,
diff --git a/core/Visualization/Graph.php b/core/Visualization/Graph.php
index adc3cebbf1..46a50c4919 100644
--- a/core/Visualization/Graph.php
+++ b/core/Visualization/Graph.php
@@ -132,6 +132,7 @@ abstract class Graph extends DataTableVisualization
     public static function getDefaultPropertyValues()
     {
         return array(
+            'show_limit_control' => false,
             'visualization_properties' => array(
                 'graph' => array(
                     'add_total_row' => 0,
diff --git a/plugins/CoreHome/javascripts/dataTable.js b/plugins/CoreHome/javascripts/dataTable.js
index 5409fd877b..275e81c86c 100644
--- a/plugins/CoreHome/javascripts/dataTable.js
+++ b/plugins/CoreHome/javascripts/dataTable.js
@@ -289,12 +289,7 @@ $.extend(DataTable.prototype, UIControl.prototype, {
         // setup limit control
         $('.limitSelection', domElem).append('<div><span>' + self.param[limitParamName] + '</span></div><ul></ul>');
 
-        if (self.param.viewDataTable == 'table'
-            || self.param.viewDataTable == 'tableAllColumns'
-            || self.param.viewDataTable == 'tableGoals'
-            || self.param.viewDataTable == 'ecommerceOrder'
-            || self.param.viewDataTable == 'ecommerceAbandonedCart'
-            || self.param.viewDataTable == 'graphEvolution') {
+        if (self.props.show_limit_control) {
             $('.limitSelection ul', domElem).hide();
             for (var i = 0; i < numbers.length; i++) {
                 $('.limitSelection ul', domElem).append('<li value="' + numbers[i] + '"><span>' + numbers[i] + '</span></li>');
@@ -734,7 +729,12 @@ $.extend(DataTable.prototype, UIControl.prototype, {
                 return;
             }
             
-            DataTable._footerIconHandlers[id](self, id);
+            var handler = DataTable._footerIconHandlers[id];
+            if (!handler) {
+                handler = DataTable._footerIconHandlers['table'];
+            }
+
+            handler(self, id);
         })
 
         //Graph icon Collapsed functionality
diff --git a/plugins/CoreHome/templates/_dataTable.twig b/plugins/CoreHome/templates/_dataTable.twig
index aa039d7854..e98ff87591 100644
--- a/plugins/CoreHome/templates/_dataTable.twig
+++ b/plugins/CoreHome/templates/_dataTable.twig
@@ -26,7 +26,7 @@
                 {% endif %}
                 </div>
             {% else %}
-                {{ visualization.render(dataTable, properties)|raw }}
+                {{ visualization.render(dataTable, properties, javascriptVariablesToSet)|raw }}
             {% endif %}
 
             {% if properties.show_footer %}
diff --git a/plugins/CoreVisualizations/Visualizations/Cloud.php b/plugins/CoreVisualizations/Visualizations/Cloud.php
index d2a45349bc..37cd117b5d 100644
--- a/plugins/CoreVisualizations/Visualizations/Cloud.php
+++ b/plugins/CoreVisualizations/Visualizations/Cloud.php
@@ -47,6 +47,7 @@ class Cloud extends DataTableVisualization
         return array(
             'show_offset_information' => false,
             'show_exclude_low_population' => false,
+            'show_limit_control' => false,
             'visualization_properties' => array(
                 'cloud' => array(
                     'display_logo_instead_of_label' => false,
diff --git a/plugins/Live/Live.php b/plugins/Live/Live.php
index 4f30172753..d23a992856 100644
--- a/plugins/Live/Live.php
+++ b/plugins/Live/Live.php
@@ -61,6 +61,7 @@ class Live extends \Piwik\Plugin
     {
         $jsFiles[] = "plugins/Live/javascripts/live.js";
         $jsFiles[] = "plugins/Live/javascripts/visitorProfile.js";
+        $jsFiles[] = "plugins/Live/javascripts/visitorLog.js";
     }
 
     public function addMenu()
@@ -82,6 +83,7 @@ class Live extends \Piwik\Plugin
         $translationKeys[] = "Live_NoMoreVisits";
         $translationKeys[] = "Live_ShowMap";
         $translationKeys[] = "Live_HideMap";
+        $translationKeys[] = "Live_PageRefreshed";
     }
 
     public function getReportDisplayProperties(&$properties)
@@ -92,7 +94,7 @@ class Live extends \Piwik\Plugin
     private function getDisplayPropertiesForGetLastVisitsDetails()
     {
         return array(
-            'datatable_template'          => "@Live/getVisitorLog.twig",
+            'default_view_type'           => 'Piwik\\Plugins\\Live\\VisitorLog',
             'disable_generic_filters'     => true,
             'enable_sort'                 => false,
             'filter_sort_column'          => 'idVisit',
@@ -101,18 +103,30 @@ class Live extends \Piwik\Plugin
             'filter_limit'                => 20,
             'show_offset_information'     => false,
             'show_exclude_low_population' => false,
-            'show_all_views_icons' => false,
-            'show_table_all_columns' => false,
-            'show_export_as_rss_feed' => false,
-            'documentation' => Piwik_Translate('Live_VisitorLogDocumentation', array('<br />', '<br />')),
-            'custom_parameters' => array(
+            'show_all_views_icons'        => false,
+            'show_table_all_columns'      => false,
+            'show_export_as_rss_feed'     => false,
+            'documentation'               => Piwik_Translate('Live_VisitorLogDocumentation', array('<br />', '<br />')),
+            'custom_parameters'           => array(
                 // set a very high row count so that the next link in the footer of the data table is always shown
                 'totalRows'         => 10000000,
 
                 'filterEcommerce'   => Common::getRequestVar('filterEcommerce', 0, 'int'),
                 'pageUrlNotDefined' => Piwik_Translate('General_NotDefined', Piwik_Translate('Actions_ColumnPageURL'))
             ),
-            'visualization_properties' => array(
+            'footer_icons'                => array(
+                array(
+                    'class' => 'tableAllColumnsSwitch',
+                    'buttons' => array(
+                        array(
+                            'id' => 'Piwik\\Plugins\\Live\\VisitorLog',
+                            'title' => Piwik_Translate('Live_LinkVisitorLog'),
+                            'icon' => 'plugins/Zeitgeist/images/table.png'
+                        )
+                    )
+                )
+            ),
+            'visualization_properties'    => array(
                 'table' => array(
                     'disable_row_actions' => true,
                 )
diff --git a/plugins/Live/VisitorLog.php b/plugins/Live/VisitorLog.php
new file mode 100644
index 0000000000..daa09af391
--- /dev/null
+++ b/plugins/Live/VisitorLog.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ * @category Piwik_Plugins
+ * @package Live
+ */
+namespace Piwik\Plugins\Live;
+
+use Piwik\View;
+use Piwik\DataTableVisualization;
+
+/**
+ * A special DataTable visualization for the Live.getLastVisitsDetails API method.
+ */
+class VisitorLog extends DataTableVisualization
+{
+    static public $clientSideParameters = array(
+        'filter_limit',
+        'filter_offset',
+        'filter_sort_column',
+        'filter_sort_order',
+    );
+
+    /**
+     * Constructor.
+     */
+    public function __construct($view)
+    {
+        $view->datatable_js_type = 'VisitorLog';
+    }
+
+    /**
+     * Renders this visualization.
+     *
+     * @param DataTable $dataTable
+     * @param array $properties View Properties.
+     * @return string
+     */
+    public function render($dataTable, $properties, $javascriptVariablesToSet)
+    {
+        $view = new View("@Live/_dataTableViz_visitorLog.twig");
+        $view->properties = $properties;
+        $view->dataTable = $dataTable;
+        $view->javascriptVariablesToSet = $javascriptVariablesToSet;
+        return $view->render();
+    }
+}
\ No newline at end of file
diff --git a/plugins/Live/javascripts/visitorLog.js b/plugins/Live/javascripts/visitorLog.js
new file mode 100644
index 0000000000..81f5db69c4
--- /dev/null
+++ b/plugins/Live/javascripts/visitorLog.js
@@ -0,0 +1,77 @@
+/**
+ * Piwik - Web Analytics
+ *
+ * Visitor profile popup control.
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+(function ($, require) {
+
+    var exports = require('piwik/UI'),
+        DataTable = exports.DataTable,
+        dataTablePrototype = DataTable.prototype;
+
+    /**
+     * DataTable UI class for jqPlot graph datatable visualizations.
+     * 
+     * @constructor
+     */
+    exports.VisitorLog = function (element) {
+        DataTable.call(this, element);
+    };
+
+    $.extend(exports.VisitorLog.prototype, dataTablePrototype, {
+
+        /**
+         * Initializes this class.
+         */
+        init: function () {
+            dataTablePrototype.init.call(this);
+
+            // Replace duplicated page views by a NX count instead of using too much vertical space
+            $("ol.visitorLog").each(function () {
+                var prevelement;
+                var prevhtml;
+                var counter = 0;
+                $(this).find("li").each(function () {
+                    counter++;
+                    $(this).val(counter);
+                    var current = $(this).html();
+                    if (current == prevhtml) {
+                        var repeat = prevelement.find(".repeat");
+                        if (repeat.length) {
+                            repeat.html((parseInt(repeat.html()) + 1) + "x");
+                        } else {
+                            prevelement.append($("<em>2x</em>").attr({'class': 'repeat', 'title': _pk_translate('Live_PageRefreshed')}));
+                        }
+                        $(this).hide();
+                    } else {
+                        prevhtml = current;
+                        prevelement = $(this);
+                    }
+                    
+                    $(this).tooltip({
+                        track: true,
+                        show: false,
+                        hide: false,
+                        content: function() {
+                            var title = $(this).attr('title');
+                            return $('<a>').text( title ).html().replace(/\n/g, '<br />');
+                        },
+                        tooltipClass: 'small'
+                    });
+                });
+            });
+
+            // launch visitor profile on visitor profile link click
+            this.$element.on('click', '.visitor-log-visitor-profile-link', function (e) {
+                e.preventDefault();
+                broadcast.propagateNewPopoverParameter('visitorProfile', $(this).attr('data-visitor-id'));
+                return false;
+            });
+        },
+    });
+
+})(jQuery, require);
\ No newline at end of file
diff --git a/plugins/Live/stylesheets/live.less b/plugins/Live/stylesheets/live.less
index 46b844dd76..681f7cbd3f 100644
--- a/plugins/Live/stylesheets/live.less
+++ b/plugins/Live/stylesheets/live.less
@@ -54,7 +54,7 @@
     line-height: 2.5em;
 }
 
-.visitorLog table img {
+.dataTableVizVisitorLog table img {
     margin: 0 3px 0 0;
 }
 
@@ -128,7 +128,7 @@ ol.visitorLog li {
     padding: 2px;
 }
 
-.visitorLog hr {
+.dataTableVizVisitorLog hr {
     background: none repeat scroll 0 0 transparent;
     border: 0 none #000;
     border-bottom: 1px solid #ccc;
@@ -169,4 +169,8 @@ ol.visitorLog li {
 ol.visitorLog p {
     margin:0;
     padding:0;
+}
+
+.dataTableVizVisitorLog .dataTableWrapper {
+    width:100%;
 }
\ No newline at end of file
diff --git a/plugins/Live/templates/_dataTableViz_visitorLog.twig b/plugins/Live/templates/_dataTableViz_visitorLog.twig
new file mode 100644
index 0000000000..5ca71b0c36
--- /dev/null
+++ b/plugins/Live/templates/_dataTableViz_visitorLog.twig
@@ -0,0 +1,187 @@
+{% set displayVisitorsInOwnColumn %}{% if isWidget %}0{% else %}1{% endif %}{% endset %}
+<table class="dataTable" cellspacing="0" width="100%" style="width:100%;table-layout:fixed;">
+<thead>
+<tr>
+    <th style="display:none;"></th>
+    <th id="label" class="sortable label" style="cursor: auto;width:190px;" width="190px">
+        <div id="thDIV">{{ 'General_Date'|translate }}
+            <div>
+    </th>
+    {% if displayVisitorsInOwnColumn %}
+        <th id="label" class="sortable label" style="cursor: auto;width:225px;" width="225px">
+            <div id="thDIV">{{ 'General_Visitors'|translate }}
+                <div>
+        </th>
+    {% endif %}
+    <th id="label" class="sortable label" style="cursor: auto;width:230px;" width="230px">
+        <div id="thDIV">{{ 'Live_Referrer_URL'|translate }}
+            <div>
+    </th>
+    <th id="label" class="sortable label" style="cursor: auto;">
+        <div id="thDIV">{{ 'General_ColumnNbActions'|translate }}
+            <div>
+    </th>
+</tr>
+</thead>
+<tbody>
+{% set cycleIndex=0 %}
+{% for visitor in dataTable.getRows() %}
+    {% set visitorColumnContent %}
+        &nbsp;
+        <img src="{{ visitor.getColumn('countryFlag') }}" title="{{ visitor.getColumn('location') }}, Provider {{ visitor.getColumn('providerName') }}"/>
+        &nbsp;
+        {% if visitor.getColumn('plugins') %}
+        <img src="{{ visitor.getColumn('browserIcon') }}" title="{{ 'UserSettings_BrowserWithPluginsEnabled'|translate(visitor.getColumn('browserName'),visitor.getColumn('plugins')) }}"/>
+        {% else %}
+        <img src="{{ visitor.getColumn('browserIcon') }}" title="{{ 'UserSettings_BrowserWithNoPluginsEnabled'|translate(visitor.getColumn('browserName')) }}"/>
+        {% endif %}
+        &nbsp;
+        <img src="{{ visitor.getColumn('operatingSystemIcon') }}"
+             title="{{ visitor.getColumn('operatingSystem') }}, {{ visitor.getColumn('resolution') }} ({{ visitor.getColumn('screenType') }})"/>
+        {% if visitor.getColumn('visitorTypeIcon') %}
+            &nbsp;-
+            <img src="{{ visitor.getColumn('visitorTypeIcon') }}"
+                 title="{{ 'General_ReturningVisitor'|translate }}{% if visitor.getColumn('visitorId') is not empty %}{% endif %}"/>
+            {% if visitor.getColumn('visitorId') is not empty %}
+            <a class="rightLink" title="{{ 'Live_ViewVisitorProfile'|translate }}" data-visitor-id="{{ visitor.getColumn("visitorId") }}" class="visitor-log-visitor-profile-link">
+                <img src="plugins/Live/images/visitorProfileLaunch.png"/>
+            </a>
+            {% endif %}
+        {% endif %}
+
+        {% if not displayVisitorsInOwnColumn %}<br/><br/>{% endif %}
+        &nbsp;
+        {% if visitor.getColumn('visitConverted') %}
+            <span title="{{ 'General_VisitConvertedNGoals'|translate(visitor.getColumn('goalConversions')) }}" class='visitorRank'
+                {% if not displayVisitorsInOwnColumn %}style='margin-left:0;'{% endif %}>
+            <img src="{{ visitor.getColumn('visitConvertedIcon') }}"/>
+            <span class='hash'>#</span>
+            {{ visitor.getColumn('goalConversions') }}
+            {% if visitor.getColumn('visitEcommerceStatusIcon') %}
+                &nbsp;-
+                <img src="{{ visitor.getColumn('visitEcommerceStatusIcon') }}" title="{{ visitor.getColumn('visitEcommerceStatus') }}"/>
+            {% endif %}
+            </span>
+        {% endif %}
+        <br/>
+        {% if displayVisitorsInOwnColumn %}
+            {% if visitor.getColumn('pluginsIcons')|length > 0 %}
+                <hr/>
+                {{ 'General_Plugins'|translate }}:
+                {% for pluginIcon in visitor.getColumn('pluginsIcons') %}
+                    <img src="{{ pluginIcon.pluginIcon }}" title="{{ pluginIcon.pluginName|capitalize(true) }}" alt="{{ pluginIcon.pluginName|capitalize(true) }}"/>
+                {% endfor %}
+            {% endif %}
+        {% endif %}
+    {% endset %}
+
+    {% set visitorRow %}
+        <tr class="label{{ cycle(['odd','even'], cycleIndex) }}">
+        {% set cycleIndex=cycleIndex+1 %}
+            <td style="display:none;"></td>
+            <td class="label">
+                <strong title="{% if visitor.getColumn('visitorType')=='new' %}{{ 'General_NewVisitor'|translate }}{% else %}{{ 'Live_VisitorsLastVisit'|translate(visitor.getColumn('daysSinceLastVisit')) }}{% endif %}">
+                    {{ visitor.getColumn('serverDatePrettyFirstAction') }}
+                    {% if isWidget %}<br/>{% else %}-{% endif %} {{ visitor.getColumn('serverTimePrettyFirstAction') }}</strong>
+                {% if visitor.getColumn('visitIp') is not empty %}
+                    <br/>
+                <span title="{% if visitor.getColumn('visitorId') is not empty %}{{ 'General_VisitorID'|translate }}: {{ visitor.getColumn('visitorId') }}{% endif -%}
+                {%- if visitor.getColumn('latitude') or visitor.getColumn('longitude') %}
+
+GPS (lat/long): {{ visitor.getColumn('latitude') }},{{ visitor.getColumn('longitude') }}{% endif %}">
+                    IP: {{ visitor.getColumn('visitIp') }}</span>{% endif %}
+
+                {% if visitor.getColumn('provider') and visitor.getColumn('providerName')!='IP' %}
+                    <br/>
+                    {{ 'Provider_ColumnProvider'|translate }}:
+                    <a href="{{ visitor.getColumn('providerUrl') }}" target="_blank" title="{{ visitor.getColumn('providerUrl') }}" style="text-decoration:underline;">
+                        {{ visitor.getColumn('providerName') }}
+                    </a>
+                {% endif %}
+                {% if visitor.getColumn('customVariables') %}
+                    <br/>
+                    {% for id,customVariable in visitor.getColumn('customVariables') %}
+                        {% set name='customVariableName' ~ id %}
+                        {% set value='customVariableValue' ~ id %}
+                        <br/>
+                        <acronym title="{{ 'CustomVariables_CustomVariables'|translate }} (index {{ id }})">
+                            {{ customVariable[name]|truncate(30) }}
+                        </acronym>
+                    {% if customVariable[value]|length > 0 %}: {{ customVariable[value]|truncate(50) }}{% endif %}
+                    {% endfor %}
+                {% endif %}
+                {% if not displayVisitorsInOwnColumn %}
+                    <br/>
+                    {{ visitorColumnContent }}
+                {% endif %}
+            </td>
+
+            {% if displayVisitorsInOwnColumn %}
+                <td class="label">
+                    {{ visitorColumnContent }}
+                </td>
+            {% endif %}
+
+            <td class="column">
+                <div class="referer">
+                    {% if visitor.getColumn('referrerType') == 'website' %}
+                        {{ 'Referers_ColumnWebsite'|translate }}:
+                        <a href="{{ visitor.getColumn('referrerUrl') }}" target="_blank" title="{{ visitor.getColumn('referrerUrl') }}"
+                           style="text-decoration:underline;">
+                            {{ visitor.getColumn('referrerName') }}
+                        </a>
+                    {% endif %}
+                    {% if visitor.getColumn('referrerType') == 'campaign' %}
+                        {{ 'Referers_ColumnCampaign'|translate }}
+                        <br/>
+                        {{ visitor.getColumn('referrerName') }}
+                        {% if visitor.getColumn('referrerKeyword') is not empty %} - {{ visitor.getColumn('referrerKeyword') }}{% endif %}
+                    {% endif %}
+                    {% if visitor.getColumn('referrerType') == 'search' %}
+                        {% if visitor.getColumn('searchEngineIcon') %}
+                            <img src="{{ visitor.getColumn('searchEngineIcon') }}" alt="{{ visitor.getColumn('referrerName') }}"/>
+                        {% endif %}
+                        {{ visitor.getColumn('referrerName') }}
+                        {% if visitor.getColumn('referrerKeyword') is not empty %}{{ 'Referers_Keywords'|translate }}:
+                            <br/>
+                            <a href="{{ visitor.getColumn('referrerUrl') }}" target="_blank" style="text-decoration:underline;">
+                                "{{ visitor.getColumn('referrerKeyword') }}"</a>
+                        {% endif %}
+                        {% set keyword %}{{ visitor.getColumn('referrerKeyword') }}{% endset %}
+                        {% set searchName %}{{ visitor.getColumn('referrerName') }}{% endset %}
+                        {% set position %}#{{ visitor.getColumn('referrerKeywordPosition') }}{% endset %}
+                        {% if visitor.getColumn('referrerKeywordPosition') %}
+                            <span title='{{ 'Live_KeywordRankedOnSearchResultForThisVisitor'|translate(keyword,position,searchName) }}' class='visitorRank'>
+                                <span class='hash'>#</span>
+                                {{ visitor.getColumn('referrerKeywordPosition') }}
+                            </span>
+                        {% endif %}
+                    {% endif %}
+                    {% if visitor.getColumn('referrerType') == 'direct' %}{{ 'Referers_DirectEntry'|translate }}{% endif %}
+                </div>
+            </td>
+            <td class="column {% if visitor.getColumn('visitConverted') and not isWidget %}highlightField{% endif %}">
+                <strong>
+                    {{ visitor.getColumn('actionDetails')|length }}
+                    {% if visitor.getColumn('actionDetails')|length <= 1 %}
+                        {{ 'General_Action'|translate }}
+                    {% else %}
+                        {{ 'General_Actions'|translate }}
+                    {% endif %}
+                    {% if visitor.getColumn('visitDuration') > 0 %}- {{ visitor.getColumn('visitDurationPretty')|raw }}{% endif %}
+                </strong>
+                <br/>
+                <ol class='visitorLog'>
+                    {% include "@Live/_actionsList.twig" with {'actionDetails': visitor.getColumn('actionDetails')} %}
+                </ol>
+            </td>
+        </tr>
+    {% endset %}
+
+    {% if not javascriptVariablesToSet.filterEcommerce or visitorHasSomeEcommerceActivity is not empty %}
+        {{ visitorRow }}
+    {% endif %}
+{% endfor %}
+
+</tbody>
+</table>
\ No newline at end of file
diff --git a/plugins/Live/templates/getVisitorLog.twig b/plugins/Live/templates/getVisitorLog.twig
deleted file mode 100644
index ab80e9c016..0000000000
--- a/plugins/Live/templates/getVisitorLog.twig
+++ /dev/null
@@ -1,256 +0,0 @@
-<div class="visitorLog dataTable"
-     data-report="{{ properties.report_id }}"
-     data-params="{{ javascriptVariablesToSet|json_encode }}">
-
-{% if properties.documentation|default is not empty %}
-    <div class="reportDocumentation"><p>{{ properties.documentation|raw }}</p></div>
-{% endif %}
-{% set displayVisitorsInOwnColumn %}{% if isWidget %}0{% else %}1{% endif %}{% endset %}
-
-<span data-graph-id="VisitsSummary.getEvolutionGraph"></span>
-
-{% if error is defined %}
-    {{ error.message }}
-{% else %}
-{% if dataTable.getRowsCount() == 0 %}
-    <div class="pk-emptyDataTable">{{ 'CoreHome_ThereIsNoDataForThisReport'|translate }}</div>
-{% else %}
-    <table class="dataTable" cellspacing="0" width="100%" style="width:100%;table-layout:fixed;">
-    <thead>
-    <tr>
-        <th style="display:none;"></th>
-        <th id="label" class="sortable label" style="cursor: auto;width:190px;" width="190px">
-            <div id="thDIV">{{ 'General_Date'|translate }}
-                <div>
-        </th>
-        {% if displayVisitorsInOwnColumn %}
-            <th id="label" class="sortable label" style="cursor: auto;width:225px;" width="225px">
-                <div id="thDIV">{{ 'General_Visitors'|translate }}
-                    <div>
-            </th>
-        {% endif %}
-        <th id="label" class="sortable label" style="cursor: auto;width:230px;" width="230px">
-            <div id="thDIV">{{ 'Live_Referrer_URL'|translate }}
-                <div>
-        </th>
-        <th id="label" class="sortable label" style="cursor: auto;">
-            <div id="thDIV">{{ 'General_ColumnNbActions'|translate }}
-                <div>
-        </th>
-    </tr>
-    </thead>
-    <tbody>
-    {% set cycleIndex=0 %}
-    {% for visitor in dataTable.getRows() %}
-        {% set visitorColumnContent %}
-            &nbsp;
-            <img src="{{ visitor.getColumn('countryFlag') }}" title="{{ visitor.getColumn('location') }}, Provider {{ visitor.getColumn('providerName') }}"/>
-            &nbsp;
-            {% if visitor.getColumn('plugins') %}
-            <img src="{{ visitor.getColumn('browserIcon') }}" title="{{ 'UserSettings_BrowserWithPluginsEnabled'|translate(visitor.getColumn('browserName'),visitor.getColumn('plugins')) }}"/>
-            {% else %}
-            <img src="{{ visitor.getColumn('browserIcon') }}" title="{{ 'UserSettings_BrowserWithNoPluginsEnabled'|translate(visitor.getColumn('browserName')) }}"/>
-            {% endif %}
-            &nbsp;
-            <img src="{{ visitor.getColumn('operatingSystemIcon') }}"
-                 title="{{ visitor.getColumn('operatingSystem') }}, {{ visitor.getColumn('resolution') }} ({{ visitor.getColumn('screenType') }})"/>
-            {% if visitor.getColumn('visitorTypeIcon') %}
-                &nbsp;-
-                <img src="{{ visitor.getColumn('visitorTypeIcon') }}"
-                     title="{{ 'General_ReturningVisitor'|translate }}{% if visitor.getColumn('visitorId') is not empty %}{% endif %}"/>
-                {% if visitor.getColumn('visitorId') is not empty %}
-                <a class="rightLink" title="{{ 'Live_ViewVisitorProfile'|translate }}" href="javascript:Piwik_Live_LoadVisitorPopover('{{ visitor.getColumn("visitorId") }}')">
-                    <img src="plugins/Live/images/visitorProfileLaunch.png"/>
-                </a>
-                {% endif %}
-            {% endif %}
-
-            {% if not displayVisitorsInOwnColumn %}<br/><br/>{% endif %}
-            &nbsp;
-            {% if visitor.getColumn('visitConverted') %}
-                <span title="{{ 'General_VisitConvertedNGoals'|translate(visitor.getColumn('goalConversions')) }}" class='visitorRank'
-                    {% if not displayVisitorsInOwnColumn %}style='margin-left:0;'{% endif %}>
-                <img src="{{ visitor.getColumn('visitConvertedIcon') }}"/>
-                <span class='hash'>#</span>
-                {{ visitor.getColumn('goalConversions') }}
-                {% if visitor.getColumn('visitEcommerceStatusIcon') %}
-                    &nbsp;-
-                    <img src="{{ visitor.getColumn('visitEcommerceStatusIcon') }}" title="{{ visitor.getColumn('visitEcommerceStatus') }}"/>
-                {% endif %}
-                </span>
-            {% endif %}
-            <br/>
-            {% if displayVisitorsInOwnColumn %}
-                {% if visitor.getColumn('pluginsIcons')|length > 0 %}
-                    <hr/>
-                    {{ 'General_Plugins'|translate }}:
-                    {% for pluginIcon in visitor.getColumn('pluginsIcons') %}
-                        <img src="{{ pluginIcon.pluginIcon }}" title="{{ pluginIcon.pluginName|capitalize(true) }}" alt="{{ pluginIcon.pluginName|capitalize(true) }}"/>
-                    {% endfor %}
-                {% endif %}
-            {% endif %}
-        {% endset %}
-
-        {% set visitorRow %}
-            <tr class="label{{ cycle(['odd','even'], cycleIndex) }}">
-            {% set cycleIndex=cycleIndex+1 %}
-                <td style="display:none;"></td>
-                <td class="label">
-                    <strong title="{% if visitor.getColumn('visitorType')=='new' %}{{ 'General_NewVisitor'|translate }}{% else %}{{ 'Live_VisitorsLastVisit'|translate(visitor.getColumn('daysSinceLastVisit')) }}{% endif %}">
-                        {{ visitor.getColumn('serverDatePrettyFirstAction') }}
-                        {% if isWidget %}<br/>{% else %}-{% endif %} {{ visitor.getColumn('serverTimePrettyFirstAction') }}</strong>
-                    {% if visitor.getColumn('visitIp') is not empty %}
-                        <br/>
-                    <span title="{% if visitor.getColumn('visitorId') is not empty %}{{ 'General_VisitorID'|translate }}: {{ visitor.getColumn('visitorId') }}{% endif -%}
-                    {%- if visitor.getColumn('latitude') or visitor.getColumn('longitude') %}
-
-GPS (lat/long): {{ visitor.getColumn('latitude') }},{{ visitor.getColumn('longitude') }}{% endif %}">
-                        IP: {{ visitor.getColumn('visitIp') }}</span>{% endif %}
-
-                    {% if visitor.getColumn('provider') and visitor.getColumn('providerName')!='IP' %}
-                        <br/>
-                        {{ 'Provider_ColumnProvider'|translate }}:
-                        <a href="{{ visitor.getColumn('providerUrl') }}" target="_blank" title="{{ visitor.getColumn('providerUrl') }}" style="text-decoration:underline;">
-                            {{ visitor.getColumn('providerName') }}
-                        </a>
-                    {% endif %}
-                    {% if visitor.getColumn('customVariables') %}
-                        <br/>
-                        {% for id,customVariable in visitor.getColumn('customVariables') %}
-                            {% set name='customVariableName' ~ id %}
-                            {% set value='customVariableValue' ~ id %}
-                            <br/>
-                            <acronym title="{{ 'CustomVariables_CustomVariables'|translate }} (index {{ id }})">
-                                {{ customVariable[name]|truncate(30) }}
-                            </acronym>
-                        {% if customVariable[value]|length > 0 %}: {{ customVariable[value]|truncate(50) }}{% endif %}
-                        {% endfor %}
-                    {% endif %}
-                    {% if not displayVisitorsInOwnColumn %}
-                        <br/>
-                        {{ visitorColumnContent }}
-                    {% endif %}
-                </td>
-
-                {% if displayVisitorsInOwnColumn %}
-                    <td class="label">
-                        {{ visitorColumnContent }}
-                    </td>
-                {% endif %}
-
-                <td class="column">
-                    <div class="referer">
-                        {% if visitor.getColumn('referrerType') == 'website' %}
-                            {{ 'Referers_ColumnWebsite'|translate }}:
-                            <a href="{{ visitor.getColumn('referrerUrl') }}" target="_blank" title="{{ visitor.getColumn('referrerUrl') }}"
-                               style="text-decoration:underline;">
-                                {{ visitor.getColumn('referrerName') }}
-                            </a>
-                        {% endif %}
-                        {% if visitor.getColumn('referrerType') == 'campaign' %}
-                            {{ 'Referers_ColumnCampaign'|translate }}
-                            <br/>
-                            {{ visitor.getColumn('referrerName') }}
-                            {% if visitor.getColumn('referrerKeyword') is not empty %} - {{ visitor.getColumn('referrerKeyword') }}{% endif %}
-                        {% endif %}
-                        {% if visitor.getColumn('referrerType') == 'search' %}
-                            {% if visitor.getColumn('searchEngineIcon') %}
-                                <img src="{{ visitor.getColumn('searchEngineIcon') }}" alt="{{ visitor.getColumn('referrerName') }}"/>
-                            {% endif %}
-                            {{ visitor.getColumn('referrerName') }}
-                            {% if visitor.getColumn('referrerKeyword') is not empty %}{{ 'Referers_Keywords'|translate }}:
-                                <br/>
-                                <a href="{{ visitor.getColumn('referrerUrl') }}" target="_blank" style="text-decoration:underline;">
-                                    "{{ visitor.getColumn('referrerKeyword') }}"</a>
-                            {% endif %}
-                            {% set keyword %}{{ visitor.getColumn('referrerKeyword') }}{% endset %}
-                            {% set searchName %}{{ visitor.getColumn('referrerName') }}{% endset %}
-                            {% set position %}#{{ visitor.getColumn('referrerKeywordPosition') }}{% endset %}
-                            {% if visitor.getColumn('referrerKeywordPosition') %}
-                                <span title='{{ 'Live_KeywordRankedOnSearchResultForThisVisitor'|translate(keyword,position,searchName) }}' class='visitorRank'>
-                                    <span class='hash'>#</span>
-                                    {{ visitor.getColumn('referrerKeywordPosition') }}
-                                </span>
-                            {% endif %}
-                        {% endif %}
-                        {% if visitor.getColumn('referrerType') == 'direct' %}{{ 'Referers_DirectEntry'|translate }}{% endif %}
-                    </div>
-                </td>
-                <td class="column {% if visitor.getColumn('visitConverted') and not isWidget %}highlightField{% endif %}">
-                    <strong>
-                        {{ visitor.getColumn('actionDetails')|length }}
-                        {% if visitor.getColumn('actionDetails')|length <= 1 %}
-                            {{ 'General_Action'|translate }}
-                        {% else %}
-                            {{ 'General_Actions'|translate }}
-                        {% endif %}
-                        {% if visitor.getColumn('visitDuration') > 0 %}- {{ visitor.getColumn('visitDurationPretty')|raw }}{% endif %}
-                    </strong>
-                    <br/>
-                    <ol class='visitorLog'>
-                        {% include "@Live/_actionsList.twig" with {'actionDetails': visitor.getColumn('actionDetails')} %}
-                    </ol>
-                </td>
-            </tr>
-        {% endset %}
-
-        {% if not javascriptVariablesToSet.filterEcommerce or visitorHasSomeEcommerceActivity is not empty %}
-            {{ visitorRow }}
-        {% endif %}
-    {% endfor %}
-
-    </tbody>
-    </table>
-{% endif %}
-
-    {% if properties.show_footer %}
-        {% include "@CoreHome/_dataTableFooter.twig" %}
-    {% endif %}
-
-    {% include "@CoreHome/_dataTableJS.twig" %}
-    <script type="text/javascript" defer="defer">
-
-        function Piwik_Live_LoadVisitorPopover(visitorId) {
-            broadcast.propagateNewPopoverParameter('visitorProfile', visitorId);
-        }
-
-        $(document).ready(function () {
-            // Replace duplicated page views by a NX count instead of using too much vertical space
-            $("ol.visitorLog").each(function () {
-                var prevelement;
-                var prevhtml;
-                var counter = 0;
-                $(this).find("li").each(function () {
-                    counter++;
-                    $(this).val(counter);
-                    var current = $(this).html();
-                    if (current == prevhtml) {
-                        var repeat = prevelement.find(".repeat");
-                        if (repeat.length) {
-                            repeat.html((parseInt(repeat.html()) + 1) + "x");
-                        } else {
-                            prevelement.append($("<em>2x</em>").attr({'class': 'repeat', 'title': '{{ 'Live_PageRefreshed'|translate|e('js') }}'}));
-                        }
-                        $(this).hide();
-                    } else {
-                        prevhtml = current;
-                        prevelement = $(this);
-                    }
-                    
-                    $(this).tooltip({
-                        track: true,
-                        show: false,
-                        hide: false,
-                        content: function() {
-                            var title = $(this).attr('title');
-                            return $('<a>').text( title ).html().replace(/\n/g, '<br />');
-                        },
-                        tooltipClass: 'small'
-                    });
-                });
-            });
-        });
-    </script>
-{% endif %}
-
-</div>
diff --git a/plugins/Referers/Referers.php b/plugins/Referers/Referers.php
index e06b97b00d..a85a10c9de 100644
--- a/plugins/Referers/Referers.php
+++ b/plugins/Referers/Referers.php
@@ -332,6 +332,7 @@ class Referers extends \Piwik\Plugin
             'show_search' => false,
             'show_offset_information' => false,
             'show_pagination_control' => false,
+            'show_limit_control' => false,
             'show_exclude_low_population' => false,
             'show_goals' => true,
             'filter_limit' => 10,
diff --git a/plugins/UserCountry/UserCountry.php b/plugins/UserCountry/UserCountry.php
index 2e071cdd02..cf5bbfdd68 100644
--- a/plugins/UserCountry/UserCountry.php
+++ b/plugins/UserCountry/UserCountry.php
@@ -318,6 +318,7 @@ class UserCountry extends \Piwik\Plugin
             'show_search'                 => false,
             'show_offset_information'     => false,
             'show_pagination_control'     => false,
+            'show_limit_control'          => false,
             'translations'                => array('label' => Piwik_Translate('UserCountry_Continent')),
             'documentation'               => Piwik_Translate('UserCountry_getContinentDocumentation')
         );
diff --git a/plugins/UserSettings/UserSettings.php b/plugins/UserSettings/UserSettings.php
index a71d13c64f..6a810c273f 100644
--- a/plugins/UserSettings/UserSettings.php
+++ b/plugins/UserSettings/UserSettings.php
@@ -251,6 +251,7 @@ class UserSettings extends \Piwik\Plugin
             'translations'            => array('label' => Piwik_Translate('UserSettings_ColumnBrowserFamily')),
             'show_offset_information' => false,
             'show_pagination_control' => false,
+            'show_limit_control'      => false,
             'default_view_type'       => 'graphPie',
         ));
     }
@@ -261,6 +262,7 @@ class UserSettings extends \Piwik\Plugin
             'translations'            => array('label' => Piwik_Translate('UserSettings_ColumnTypeOfScreen')),
             'show_offset_information' => false,
             'show_pagination_control' => false,
+            'show_limit_control'      => false,
             'title'                   => Piwik_Translate('UserSettings_ColumnTypeOfScreen'),
             'related_reports'          => $this->getWideScreenDeviceTypeRelatedReports()
         ));
@@ -285,6 +287,7 @@ class UserSettings extends \Piwik\Plugin
             ),
             'show_offset_information'  => false,
             'show_pagination_control'  => false,
+            'show_limit_control'       => false,
             'show_all_views_icons'     => false,
             'show_table_all_columns'   => false,
             'columns_to_display'       => array('label', 'nb_visits_percentage', 'nb_visits'),
diff --git a/plugins/VisitTime/VisitTime.php b/plugins/VisitTime/VisitTime.php
index ba35e5a247..5238e3699d 100644
--- a/plugins/VisitTime/VisitTime.php
+++ b/plugins/VisitTime/VisitTime.php
@@ -129,6 +129,7 @@ class VisitTime extends \Piwik\Plugin
             'show_exclude_low_population' => false,
             'show_offset_information'     => false,
             'show_pagination_control'     => false,
+            'show_limit_control'          => false,
             'default_view_type'           => 'graphVerticalBar'
         );
 
diff --git a/plugins/VisitorInterest/VisitorInterest.php b/plugins/VisitorInterest/VisitorInterest.php
index eaddff902f..9f89e35f56 100644
--- a/plugins/VisitorInterest/VisitorInterest.php
+++ b/plugins/VisitorInterest/VisitorInterest.php
@@ -172,6 +172,7 @@ class VisitorInterest extends \Piwik\Plugin
             'show_exclude_low_population' => false,
             'show_offset_information' => false,
             'show_pagination_control' => false,
+            'show_limit_control' => false,
             'show_search' => false,
             'show_table_all_columns' => false,
             'visualization_properties' => array(
@@ -193,6 +194,7 @@ class VisitorInterest extends \Piwik\Plugin
             'show_exclude_low_population' => false,
             'show_offset_information' => false,
             'show_pagination_control' => false,
+            'show_limit_control' => false,
             'show_search' => false,
             'show_table_all_columns' => false,
             'visualization_properties' => array(
@@ -214,6 +216,7 @@ class VisitorInterest extends \Piwik\Plugin
             'show_exclude_low_population' => false,
             'show_offset_information'     => false,
             'show_pagination_control'     => false,
+            'show_limit_control'          => false,
             'filter_limit'                => 15,
             'show_search'                 => false,
             'enable_sort'                 => false,
@@ -231,6 +234,7 @@ class VisitorInterest extends \Piwik\Plugin
             'show_exclude_low_population' => false,
             'show_offset_information'     => false,
             'show_pagination_control'     => false,
+            'show_limit_control'          => false,
             'show_all_views_icons'        => false,
             'filter_limit'                => 15,
             'show_search'                 => false,
@@ -238,4 +242,4 @@ class VisitorInterest extends \Piwik\Plugin
             'show_table_all_columns'      => false
         );
     }
-}
+}
\ No newline at end of file
-- 
GitLab