PDF rausgenommen

This commit is contained in:
aschwarz
2023-01-23 11:03:31 +01:00
parent 82d562a322
commit a6523903eb
28078 changed files with 4247552 additions and 2 deletions

View File

@ -0,0 +1,770 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* broadcast object is to help maintain a hash for link clicks and ajax calls
* so we can have back button and refresh button working.
*
* @type {object}
*/
var broadcast = {
/**
* Initialisation state
* @type {Boolean}
*/
_isInit: false,
/**
* Last known hash url without popover parameter
*/
currentHashUrl: false,
/**
* Last known popover parameter
*/
currentPopoverParameter: false,
/**
* Callbacks for popover parameter change
*/
popoverHandlers: [],
/**
* Holds the stack of popovers opened in sequence. When closing a popover, the last popover in the stack
* is opened (if any).
*/
popoverParamStack: [],
/**
* Force reload once
*/
forceReload: false,
/**
* Suppress content update on hash changing
*/
updateHashOnly: false,
/**
* Initializes broadcast object
*
* @deprecated in 3.2.2, will be removed in Piwik 4
*
* @return {void}
*/
init: function (noLoadingMessage) {
if (broadcast._isInit) {
return;
}
broadcast._isInit = true;
angular.element(document).injector().invoke(function (historyService) {
historyService.init();
});
if(noLoadingMessage != true) {
piwikHelper.showAjaxLoading();
}
},
/**
* ========== PageLoad function =================
* This function is called when:
* 1. after calling $.history.init();
* 2. after calling $.history.load(); //look at broadcast.changeParameter();
* 3. after pushing "Go Back" button of a browser
*
* * Note: the method is manipulated in Overlay/javascripts/Piwik_Overlay.js - keep this in mind when making changes.
*
* @deprecated since 3.2.2, will be removed in Piwik 4
*
* @param {string} hash to load page with
* @return {void}
*/
pageload: function (hash) {
broadcast.init();
// Unbind any previously attached resize handlers
$(window).off('resize');
// do not update content if it should be suppressed
if (broadcast.updateHashOnly) {
broadcast.updateHashOnly = false;
return;
}
// hash doesn't contain the first # character.
if (hash && 0 === (''+hash).indexOf('/')) {
hash = (''+hash).substr(1);
}
if (hash) {
if (/^popover=/.test(hash)) {
var hashParts = [
'',
hash.replace(/^popover=/, '')
];
} else {
var hashParts = hash.split('&popover=');
}
var hashUrl = hashParts[0];
var popoverParam = '';
if (hashParts.length > 1) {
popoverParam = hashParts[1];
// in case the $ was encoded (e.g. when using copy&paste on urls in some browsers)
popoverParam = decodeURIComponent(popoverParam);
// revert special encoding from broadcast.propagateNewPopoverParameter()
popoverParam = popoverParam.replace(/\$/g, '%');
popoverParam = decodeURIComponent(popoverParam);
}
var pageUrlUpdated = (popoverParam == '' ||
(broadcast.currentHashUrl !== false && broadcast.currentHashUrl != hashUrl));
var popoverParamUpdated = (popoverParam != '' && hashUrl == broadcast.currentHashUrl);
if (broadcast.currentHashUrl === false) {
// new page load
pageUrlUpdated = true;
popoverParamUpdated = (popoverParam != '');
}
if (!broadcast.isWidgetizedDashboard() && (pageUrlUpdated || broadcast.forceReload)) {
Piwik_Popover.close();
if (hashUrl != broadcast.currentHashUrl || broadcast.forceReload) {
// restore ajax loaded state
broadcast.loadAjaxContent(hashUrl);
// make sure the "Widgets & Dashboard" is deleted on reload
$('.top_controls .dashboard-manager').hide();
$('#dashboardWidgetsArea').dashboard('destroy');
// remove unused controls
require('piwik/UI').UIControl.cleanupUnusedControls();
}
}
broadcast.forceReload = false;
broadcast.currentHashUrl = hashUrl;
broadcast.currentPopoverParameter = popoverParam;
Piwik_Popover.close();
if (popoverParamUpdated) {
var popoverParamParts = popoverParam.split(':');
var handlerName = popoverParamParts[0];
popoverParamParts.shift();
var param = popoverParamParts.join(':');
if (typeof broadcast.popoverHandlers[handlerName] != 'undefined' && !broadcast.isLoginPage()) {
broadcast.popoverHandlers[handlerName](param);
}
}
} else {
// start page
Piwik_Popover.close();
if (!broadcast.isWidgetizedDashboard()) {
$('.pageWrap #content:not(.admin)').empty();
}
}
},
isWidgetizedDashboard: function() {
return broadcast.getValueFromUrl('module') == 'Widgetize' && broadcast.getValueFromUrl('moduleToWidgetize') == 'Dashboard';
},
/**
* Returns if the current page is the login page
* @return {boolean}
*/
isLoginPage: function() {
return !!$('body#loginPage').length;
},
/**
* propagateAjax -- update hash values then make ajax calls.
* example :
* 1) <a href="javascript:broadcast.propagateAjax('module=Referrers&action=getKeywords')">View keywords report</a>
* 2) Main menu li also goes through this function.
*
* Will propagate your new value into the current hash string and make ajax calls.
*
* NOTE: this method will only make ajax call and replacing main content.
*
* @deprecated in 3.2.2, will be removed in Piwik 4.
*
* @param {string} ajaxUrl querystring with parameters to be updated
* @param {boolean} [disableHistory] the hash change won't be available in the browser history
* @return {void}
*/
propagateAjax: function (ajaxUrl, disableHistory) {
broadcast.init();
// abort all existing ajax requests
globalAjaxQueue.abort();
// available in global scope
var currentHashStr = broadcast.getHash();
ajaxUrl = ajaxUrl.replace(/^\?|&#/, '');
var params_vals = ajaxUrl.split("&");
for (var i = 0; i < params_vals.length; i++) {
currentHashStr = broadcast.updateParamValue(params_vals[i], currentHashStr);
}
// if the module is not 'Goals', we specifically unset the 'idGoal' parameter
// this is to ensure that the URLs are clean (and that clicks on graphs work as expected - they are broken with the extra parameter)
var action = broadcast.getParamValue('action', currentHashStr);
if (action != 'goalReport'
&& action != 'ecommerceReport'
&& action != 'products'
&& action != 'sales'
&& (''+ ajaxUrl).indexOf('&idGoal=') === -1) {
currentHashStr = broadcast.updateParamValue('idGoal=', currentHashStr);
}
// unset idDashboard if use doesn't display a dashboard
var module = broadcast.getParamValue('module', currentHashStr);
if (module != 'Dashboard') {
currentHashStr = broadcast.updateParamValue('idDashboard=', currentHashStr);
}
if (module != 'CustomDimensions') {
currentHashStr = broadcast.updateParamValue('idDimension=', currentHashStr);
}
if (disableHistory) {
var newLocation = window.location.href.split('#')[0] + '#?' + currentHashStr;
// window.location.replace changes the current url without pushing it on the browser's history stack
window.location.replace(newLocation);
}
else {
// Let history know about this new Hash and load it.
broadcast.forceReload = true;
angular.element(document).injector().invoke(function (historyService) {
historyService.load(currentHashStr);
});
}
},
/**
* Returns the current hash with updated parameters that were provided in ajaxUrl
*
* Parameters like idGoal and idDashboard will be automatically reset if the won't be relevant anymore
*
* NOTE: this method does not issue any ajax call, but returns the hash instead
*
* @param {string} ajaxUrl querystring with parameters to be updated
* @return {string} current hash with updated parameters
*/
buildReportingUrl: function (ajaxUrl) {
// available in global scope
var currentHashStr = broadcast.getHash();
ajaxUrl = ajaxUrl.replace(/^\?|&#/, '');
var params_vals = ajaxUrl.split("&");
for (var i = 0; i < params_vals.length; i++) {
currentHashStr = broadcast.updateParamValue(params_vals[i], currentHashStr);
}
// if the module is not 'Goals', we specifically unset the 'idGoal' parameter
// this is to ensure that the URLs are clean (and that clicks on graphs work as expected - they are broken with the extra parameter)
var action = broadcast.getParamValue('action', currentHashStr);
if (action != 'goalReport' && action != 'ecommerceReport' && action != 'products' && action != 'sales') {
currentHashStr = broadcast.updateParamValue('idGoal=', currentHashStr);
}
// unset idDashboard if use doesn't display a dashboard
var module = broadcast.getParamValue('module', currentHashStr);
if (module != 'Dashboard') {
currentHashStr = broadcast.updateParamValue('idDashboard=', currentHashStr);
}
return '#' + currentHashStr;
},
/**
* propagateNewPage() -- update url value and load new page,
* Example:
* 1) We want to update idSite to both search query and hash then reload the page,
* 2) update period to both search query and hash then reload page.
*
* Expecting:
* str = "param1=newVal1&param2=newVal2";
*
* NOTE: This method will refresh the page with new values.
*
* @param {string} str url with parameters to be updated
* @param {boolean} [showAjaxLoading] whether to show the ajax loading gif or not.
* @param {string} strHash additional parameters that should be updated on the hash
* @return {void}
*/
propagateNewPage: function (str, showAjaxLoading, strHash) {
// abort all existing ajax requests
globalAjaxQueue.abort();
if (typeof showAjaxLoading === 'undefined' || showAjaxLoading) {
piwikHelper.showAjaxLoading();
}
var params_vals = str.split("&");
// available in global scope
var currentSearchStr = window.location.search;
var currentHashStr = broadcast.getHashFromUrl();
if (!currentSearchStr) {
currentSearchStr = '?';
}
var oldUrl = currentSearchStr + currentHashStr;
for (var i = 0; i < params_vals.length; i++) {
if(params_vals[i].length == 0) {
continue; // updating with empty string would destroy some values
}
// update both the current search query and hash string
currentSearchStr = broadcast.updateParamValue(params_vals[i], currentSearchStr);
if (currentHashStr.length != 0) {
currentHashStr = broadcast.updateParamValue(params_vals[i], currentHashStr);
}
}
var updatedUrl = new RegExp('&updated=([0-9]+)');
var updatedCounter = updatedUrl.exec(currentSearchStr);
if (!updatedCounter) {
currentSearchStr += '&updated=1';
} else {
updatedCounter = 1 + parseInt(updatedCounter[1]);
currentSearchStr = currentSearchStr.replace(new RegExp('(&updated=[0-9]+)'), '&updated=' + updatedCounter);
}
if (strHash && currentHashStr.length != 0) {
var params_hash_vals = strHash.split("&");
for (var i = 0; i < params_hash_vals.length; i++) {
currentHashStr = broadcast.updateParamValue(params_hash_vals[i], currentHashStr);
}
}
// Now load the new page.
var newUrl = currentSearchStr + currentHashStr;
var $rootScope = piwikHelper.getAngularDependency('$rootScope');
if ($rootScope) {
$rootScope.$on('$locationChangeStart', function (event) {
if (event) {
event.preventDefault();
}
})
}
if (oldUrl == newUrl) {
window.location.reload();
} else {
this.forceReload = true;
window.location.href = newUrl;
}
return false;
},
/*************************************************
*
* Broadcast Supporter Methods:
*
*************************************************/
/**
* updateParamValue(newParamValue,urlStr) -- Helping propagate functions to update value to url string.
* eg. I want to update date value to search query or hash query
*
* Expecting:
* urlStr : A Hash or search query string. e.g: module=whatever&action=index=date=yesterday
* newParamValue : A param value pair: e.g: date=2009-05-02
*
* Return module=whatever&action=index&date=2009-05-02
*
* @param {string} newParamValue param to be updated
* @param {string} urlStr url to be updated
* @return {string} urlStr with updated param
*/
updateParamValue: function (newParamValue, urlStr) {
var p_v = newParamValue.split("=");
var paramName = p_v[0];
var valFromUrl = broadcast.getParamValue(paramName, urlStr);
// if set 'idGoal=' then we remove the parameter from the URL automatically (rather than passing an empty value)
var paramValue = p_v[1];
if (paramValue == '') {
newParamValue = '';
}
var getQuotedRegex = function(str) {
return (str+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
};
if (valFromUrl != '') {
// replacing current param=value to newParamValue;
valFromUrl = getQuotedRegex(valFromUrl);
var regToBeReplace = new RegExp(paramName + '=' + valFromUrl, 'ig');
if (newParamValue == '') {
// if new value is empty remove leading &, as well
regToBeReplace = new RegExp('[\&]?' + paramName + '=' + valFromUrl, 'ig');
}
urlStr = urlStr.replace(regToBeReplace, newParamValue);
} else if (newParamValue != '') {
urlStr += (urlStr == '') ? newParamValue : '&' + newParamValue;
}
return urlStr;
},
/**
* Loads a popover by adding a 'popover' query parameter to the current URL and
* indirectly executing the popover handler.
*
* This function should be called to open popovers that can be opened by URL alone.
* That is, if you want users to be able to copy-paste the URL displayed when a popover
* is open into a new browser window/tab and have the same popover open, you should
* call this function.
*
* In order for this function to open a popover, there must be a popover handler
* associated with handlerName. To associate one, call broadcast.addPopoverHandler.
*
* @param {String} handlerName The name of the popover handler.
* @param {String} value The String value that should be passed to the popover
* handler.
*/
propagateNewPopoverParameter: function (handlerName, value) {
var $location = angular.element(document).injector().get('$location');
var popover = '';
if (handlerName && '' != value && 'undefined' != typeof value) {
popover = handlerName + ':' + value;
// between jquery.history and different browser bugs, it's impossible to ensure
// that the parameter is en- and decoded the same number of times. in order to
// make sure it doesn't change, we have to manipulate the url encoding a bit.
popover = encodeURIComponent(popover);
popover = popover.replace(/%/g, '\$');
broadcast.popoverParamStack.push(popover);
} else {
broadcast.popoverParamStack.pop();
if (broadcast.popoverParamStack.length) {
popover = broadcast.popoverParamStack[broadcast.popoverParamStack.length - 1];
}
}
$location.search('popover', popover);
setTimeout(function () {
angular.element(document).injector().get('$rootScope').$apply();
}, 1);
},
/**
* Resets the popover param stack ensuring when a popover is closed, no new popover will
* be loaded.
*/
resetPopoverStack: function () {
broadcast.popoverParamStack = [];
},
/**
* Adds a handler for the 'popover' query parameter.
*
* @see broadcast#propagateNewPopoverParameter
*
* @param {String} handlerName The handler name, eg, 'visitorProfile'. Should identify
* the popover that the callback will open up.
* @param {Function} callback This function should open the popover. It should take
* one string parameter.
*/
addPopoverHandler: function (handlerName, callback) {
broadcast.popoverHandlers[handlerName] = callback;
},
/**
* Loads the given url with ajax and replaces the content
*
* Note: the method is replaced in Overlay/javascripts/Piwik_Overlay.js - keep this in mind when making changes.
*
* @param {string} urlAjax url to load
* @return {Boolean}
*/
loadAjaxContent: function (urlAjax) {
if(broadcast.getParamValue('module', urlAjax) == 'API') {
broadcast.lastUrlRequested = null;
$('#content').html("Loading content from the API and displaying it within Piwik is not allowed.");
piwikHelper.hideAjaxLoading();
return false;
}
piwikHelper.hideAjaxError('loadingError');
piwikHelper.showAjaxLoading();
$('#content').empty();
$("object").remove();
urlAjax = urlAjax.match(/^\?/) ? urlAjax : "?" + urlAjax;
broadcast.lastUrlRequested = urlAjax;
function sectionLoaded(content, status, request) {
if (request) {
var responseHeader = request.getResponseHeader('Content-Type');
if (responseHeader && 0 <= responseHeader.toLowerCase().indexOf('json')) {
var message = 'JSON cannot be displayed for';
if (this.getParams && this.getParams['module']) {
message += ' module=' + this.getParams['module'];
}
if (this.getParams && this.getParams['action']) {
message += ' action=' + this.getParams['action'];
}
$('#content').text(message);
piwikHelper.hideAjaxLoading();
return;
}
}
// if content is whole HTML document, do not show it, otherwise recursive page load could occur
var htmlDocType = '<!DOCTYPE';
if (content.substring(0, htmlDocType.length) == htmlDocType) {
// if the content has an error message, display it
if ($(content).filter('title').text() == 'Piwik Error') {
content = $(content).filter('#contentsimple');
} else {
return;
}
}
if (urlAjax == broadcast.lastUrlRequested) {
$('#content').html(content).show();
$(broadcast).trigger('locationChangeSuccess', {element: $('#content'), content: content});
piwikHelper.hideAjaxLoading();
broadcast.lastUrlRequested = null;
piwikHelper.compileAngularComponents('#content');
}
initTopControls();
}
var ajax = new ajaxHelper();
ajax.setUrl(urlAjax);
ajax._getDefaultPostParams = function () {
return {};
};
ajax.setErrorCallback(broadcast.customAjaxHandleError);
ajax.setCallback(sectionLoaded);
ajax.setFormat('html');
ajax.send();
return false;
},
/**
* Method to handle ajax errors
* @param {XMLHttpRequest} deferred
* @param {string} status
* @return {void}
*/
customAjaxHandleError: function (deferred, status) {
broadcast.lastUrlRequested = null;
piwikHelper.hideAjaxLoading();
// do not display error message if request was aborted
if(status == 'abort') {
return;
}
$('#loadingError').show();
},
/**
* Return hash string if hash exists on address bar.
* else return false;
*
* @return {string|boolean} current hash or false if it is empty
*/
isHashExists: function () {
var hashStr = broadcast.getHashFromUrl();
if (hashStr != "") {
return hashStr;
} else {
return false;
}
},
/**
* Get Hash from given url or from current location.
* return empty string if no hash present.
*
* @param {string} [url] url to get hash from (defaults to current location)
* @return {string} the hash part of the given url
*/
getHashFromUrl: function (url) {
var hashStr = "";
// If url provided, give back the hash from url, else get hash from current address.
if (url && url.match('#')) {
hashStr = url.substring(url.indexOf("#"), url.length);
}
else {
locationSplit = location.href.split('#');
if(typeof locationSplit[1] != 'undefined') {
hashStr = '#' + locationSplit[1];
}
}
return hashStr;
},
/**
* Get search query from given url or from current location.
* return empty string if no search query present.
*
* @param {string} url
* @return {string} the query part of the given url
*/
getSearchFromUrl: function (url) {
var searchStr = "";
// If url provided, give back the query string from url, else get query string from current address.
if (url && url.match(/\?/)) {
searchStr = url.substring(url.indexOf("?"), url.length);
} else {
searchStr = location.search;
}
return searchStr;
},
/**
* Extracts from a query strings, the request array
* @param queryString
* @returns {object}
*/
extractKeyValuePairsFromQueryString: function (queryString) {
var pairs = queryString.split('&');
var result = {};
for (var i = 0; i != pairs.length; ++i) {
// attn: split with regex has bugs in several browsers such as IE 8
// so we need to split, use the first part as key and rejoin the rest
var pair = pairs[i].split('=');
var key = pair.shift();
result[key] = pair.join('=');
}
return result;
},
/**
* Returns all key-value pairs in query string of url.
*
* @param {string} url url to check. if undefined, null or empty, current url is used.
* @return {object} key value pair describing query string parameters
*/
getValuesFromUrl: function (url) {
var searchString = this._removeHashFromUrl(url).split('?')[1] || '';
return this.extractKeyValuePairsFromQueryString(searchString);
},
/**
* help to get param value for any given url string with provided param name
* if no url is provided, it will get param from current address.
* return:
* Empty String if param is not found.
*
* @param {string} param parameter to search for
* @param {string} [url] url to check, defaults to current location
* @return {string} value of the given param within the given url
*/
getValueFromUrl: function (param, url) {
var searchString = this._removeHashFromUrl(url);
return broadcast.getParamValue(param, searchString);
},
/**
* NOTE: you should probably be using broadcast.getValueFromUrl instead!
*
* @param {string} param parameter to search for
* @param {string} [url] url to check
* @return {string} value of the given param within the hash part of the given url
*/
getValueFromHash: function (param, url) {
var hashStr = broadcast.getHashFromUrl(url);
if (hashStr.substr(0, 1) == '#') {
hashStr = hashStr.substr(1);
}
hashStr = hashStr.split('#')[0];
return broadcast.getParamValue(param, hashStr);
},
/**
* return value for the requested param, will return the first match.
* out side of this class should use getValueFromHash() or getValueFromUrl() instead.
* return:
* Empty String if param is not found.
*
* @param {string} param parameter to search for
* @param {string} url url to check
* @return {string} value of the given param within the given url
*/
getParamValue: function (param, url) {
var lookFor = param + '=';
var startStr = url.indexOf(lookFor);
if (startStr >= 0) {
var endStr = url.indexOf("&", startStr);
if (endStr == -1) {
endStr = url.length;
}
var value = url.substring(startStr + param.length + 1, endStr);
// we sanitize values to add a protection layer against XSS
// &segment= value is not sanitized, since segments are designed to accept any user input
if(param != 'segment') {
value = value.replace(/[^_%~\*\+\-\<\>!@\$\.()=,;0-9a-zA-Z]/gi, '');
}
return value;
} else {
return '';
}
},
/**
* Returns the hash without the starting #
* @return {string} hash part of the current url
*/
getHash: function () {
return broadcast.getHashFromUrl().replace(/^#/, '').split('#')[0];
},
/**
* Removes the hash portion of a URL and returns the rest.
*
* @param {string} url
* @return {string} url w/o hash
*/
_removeHashFromUrl: function (url) {
var searchString = '';
if (url) {
var urlParts = url.split('#');
searchString = urlParts[0];
} else {
searchString = location.search;
}
return searchString;
}
};

View File

@ -0,0 +1,92 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function ($) {
// min/max date for picker
var piwikMinDate = new Date(piwik.minDateYear, piwik.minDateMonth - 1, piwik.minDateDay),
piwikMaxDate = new Date(piwik.maxDateYear, piwik.maxDateMonth - 1, piwik.maxDateDay);
piwik.getBaseDatePickerOptions = function (defaultDate) {
return {
showOtherMonths: false,
dateFormat: 'yy-mm-dd',
firstDay: 1,
minDate: piwikMinDate,
maxDate: piwikMaxDate,
prevText: "",
nextText: "",
currentText: "",
defaultDate: defaultDate,
changeMonth: true,
changeYear: true,
stepMonths: 1,
// jquery-ui-i18n 1.7.2 lacks some translations, so we use our own
dayNamesMin: [
_pk_translate('Intl_Day_Min_StandAlone_7'),
_pk_translate('Intl_Day_Min_StandAlone_1'),
_pk_translate('Intl_Day_Min_StandAlone_2'),
_pk_translate('Intl_Day_Min_StandAlone_3'),
_pk_translate('Intl_Day_Min_StandAlone_4'),
_pk_translate('Intl_Day_Min_StandAlone_5'),
_pk_translate('Intl_Day_Min_StandAlone_6')],
dayNamesShort: [
_pk_translate('Intl_Day_Short_StandAlone_7'), // start with sunday
_pk_translate('Intl_Day_Short_StandAlone_1'),
_pk_translate('Intl_Day_Short_StandAlone_2'),
_pk_translate('Intl_Day_Short_StandAlone_3'),
_pk_translate('Intl_Day_Short_StandAlone_4'),
_pk_translate('Intl_Day_Short_StandAlone_5'),
_pk_translate('Intl_Day_Short_StandAlone_6')],
dayNames: [
_pk_translate('Intl_Day_Long_StandAlone_7'), // start with sunday
_pk_translate('Intl_Day_Long_StandAlone_1'),
_pk_translate('Intl_Day_Long_StandAlone_2'),
_pk_translate('Intl_Day_Long_StandAlone_3'),
_pk_translate('Intl_Day_Long_StandAlone_4'),
_pk_translate('Intl_Day_Long_StandAlone_5'),
_pk_translate('Intl_Day_Long_StandAlone_6')],
monthNamesShort: [
_pk_translate('Intl_Month_Short_StandAlone_1'),
_pk_translate('Intl_Month_Short_StandAlone_2'),
_pk_translate('Intl_Month_Short_StandAlone_3'),
_pk_translate('Intl_Month_Short_StandAlone_4'),
_pk_translate('Intl_Month_Short_StandAlone_5'),
_pk_translate('Intl_Month_Short_StandAlone_6'),
_pk_translate('Intl_Month_Short_StandAlone_7'),
_pk_translate('Intl_Month_Short_StandAlone_8'),
_pk_translate('Intl_Month_Short_StandAlone_9'),
_pk_translate('Intl_Month_Short_StandAlone_10'),
_pk_translate('Intl_Month_Short_StandAlone_11'),
_pk_translate('Intl_Month_Short_StandAlone_12')],
monthNames: [
_pk_translate('Intl_Month_Long_StandAlone_1'),
_pk_translate('Intl_Month_Long_StandAlone_2'),
_pk_translate('Intl_Month_Long_StandAlone_3'),
_pk_translate('Intl_Month_Long_StandAlone_4'),
_pk_translate('Intl_Month_Long_StandAlone_5'),
_pk_translate('Intl_Month_Long_StandAlone_6'),
_pk_translate('Intl_Month_Long_StandAlone_7'),
_pk_translate('Intl_Month_Long_StandAlone_8'),
_pk_translate('Intl_Month_Long_StandAlone_9'),
_pk_translate('Intl_Month_Long_StandAlone_10'),
_pk_translate('Intl_Month_Long_StandAlone_11'),
_pk_translate('Intl_Month_Long_StandAlone_12')]
};
};
piwikHelper.registerShortcut('d', _pk_translate('CoreHome_ShortcutCalendar'), function(event) {
if (event.altKey) {
return;
}
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false; // IE
}
$('#periodString .title').trigger('click').focus();
});
}(jQuery));

View File

@ -0,0 +1,225 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function ($) {
var colorNames = {"aliceblue":"#f0f8ff","antiquewhite":"#faebd7","aqua":"#00ffff","aquamarine":"#7fffd4","azure":"#f0ffff",
"beige":"#f5f5dc","bisque":"#ffe4c4","black":"#000000","blanchedalmond":"#ffebcd","blue":"#0000ff","blueviolet":"#8a2be2","brown":"#a52a2a","burlywood":"#deb887",
"cadetblue":"#5f9ea0","chartreuse":"#7fff00","chocolate":"#d2691e","coral":"#ff7f50","cornflowerblue":"#6495ed","cornsilk":"#fff8dc","crimson":"#dc143c","cyan":"#00ffff",
"darkblue":"#00008b","darkcyan":"#008b8b","darkgoldenrod":"#b8860b","darkgray":"#a9a9a9","darkgreen":"#006400","darkkhaki":"#bdb76b","darkmagenta":"#8b008b","darkolivegreen":"#556b2f",
"darkorange":"#ff8c00","darkorchid":"#9932cc","darkred":"#8b0000","darksalmon":"#e9967a","darkseagreen":"#8fbc8f","darkslateblue":"#483d8b","darkslategray":"#2f4f4f","darkturquoise":"#00ced1",
"darkviolet":"#9400d3","deeppink":"#ff1493","deepskyblue":"#00bfff","dimgray":"#696969","dodgerblue":"#1e90ff",
"firebrick":"#b22222","floralwhite":"#fffaf0","forestgreen":"#228b22","fuchsia":"#ff00ff","gainsboro":"#dcdcdc","ghostwhite":"#f8f8ff","gold":"#ffd700","goldenrod":"#daa520","gray":"#808080","green":"#43a047","greenyellow":"#adff2f",
"honeydew":"#f0fff0","hotpink":"#ff69b4","indianred ":"#cd5c5c","indigo ":"#4b0082","ivory":"#fffff0","khaki":"#f0e68c",
"lavender":"#e6e6fa","lavenderblush":"#fff0f5","lawngreen":"#7cfc00","lemonchiffon":"#fffacd","lightblue":"#add8e6","lightcoral":"#f08080","lightcyan":"#e0ffff","lightgoldenrodyellow":"#fafad2",
"lightgrey":"#d3d3d3","lightgreen":"#90ee90","lightpink":"#ffb6c1","lightsalmon":"#ffa07a","lightseagreen":"#20b2aa","lightskyblue":"#87cefa","lightslategray":"#778899","lightsteelblue":"#b0c4de",
"lightyellow":"#ffffe0","lime":"#00ff00","limegreen":"#32cd32","linen":"#faf0e6","magenta":"#ff00ff","maroon":"#800000","mediumaquamarine":"#66cdaa","mediumblue":"#0000cd","mediumorchid":"#ba55d3","mediumpurple":"#9370d8","mediumseagreen":"#3cb371","mediumslateblue":"#7b68ee",
"mediumspringgreen":"#00fa9a","mediumturquoise":"#48d1cc","mediumvioletred":"#c71585","midnightblue":"#191970","mintcream":"#f5fffa","mistyrose":"#ffe4e1","moccasin":"#ffe4b5",
"navajowhite":"#ffdead","navy":"#000080","oldlace":"#fdf5e6","olive":"#808000","olivedrab":"#6b8e23","orange":"#ffa500","orangered":"#ff4500","orchid":"#da70d6",
"palegoldenrod":"#eee8aa","palegreen":"#98fb98","paleturquoise":"#afeeee","palevioletred":"#d87093","papayawhip":"#ffefd5","peachpuff":"#ffdab9","peru":"#cd853f","pink":"#ffc0cb","plum":"#dda0dd","powderblue":"#b0e0e6","purple":"#800080",
"red":"#ff0000","rosybrown":"#bc8f8f","royalblue":"#4169e1","saddlebrown":"#8b4513","salmon":"#fa8072","sandybrown":"#f4a460","seagreen":"#2e8b57","seashell":"#fff5ee","sienna":"#a0522d","silver":"#c0c0c0","skyblue":"#87ceeb","slateblue":"#6a5acd","slategray":"#708090","snow":"#fffafa","springgreen":"#00ff7f","steelblue":"#4682b4",
"tan":"#d2b48c","teal":"#008080","thistle":"#d8bfd8","tomato":"#ff6347","turquoise":"#40e0d0","violet":"#ee82ee","wheat":"#f5deb3","white":"#ffffff","whitesmoke":"#f5f5f5","yellow":"#ffff00","yellowgreen":"#9acd32"};
/**
* The ColorManager class allows JS code to grab colors defined in CSS for
* components that don't manage HTML (like jqPlot or sparklines). Such components
* can't use CSS colors directly since the colors are used to generate images
* or by <canvas> elements.
*
* Colors obtained via ColorManager are defined in CSS like this:
*
* .my-color-namespace[data-name=color-name] {
* color: #fff
* }
*
* and can be accessed in JavaScript like this:
*
* piwik.ColorManager.getColor("my-color-namespace", "color-name");
*
* The singleton instance of this class can be accessed via piwik.ColorManager.
*/
var ColorManager = function () {
// empty
};
ColorManager.prototype = {
/**
* Returns the color for a namespace and name.
*
* @param {String} namespace The string identifier that groups related colors
* together. For example, 'sparkline-colors'.
* @param {String} name The name of the color to retrieve. For example, 'lineColor'.
* @return {String} A hex color, eg, '#fff'.
*/
getColor: function (namespace, name) {
var element = this._getElement();
element.attr('class', 'color-manager ' + namespace).attr('data-name', name);
return this._normalizeColor(element.css('color'));
},
/**
* Returns the colors for a namespace and a list of names.
*
* @param {String} namespace The string identifier that groups related colors
* together. For example, 'sparkline-colors'.
* @param {Array} names An array of color names to retrieve.
* @param {Boolean} asArray Whether the result should be an array or an object.
* @return {Object|Array} An object mapping color names with color values or an
* array of colors.
*/
getColors: function (namespace, names, asArray) {
var colors = asArray ? [] : {};
for (var i = 0; i != names.length; ++i) {
var name = names[i],
color = this.getColor(namespace, name);
if (color) {
if (asArray) {
colors.push(color);
} else {
colors[name] = color;
}
}
}
return colors;
},
/**
* Returns a color that is N % between two other colors.
*
* @param {String|Array} spectrumStart The start color. If percentFromStart is 0, this color will
* be returned. Can be either a hex color or RGB array.
* It will be converted to an RGB array if a hex color is supplied.
* @param {String|Array} spectrumEnd The end color. If percentFromStart is 1, this color will be
* returned. Can be either a hex color or RGB array. It will be
* converted to an RGB array if a hex color is supplied.
* @param {Number} percentFromStart The percent from spectrumStart and twoard spectrumEnd that the
* result color should be. Must be a value between 0.0 & 1.0.
* @return {String} A hex color.
*/
getSingleColorFromGradient: function (spectrumStart, spectrumEnd, percentFromStart) {
if (!(spectrumStart instanceof Array)) {
spectrumStart = this.getRgb(spectrumStart);
}
if (!(spectrumEnd instanceof Array)) {
spectrumEnd = this.getRgb(spectrumEnd);
}
var result = [];
for (var channel = 0; channel != spectrumStart.length; ++channel) {
var delta = (spectrumEnd[channel] - spectrumStart[channel]) * percentFromStart;
result[channel] = Math.floor(spectrumStart[channel] + delta);
}
return this.getHexColor(result);
},
/**
* Utility function that converts a hex color (ie, #fff or #1a1a1a) to an array of
* RGB values.
*
* @param {String} hexColor The color to convert.
* @return {Array} An array with three integers between 0 and 255.
*/
getRgb: function (hexColor) {
if (hexColor[0] == '#') {
hexColor = hexColor.substring(1);
}
if (hexColor.length == 3) {
return [
parseInt(hexColor[0], 16),
parseInt(hexColor[1], 16),
parseInt(hexColor[2], 16)
];
} else {
return [
parseInt(hexColor.substring(0,2), 16),
parseInt(hexColor.substring(2,4), 16),
parseInt(hexColor.substring(4,6), 16)
];
}
},
/**
* Utility function that converts an RGB array to a hex color.
*
* @param {Array} rgbColor An array with three integers between 0 and 255.
* @return {String} The hex color, eg, #1a1a1a.
*/
getHexColor: function (rgbColor) {
// convert channels to hex with one leading 0
for (var i = 0; i != rgbColor.length; ++i) {
rgbColor[i] = ("00" + rgbColor[i].toString(16)).slice(-2);
}
// create hex string
return '#' + rgbColor.join('');
},
/**
* Turns a color string that might be an rgb value rgb(12, 34, 56) into
* a hex color string.
*/
_normalizeColor: function (color) {
if (color == this._getTransparentColor()) {
return null;
}
if (color && colorNames[color]) {
return colorNames[color];
}
if (color
&& color[0] != '#'
) {
// parse rgb(#, #, #) and get rgb numbers
var parts = color.split(/[()rgb,\s]+/);
parts = [+parts[1], +parts[2], +parts[3]];
// convert to hex
color = this.getHexColor(parts);
}
return color;
},
/**
* Returns the manufactured <div> element used to obtain color data. When
* getting color data the class and data-name attribute of this element are
* changed.
*/
_getElement: function () {
if (!this.$element) {
$('body').append('<div id="color-manager"></div>');
this.$element = $('#color-manager');
}
return this.$element;
},
/**
* Returns this browser's representation of the 'transparent' color. Used to
* compare against colors obtained in getColor. If a color is 'transparent'
* it means there's no color for that namespace/name combination.
*/
_getTransparentColor: function () {
if (!this.transparentColor) {
this.transparentColor =
$('<div style="color:transparent;display:none;"></div>').appendTo($('body')).css('color');
}
return this.transparentColor;
}
};
piwik.ColorManager = new ColorManager();
}(jQuery));

View File

@ -0,0 +1,130 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function ($) {
$(function () {
//
// 'check for updates' behavior
//
var headerMessageParent = $('#header_message').parent();
initTopControls();
// when 'check for updates...' link is clicked, force a check & display the result
headerMessageParent.on('click', '#updateCheckLinkContainer', function (e) {
var headerMessage = $(this).closest('#header_message');
var $titleElement = headerMessage.find('.title');
if ($titleElement.attr('target')) { // if this is an external link, internet access is not available on the server
return;
}
e.preventDefault();
var ajaxRequest = new ajaxHelper();
ajaxRequest.setLoadingElement('#header_message .loadingPiwik');
ajaxRequest.addParams({
module: 'CoreHome',
action: 'checkForUpdates'
}, 'get');
ajaxRequest.withTokenInUrl();
$titleElement.addClass('activityIndicator');
ajaxRequest.setCallback(function (response) {
headerMessage.fadeOut('slow', function () {
response = $('#header_message', $('<div>' + response + '</div>'));
$titleElement.removeClass('activityIndicator');
var newVersionAvailable = response.hasClass('update_available');
if (newVersionAvailable) {
headerMessage.replaceWith(response);
headerMessage.show();
}
else {
headerMessage.find('.title').html(_pk_translate('CoreHome_YouAreUsingTheLatestVersion'));
headerMessage.show();
setTimeout(function () {
headerMessage.fadeOut('slow', function () {
headerMessage.replaceWith(response);
});
}, 4000);
}
});
});
ajaxRequest.setFormat('html');
ajaxRequest.send();
return false;
});
// when clicking the header message, show the long message w/o needing to hover
headerMessageParent.on('click', '#header_message', function (e) {
if (e.target.tagName.toLowerCase() != 'a') {
$(this).toggleClass('expanded');
}
});
});
}(jQuery));
$( document ).ready(function() {
$('.accessibility-skip-to-content').click(function(e){
$('a[name="main"]').attr('tabindex', -1).focus();
$(window).scrollTo($('a[name="main"]'));
});
$("nav .activateTopMenu").sideNav({
closeOnClick: true,
edge: 'right'
});
$('select').material_select();
piwikHelper.registerShortcut('?', _pk_translate('CoreHome_ShortcutHelp') , function (event) {
// don't open if an modal is already shown
if (event.altKey || $('.modal.open').length) {
return;
}
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false; // IE
}
var list = $('#shortcuthelp dl');
list.empty();
var keys = Object.keys(piwikHelper.shortcuts).sort();
jQuery.each(keys, function(i, key) {
if (piwikHelper.shortcuts.hasOwnProperty(key)) {
list.append($('<dt />').append($('<kbd />').text(key)));
list.append($('<dd />').text(piwikHelper.shortcuts[key]));
}
});
var isMac = navigator.userAgent.indexOf('Mac OS X') != -1;
list.append($('<dt />').append($('<kbd />').text(_pk_translate(isMac ? "CoreHome_MacPageUp" : "CoreHome_HomeShortcut"))));
list.append($('<dd />').text(_pk_translate('CoreHome_PageUpShortcutDescription')));
list.append($('<dt />').append($('<kbd />').text(_pk_translate(isMac ? "CoreHome_MacPageDown" : "CoreHome_EndShortcut"))));
list.append($('<dd />').text(_pk_translate('CoreHome_PageDownShortcutDescription')));
piwikHelper.modalConfirm('#shortcuthelp');
});
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,431 @@
/**
* Registry for row actions
*
* Plugins can call DataTable_RowActions_Registry.register() from their JS
* files in order to add new actions to arbitrary data tables. The register()
* method takes an object containing:
* - name: string identifying the action. must be short, no spaces.
* - dataTableIcon: path to the icon for the action
* - createInstance: a factory method to create an instance of the appropriate
* subclass of DataTable_RowAction
* - isAvailable: a method to determine whether the action is available in a
* given row of a data table
*/
var DataTable_RowActions_Registry = {
registry: [],
register: function (action) {
var createInstance = action.createInstance;
action.createInstance = function (dataTable, param) {
var instance = createInstance(dataTable, param);
instance.actionName = action.name;
return instance;
};
this.registry.push(action);
},
getAvailableActionsForReport: function (dataTableParams, tr) {
if (dataTableParams.disable_row_actions == '1') {
return [];
}
var available = [];
for (var i = 0; i < this.registry.length; i++) {
if (this.registry[i].isAvailableOnReport(dataTableParams, tr)) {
available.push(this.registry[i]);
}
}
available.sort(function (a, b) {
return b.order - a.order;
});
return available;
},
getActionByName: function (name) {
for (var i = 0; i < this.registry.length; i++) {
if (this.registry[i].name == name) {
return this.registry[i];
}
}
return false;
}
};
// Register Row Evolution (also servers as example)
DataTable_RowActions_Registry.register({
name: 'RowEvolution',
dataTableIcon: 'icon-evolution',
order: 50,
dataTableIconTooltip: [
_pk_translate('General_RowEvolutionRowActionTooltipTitle'),
_pk_translate('General_RowEvolutionRowActionTooltip')
],
createInstance: function (dataTable, param) {
if (dataTable !== null && typeof dataTable.rowEvolutionActionInstance != 'undefined') {
return dataTable.rowEvolutionActionInstance;
}
if (dataTable === null && param) {
// when row evolution is triggered from the url (not a click on the data table)
// we look for the data table instance in the dom
var report = param.split(':')[0];
var div = $(require('piwik/UI').DataTable.getDataTableByReport(report));
if (div.length && div.data('uiControlObject')) {
dataTable = div.data('uiControlObject');
if (typeof dataTable.rowEvolutionActionInstance != 'undefined') {
return dataTable.rowEvolutionActionInstance;
}
}
}
var instance = new DataTable_RowActions_RowEvolution(dataTable);
if (dataTable !== null) {
dataTable.rowEvolutionActionInstance = instance;
}
return instance;
},
isAvailableOnReport: function (dataTableParams) {
return (
typeof dataTableParams.disable_row_evolution == 'undefined'
|| dataTableParams.disable_row_evolution == "0"
);
},
isAvailableOnRow: function (dataTableParams, tr) {
return !tr.hasClass('totalsRow');
}
});
/**
* DataTable Row Actions
*
* The lifecycle of an action is as follows:
* - for each data table, a new instance of the action is created using the factory
* - when the table is loaded, initTr is called for each tr
* - when the action icon is clicked, trigger is called
* - the label is put together and performAction is called
* - performAction must call openPopover on the base class
* - openPopover calls back doOpenPopover after doing general stuff
*
* The two template methods are performAction and doOpenPopover
*/
//
// BASE CLASS
//
function DataTable_RowAction(dataTable) {
this.dataTable = dataTable;
// has to be overridden in subclasses
this.trEventName = 'piwikTriggerRowAction';
// set in registry
this.actionName = 'RowAction';
}
/** Initialize a row when the table is loaded */
DataTable_RowAction.prototype.initTr = function (tr) {
var self = this;
// For subtables, we need to make sure that the actions are always triggered on the
// action instance connected to the root table. Otherwise sharing data (e.g. for
// for multi-row evolution) wouldn't be possible. Also, sub-tables might have different
// API actions. For the label filter to work, we need to use the parent action.
// We use jQuery events to let subtables access their parents.
tr.bind(self.trEventName, function (e, params) {
self.trigger($(this), params.originalEvent, params.label);
});
};
/**
* This method is called from the click event and the tr event (see this.trEventName).
* It derives the label and calls performAction.
*/
DataTable_RowAction.prototype.trigger = function (tr, e, subTableLabel) {
var label = this.getLabelFromTr(tr);
// if we have received the event from the sub table, add the label
if (subTableLabel) {
var separator = ' > '; // LabelFilter::SEPARATOR_RECURSIVE_LABEL
label += separator + subTableLabel;
}
// handle sub tables in nested reports: forward to parent
var subtable = tr.closest('table');
if (subtable.is('.subDataTable')) {
subtable.closest('tr').prev().trigger(this.trEventName, {
label: label,
originalEvent: e
});
return;
}
// ascend in action reports
if (subtable.closest('div.dataTableActions').length) {
var allClasses = tr.attr('class');
var matches = allClasses.match(/level[0-9]+/);
var level = parseInt(matches[0].substring(5, matches[0].length), 10);
if (level > 0) {
// .prev(.levelX) does not work for some reason => do it "by hand"
var findLevel = 'level' + (level - 1);
var ptr = tr;
while ((ptr = ptr.prev()).length) {
if (!ptr.hasClass(findLevel) || ptr.hasClass('nodata')) {
continue;
}
ptr.trigger(this.trEventName, {
label: label,
originalEvent: e
});
return;
}
}
}
this.performAction(label, tr, e);
};
/** Get the label string from a tr dom element */
DataTable_RowAction.prototype.getLabelFromTr = function (tr) {
var label = tr.find('span.label');
// handle truncation
var value = label.data('originalText');
if (!value) {
value = label.text();
}
value = value.trim();
value = encodeURIComponent(value);
// if tr is a terminal node, we use the @ operator to distinguish it from branch nodes w/ the same name
if (!tr.hasClass('subDataTable')) {
value = '@' + value;
}
return value;
};
/** Get row metadata object */
DataTable_RowAction.prototype.getRowMetadata = function (tr) {
return tr.data('row-metadata') || {};
};
/**
* Base method for opening popovers.
* This method will remember the parameter in the url and call doOpenPopover().
*/
DataTable_RowAction.prototype.openPopover = function (parameter) {
broadcast.propagateNewPopoverParameter('RowAction', this.actionName + ':' + parameter);
};
broadcast.addPopoverHandler('RowAction', function (param) {
var paramParts = param.split(':');
var rowActionName = paramParts[0];
paramParts.shift();
param = paramParts.join(':');
var rowAction = DataTable_RowActions_Registry.getActionByName(rowActionName);
if (rowAction) {
rowAction.createInstance(null, param).doOpenPopover(param);
}
});
/** To be overridden */
DataTable_RowAction.prototype.performAction = function (label, tr, e) {
};
DataTable_RowAction.prototype.doOpenPopover = function (parameter) {
};
//
// ROW EVOLUTION
//
function DataTable_RowActions_RowEvolution(dataTable) {
this.dataTable = dataTable;
this.trEventName = 'piwikTriggerRowEvolution';
/** The rows to be compared in multi row evolution */
this.multiEvolutionRows = [];
}
/** Static helper method to launch row evolution from anywhere */
DataTable_RowActions_RowEvolution.launch = function (apiMethod, label) {
var param = 'RowEvolution:' + apiMethod + ':0:' + label;
broadcast.propagateNewPopoverParameter('RowAction', param);
};
DataTable_RowActions_RowEvolution.prototype = new DataTable_RowAction;
DataTable_RowActions_RowEvolution.prototype.performAction = function (label, tr, e) {
if (e.shiftKey) {
// only mark for multi row evolution if shift key is pressed
this.addMultiEvolutionRow(label);
return;
}
this.addMultiEvolutionRow(label);
// check whether we have rows marked for multi row evolution
var extraParams = {};
if (this.multiEvolutionRows.length > 1) {
extraParams.action = 'getMultiRowEvolutionPopover';
label = this.multiEvolutionRows.join(',');
}
$.each(this.dataTable.param, function (index, value) {
// we automatically add fields like idDimension, idGoal etc.
if (index !== 'idSite' && index.indexOf('id') === 0 && $.isNumeric(value)) {
extraParams[index] = value;
}
});
// check if abandonedCarts is in the dataTable params and if so, propagate to row evolution request
if (this.dataTable.param.abandonedCarts !== undefined) {
extraParams['abandonedCarts'] = this.dataTable.param.abandonedCarts;
}
if (this.dataTable.param.flat !== undefined) {
extraParams['flat'] = this.dataTable.param.flat;
}
var apiMethod = this.dataTable.param.module + '.' + this.dataTable.param.action;
this.openPopover(apiMethod, extraParams, label);
};
DataTable_RowActions_RowEvolution.prototype.addMultiEvolutionRow = function (label) {
if ($.inArray(label, this.multiEvolutionRows) == -1) {
this.multiEvolutionRows.push(label);
}
};
DataTable_RowActions_RowEvolution.prototype.openPopover = function (apiMethod, extraParams, label) {
var urlParam = apiMethod + ':' + encodeURIComponent(JSON.stringify(extraParams)) + ':' + label;
DataTable_RowAction.prototype.openPopover.apply(this, [urlParam]);
};
DataTable_RowActions_RowEvolution.prototype.doOpenPopover = function (urlParam) {
var urlParamParts = urlParam.split(':');
var apiMethod = urlParamParts.shift();
var extraParamsString = urlParamParts.shift(),
extraParams = {}; // 0/1 or "0"/"1"
try {
extraParams = JSON.parse(decodeURIComponent(extraParamsString));
} catch (e) {
// assume the parameter is an int/string describing whether to use multi row evolution
if (extraParamsString == '1') {
extraParams.action = 'getMultiRowEvolutionPopover';
} else if (extraParamsString != '0') {
extraParams.action = 'getMultiRowEvolutionPopover';
extraParams.column = extraParamsString;
}
}
var label = urlParamParts.join(':');
this.showRowEvolution(apiMethod, label, extraParams);
};
/** Open the row evolution popover */
DataTable_RowActions_RowEvolution.prototype.showRowEvolution = function (apiMethod, label, extraParams) {
var self = this;
// open the popover
var box = Piwik_Popover.showLoading('Row Evolution');
box.addClass('rowEvolutionPopover');
// prepare loading the popover contents
var requestParams = {
apiMethod: apiMethod,
label: label,
disableLink: 1
};
var callback = function (html) {
Piwik_Popover.setContent(html);
// use the popover title returned from the server
var title = box.find('div.popover-title');
if (title.length) {
Piwik_Popover.setTitle(title.html());
title.remove();
}
Piwik_Popover.onClose(function () {
// reset rows marked for multi row evolution on close
self.multiEvolutionRows = [];
});
if (self.dataTable !== null) {
// remember label for multi row evolution
box.find('.rowevolution-startmulti').click(function () {
Piwik_Popover.onClose(false); // unbind listener that resets multiEvolutionRows
broadcast.propagateNewPopoverParameter(false);
return false;
});
} else {
// when the popover is launched by copy&pasting a url, we don't have the data table.
// in this case, we can't remember the row marked for multi row evolution so
// we disable the picker.
box.find('.compare-container, .rowevolution-startmulti').remove();
}
// switch metric in multi row evolution
box.find('select.multirowevoltion-metric').change(function () {
var metric = $(this).val();
Piwik_Popover.onClose(false); // unbind listener that resets multiEvolutionRows
var extraParams = {action: 'getMultiRowEvolutionPopover', column: metric};
self.openPopover(apiMethod, extraParams, label);
return true;
});
};
requestParams.module = 'CoreHome';
requestParams.action = 'getRowEvolutionPopover';
requestParams.colors = JSON.stringify(piwik.getSparklineColors());
var idDimension;
if (broadcast.getValueFromUrl('module') === 'Widgetize') {
idDimension = broadcast.getValueFromUrl('subcategory');
} else {
idDimension = broadcast.getValueFromHash('subcategory');
}
if (idDimension && ('' + idDimension).indexOf('customdimension') === 0) {
idDimension = ('' + idDimension).replace('customdimension', '');
idDimension = parseInt(idDimension, 10);
if (idDimension > 0) {
requestParams.idDimension = idDimension;
}
}
if (self.dataTable && self.dataTable.jsViewDataTable === 'tableGoals') {
// remove idGoal param, when it's set for goal visualizations
if (extraParams['idGoal']) {
delete(extraParams['idGoal']);
}
}
$.extend(requestParams, extraParams);
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams(requestParams, 'get');
ajaxRequest.setCallback(callback);
ajaxRequest.setFormat('html');
ajaxRequest.send();
};

View File

@ -0,0 +1,142 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function ($) {
$(document).ready(function () {
var donateAmounts = [0, 30, 60, 90, 120];
// returns the space between each donation amount in the donation slider
var getTickWidth = function (slider) {
var effectiveSliderWidth = $('.slider-range', slider).width() - $('.slider-position', slider).width();
return effectiveSliderWidth / (donateAmounts.length - 1);
};
// returns the position index on a slider based on a x coordinate value
var getPositionFromPageCoord = function (slider, pageX) {
return Math.round((pageX - $('.slider-range', slider).offset().left) / getTickWidth(slider));
};
// set's the correct amount text & smiley face image based on the position of the slider
var setSmileyFaceAndAmount = function (slider, pos) {
// set text yearly amount
$('.slider-donate-amount', slider).text('$' + donateAmounts[pos] + '/' + _pk_translate('Intl_Year_Short'));
// set the right smiley face
$('.slider-smiley-face').attr('src', 'plugins/Morpheus/images/smileyprog_' + pos + '.png');
// set the hidden option input for paypal
var option = Math.max(1, pos);
$('.piwik-donate-call input[name=os0]').val("Option " + option);
};
// move's a slider's position to a specific spot
var moveSliderPosition = function (slider, to) {
// make sure 'to' is valid
if (to < 0) {
to = 0;
}
else if (to >= donateAmounts.length) {
to = donateAmounts.length - 1;
}
// set the slider position
var left = to * getTickWidth(slider);
if (left == 0) {
left = -1; // at position 0 we move one pixel left to cover up some of slider bar
}
$('.slider-position', slider).css({
left: left + 'px'
});
// reset the smiley face & amount based on the new position
setSmileyFaceAndAmount(slider, to);
};
// when a slider is clicked, set the amount & smiley face appropriately
$('body').on('click', '.piwik-donate-slider>.slider-range', function (e) {
var slider = $(this).parent(),
currentPageX = $('.slider-position', this).offset().left,
currentPos = getPositionFromPageCoord(slider, currentPageX),
pos = getPositionFromPageCoord(slider, e.pageX);
// if the closest position is the current one, use the other position since
// the user obviously wants to move the slider.
if (currentPos == pos) {
// if click is to right, go forward one, else backwards one
if (e.pageX > currentPageX) {
++pos;
}
else {
--pos;
}
}
moveSliderPosition(slider, pos);
});
// when the smiley icon is clicked, move the position up one to demonstrate how to use the slider
$('body').on('click', '.piwik-donate-slider .slider-smiley-face,.piwik-donate-slider .slider-donate-amount',
function (e) {
var slider = $(this).closest('.piwik-donate-slider'),
currentPageX = $('.slider-position', slider).offset().left,
currentPos = getPositionFromPageCoord(slider, currentPageX);
moveSliderPosition(slider, currentPos + 1);
}
);
// stores the current slider being dragged
var draggingSlider = false;
// start dragging on mousedown for a slider's position bar
$('body').on('mousedown', '.piwik-donate-slider .slider-position', function () {
draggingSlider = $(this).parent().parent();
});
// move the slider position if currently dragging when the mouse moves anywhere over the entire page
$('body').on('mousemove', function (e) {
if (draggingSlider) {
var slider = draggingSlider.find('.slider-range'),
sliderPos = slider.find('.slider-position'),
left = e.pageX - slider.offset().left;
// only move slider if the mouse x-coord is still on the slider (w/ some padding for borders)
if (left <= slider.width() - sliderPos.width() + 2
&& left >= -2) {
sliderPos.css({left: left + 'px'});
var closestPos = Math.round(left / getTickWidth(draggingSlider));
setSmileyFaceAndAmount(draggingSlider, closestPos);
}
}
});
// stop dragging and normalize a slider's position on mouseup over the entire page
$('body').on('mouseup', function () {
if (draggingSlider) {
var sliderPos = $('.slider-position', draggingSlider),
slider = sliderPos.parent();
if (sliderPos.length) {
// move the slider to the nearest donation amount position
var pos = getPositionFromPageCoord(draggingSlider, sliderPos.offset().left);
moveSliderPosition(draggingSlider, pos);
}
draggingSlider = false; // stop dragging
}
});
// event for programatically changing the position
$('body').on('piwik:changePosition', '.piwik-donate-slider', function (e, data) {
moveSliderPosition(this, data.position);
});
});
}(jQuery));

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,48 @@
{
"short_name": "Matomo",
"name": "Matomo - Open Source Analytics",
"icons": [
{
"src": "../images/applogo_32.png",
"sizes": "32x32",
"type": "image/png"
},
{
"src": "../images/applogo_72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "../images/applogo_128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "../images/applogo_144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "../images/applogo_192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "../images/applogo_256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"start_url": "../../../",
"display": "standalone",
"orientation": "portrait",
"background_color": "#edecec",
"theme_color": "#3450A3",
"prefer_related_applications": true,
"related_applications": [
{
"platform": "play",
"id": "org.piwik.mobile2"
}
]
}

View File

@ -0,0 +1,18 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
$(function () {
if ($.browser.msie && parseInt($.browser.version) === 10) {
$(document).on('click', 'a[rel~="noreferrer"]', function (event) {
event.preventDefault();
var a = event.currentTarget;
var w = window.open(a.href, a.target || '_self');
if (/\bnoopener\b/.test(a.rel)) {
w.opener = null;
}
});
}
});

View File

@ -0,0 +1,204 @@
/**
* Piwik - free/libre analytics platform
*
* @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');
/**
* Creates a new notifications.
*
* Example:
* var UI = require('piwik/UI');
* var notification = new UI.Notification();
* notification.show('My Notification Message', {title: 'Low space', context: 'warning'});
*/
var Notification = function () {
this.$node = null;
};
/**
* Makes the notification visible.
*
* @param {string} message The actual message that will be displayed. Must be set.
* @param {Object} [options]
* @param {string} [options.id] Only needed for persistent notifications. The id will be sent to the
* frontend once the user closes the notifications. The notification has to
* be registered/notified under this name
* @param {string} [options.title] The title of the notification. For instance the plugin name.
* @param {bool} [options.animate=true] If enabled, the notification will be faded in.
* @param {string} [options.context=warning] Context of the notification: 'info', 'warning', 'success' or
* 'error'
* @param {string} [options.type=transient] The type of the notification: Either 'toast' or 'transitent'
* @param {bool} [options.noclear=false] If set, the close icon is not displayed.
* @param {object} [options.style] Optional style/css dictionary. For instance {'display': 'inline-block'}
* @param {string} [options.placeat] By default, the notification will be displayed in the "stats bar".
* You can specify any other CSS selector to place the notifications
* wherever you want.
*/
Notification.prototype.show = function (message, options) {
checkMessage(message);
options = checkOptions(options);
var template = generateNotificationHtmlMarkup(options, message);
this.$node = placeNotification(template, options);
};
/**
* Removes a previously shown notification having the given notification id.
*
*
* @param {string} notificationId The id of a notification that was previously registered.
*/
Notification.prototype.remove = function (notificationId) {
$('[piwik-notification][notification-id=' + notificationId + ']').remove();
};
Notification.prototype.scrollToNotification = function () {
if (this.$node) {
piwikHelper.lazyScrollTo(this.$node, 250);
}
};
/**
* Shows a notification at a certain point with a quick upwards animation.
*
* TODO: if the materializecss version matomo uses is updated, should use their toasts.
*
* @type {Notification}
* @param {string} message The actual message that will be displayed. Must be set.
* @param {Object} options
* @param {string} options.placeat Where to place the notification. Required.
* @param {string} [options.id] Only needed for persistent notifications. The id will be sent to the
* frontend once the user closes the notifications. The notification has to
* be registered/notified under this name
* @param {string} [options.title] The title of the notification. For instance the plugin name.
* @param {string} [options.context=warning] Context of the notification: 'info', 'warning', 'success' or
* 'error'
* @param {string} [options.type=transient] The type of the notification: Either 'toast' or 'transitent'
* @param {bool} [options.noclear=false] If set, the close icon is not displayed.
* @param {object} [options.style] Optional style/css dictionary. For instance {'display': 'inline-block'}
*/
Notification.prototype.toast = function (message, options) {
checkMessage(message);
options = checkOptions(options);
var $placeat = $(options.placeat);
if (!$placeat.length) {
throw new Error("A valid selector is required for the placeat option when using Notification.toast().");
}
var $template = $(generateNotificationHtmlMarkup(options, message)).hide();
$('body').append($template);
compileNotification($template);
$template.css({
position: 'absolute',
left: $placeat.offset().left,
top: $placeat.offset().top
});
setTimeout(function () {
$template.animate(
{
top: $placeat.offset().top - $template.height()
},
{
duration: 300,
start: function () {
$template.show();
}
}
);
});
};
exports.Notification = Notification;
function generateNotificationHtmlMarkup(options, message) {
var attributeMapping = {
id: 'notification-id',
title: 'notification-title',
context: 'context',
type: 'type',
noclear: 'noclear',
class: 'class',
toastLength: 'toast-length'
},
html = '<div piwik-notification';
for (var key in attributeMapping) {
if (attributeMapping.hasOwnProperty(key)
&& options[key]
) {
html += ' ' + attributeMapping[key] + '="' + options[key].toString().replace(/"/g, "&quot;") + '"';
}
}
html += '>' + message + '</div>';
return html;
}
function compileNotification($node) {
angular.element(document).injector().invoke(function ($compile, $rootScope) {
$compile($node)($rootScope.$new(true));
});
}
function placeNotification(template, options) {
var $notificationNode = $(template);
// compile the template in angular
compileNotification($notificationNode);
if (options.style) {
$notificationNode.css(options.style);
}
var notificationPosition = '#notificationContainer';
var method = 'append';
if (options.placeat) {
notificationPosition = options.placeat;
} else {
// If a modal is open, we want to make sure the error message is visible and therefore show it within the opened modal
var modalSelector = '.modal.open .modal-content';
var modalOpen = $(modalSelector);
if (modalOpen.length) {
notificationPosition = modalSelector;
method = 'prepend';
}
}
$notificationNode = $notificationNode.hide();
$(notificationPosition)[method]($notificationNode);
if (false === options.animate) {
$notificationNode.show();
} else {
$notificationNode.fadeIn(1000);
}
return $notificationNode;
}
function checkMessage(message) {
if (!message) {
throw new Error('No message given, cannot display notification');
}
}
function checkOptions(options) {
if (options && !$.isPlainObject(options)) {
throw new Error('Options has the wrong format, cannot display notification');
} else if (!options) {
options = {};
}
return options;
}
})(jQuery, require);

View File

@ -0,0 +1,143 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Number Formatter for formatting numbers, percent and currencies values
*
* @type {object}
*/
var NumberFormatter = (function () {
var minimumFractionDigits = 0;
var maximumFractionDigits = 2;
/**
* Formats the given numeric value with the given pattern
*
* @param value
* @param pattern
* @returns {string}
*/
function format(value, pattern) {
if (!$.isNumeric(value)) {
return value;
}
pattern = pattern || piwik.numbers.patternNumber;
var patterns = pattern.split(';');
if (patterns.length == 1) {
// No explicit negative pattern was provided, construct it.
patterns.push('-' + patterns[0])
}
// Ensure that the value is positive and has the right number of digits.
var negative = value < 0;
pattern = negative ? patterns[1] : patterns[0];
var usesGrouping = (pattern.indexOf(',') != -1);
// if pattern has number groups, parse them.
if (usesGrouping) {
var primaryGroupMatches = pattern.match(/#+0/);
var primaryGroupSize = primaryGroupMatches[0].length;
var secondaryGroupSize = primaryGroupMatches[0].length;
var numberGroups = pattern.split(',');
// check for distinct secondary group size.
if (numberGroups.length > 2) {
secondaryGroupSize = numberGroups[1].length;
}
}
var signMultiplier = negative ? '-1' : '1';
value = value * signMultiplier;
// Split the number into major and minor digits.
var valueParts = value.toString().split('.');
var majorDigits = valueParts[0];
// Account for maximumFractionDigits = 0, where the number won't
// have a decimal point, and $valueParts[1] won't be set.
minorDigits = valueParts[1] || '';
if (usesGrouping) {
// Reverse the major digits, since they are grouped from the right.
majorDigits = majorDigits.split('').reverse();
// Group the major digits.
var groups = [];
groups.push(majorDigits.splice(0, primaryGroupSize).reverse().join(''));
while (majorDigits.length) {
groups.push(majorDigits.splice(0, secondaryGroupSize).reverse().join(''));
}
// Reverse the groups and the digits inside of them.
groups = groups.reverse();
// Reconstruct the major digits.
majorDigits = groups.join(',');
}
if (minimumFractionDigits < maximumFractionDigits) {
// Strip any trailing zeroes.
var minorDigits = minorDigits.replace(/0+$/,'');
if (minorDigits.length < minimumFractionDigits) {
// Now there are too few digits, re-add trailing zeroes
// until the desired length is reached.
var neededZeroes = minimumFractionDigits - minorDigits.length;
minorDigits += (new Array(neededZeroes+1)).join('0');
}
}
// Assemble the final number and insert it into the pattern.
value = minorDigits ? majorDigits + '.' + minorDigits : majorDigits;
value = pattern.replace(/#(?:[\.,]#+)*0(?:[,\.][0#]+)*/, value);
// Localize the number.
return replaceSymbols(value);
}
/**
* Replaces the placeholders with real symbols
*
* @param value
* @returns {string}
*/
function replaceSymbols(value) {
var replacements = {
'.': piwik.numbers.symbolDecimal,
',': piwik.numbers.symbolGroup,
'+': piwik.numbers.symbolPlus,
'-': piwik.numbers.symbolMinus,
'%': piwik.numbers.symbolPercent
};
var newValue = '';
var valueParts = value.split('');
$.each(valueParts, function(index, value) {
$.each(replacements, function(char, replacement) {
if (value.indexOf(char) != -1) {
value = value.replace(char, replacement);
return false;
}
});
newValue += value;
});
return newValue;
}
/**
* Public available methods
*/
return {
formatNumber: function (value) {
return format(value, piwik.numbers.patternNumber);
},
formatPercent: function (value) {
return format(value, piwik.numbers.patternPercent);
},
formatCurrency: function (value, currency) {
var formatted = format(value, piwik.numbers.patternCurrency);
return formatted.replace('¤', currency);
}
}
})();

View File

@ -0,0 +1,301 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
var Piwik_Popover = (function () {
var container = false;
var isOpen = false;
var closeCallback = false;
var isProgrammaticClose = false;
var createContainer = function () {
if (container === false) {
container = $(document.createElement('div')).attr('id', 'Piwik_Popover');
}
};
var openPopover = function (title, dialogClass) {
createContainer();
var options =
{
title: title,
modal: true,
width: '1050px',
position: ['center', 'center'],
resizable: false,
autoOpen: true,
open: function (event, ui) {
if (dialogClass) {
$(this).parent().addClass(dialogClass).attr('style', '');
}
$('.ui-widget-overlay').on('click.popover', function () {
container.dialog('close');
});
// sometimes the modal can be displayed outside of the current viewport, in this case
// we scroll to it to make sure it's visible. this isn't a perfect workaround, since it
// doesn't center the modal.g
var self = this;
setTimeout(function () {
piwikHelper.lazyScrollTo(self, 0);
}, 0);
},
close: function (event, ui) {
// if clicking outside of the dialog, close entire stack
if (!event.currentTarget && !$(event.currentTarget).is('button')) {
broadcast.resetPopoverStack();
}
container.find('div.jqplot-target').trigger('piwikDestroyPlot');
container[0].innerHTML = '';
container.dialog('destroy').remove();
globalAjaxQueue.abort();
$('.ui-widget-overlay').off('click.popover');
isOpen = false;
require('piwik/UI').UIControl.cleanupUnusedControls();
if (typeof closeCallback == 'function') {
closeCallback();
closeCallback = false;
}
// just to avoid any annoying race conditions that cause tooltips to remain on the screen permanently,
// remove any that still exist
$('body > .ui-tooltip').remove();
// if we were not called by Piwik_Popover.close(), then the user clicked the close button or clicked
// the overlay, in which case we want to handle the popover URL as well as the actual modal.
if (!isProgrammaticClose) {
broadcast.propagateNewPopoverParameter(false);
}
}
};
container.dialog(options);
// override the undocumented _title function to ensure that the title attribute is not escaped (according to jQueryUI bug #6016)
container.data( "uiDialog" )._title = function(title) {
title.html( this.options.title );
};
isOpen = true;
};
var centerPopover = function () {
if (container !== false) {
container.dialog({position: ['center', 'center']});
}
};
return {
/**
* Open the popover with a loading message
*
* @param {string} popoverName name of the popover
* @param {string} [popoverSubject] subject of the popover (e.g. url, optional)
* @param {int} [height] height of the popover in px (optional)
* @param {string} [dialogClass] css class to add to dialog
*/
showLoading: function (popoverName, popoverSubject, height, dialogClass) {
var loading = $(document.createElement('div')).addClass('Piwik_Popover_Loading');
var loadingMessage = popoverSubject ? translations.General_LoadingPopoverFor :
translations.General_LoadingPopover;
loadingMessage = sprintf(loadingMessage, popoverName);
var p1 = $(document.createElement('p')).addClass('Piwik_Popover_Loading_Name');
loading.append(p1.text(loadingMessage));
var p2;
if (popoverSubject) {
popoverSubject = piwikHelper.addBreakpointsToUrl(popoverSubject);
p1.addClass('Piwik_Popover_Loading_NameWithSubject');
p2 = $(document.createElement('p')).addClass('Piwik_Popover_Loading_Subject');
loading.append(p2.html(popoverSubject));
}
if (height) {
loading.height(height);
}
if (!isOpen) {
openPopover(null, dialogClass);
}
this.setContent(loading);
this.setTitle('');
if (height) {
var offset = loading.height() - p1.outerHeight();
if (popoverSubject) {
offset -= p2.outerHeight();
}
var spacingEl = $(document.createElement('div'));
spacingEl.height(Math.round(offset / 2));
loading.prepend(spacingEl);
}
return container;
},
/**
* Add a help button to the current popover
*
* @param {string} helpUrl
*/
addHelpButton: function (helpUrl) {
if (!isOpen) {
return;
}
var titlebar = container.parent().find('.ui-dialog-titlebar');
var button = $(document.createElement('a')).addClass('ui-dialog-titlebar-help');
button.attr({href: helpUrl, target: '_blank'});
titlebar.append(button);
},
/** Set the title of the popover */
setTitle: function (titleHtml) {
var titleText = piwikHelper.htmlDecode(titleHtml);
if (titleText.length > 60) {
titleHtml = $('<span>').attr('class', 'tooltip').attr('title', titleText).html(titleHtml);
}
container.dialog('option', 'title', titleHtml);
try {
$('.tooltip', container.parentNode).tooltip('destroy');
} catch (e) {}
if (titleText.length > 60) {
$('.tooltip', container.parentNode).tooltip({track: true, items: '.tooltip'});
}
},
/** Set inner HTML of the popover */
setContent: function (html) {
if (typeof closeCallback == 'function') {
closeCallback();
closeCallback = false;
}
container.html(html);
container.children().each(function (i, childNode) {
piwikHelper.compileAngularComponents(childNode);
})
centerPopover();
},
/**
* Show an error message. All params are HTML!
*
* @param {string} title
* @param {string} [message]
* @param {string} [backLabel]
*/
showError: function (title, message, backLabel) {
var error = $(document.createElement('div')).addClass('Piwik_Popover_Error');
var p = $(document.createElement('p')).addClass('Piwik_Popover_Error_Title');
error.append(p.html(title));
if (message) {
p = $(document.createElement('p')).addClass('Piwik_Popover_Error_Message');
error.append(p.html(message));
}
if (backLabel) {
var back = $(document.createElement('a')).addClass('Piwik_Popover_Error_Back');
back.attr('href', '#').click(function () {
history.back();
return false;
});
error.append(back.html(backLabel));
}
if (!isOpen) {
openPopover();
}
this.setContent(error);
},
/**
* Add a callback for the next time the popover is closed or the content changes
*
* @param {function} callback
*/
onClose: function (callback) {
closeCallback = callback;
},
/**
* Close the popover.
*
* Note: this method shouldn't normally be used to close a popover, instead
* `broadcast.propagateNewPopoverHandler(false)` should be used.
*/
close: function () {
if (isOpen) {
isProgrammaticClose = true;
container.dialog('close');
isProgrammaticClose = false;
}
},
/**
* Create a Popover and load the specified URL in it.
*
* Note: If you want the popover to be persisted in the URL (so if the URL is copy/pasted
* to a new window/tab it will be opened there), use broadcast.propagateNewPopoverParameter
* with a popover handler function that calls this one.
*
* @param {string} url
* @param {string} loadingName
* @param {string} [dialogClass] css class to add to dialog
* @param {object} [ajaxRequest] optional instance of ajaxHelper
*/
createPopupAndLoadUrl: function (url, loadingName, dialogClass, ajaxRequest) {
// make sure the minimum top position of the popover is 15px
var ensureMinimumTop = function () {
var popoverContainer = $('#Piwik_Popover').parent();
if (popoverContainer.position().top < 106) {
popoverContainer.css('top', '15px');
}
};
// open the popover
var box = Piwik_Popover.showLoading(loadingName, null, null, dialogClass);
ensureMinimumTop();
var callback = function (html) {
function setPopoverTitleIfOneFoundInContainer() {
var title = $('h1,h2', container);
if (title.length == 1) {
Piwik_Popover.setTitle(title.text());
$(title).hide();
}
}
Piwik_Popover.setContent(html);
setPopoverTitleIfOneFoundInContainer();
ensureMinimumTop();
};
if ('undefined' === typeof ajaxRequest) {
ajaxRequest = new ajaxHelper();
}
ajaxRequest.addParams(piwikHelper.getArrayFromQueryString(url), 'get');
ajaxRequest.setCallback(callback);
ajaxRequest.setFormat('html');
ajaxRequest.send();
}
};
})();

View File

@ -0,0 +1,39 @@
/**
* Piwik - free/libre analytics platform
*
* Module creation & inclusion for Piwik.
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function (window) {
var MODULE_SPLIT_REGEX = /[\/.\\]/;
/**
* Returns a module for its ID. Empty modules are created if they does not exist.
*
* Modules are currently stored in the window object.
*
* @param {String} moduleId e.g. 'piwik/UserCountryMap' or 'myPlugin/Widgets/FancySchmancyThing'.
* The following characters can be used to separate individual modules:
* '/', '.' or '\'.
* @return {Object} The module object.
*/
window.require = function (moduleId) {
var parts = moduleId.split(MODULE_SPLIT_REGEX);
// TODO: we use window objects for backwards compatibility. when rest of Piwik is rewritten to use
// require, we can switch simply holding the modules in a private variable.
var currentModule = window;
for (var i = 0; i != parts.length; ++i) {
var part = parts[i];
currentModule[part] = currentModule[part] || {};
currentModule = currentModule[part];
}
return currentModule;
};
})(window);

View File

@ -0,0 +1,115 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function ($) {
var sparklineColorNames = ['backgroundColor', 'lineColor', 'minPointColor', 'maxPointColor', 'lastPointColor', 'fillColor'];
var sparklineDisplayHeight = 25;
var sparklineDisplayWidth = 100;
piwik.getSparklineColors = function () {
return piwik.ColorManager.getColors('sparkline-colors', sparklineColorNames);
};
// initializes each sparkline so they use colors defined in CSS
piwik.initSparklines = function() {
$('.sparkline > img').each(function () {
var $self = $(this);
if ($self.attr('src')) {
return;
}
var colors = JSON.stringify(piwik.getSparklineColors());
var appendToSparklineUrl = '&colors=' + encodeURIComponent(colors);
// Append the token_auth to the URL if it was set (eg. embed dashboard)
var token_auth = broadcast.getValueFromUrl('token_auth');
if (token_auth.length && piwik.shouldPropagateTokenAuth) {
appendToSparklineUrl += '&token_auth=' + token_auth;
}
$self.attr('width', sparklineDisplayWidth);
$self.attr('height', sparklineDisplayHeight);
$self.attr('src', $self.attr('data-src') + appendToSparklineUrl);
});
};
window.initializeSparklines = function () {
var sparklineUrlParamsToIgnore = ['module', 'action', 'idSite', 'period', 'date', 'showtitle', 'viewDataTable', 'forceView', 'random'];
$('.dataTableVizEvolution[data-report]').each(function () {
var graph = $(this);
// we search for .widget to make sure eg in the Dashboard to not update any graph of another report
var selectorsToFindParent = ['.widget', '[piwik-widget-container]', '.reporting-page', 'body'];
var index = 0, selector, parent;
for (index; index < selectorsToFindParent.length; index++) {
selector = selectorsToFindParent[index];
parent = graph.parents(selector).first();
if (parent && parent.length) {
break;
}
}
if (!parent || !parent.length) {
return;
}
var sparklines = parent.find('div.sparkline:not(.notLinkable)');
// try to find sparklines and add them clickable behaviour
sparklines.each(function () {
// find the sparkline and get it's src attribute
var sparklineUrl = $('img', this).attr('data-src');
var $this = $(this);
if (sparklineUrl != "") {
$this.addClass('linked');
var params = broadcast.getValuesFromUrl(sparklineUrl);
for (var i = 0; i != sparklineUrlParamsToIgnore.length; ++i) {
delete params[sparklineUrlParamsToIgnore[i]];
}
for (var key in params) {
if (typeof params[key] == 'undefined') {
// this happens for example with an empty segment parameter
delete params[key];
} else {
params[key] = decodeURIComponent(params[key]);
}
}
// on click, reload the graph with the new url
$this.off('click.sparkline');
$this.on('click.sparkline', function () {
var reportId = graph.attr('data-report'),
dataTable = graph;
// when the metrics picker is used, the id of the data table might be updated (which is correct behavior).
// for example, in goal reports it might change from GoalsgetEvolutionGraph to GoalsgetEvolutionGraph1.
// if this happens, we can't find the graph using $('#'+idDataTable+"Chart");
// instead, we just use the first evolution graph we can find.
if (dataTable.length == 0) {
if ($(this).closest('.widget').length) {
dataTable = $(this).closest('.widget').find('div.dataTableVizEvolution');
} else {
dataTable = $('div.dataTableVizEvolution');
}
}
// reload the datatable w/ a new column & scroll to the graph
dataTable.trigger('reload', params);
});
}
});
});
};
}(jQuery));

View File

@ -0,0 +1,110 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
function initTopControls() {
function getOverlap(element1, element2)
{
if (!element1 || !element1.getBoundingClientRect || !element2 || !element2.getBoundingClientRect) {
return 0;
}
var rect1 = element1.getBoundingClientRect();
var rect2 = element2.getBoundingClientRect();
var doOverlap = !(rect1.right < rect2.left || rect1.left > rect2.right);
if (doOverlap) {
return rect1.left - rect2.right;
}
return 0;
}
var $topControlsContainer = $('.top_controls');
var allRendered = true;
if ($topControlsContainer.length) {
$topControlsContainer.find('.piwikTopControl').each(function () {
var $control = $(this);
if ($control.css('display') == 'none') {
return;
}
var width = $control.outerWidth(true);
var isControlFullyRendered = width >= 30;
if (!isControlFullyRendered) {
allRendered = false;
}
});
if (allRendered) {
// we make top controls visible only after all selectors are rendered
$('.top_controls').css('visibility', 'visible');
$('.top_controls').css('opacity', '1');
}
}
}
//Keyboard controls for Top Controls Calendar through tab and enter.
$( document ).ready(function() {
$('.periodSelector').keydown(function(e){
toggleCalendar(e);
})
blockPropegation();
$('.periodSelector .form-radio').keydown(function(e){
e.stopPropagation();
if(e.which==13){
selectPeriodRadioButton($(this));
}
})
});
//Keyboard controls for Top Controls Calendar through tab and enter.
$( document ).ready(function() {
$('.periodSelector').keydown(function(e){
toggleCalendar(e);
})
blockPropegation();
$('.periodSelector .form-radio').keydown(function(e){
e.stopPropagation();
if(e.which==13){
selectPeriodRadioButton($(this));
}
})
});
function toggleCalendar(e){
var calendarOpen = $('.periodSelector').hasClass('expanded');
if(e.which==13){
if(calendarOpen){
$('.periodSelector').removeClass('expanded');
}else{
$('.periodSelector').addClass('expanded');
}
}
}
function selectPeriodRadioButton(button){
$('.periodSelector .form-radio').removeClass('checked');
button.addClass('checked');
button.find('input').click();
blockPropegation();
}
function blockPropegation(){
$('.ui-datepicker-month, .ui-datepicker-year, .periodSelector td a').keydown(function(e){
e.stopPropagation();
})
}

View File

@ -0,0 +1,124 @@
/**
* Piwik - free/libre analytics platform
*
* 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');
/**
* Base type for Piwik UI controls. Provides functionality that all controls need (such as
* cleanup on destruction).
*
* @param {Element} element The root element of the control.
*/
var UIControl = function (element) {
if (!element) {
throw new Error("no element passed to UIControl constructor");
}
this._controlId = UIControl._nextControlId++;
UIControl._controls.push(this);
var $element = this.$element = $(element);
$element.data('uiControlObject', this);
var params = JSON.parse($element.attr('data-params') || '{}');
for (var key in params) { // convert values in params that are arrays to comma separated string lists
if (params[key] instanceof Array) {
params[key] = params[key].join(',');
}
}
this.param = params;
this.props = JSON.parse($element.attr('data-props') || '{}');
};
/**
* Contains all active control instances.
*/
UIControl._controls = [];
/**
* Specifies the next unique control ID to use.
*/
UIControl._nextControlId = 0;
/**
* Utility method that will clean up all piwik UI controls whose elements are not attached
* to the DOM.
*
* TODO: instead of having other pieces of the UI manually calling cleanupUnusedControls,
* MutationObservers should be used
*/
UIControl.cleanupUnusedControls = function () {
var controls = UIControl._controls;
// reset _controls; we will repopulate it with only active
// controls in the loop below.
var activeControls = UIControl._controls = [];
for (var i = 0; i != controls.length; ++i) {
var control = controls[i];
if (control
&& control.$element
&& !$.contains(document.documentElement, control.$element[0])
) {
controls[i] = null;
control._destroy();
if (!control._baseDestroyCalled) {
throw new Error("Error: " + control.constructor.name + "'s destroy method does not call " +
"UIControl.destroy. You may have a memory leak.");
}
} else {
// Control is still active / used.
activeControls.push(control);
}
}
};
UIControl.initElements = function (klass, selector) {
$(selector).each(function () {
if (!$(this).attr('data-inited')) {
var control = new klass(this);
$(this).attr('data-inited', 1);
}
});
};
UIControl.prototype = {
/**
* Perform cleanup. Called when the control has been removed from the DOM. Derived
* classes should overload this function to perform their own cleanup.
*/
_destroy: function () {
this.$element.removeData('uiControlObject');
delete this.$element;
this._baseDestroyCalled = true;
},
/**
* Handle the widget resize event, if we're currently in a widget.
*
* TODO: should use proper resize detection (see
* http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/ )
* with timeouts (since resizing widgets can be expensive)
*/
onWidgetResize: function (handler) {
var $widget = this.$element.closest('.widgetContent');
$widget.on('widget:maximise', handler)
.on('widget:minimise', handler)
.on('widget:resize', handler);
}
};
exports.UIControl = UIControl;
})(jQuery, require);

View File

@ -0,0 +1,36 @@
$(function () {
angular.element(document).injector().invoke(handleZenMode);
function handleZenMode ($rootElement, $cookies) {
var zenMode = !!parseInt($cookies.get('zenMode'), 10);
var iconSwitcher = $('.top_controls .icon-arrowup');
iconSwitcher.click(function(event) {
Mousetrap.trigger('z')
});
function updateZenMode() {
if (zenMode) {
$('body').addClass('zenMode');
iconSwitcher.addClass('icon-arrowdown').removeClass('icon-arrowup');
iconSwitcher.prop('title', _pk_translate('CoreHome_ExitZenMode'));
} else {
$('body').removeClass('zenMode');
iconSwitcher.removeClass('icon-arrowdown').addClass('icon-arrowup');
iconSwitcher.prop('title', _pk_translate('CoreHome_EnterZenMode'));
}
}
piwikHelper.registerShortcut('z', _pk_translate('CoreHome_ShortcutZenMode'), function (event) {
if (event.altKey) {
return;
}
zenMode = !zenMode;
$cookies.put('zenMode', zenMode ? '1' : '0');
updateZenMode();
});
updateZenMode();
}
});