diff --git a/oss/piwik/trunk/plugins/CoreHome/Controller.php b/oss/piwik/trunk/plugins/CoreHome/Controller.php new file mode 100644 index 00000000..a70518c0 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/Controller.php @@ -0,0 +1,92 @@ +getUserPreference(Piwik::getCurrentUserLogin(), Piwik_UsersManager_API::PREFERENCE_DEFAULT_REPORT); + $module = 'CoreHome'; + $action = 'index'; + + // User preference: default report to load is the All Websites dashboard + if($defaultReport == 'MultiSites' + && Piwik_PluginsManager::getInstance()->isPluginActivated('MultiSites')) + { + $module = 'MultiSites'; + } + if($defaultReport == Piwik::getLoginPluginName()) + { + $module = Piwik::getLoginPluginName(); + } + + parent::redirectToIndex($module, $action); + } + + public function showInContext() + { + $controllerName = Piwik_Common::getRequestVar('moduleToLoad'); + $actionName = Piwik_Common::getRequestVar('actionToLoad', 'index'); + $view = $this->getDefaultIndexView(); + $view->content = Piwik_FrontController::getInstance()->fetchDispatch( $controllerName, $actionName ); + echo $view->render(); + } + + protected function getDefaultIndexView() + { + $view = Piwik_View::factory('index'); + $this->setGeneralVariablesView($view); + $view->menu = Piwik_GetMenu(); + $view->content = ''; + return $view; + } + + protected function setDateTodayIfWebsiteCreatedToday() + { + $date = Piwik_Common::getRequestVar('date', false); + if($date == 'today') + { + return; + } + $websiteId = Piwik_Common::getRequestVar('idSite', false); + if ($websiteId) { + $website = new Piwik_Site($websiteId); + $datetimeCreationDate = $this->site->getCreationDate()->getDatetime(); + $creationDateLocalTimezone = Piwik_Date::factory($datetimeCreationDate, $website->getTimezone())->toString('Y-m-d'); + $todayLocalTimezone = Piwik_Date::factory('now', $website->getTimezone())->toString('Y-m-d'); + if( $creationDateLocalTimezone == $todayLocalTimezone ) + { + Piwik::redirectToModule( 'CoreHome', 'index', + array( 'date' => 'today', + 'idSite' => $websiteId, + 'period' => Piwik_Common::getRequestVar('period')) + ); + } + } + } + + public function index() + { + $this->setDateTodayIfWebsiteCreatedToday(); + $view = $this->getDefaultIndexView(); + echo $view->render(); + } +} diff --git a/oss/piwik/trunk/plugins/CoreHome/CoreHome.php b/oss/piwik/trunk/plugins/CoreHome/CoreHome.php new file mode 100644 index 00000000..12e9d294 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/CoreHome.php @@ -0,0 +1,28 @@ + Piwik_Translate('CoreHome_PluginDescription'), + 'author' => 'Piwik', + 'author_homepage' => 'http://piwik.org/', + 'version' => Piwik_Version::VERSION, + ); + } +} diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/_notes/dwsync.xml b/oss/piwik/trunk/plugins/CoreHome/templates/_notes/dwsync.xml new file mode 100644 index 00000000..f1c11f93 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/_notes/dwsync.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/broadcast.js b/oss/piwik/trunk/plugins/CoreHome/templates/broadcast.js new file mode 100644 index 00000000..74d1149e --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/broadcast.js @@ -0,0 +1,319 @@ +/* broadcast object is to help maintain a hash for link clicks and ajax calls + * so we can have back button and refresh button working. + * + * Other file that currently depending on this are: + * calendar.js + * period_select.tpl + * sites_selections.tpl + * menu.js, ...etc + */ + +// Load this once and only once. +broadcast = {}; + +broadcast.init = function() { + if(typeof broadcast.isInit != 'undefined') { + return; + } + broadcast.isInit = true; + // Initialize history plugin. + // The callback is called at once by present location.hash + $.historyInit(broadcast.pageload); + + piwikHelper.showAjaxLoading(); +} + +/************************************************ + * + * Broadcast Main Methods: + * + ************************************************/ +/**========== PageLoad function ================= +* This function is called when: +* 1. after calling $.historyInit(); +* 2. after calling $.historyLoad(); //look at broadcast.changeParameter(); +* 3. after pushing "Go Back" button of a browser +*/ +broadcast.pageload = function( hash ) { + broadcast.init(); + // hash doesn't contain the first # character. + if( hash ) { + // restore ajax loaded state + broadcast.loadAjaxContent(hash); + } else { + // start page + $('#content').empty(); + } +}; + +/* ============================================ + * propagateAjax -- update hash values then make ajax calls. + * example : + * 1) View keywords report + * 2) Main menu li also goes through this function. check out onClickLi(); + * + * 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. + */ + +broadcast.propagateAjax = function (ajaxUrl) +{ + broadcast.init(); + // available in global scope + var currentHashStr = window.location.hash; + + // Because $.history plugin doesn't care about # or ? sign in front of the query string + // We take it out if it exists + currentHashStr = currentHashStr.replace(/^\?|^#/,''); + ajaxUrl = ajaxUrl.replace(/^\?|&#/,''); + + var params_vals = ajaxUrl.split("&"); + for( var i=0; i= 0 ) { + var endStr = url.indexOf("&", startStr); + if( endStr == -1 ) { + endStr = url.length; + } + return url.substring(startStr + param.length +1,endStr); + } else { + return ''; + } +}; diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/calendar.js b/oss/piwik/trunk/plugins/CoreHome/templates/calendar.js new file mode 100644 index 00000000..506f994e --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/calendar.js @@ -0,0 +1,164 @@ +Date.prototype.getWeek = function() { + var onejan = new Date(this.getFullYear(),0,1); + return Math.ceil((((this - onejan) / 86400000) + onejan.getDay())/7); +} + +var splitDate = piwik.currentDateString.split("-"); +var currentYear = splitDate[0]; +var currentMonth = splitDate[1] - 1; +var currentDay = splitDate[2]; +var currentDate = new Date(currentYear, currentMonth, currentDay); + +var todayDate = new Date; +var todayMonth = todayDate.getMonth(); +var todayYear = todayDate.getFullYear(); +var todayDay = todayDate.getDate(); + +function highlightCurrentPeriod( date ) +{ + var valid = false; + + var dateMonth = date.getMonth(); + var dateYear = date.getFullYear(); + var dateDay = date.getDate(); + var style = ''; + + // we don't color dates in the future + if( dateMonth == todayMonth + && dateYear == todayYear + && dateDay >= todayDay + ) + { + return [true, '']; + } + + // we don't color dates before the minimum date + if( dateYear < piwik.minDateYear + || ( dateYear == piwik.minDateYear + && + ( + (dateMonth == piwik.minDateMonth - 1 + && dateDay < piwik.minDateDay) + || (dateMonth < piwik.minDateMonth - 1) + ) + ) + ) + { + return [true, '']; + } + + // we color all day of the month for the same year for the month period + if(piwik.period == "month" + && dateMonth == currentMonth + && dateYear == currentYear + ) + { + valid = true; + } + // we color all day of the year for the year period + else if(piwik.period == "year" + && dateYear == currentYear + ) + { + valid = true; + } + else if(piwik.period == "week" + && date.getWeek() == currentDate.getWeek() + && dateYear == currentYear + ) + { + valid = true; + } + else if( piwik.period == "day" + && dateDay == currentDay + && dateMonth == currentMonth + && dateYear == currentYear + ) + { + valid = true; + } + + if(valid) + { + return [true, 'ui-datepicker-current-period']; + } + + return [true, '']; +} + +function updateDate(dateText, inst) +{ + var date = dateText; + // Let broadcast do its job: + // It will replace date value to both search query and hash and load the new page. + broadcast.propagateNewPage('date=' + date); +} + +$(document).ready(function(){ + $('#datepicker').datepicker({ + onSelect: updateDate, + showOtherMonths: false, + dateFormat: 'yy-mm-dd', + firstDay: 1, + minDate: new Date(piwik.minDateYear, piwik.minDateMonth - 1, piwik.minDateDay), + maxDate: new Date(piwik.maxDateYear, piwik.maxDateMonth - 1, piwik.maxDateDay), + prevText: "", + nextText: "", + currentText: "", + beforeShowDay: highlightCurrentPeriod, + defaultDate: currentDate, + changeMonth: true, + changeYear: true, + // jquery-ui-i18n 1.7.2 lacks some translations, so we use our own + dayNamesMin: [ + _pk_translate('CoreHome_DaySu_js'), + _pk_translate('CoreHome_DayMo_js'), + _pk_translate('CoreHome_DayTu_js'), + _pk_translate('CoreHome_DayWe_js'), + _pk_translate('CoreHome_DayTh_js'), + _pk_translate('CoreHome_DayFr_js'), + _pk_translate('CoreHome_DaySa_js')], + dayNamesShort: [ + _pk_translate('CoreHome_ShortDay_1_js'), + _pk_translate('CoreHome_ShortDay_2_js'), + _pk_translate('CoreHome_ShortDay_3_js'), + _pk_translate('CoreHome_ShortDay_4_js'), + _pk_translate('CoreHome_ShortDay_5_js'), + _pk_translate('CoreHome_ShortDay_6_js'), + _pk_translate('CoreHome_ShortDay_7_js')], + dayNames: [ + _pk_translate('CoreHome_LongDay_1_js'), + _pk_translate('CoreHome_LongDay_2_js'), + _pk_translate('CoreHome_LongDay_3_js'), + _pk_translate('CoreHome_LongDay_4_js'), + _pk_translate('CoreHome_LongDay_5_js'), + _pk_translate('CoreHome_LongDay_6_js'), + _pk_translate('CoreHome_LongDay_7_js')], + monthNamesShort: [ + _pk_translate('CoreHome_ShortMonth_1_js'), + _pk_translate('CoreHome_ShortMonth_2_js'), + _pk_translate('CoreHome_ShortMonth_3_js'), + _pk_translate('CoreHome_ShortMonth_4_js'), + _pk_translate('CoreHome_ShortMonth_5_js'), + _pk_translate('CoreHome_ShortMonth_6_js'), + _pk_translate('CoreHome_ShortMonth_7_js'), + _pk_translate('CoreHome_ShortMonth_8_js'), + _pk_translate('CoreHome_ShortMonth_9_js'), + _pk_translate('CoreHome_ShortMonth_10_js'), + _pk_translate('CoreHome_ShortMonth_11_js'), + _pk_translate('CoreHome_ShortMonth_12_js')], + monthNames: [ + _pk_translate('CoreHome_MonthJanuary_js'), + _pk_translate('CoreHome_MonthFebruary_js'), + _pk_translate('CoreHome_MonthMarch_js'), + _pk_translate('CoreHome_MonthApril_js'), + _pk_translate('CoreHome_MonthMay_js'), + _pk_translate('CoreHome_MonthJune_js'), + _pk_translate('CoreHome_MonthJuly_js'), + _pk_translate('CoreHome_MonthAugust_js'), + _pk_translate('CoreHome_MonthSeptember_js'), + _pk_translate('CoreHome_MonthOctober_js'), + _pk_translate('CoreHome_MonthNovember_js'), + _pk_translate('CoreHome_MonthDecember_js')] + }); +}); diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/cloud.css b/oss/piwik/trunk/plugins/CoreHome/templates/cloud.css new file mode 100644 index 00000000..9854942a --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/cloud.css @@ -0,0 +1,44 @@ +.tagCloud { + width:100%; +} +.tagCloud img { + border:0; +} +.tagCloud .word a { + text-decoration:none; +} +.tagCloud .word { + padding: 4px 8px 4px 0; + white-space: nowrap; +} +.tagCloud .valueIsZero { + text-decoration: line-through; +} +.tagCloud span.size0, .tagCloud span.size0 a { + color: #344971; + font-size: 28px; +} +.tagCloud span.size1, .tagCloud span.size1 a { + color: #344971; + font-size: 24px; +} +.tagCloud span.size2, .tagCloud span.size2 a { + color: #4B74AD; + font-size:20px; +} +.tagCloud span.size3, .tagCloud span.size3 a { + color: #A3A8B6; + font-size: 16px; +} +.tagCloud span.size4, .tagCloud span.size4 a { + color: #A3A8B6; + font-size: 15px; +} +.tagCloud span.size5, .tagCloud span.size5 a { + color: #A3A8B6; + font-size: 14px; +} +.tagCloud span.size6, .tagCloud span.size6 a { + color: #A3A8B6; + font-size: 11px; +} diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/cloud.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/cloud.tpl new file mode 100644 index 00000000..b05a8db2 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/cloud.tpl @@ -0,0 +1,18 @@ +
+
+{if count($cloudValues) == 0} +
{'General_NoDataForTagCloud'|translate}
+{else} + {foreach from=$cloudValues key=word item=value} + + {if false !== $labelMetadata[$value.word].url}{/if} + {if false !== $labelMetadata[$value.word].logo}{else} + {$value.wordTruncated}{/if}{if false !== $labelMetadata[$value.word].url}{/if} + {/foreach} +{/if} +{if $properties.show_footer} + {include file="CoreHome/templates/datatable_footer.tpl"} +{/if} +{include file="CoreHome/templates/datatable_js.tpl"} +
+
diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/datatable.css b/oss/piwik/trunk/plugins/CoreHome/templates/datatable.css new file mode 100644 index 00000000..47f89df3 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/datatable.css @@ -0,0 +1,365 @@ + +/*Overriding some dataTable css for better dashboard display*/ +.widget .dataTableWrapper, +.widget .dataTableAllColumnsWrapper, +.widget .dataTableGraphWrapper, +.widget .dataTableActionsWrapper, +.widget .dataTableGraphEvolutionWrapper { + width: 100%; +} + +.widget { + z-index:1; +} + +/* container of each table */ +.dataTableWrapper { + width: 450px; + /* not more than 450px to make sure 2 tables can fit horizontally on a 1024 screen */ +} +.dataTableAllColumnsWrapper { + width: 535px; +} + +.subdataTableWrapper{ + width: 95%; +} +.subdataTableAllColumnsWrapper { + width: 95%; +} + +.dataTableActionsWrapper { + width: 500px; +} + +.dataTableGraphWrapper { + width: 500px; +} + +.dataTableGraphEvolutionWrapper { + width: 100%; +} + +/* main data table */ +table.dataTable { + width: 100%; + padding: 0; + border-spacing: 0; + margin: 0; + font-size: 0.9em; +} + +table.dataTable td.label, +table.subDataTable td.label, +table.dataTableActions td.label { + width: 100%; + white-space:nowrap; +} + +table.dataTable img, +table.subDataTable img, +table.dataTableActions img { + vertical-align: middle; +} + +table.dataTable img { + border: 0; + margin-right: 1em; + margin-left: 0.5em; +} + +table.dataTable tr.subDataTable { + cursor: pointer; +} + +table.dataTable th { + margin: 0; + color: #6D929B; + border-right: 1px solid #C1DAD7; + border-bottom: 1px solid #C1DAD7; + border-top: 1px solid #C1DAD7; + text-align: left; + padding: 6px 6px 6px 12px; + background: #D4E3ED url(images/bg_header.jpg) repeat-x; +} + +table.dataTable th.first { + -moz-border-radius:6px 0 0 0; + -webkit-border-radius:6px 0 0 0; +} + +table.dataTable th.last { + -moz-border-radius:0 6px 0 0; + -webkit-border-radius:0 6px 0 0; +} +table.dataTable th.columnSorted { + font-weight: bold; + padding-right: 20px; +} + +table.dataTable td { + border-right: 1px solid #C1DAD7; + border-bottom: 1px solid #C1DAD7; + border-left: 0; + padding: 5px 5px 5px 12px; + background: #fff; +} + +table.dataTable td,table.dataTable td a { + margin: 0; + text-decoration: none; + color: #4f6b72; +} + +table.dataTable td.labeleven,table.dataTable td.columneven { + background: #F9FAFA; +} + +table.dataTable td.columneven { + color: #797268; +} + +table.dataTable td.labeleven { + background-image: url(images/bullet2.gif); + background-repeat: no-repeat; + color: #797268; +} + +table.dataTable td.labelodd { + background: #fff url(images/bullet1.gif) no-repeat; +} + +table.dataTable td.label,table.subActionsDataTable td.label,table.actionsDataTable td.label +{ + border-top: 0; + border-left: 1px solid #C1DAD7; +} + +table.dataTable th.label { + border-left: 1px solid #C1DAD7; +} + +/* the cell containing the subdatatable */ +table.dataTable .cellSubDataTable { + border-left: 1px solid #C1DAD7; + padding: 0; + margin: 0; +} + +/* A link in a column in the DataTable */ +table.dataTable td #urlLink { + display: none; +} + +/* SUBDATATABLE */ /* a datatable inside another datatable */ +table.subDataTable { + background: #FFFFFF; + margin: 10px; +} + +table.subDataTable td { + border: 0; +} + +table.subDataTable thead th { + font-weight: normal; + font-size: 1.1em; + text-align: left; + border: 0; + border-bottom:1px solid #D1D1D1; + border-right:1px solid #D1D1D1; + border-top:1px solid #D1D1D1; + padding: .3em 1em; + color: #333333; + background: #FFE9C6; +} + +table.subDataTable td.labeleven,table.subDataTable td.labelodd { + background-image: none; +} + +table.subDataTable td { + border-right: 1px solid #E5E5E5; + border-bottom: 1px solid #E5E5E5; + border-left: 0; +} + +table.subDataTable td,table.subDataTable td a { + color: #615B53; +} + +table.subDataTable td.labeleven,table.subDataTable td.columneven { + color: #2D2A27; +} + +table.subDataTable td.label { + width: 80%; +} + +table.subDataTable td.labelodd,table.subDataTable td.labelodd a { + background: #ffffff; +} +table.subDataTable td.label { + padding: 5px; +} +table.dataTable img { + margin-left:0; +} +/* misc SPAN and DIV */ +table thead div { + +} + +#sortIconContainer { + float: right; +} + +#sortIcon { + margin: 0px; + position: absolute; +} + +.dataTablePages { + color: #BFBFBF; + font-weight: bold; + margin: 10px; + font-size: 0.9em; +} + +.dataTableSearchPattern { + display: inline; + white-space: nowrap; +} + +.dataTableSearchPattern input { + font-size: 0.7em; + padding: 2px; + border: 1px solid #B3B3B3; + color: #0C183A; +} + +.dataTableSearchPattern input:hover { + background: #F7F7FF none repeat scroll 0%; +} + +.dataTableSearchPattern #keyword { + background: transparent url(images/search.png) no-repeat scroll 4px + center; + padding: 3px 3px 3px 20px; +} + +.dataTableExcludeLowPopulation,.dataTableNext,.dataTablePrevious { + font-size: 0.9em; + color: #184A83; + text-decoration: underline; + cursor: pointer; +} + +/* @todo are these supposed to be together? */ +.subDataTable.dataTableFeatures { + padding-top: 0px; + padding-bottom: 5px; + width: 100%; +} + +.dataTableFeatures { + padding-top: 10px; + padding-bottom: 10px; + width: 100%; + text-align: center; +} + +.dataTableExcludeLowPopulation { + float: right; + font-size: 0.8em; + color: #C3C6D8; + text-align: right; +} + +.dataTableNext,.dataTablePrevious,.dataTableSearchPattern,.pk-loadingDataTable +{ + display: none; +} + +.subDataTable .dataTableFooterIcons { + height: 0px; +} + +.dataTableFooterIcons { + float: right; + height: 18px; +} + +.exportToFormatIcons { + float: right; +} + +.dataTableFooterIconsShow { + float: right; +} + +.dataTableFooterIcons,.dataTableFooterIcons a { + text-decoration: none; + color: #8894B1; + font-size:0.9em; +} + +.dataTableSpacer { + clear: both; +} + +.pk-loadingDataTable { + float: left; + font-size: 0.9em; + color: #193B6C; + padding: 0.5em; +} + +/* Actions table */ +table.dataTableActions tr td.labelodd { + background-image: none; +} + + +/* levels higher than 4 have a default padding left */ +tr.subActionsDataTable td.label,tr.actionsDataTable td.label { + padding-left: 7em; +} + +tr.level0 td.label { + padding-left: +1.5em; +} + +tr.level1 td.label { + padding-left: +2.5em; +} + +tr.level2 td.label { + padding-left: +3.5em; +} + +tr.level3 td.label { + padding-left: +4.5em; +} + +tr.level4 td.label { + padding-left: +5em; +} + +/* less right margins for the link image in the Pa*/ +table.dataTableActions img.link { + margin-right: 0.3em; + margin-left:-0.5em; +} +tr td.label img.plusMinus { + margin-right: 0em; + margin-left:-1em; +} + +.pk-emptyDataTable { + padding-top: 20px; + padding-bottom: 10px; + text-align: center; + font-size: 0.9em; + font-style: italic; +} diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/datatable.js b/oss/piwik/trunk/plugins/CoreHome/templates/datatable.js new file mode 100644 index 00000000..75c11a62 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/datatable.js @@ -0,0 +1,1040 @@ +//----------------------------------------------------------------------------- +// DataTable +//----------------------------------------------------------------------------- +//A list of all our DataTables +//Test if the object have already been initialized (multiple includes) +if(typeof dataTables == "undefined") + var dataTables = {}; + +//DataTable constructor +function dataTable() +{ + this.param = {}; +} + +//Prototype of the DataTable object +dataTable.prototype = +{ + //initialisation function + init: function(workingDivId, domElem) + { + if(typeof domElem == "undefined") + { + domElem = $('#'+workingDivId); + } + + this.workingDivId = workingDivId; + this.loadedSubDataTable = {}; + this.bindEventsAndApplyStyle(domElem); + this.initialized = true; + }, + + //function triggered when user click on column sort + onClickSort: function(domElem) + { + var self = this; + var newColumnToSort = $(domElem).attr('id'); + // we lookup if the column to sort was already this one, if it is the case then we switch from desc <-> asc + if(self.param.filter_sort_column == newColumnToSort) + { + // toggle the sorted order + if(this.param.filter_sort_order == 'asc') + { + self.param.filter_sort_order = 'desc'; + } + else + { + self.param.filter_sort_order = 'asc'; + } + } + self.param.filter_offset = 0; + self.param.filter_sort_column = newColumnToSort; + self.reloadAjaxDataTable(); + }, + + //Reset DataTable filters (used before a reload or view change) + resetAllFilters: function() + { + var self = this; + var FiltersToRestore = new Array(); + filters = [ + 'filter_column', + 'filter_pattern', + 'filter_column_recursive', + 'filter_pattern_recursive', + 'enable_filter_excludelowpop', + 'filter_offset', + 'filter_limit', + 'filter_sort_column', + 'filter_sort_order' + ]; + + for(var key in filters) + { + var value = filters[key]; + FiltersToRestore[value] = self.param[value]; + delete self.param[value]; + } + + return FiltersToRestore; + }, + + //Restores the filters to the values given in the array in parameters + restoreAllFilters: function(FiltersToRestore) + { + var self = this; + for(key in FiltersToRestore) + { + self.param[key] = FiltersToRestore[key]; + } + }, + + //Translate string parameters to javascript builtins + //'true' -> true, 'false' -> false + //it simplifies condition tests in the code + cleanParams: function() + { + var self = this; + for(var key in self.param) + { + if(self.param[key] == 'true') self.param[key]=true; + if(self.param[key] == 'false') self.param[key]=false; + } + }, + + // Returns the standard Ajax request object used by the Jquery .ajax method + buildAjaxRequest: function(callbackSuccess) + { + var self = this; + + //prepare the ajax request + var ajaxRequest = + { + type: 'GET', + url: 'index.php', + dataType: 'html', + async: true, + error: piwikHelper.ajaxHandleError, // Callback when the request fails + success: callbackSuccess, // Callback when the request succeeds + data: new Object + }; + + //Extract the configuration from the datatable and pass it to the API + for(var key in self.param) + { + if(typeof self.param[key] != "undefined") + ajaxRequest.data[key] = self.param[key]; + } + + return ajaxRequest; + }, + + // Function called to trigger the AJAX request + // The ajax request contains the function callback to trigger if the request is successful or failed + // displayLoading = false When we don't want to display the Loading... DIV .pk-loadingDataTable + // for example when the script add a Loading... it self and doesn't want to display the generic Loading + reloadAjaxDataTable: function(displayLoading, callbackSuccess) + { + var self = this; + + if (typeof displayLoading == "undefined") + { + displayLoading = true; + } + if (typeof callbackSuccess == "undefined") + { + callbackSuccess = self.dataTableLoaded; + } + + if(displayLoading) + { + $('#'+self.workingDivId+' .pk-loadingDataTable').last().css('display','block'); + } + + $.ajax(self.buildAjaxRequest(callbackSuccess)); + }, + + + // Function called when the AJAX request is successful + // it looks for the ID of the response and replace the very same ID + // in the current page with the AJAX response + dataTableLoaded: function(response) + { + var content = $(response); + var idToReplace = $(content).attr('id'); + var dataTableSel = $('#'+idToReplace); + + // if the current dataTable is located inside another datatable + table = $(content).parents('table.dataTable'); + if(dataTableSel.parents('.dataTable').is('table')) + { + // we add class to the table so that we can give a different style to the subtable + $(content).find('table.dataTable').addClass('subDataTable'); + $(content).find('.dataTableFeatures').addClass('subDataTable'); + + //we force the initialisation of subdatatables + dataTableSel.html( $(content) ); + } + else + { + dataTableSel.find('object').remove(); + dataTableSel.html( $(content) ); + piwikHelper.lazyScrollTo(dataTableSel[0], 400); + } + }, + + + /* This method is triggered when a new DIV is loaded, which happens + - at the first loading of the page + - after any AJAX loading of a DataTable + + This method basically add features to the DataTable, + - such as column sorting, searching in the rows, displaying Next / Previous links, etc. + - add styles to the cells and rows (odd / even styles) + - modify some rows to add images if a span img is found, or add a link if a span urlLink is found + or truncate the labels when they are too big + - bind new events onclick / hover / etc. to trigger AJAX requests, + nice hovertip boxes for truncated cells + */ + bindEventsAndApplyStyle: function(domElem) + { + var self = this; + self.cleanParams(); + self.handleSort(domElem); + self.handleSearchBox(domElem); + self.handleLowPopulationLink(domElem); + self.handleOffsetInformation(domElem); + self.handleExportBox(domElem); + self.handleLinkedRows(domElem); + self.applyCosmetics(domElem); + self.handleSubDataTable(domElem); + }, + + handleLinkedRows: function(domElem) + { + var self = this; + + var urlLinkFoundDom = $("tr td:first-child:has('.urlLink')", domElem); + if(urlLinkFoundDom.length == 0) + { + self.truncate( $("table tr td:first-child", domElem) ); + } + else + { + urlLinkFoundDom.each( function(){ + // we add a link based on the present in the column label (the first column) + // if this span is there, we add the link around the HTML in the TD + // but we add this link only for the rows that are not clickable already (subDataTable) + var imgToPrepend = ''; + if( $(this).find('img').length == 0 ) + { + var imageLinkWidth = 10; + var imageLinkHeight = 9; + imgToPrepend = ' '; + } + var urlLinkDom = $('.urlLink',this); + var urlToLink = $(urlLinkDom).html(); + $(urlLinkDom).remove(); + + var truncationOffsetBecauseImageIsPrepend = -2; //website subtable needs -9. + + self.truncate( $(this), truncationOffsetBecauseImageIsPrepend ); + if( urlToLink.match("javascript:") ) + { + $(this).prepend(imgToPrepend).wrapInner(''); + } + else + { + $(this).prepend(imgToPrepend).wrapInner(''); + } + }); + } + }, + + // if sorting the columns is enabled, when clicking on a column, + // - if this column was already the one used for sorting, we revert the order desc<->asc + // - we send the ajax request with the new sorting information + handleSort: function(domElem) + { + var self = this; + if( self.param.enable_sort ) + { + $('.sortable', domElem).click( + function() + { + $(this).unbind('click'); + self.onClickSort(this); + } + ); + + // are we in a subdatatable? + var currentIsSubDataTable = $(domElem).parent().hasClass('cellSubDataTable'); + + var prefixSortIcon = ''; + if(currentIsSubDataTable) + { + prefixSortIcon = '_subtable_'; + } + var imageSortWidth = 16; + var imageSortHeight = 16; + // we change the style of the column currently used as sort column + // adding an image and the class columnSorted to the TD + $(".sortable#"+self.param.filter_sort_column+' #thDIV', domElem).parent() + .addClass('columnSorted') + .prepend('
'); + + + $("th.sortable", domElem) + .hover( function() { $(this).css({ cursor: "pointer"}); }, + function() { $(this).css({ cursor: "auto"}); + }); + } + }, + + // Add behaviour to the low population link + handleLowPopulationLink: function(domElem, callbackSuccess) + { + var self = this; + + // Set the string for the DIV, either "Exclude low pop" or "Include all" + $('.dataTableExcludeLowPopulation', domElem) + .each( + function() + { + if(typeof self.param.enable_filter_excludelowpop == 'undefined') + { + self.param.enable_filter_excludelowpop = 0; + } + if(Number(self.param.enable_filter_excludelowpop) != 0) + { + string = _pk_translate('CoreHome_IncludeAllPopulation_js'); + self.param.enable_filter_excludelowpop = 1; + } + else + { + string = _pk_translate('CoreHome_ExcludeLowPopulation_js'); + self.param.enable_filter_excludelowpop = 0; + } + $(this).html(string); + } + ) + // Bind a click event to the DIV that triggers the ajax request + .click( + function() + { + self.param.enable_filter_excludelowpop = 1 - self.param.enable_filter_excludelowpop; + self.param.filter_offset = 0; + self.reloadAjaxDataTable(true, callbackSuccess); + } + ); + }, + + //behaviour for the DataTable 'search box' + handleSearchBox: function(domElem, callbackSuccess) + { + var self = this; + + var currentPattern = self.param.filter_pattern; + if(typeof self.param.filter_pattern != "undefined" + && self.param.filter_pattern.length > 0) + { + currentPattern = self.param.filter_pattern; + } + else if(typeof self.param.filter_pattern_recursive != "undefined" + && self.param.filter_pattern_recursive.length > 0) + { + currentPattern = self.param.filter_pattern_recursive; + } + else + { + currentPattern = ''; + } + + $('.dataTableSearchPattern', domElem) + .show() + .each(function(){ + // when enter is pressed in the input field we submit the form + $('#keyword', this) + .keypress( + function(e) + { + if(submitOnEnter(e)) + { + $(this).siblings(':submit').submit(); + } + } + ) + .val(currentPattern) + ; + + $(':submit', this).submit( + function() + { + var keyword = $(this).siblings('#keyword').val(); + self.param.filter_offset = 0; + + if(self.param.search_recursive) + { + self.param.filter_column_recursive = 'label'; + self.param.filter_pattern_recursive = keyword; + } + else + { + self.param.filter_column = 'label'; + self.param.filter_pattern = keyword; + } + self.reloadAjaxDataTable(true, callbackSuccess); + } + ); + + $(':submit', this) + .click( function(){ $(this).submit(); }) + ; + + // in the case there is a searched keyword we display the RESET image + if(currentPattern) + { + var target = this; + var clearImg = $('\ + \ + ') + .click( function() { + $('#keyword', target).val(''); + $(':submit', target).submit(); + }); + $('#keyword',this).after(clearImg); + + } + } + ); + }, + + //behaviour for '< prev' 'next >' links and page count + handleOffsetInformation: function(domElem) + { + var self = this; + + $('.dataTablePages', domElem).each( + function(){ + var offset = 1+Number(self.param.filter_offset); + var offsetEnd = Number(self.param.filter_offset) + Number(self.param.filter_limit); + var totalRows = Number(self.param.totalRows); + offsetEndDisp = offsetEnd; + + if(offsetEnd > totalRows) offsetEndDisp = totalRows; + + // only show this string if there is some rows in the datatable + if(totalRows != 0) + { + var str = sprintf(_pk_translate('CoreHome_PageOf_js'),offset + '-' + offsetEndDisp,totalRows); + $(this).text(str); + } + } + ); + + // Display the next link if the total Rows is greater than the current end row + $('.dataTableNext', domElem) + .each(function(){ + var offsetEnd = Number(self.param.filter_offset) + + Number(self.param.filter_limit); + var totalRows = Number(self.param.totalRows); + if(offsetEnd < totalRows) + { + $(this).css('display','inline'); + } + }) + // bind the click event to trigger the ajax request with the new offset + .click(function(){ + $(this).unbind('click'); + self.param.filter_offset = Number(self.param.filter_offset) + Number(self.param.filter_limit); + self.reloadAjaxDataTable(); + }) + ; + + // Display the previous link if the current offset is not zero + $('.dataTablePrevious', domElem) + .each(function(){ + var offset = 1+Number(self.param.filter_offset); + if(offset != 1) + { + $(this).css('display','inline'); + } + } + ) + // bind the click event to trigger the ajax request with the new offset + // take care of the negative offset, we setup 0 + .click( + function(){ + $(this).unbind('click'); + var offset = Number(self.param.filter_offset) - Number(self.param.filter_limit); + if(offset < 0) { offset = 0; } + self.param.filter_offset = offset; + self.reloadAjaxDataTable(); + } + ); + }, + + // DataTable view box (data, table, cloud, graph, ...) + handleExportBox: function(domElem) + { + var self = this; + if( self.param.idSubtable ) + { + // no view box for subtables + return; + } + + // When the (+) image is hovered, the export buttons are displayed + $('.dataTableFooterIconsShow', domElem) + .show() + .hover( function() { + $(this).fadeOut('slow'); + $('.exportToFormatIcons', $(this).parent()).show('slow'); + }, function(){} + ); + + //timeout object used to hide the datatable export buttons + var timeout = null; + + $('.dataTableFooterIcons', domElem) + .hover( function() { + //display 'hand' cursor + $(this).css({ cursor: "pointer"}); + + //cancel timeout if necessary + if(timeout != null) + { + clearTimeout(timeout); + timeout = null; + } + }, + function() { + //display standard cursor + $(this).css({ cursor: "auto"}); + + //set a timeout that will hide export buttons after a few moments + var dom = this; + timeout = setTimeout(function(){ + $('.exportToFormatIcons', dom).fadeOut('fast', function(){ //queue the two actions + $('.dataTableFooterIconsShow', dom).show('fast');}); + }, 1000); + } + ); + + $('.viewDataTable', domElem).click( + function(){ + var viewDataTable = $(this).attr('format'); + self.resetAllFilters(); + self.param.viewDataTable = viewDataTable; + self.reloadAjaxDataTable(); + } + ); + + $('.tableGoals', domElem) + .show() + .click( + function(){ + // we only reset the limit filter, in case switch to table view from cloud view where limit is custom set to 30 + // this value is stored in config file General->datatable_default_limit but this is more an edge case so ok to set it to 10 + delete self.param.filter_limit; + delete self.param.enable_filter_excludelowpop; + self.param.viewDataTable = 'tableGoals'; + self.reloadAjaxDataTable(); + } + ); + + $('.tableAllColumnsSwitch', domElem) + .show() + .click( + function(){ + // we only reset the limit filter, in case switch to table view from cloud view where limit is custom set to 30 + // this value is stored in config file General->datatable_default_limit but this is more an edge case so ok to set it to 10 + delete self.param.filter_limit; + self.param.viewDataTable = self.param.viewDataTable == 'table' ? 'tableAllColumns' : 'table'; + // when switching to display simple table, do not exclude low pop by default + if(self.param.viewDataTable == 'table') + { + self.param.enable_filter_excludelowpop = 0; + } + self.reloadAjaxDataTable(); + } + ); + + $('.exportToFormatIcons img', domElem).click(function(){ + $(this).siblings('.linksExportToFormat').toggle(); + }); + + $('.exportToFormat', domElem).attr( 'href', function(){ + var format = $(this).attr('format'); + var method = $(this).attr('methodToCall'); + var filter_limit = $(this).attr('filter_limit'); + + var param_date = self.param.date; + var date = $(this).attr('date'); + if(typeof date != 'undefined') { + param_date = date; + } + var str = 'index.php?module=API' + +'&method='+method + +'&format='+format + +'&idSite='+self.param.idSite + +'&period='+self.param.period + +'&date='+param_date + +'&token_auth='+piwik.token_auth; + if( filter_limit ) + { + str += '&filter_limit=' + filter_limit; + } + return str; + } + ); + }, + + truncate: function(domElemToTruncate, truncationOffset) + { + var self = this; + + if(typeof truncationOffset == 'undefined') { + truncationOffset = 0; + } + var truncationLimit = 30; + // in a subtable + if(typeof self.param.idSubtable != 'undefined') + { + truncationLimit = 25; + } + // when showing all columns + if(typeof self.param.idSubtable == 'undefined' + && self.param.viewDataTable == 'tableAllColumns') + { + truncationLimit = 17; + } + // when showing all columns in a subtable, space is restricted + else if(self.param.viewDataTable == 'tableAllColumns') + { + truncationLimit = 10; + } + + truncationLimit += truncationOffset; + + $(domElemToTruncate).truncate(truncationLimit); + $('.truncated', domElemToTruncate) + .tooltip(); + }, + + //Apply some miscelleaneous style to the DataTable + applyCosmetics: function(domElem) + { + var self = this; + + // Add some styles on the cells even/odd + // label (first column of a data row) or not + $("th:first-child", domElem).addClass('label'); + $("td:first-child:odd", domElem).addClass('label labeleven'); + $("td:first-child:even", domElem).addClass('label labelodd'); + $("tr:odd td", domElem).slice(1).addClass('columnodd'); + $("tr:even td", domElem).slice(1).addClass('columneven'); + + }, + + //behaviour for 'nested DataTable' (DataTable loaded on a click on a row) + handleSubDataTable: function(domElem) + { + var self = this; + // When the TR has a subDataTable class it means that this row has a link to a subDataTable + $('tr.subDataTable', domElem) + .click( + function() + { + // get the idSubTable + var idSubTable = $(this).attr('id'); + var divIdToReplaceWithSubTable = 'subDataTable_'+idSubTable; + + // if the subDataTable is not already loaded + if (typeof self.loadedSubDataTable[divIdToReplaceWithSubTable] == "undefined") + { + var numberOfColumns = $(this).children().length; + + // at the end of the query it will replace the ID matching the new HTML table #ID + // we need to create this ID first + $(this).after( + ''+ + ''+ + '
'+ + ''+ _pk_translate('CoreHome_Loading_js') +''+ + '
'+ + ''+ + '' + ); + + var savedActionVariable = self.param.action; + + // reset all the filters from the Parent table + var filtersToRestore = self.resetAllFilters(); + + self.param.idSubtable = idSubTable; + self.param.action = self.param.controllerActionCalledWhenRequestSubTable; + self.reloadAjaxDataTable(false); + + self.param.action = savedActionVariable; + delete self.param.idSubtable; + self.restoreAllFilters(filtersToRestore); + + self.loadedSubDataTable[divIdToReplaceWithSubTable] = true; + + $(this).next().toggle(); + } + + $(this).next().toggle(); + } + ); + } +}; + + +// Helper function : +// returns true if the event keypress passed in parameter is the ENTER key +function submitOnEnter(e) +{ + var key=e.keyCode || e.which; + if (key==13) + { + return true; + } +} + + + + +//----------------------------------------------------------------------------- +// Action Data Table +//----------------------------------------------------------------------------- + +//inheritance declaration +//actionDataTable is a child of dataTable +actionDataTable.prototype = new dataTable; +actionDataTable.prototype.constructor = actionDataTable; + + +//A list of all our actionDataTables +//Test if the object have already been initialized (multiple includes) +if(typeof actionDataTables == "undefined") +{ + var actionDataTables = {}; +} + +//actionDataTable constructor +function actionDataTable() +{ + dataTable.call(this); + this.parentAttributeParent = ''; + this.parentId = ''; + this.disabledRowDom = {}; //to handle double click on '+' row +} + +//Prototype of the actionDataTable object +actionDataTable.prototype = +{ + //method inheritance + cleanParams: dataTable.prototype.cleanParams, + reloadAjaxDataTable: dataTable.prototype.reloadAjaxDataTable, + buildAjaxRequest: dataTable.prototype.buildAjaxRequest, + handleLowPopulationLink: dataTable.prototype.handleLowPopulationLink, + handleSearchBox: dataTable.prototype.handleSearchBox, + handleExportBox: dataTable.prototype.handleExportBox, + handleSort: dataTable.prototype.handleSort, + onClickSort: dataTable.prototype.onClickSort, + handleLinkedRows: dataTable.prototype.handleLinkedRows, + truncate: dataTable.prototype.truncate, + handleOffsetInformation: dataTable.prototype.handleOffsetInformation, + + //initialisation of the actionDataTable + init: function(workingDivId, domElem) + { + if(typeof domElem == "undefined") + { + domElem = $('#'+workingDivId); + } + this.workingDivId = workingDivId; + this.bindEventsAndApplyStyle(domElem); + this.initialized = true; + }, + + //see dataTable::bindEventsAndApplyStyle + bindEventsAndApplyStyle: function(domElem) + { + var self = this; + + self.cleanParams(); + + // we dont display the link on the row with subDataTable when we are already + // printing all the subTables (case of recursive search when the content is + // including recursively all the subtables + if(!self.param.filter_pattern_recursive) + { + $('tr.subActionsDataTable.rowToProcess') + .click( function() + { + self.onClickActionSubDataTable(this) + }) + .hover( function() { $(this).css({ cursor: "pointer"}); }, + function() { $(this).css({ cursor: "auto"}); } + ); + } + + self.applyCosmetics(domElem); + + self.handleExportBox(domElem); + self.handleSort(domElem); + self.handleLinkedRows(domElem); + self.handleOffsetInformation(domElem); + if( self.workingDivId != undefined) + { + self.handleSearchBox(domElem, self.dataTableLoaded ); + self.handleLowPopulationLink(domElem, self.dataTableLoaded ); + } + }, + + //see dataTable::applyCosmetics + applyCosmetics: function(domElem) + { + var self = this; + + $('tr.subActionsDataTable.rowToProcess') + .css('font-weight','bold'); + + $("th:first-child", domElem).addClass('label'); + + var imagePlusMinusWidth = 12; + var imagePlusMinusHeight = 12; + $('tr.subActionsDataTable.rowToProcess td:first-child') + .each( function(){ + $(this).prepend(''); + if(self.param.filter_pattern_recursive) + { + setImageMinus(this); + } + else + { + setImagePlus(this); + } + }); + + $('tr.rowToProcess') + .each( function() { + // we add the CSS style depending on the level of the current loading category + // we look at the style of the parent row + var style = $(this).prev().attr('class'); + var currentStyle = $(this).attr('class'); + + if( (typeof currentStyle != 'undefined') + && currentStyle.indexOf('level') >= 0 ) + { + } + else + { + var level = getNextLevelFromClass( style ); + $(this).addClass('level'+ level); + } + + // we add an attribute parent that contains the ID of all the parent categories + // this ID is used when collapsing a parent row, it searches for all children rows + // which 'parent' attribute's value contains the collapsed row ID + $(this).attr('parent', function(){ + return self.parentAttributeParent + ' ' + self.parentId; + } + ); + + // Add some styles on the cells even/odd + // label (first column of a data row) or not + $("td:first-child:odd", this).addClass('label labeleven'); + $("td:first-child:even", this).addClass('label labelodd'); + // we truncate the labels columns from the second row +// $("td:first-child", this).truncate(30); +// $('.truncated', this).tooltip(); + }) + .removeClass('rowToProcess'); + }, + + // Called when the user click on an actionDataTable row + onClickActionSubDataTable: function(domElem) + { + var self = this; + + // get the idSubTable + var idSubTable = $(domElem).attr('id'); + + var divIdToReplaceWithSubTable = 'subDataTable_'+idSubTable; + + var NextStyle = $(domElem).next().attr('class'); + var CurrentStyle = $(domElem).attr('class'); + + var currentRowLevel = getLevelFromClass(CurrentStyle); + var nextRowLevel = getLevelFromClass(NextStyle); + + // if the row has not been clicked + // which is the same as saying that the next row level is equal or less than the current row + // because when we click a row the level of the next rows is higher (level2 row gives level3 rows) + if(currentRowLevel >= nextRowLevel) + { + //unbind click to avoid double click problem + $(domElem).unbind('click'); + self.disabledRowDom = $(domElem); + + var numberOfColumns = $(domElem).children().length; + $(domElem).after( '\ + \ + \ + Loading...\ + \ + \ + '); + var savedActionVariable = self.param.action; + + // reset search for subcategories + delete self.param.filter_column; + delete self.param.filter_pattern; + + self.param.idSubtable = idSubTable; + self.param.action = self.param.controllerActionCalledWhenRequestSubTable; + + self.reloadAjaxDataTable(false, function(resp){self.actionsSubDataTableLoaded(resp)}); + self.param.action = savedActionVariable; + delete self.param.idSubtable; + } + // else we toggle all these rows + else + { + var plusDetected = $('td img', domElem).attr('src').indexOf('plus') >= 0; + + $(domElem).siblings().each( function(){ + var parents = $(this).attr('parent'); + if(parents) + { + if(parents.indexOf(idSubTable) >= 0 + || parents.indexOf('subDataTable_'+idSubTable) >= 0) + { + if(plusDetected) + { + $(this).css('display',''); + + //unroll everything and display '-' sign + //if the row is already opened + var NextStyle = $(this).next().attr('class'); + var CurrentStyle = $(this).attr('class'); + + var currentRowLevel = getLevelFromClass(CurrentStyle); + var nextRowLevel = getLevelFromClass(NextStyle); + + if(currentRowLevel < nextRowLevel) + setImageMinus(this); + } + else + { + $(this).css('display','none'); + } + } + } + }); + } + + // toggle the +/- image + var plusDetected = $('td img', domElem).attr('src').indexOf('plus') >= 0; + if(plusDetected) + { + setImageMinus(domElem); + } + else + { + setImagePlus(domElem); + } + }, + + //called when the full table actions is loaded + dataTableLoaded: function(response) + { + var content = $(response); + var idToReplace = $(content).attr('id'); + + //reset parents id + self.parentAttributeParent = ''; + self.parentId = ''; + + var dataTableSel = $('#'+idToReplace); + dataTableSel.html( $(content) ); + piwikHelper.lazyScrollTo(dataTableSel[0], 400); + }, + + // Called when a set of rows for a category of actions is loaded + actionsSubDataTableLoaded: function(response) + { + var self = this; + var idToReplace = $(response).attr('id'); + + // remove the first row of results which is only used to get the Id + var response = $(response).filter('tr').slice(1).addClass('rowToProcess'); + self.parentAttributeParent = $('tr#'+idToReplace).prev().attr('parent'); + self.parentId = idToReplace; + + $('tr#'+idToReplace).after( response ).remove(); + + var re = /subDataTable_(\d+)/; + ok = re.exec(self.parentId); + if(ok) + { + self.parentId = ok[1]; + } + + // we execute the bindDataTableEvent function for the new DIV + self.init(self.workingDivId, $('#'+self.workingDivId)); +// self.init(self.workingDivId, $('#'+idToReplace)); + + //bind back the click event (disabled to avoid double-click problem) + self.disabledRowDom.click( + function() + { + self.onClickActionSubDataTable(this) + }); + } +}; + +//helper function for actionDataTable +function getLevelFromClass( style) +{ + if (!style || typeof style == "undefined") return 0; + + var currentLevelIndex = style.indexOf('level'); + var currentLevel = 0; + if( currentLevelIndex >= 0) + { + currentLevel = Number(style.substr(currentLevelIndex+5,1)); + } + return currentLevel; +} + +//helper function for actionDataTable +function getNextLevelFromClass( style ) +{ + if (!style || typeof style == "undefined") return 0; + currentLevel = getLevelFromClass(style); + newLevel = currentLevel; + // if this is not a row to process so + if( style.indexOf('rowToProcess') < 0 ) + { + newLevel = currentLevel + 1; + } + return newLevel; +} + +//helper function for actionDataTable +function setImageMinus( domElem ) +{ + $('img',domElem).attr('src', 'themes/default/images/minus.png'); +} + +//helper function for actionDataTable +function setImagePlus( domElem ) +{ + $('img',domElem).attr('src', 'themes/default/images/plus.png'); +} + diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/datatable.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/datatable.tpl new file mode 100644 index 00000000..56c508a1 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/datatable.tpl @@ -0,0 +1,41 @@ +
+
+ {if isset($arrayDataTable.result) and $arrayDataTable.result == 'error'} + {$arrayDataTable.message} + {else} + {if count($arrayDataTable) == 0} +
{'CoreHome_TableNoData'|translate}
+ {else} + + + + + {foreach from=$dataTableColumns item=column name=head} + + {/foreach} + + + + + {foreach from=$arrayDataTable item=row} + +{foreach from=$dataTableColumns item=column} + +{/foreach} + + {/foreach} + +
{$columnTranslations[$column]}
+{if !$row.idsubdatatable && $column=='label' && !empty($row.metadata.url)}{$row.metadata.url}{/if} +{if $column=='label'}{logoHtml metadata=$row.metadata alt=$row.columns.label}{/if} +{if isset($row.columns[$column])}{$row.columns[$column]}{else}{$defaultWhenColumnValueNotDefined}{/if} +
+ {/if} + + {if $properties.show_footer} + {include file="CoreHome/templates/datatable_footer.tpl"} + {/if} + {include file="CoreHome/templates/datatable_js.tpl"} + {/if} +
+
diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/datatable_actions.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/datatable_actions.tpl new file mode 100644 index 00000000..44c8a169 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/datatable_actions.tpl @@ -0,0 +1,39 @@ +
+
+ {if isset($arrayDataTable.result) and $arrayDataTable.result == 'error'} + {$arrayDataTable.message} + {else} + {if count($arrayDataTable) == 0} +
{'CoreHome_TableNoData'|translate}
+ {else} + + + + {foreach from=$dataTableColumns item=column name=head} + + + + + {foreach from=$arrayDataTable item=row} + + {foreach from=$dataTableColumns item=column} + + {/foreach} + + {/foreach} + +
{if !empty($columnDescriptions[$column])}{/if}
+ {/foreach} +
+ {if !$row.idsubdatatable && $column=='label' && isset($row.metadata.url)}{$row.metadata.url}{/if} + {if isset($row.columns[$column])}{$row.columns[$column]}{else}{$defaultWhenColumnValueNotDefined}{/if} +
+ {/if} + + {if $properties.show_footer} + {include file="CoreHome/templates/datatable_footer.tpl"} + {/if} + {include file="CoreHome/templates/datatable_actions_js.tpl"} + {/if} +
+
diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/datatable_actions_js.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/datatable_actions_js.tpl new file mode 100644 index 00000000..568fe83d --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/datatable_actions_js.tpl @@ -0,0 +1,12 @@ + + diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/datatable_actions_recursive.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/datatable_actions_recursive.tpl new file mode 100644 index 00000000..48e98197 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/datatable_actions_recursive.tpl @@ -0,0 +1,36 @@ +
+
+ {if isset($arrayDataTable.result) and $arrayDataTable.result == 'error'} + {$arrayDataTable.message} + {else} + {if count($arrayDataTable) == 0} +
{'CoreHome_TableNoData'|translate}
+ {else} + + + + {foreach from=$dataTableColumns item=column} + + + + + {foreach from=$arrayDataTable item=row} + + {foreach from=$dataTableColumns item=column} + + {/foreach} + + {/foreach} + +
{$columnTranslations[$column]} + {/foreach} +
+ {if isset($row.columns[$column])}{$row.columns[$column]}{else}{$defaultWhenColumnValueNotDefined}{/if} +
+ {/if} + + {include file="CoreHome/templates/datatable_footer.tpl"} + {include file="CoreHome/templates/datatable_actions_js.tpl"} + {/if} +
+
diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/datatable_actions_subdatable.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/datatable_actions_subdatable.tpl new file mode 100644 index 00000000..dbd7b903 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/datatable_actions_subdatable.tpl @@ -0,0 +1,19 @@ + +{if isset($arrayDataTable.result) and $arrayDataTable.result == 'error'} + {$arrayDataTable.message} +{else} + {if count($arrayDataTable) == 0} + {'CoreHome_CategoryNoData'|translate} + {else} + {foreach from=$arrayDataTable item=row} + + {foreach from=$dataTableColumns item=column} + + {if !$row.idsubdatatable && $column=='label' && isset($row.metadata.url)}{$row.metadata.url}{/if} + {if isset($row.columns[$column])}{$row.columns[$column]}{else}{$defaultWhenColumnValueNotDefined}{/if} + + {/foreach} + + {/foreach} + {/if} +{/if} diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/datatable_footer.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/datatable_footer.tpl new file mode 100644 index 00000000..329a2002 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/datatable_footer.tpl @@ -0,0 +1,73 @@ +
+{if $properties.show_exclude_low_population} + +{/if} + +{if $properties.show_offset_information} +
+ + ‹ {'General_Previous'|translate} + {'General_Next'|translate} › +
+{/if} + +{if $properties.show_search} + + + + +{/if} + +{if $properties.show_footer_icons} +
+ + + + + {if $properties.show_table} + + {/if} + + {if $properties.show_goals} + + {/if} + +
+{/if} + + {'General_LoadingData'|translate} +
+ +
diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/datatable_js.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/datatable_js.tpl new file mode 100644 index 00000000..04aa61d3 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/datatable_js.tpl @@ -0,0 +1,12 @@ + + diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/date.js b/oss/piwik/trunk/plugins/CoreHome/templates/date.js new file mode 100644 index 00000000..4b33793a --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/date.js @@ -0,0 +1,92 @@ +$(document).ready(function(){ + $("#periodString").hide(); + $("#otherPeriods").hide(); + $("#datepicker").hide(); + $("#periodString").show(); + + // we get the content of the div before modifying it (append image, etc.) + // so we can restore its value when we want + var savedCurrentPeriod = $("#periodString #currentPeriod").html(); + + // timeout used to fadeout the menu + var timeout = null; + var timeoutLength; + + // restore the normal style of the current period type eg "DAY" + function restoreCurrentPeriod() + { + $("#currentPeriod") + .removeClass("hoverPeriod") + .html(savedCurrentPeriod); + } + // remove the sub menu created that contains the other periods availble + // eg. week | month | year + function removePeriodMenu() { + $("#otherPeriods").fadeOut('fast'); + setCurrentPeriodStyle = true; + } + + // state machine a bit complex and was hard to come up with + // there should be a simpler way to do it with jquery... + // if set to true, means that we want to style our current period + // for example add bold and append the image + var setCurrentPeriodStyle = true; + + $("#periodString #periods") + .hover(function(){ + $(this).css({ cursor: "pointer"}); + + // cancel the timeout + // indeed if the user goes away of the div and goes back on + // we don't hide the submenu! + if(timeout != null) + { + clearTimeout(timeout); + timeout = null; + timeoutLength = 500; + } + else + { + timeoutLength = 0; + setCurrentPeriodStyle = true; + } + if( setCurrentPeriodStyle == true) + { + $("#currentPeriod:not(.hoverPeriod)") + .addClass("hoverPeriod") + .append(' '); + } + }, function(){ + restoreCurrentPeriod(); + // we callback the function to hide the sub menu + // only if it was visible (otherwise it messes the state machine) + if($("#otherPeriods").is(":visible")) + { + timeout = setTimeout( removePeriodMenu , timeoutLength); + } + setCurrentPeriodStyle = false; + }) + .click( function() { + // we restore the initial style on the DAY link + restoreCurrentPeriod(); + // the menu shall fadeout after 500ms + timeoutLength = 500; + // appearance! + $("#otherPeriods").fadeIn(); + + }); + + $("#periodString #date") + .hover( function(){ + $(this).css({ cursor: "pointer"}); + }, function(){ + + }) + .click(function(){ + $("#datepicker").toggle(); + if($("#datepicker").is(":visible")) + { + $("#datepicker .ui-state-highlight").removeClass('ui-state-highlight'); + } + }); +} ); diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/graph.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/graph.tpl new file mode 100644 index 00000000..eab4ef04 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/graph.tpl @@ -0,0 +1,48 @@ +
+
+ + {if $flashParameters.isDataAvailable || !$flashParameters.includeData} +
+ {'General_RequiresFlash'|translate} >= {$flashParameters.requiredFlashVersion}. {'General_GraphHelp'|translate} +
+ + {else} +
+ {'General_NoDataForGraph'|translate} +
+ {/if} + + {if $properties.show_footer} + {include file="CoreHome/templates/datatable_footer.tpl"} + {include file="CoreHome/templates/datatable_js.tpl"} + {/if} + +
+
diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/header.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/header.tpl new file mode 100644 index 00000000..9fbb3f6a --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/header.tpl @@ -0,0 +1,15 @@ + + + +Piwik › {'CoreHome_WebAnalyticsReports'|translate} + + + +{loadJavascriptTranslations plugins='CoreHome'} +{include file="CoreHome/templates/js_global_variables.tpl"} +{include file="CoreHome/templates/js_css_includes.tpl"} + + +{include file="CoreHome/templates/top_bar.tpl"} +{include file="CoreHome/templates/top_screen.tpl"} diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/header_message.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/header_message.tpl new file mode 100644 index 00000000..2fca6526 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/header_message.tpl @@ -0,0 +1,14 @@ + +{if $piwikUrl == 'http://piwik.org/demo/'} + {'General_YouAreCurrentlyViewingDemoOfPiwik'|translate:"Piwik":"":"":"piwik.org"} +{elseif $latest_version_available} + + {if $isSuperUser} + {'General_PiwikXIsAvailablePleaseUpdateNow'|translate:$latest_version_available:"
":"":"":""} + {else} + {'General_PiwikXIsAvailablePleaseNotifyPiwikAdmin'|translate:"Piwik $latest_version_available"} + {/if} +{else} + {'General_PiwikIsACollaborativeProjectYouCanContribute'|translate:"":"$piwik_version":"
":"":""} +{/if} +
diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/images/bg_header.jpg b/oss/piwik/trunk/plugins/CoreHome/templates/images/bg_header.jpg new file mode 100644 index 00000000..1733b0e3 Binary files /dev/null and b/oss/piwik/trunk/plugins/CoreHome/templates/images/bg_header.jpg differ diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/images/bullet1.gif b/oss/piwik/trunk/plugins/CoreHome/templates/images/bullet1.gif new file mode 100644 index 00000000..d4ed73ee Binary files /dev/null and b/oss/piwik/trunk/plugins/CoreHome/templates/images/bullet1.gif differ diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/images/bullet2.gif b/oss/piwik/trunk/plugins/CoreHome/templates/images/bullet2.gif new file mode 100644 index 00000000..d51d938d Binary files /dev/null and b/oss/piwik/trunk/plugins/CoreHome/templates/images/bullet2.gif differ diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/images/favicon.ico b/oss/piwik/trunk/plugins/CoreHome/templates/images/favicon.ico new file mode 100644 index 00000000..cd46034b Binary files /dev/null and b/oss/piwik/trunk/plugins/CoreHome/templates/images/favicon.ico differ diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/images/more.png b/oss/piwik/trunk/plugins/CoreHome/templates/images/more.png new file mode 100644 index 00000000..cba03a05 Binary files /dev/null and b/oss/piwik/trunk/plugins/CoreHome/templates/images/more.png differ diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/images/more_date.gif b/oss/piwik/trunk/plugins/CoreHome/templates/images/more_date.gif new file mode 100644 index 00000000..87da6b49 Binary files /dev/null and b/oss/piwik/trunk/plugins/CoreHome/templates/images/more_date.gif differ diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/images/more_period.gif b/oss/piwik/trunk/plugins/CoreHome/templates/images/more_period.gif new file mode 100644 index 00000000..b0c97878 Binary files /dev/null and b/oss/piwik/trunk/plugins/CoreHome/templates/images/more_period.gif differ diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/images/reset_search.png b/oss/piwik/trunk/plugins/CoreHome/templates/images/reset_search.png new file mode 100644 index 00000000..cb1d9e80 Binary files /dev/null and b/oss/piwik/trunk/plugins/CoreHome/templates/images/reset_search.png differ diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/images/search.png b/oss/piwik/trunk/plugins/CoreHome/templates/images/search.png new file mode 100644 index 00000000..6d1f037c Binary files /dev/null and b/oss/piwik/trunk/plugins/CoreHome/templates/images/search.png differ diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/index.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/index.tpl new file mode 100644 index 00000000..6378f6bf --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/index.tpl @@ -0,0 +1,49 @@ +{assign var=showSitesSelection value=true} + +{include file="CoreHome/templates/header.tpl"} + +{if isset($menu) && $menu}{include file="CoreHome/templates/menu.tpl"}{/if} +
+{ajaxLoadingDiv} +{ajaxRequestErrorDiv} + +
+{if $content}{$content}{/if} +
+ +{include file="CoreHome/templates/piwik_tag.tpl"} + +{literal} + + + + + + +{/literal} + + + diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/js_css_includes.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/js_css_includes.tpl new file mode 100644 index 00000000..06311092 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/js_css_includes.tpl @@ -0,0 +1,26 @@ + + + + + + +{postEvent name="template_css_import"} + + + + + + + + + + + + + + + + + +{postEvent name="template_js_import"} + diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/js_disabled_notice.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/js_disabled_notice.tpl new file mode 100644 index 00000000..36e2c036 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/js_disabled_notice.tpl @@ -0,0 +1,5 @@ + diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/js_global_variables.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/js_global_variables.tpl new file mode 100644 index 00000000..139b2a45 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/js_global_variables.tpl @@ -0,0 +1,16 @@ + diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/logo.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/logo.tpl new file mode 100644 index 00000000..9c509089 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/logo.tpl @@ -0,0 +1,6 @@ + diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/menu.css b/oss/piwik/trunk/plugins/CoreHome/templates/menu.css new file mode 100644 index 00000000..a4d9df5e --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/menu.css @@ -0,0 +1,119 @@ +.nav,.nav * { + margin: 0; + padding: 0; +} + +.nav { + z-index:10; + padding-bottom: 2.5em; + height: 2.5em; + float: left; + line-height: 1.0; + margin-bottom: 1.5em; + position: relative; + width:100%; +} + +.nav ul { + background: #fff; /*IE6 needs this*/ + float: left; + position: relative; +} + +/* LEVEL1 NORMAL */ +.nav li { + background: #DFE6FF; + border-left: 1px solid #fff; + float: left; + list-style: none; + z-index: 49; + margin-right:1px; +} + +.nav li.current ul { + z-index: 49; +} + +.nav li.sfHover ul,ul.nav li:hover ul { + z-index: 50; +} + +/* LEVEL2 NORMAL */ +.nav li li { + background: #FBFFFF; + border-left-color: #AABDE6; +} + +.nav a { + border-bottom: 1px solid #CFDEFF; + color: #13a; + display: block; + float: left; + padding: .75em 0 .75em 1em; + text-decoration: none; + width: 8em; +} + +.nav li ul { + left: 0; + top: -999em; + position: absolute; +} + +.nav li, .nav li:hover,.nav li.sfHover,.nav li.current,.nav a:focus,.nav a:hover,.nav a:active{ + -moz-border-radius:5px 5px 0 0; + -webkit-border-radius:5px 5px 0 0; +} + +/* LEVEL1 HOVER */ +.nav li:hover,.nav li.sfHover,.nav li.current,.nav a:focus,.nav a:hover,.nav a:active { + background: #C9D5FF; + -moz-border-radius:5px 5px 0 0; + -webkit-border-radius:5px 5px 0 0; +} + +.nav li { + font-weight: normal; +} + +.nav li.sfHover { + font-weight: bold; +} + +/* LEVEL2 HOVER */ +.nav li li:hover,.nav li li.sfHover,.nav li li a:focus,.nav li li a:hover,.nav li li a:active + { + background: #C9F6FF; + font-weight: bold; +} + +.nav li.sfHover a,.nav li.current a,.nav a:focus,.nav a:hover,.nav a:active + { + border-bottom: none; +} + +.nav li li.current a { + font-weight: bold; +} + +.nav li:hover ul, /* pure CSS hover is removed below */ body .nav li.current ul, + /* this must be more specific than the .superfish override below */ ul.nav li.sfHover ul + { + top: 2.5em; +} + +.nav li:hover li ul,.nav li.sfHover li ul { + top: -999em; +} + +.nav li li:hover ul, /* pure CSS hover is removed below */ ul.nav li li.sfHover ul + { + top: 2.5em; +} + +/*following rule negates pure CSS hovers +so submenu remains hidden and JS controls +when and how it appears*/ +.superfish li:hover ul,.superfish li li:hover ul { + top: -999em; +} diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/menu.js b/oss/piwik/trunk/plugins/CoreHome/templates/menu.js new file mode 100644 index 00000000..eef1dd5b --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/menu.js @@ -0,0 +1,139 @@ +function menu() +{ + this.param = {}; +} + +menu.prototype = +{ + menuSectionLoaded: function (content, urlLoaded) + { + if(urlLoaded == menu.prototype.lastUrlRequested) + { + $('#content').html( content ).show(); + piwikHelper.hideAjaxLoading(); + menu.prototype.lastUrlRequested = null; + } + }, + + customAjaxHandleError: function () + { + menu.prototype.lastUrlRequested = null; + piwikHelper.ajaxHandleError(); + }, + + overMainLI: function () + { + $(this).siblings().removeClass('sfHover'); + }, + + outMainLI: function () + { + }, + + onClickLI: function () + { + var self = this; + var urlAjax = $('a',this).attr('name'); + broadcast.propagateAjax(urlAjax); + + return false; + + }, + + init: function() + { + var self = this; + this.param.superfish = $('.nav') + .superfish({ + pathClass : 'current', + animation : {opacity:'show'}, + delay : 2000 + }); + this.param.superfish.find("li") + .click( self.onClickLI ) + ; + + this.param.superfish + .find("li:has(ul)") + .hover(self.overMainLI, self.outMainLI) + ; + // add id to all li menu to suport menu identification. + // for all sub menu we want to have a unique id based on their module and action + // for main menu we want to add just the module as its id. + this.param.superfish.find('li').each(function(){ + var url = $(this).find('a').attr('name'); + var module = broadcast.getValueFromUrl("module",url); + var action = broadcast.getValueFromUrl("action",url); + var idGoal = broadcast.getValueFromUrl("idGoal",url); + + var main_menu = ($(this).parent().attr("class").match(/nav/)) ? true : false; + if(main_menu) + { + $(this).attr({id: module}); + } + else + { + // so Goals plugin is a little different than other + // we can't identify by it's modules_action so we uses its idGoals. + if(idGoal != '') { + $(this).attr({id: module + '_' + action + '_' + idGoal}); + } + else { + $(this).attr({id: module + '_' + action}); + } + } + }); + }, + + activateMenu : function(module,action,idGoal) + { + // getting the right li is a little tricky since goals uses idGoal, and overview is index. + var $li = ''; + // So, if module is Goals, idGoal is present, and action is not Index, must be one of the goals + if(module == 'Goals' && idGoal != '' && action != 'index') { + $li = $("#" + module + "_" + action + "_" + idGoal); + } else { + $li = $("#" + module + "_" + action); + } + + // we can't find this li based on Module_action? then li only be the main menu. e.g Dashboard. + var no_sub_menu = false; + if($li.size() == 0) { + $li = $("#" + module); + no_sub_menu = true; + } + + piwikMenu.param.superfish.find("li").removeClass('sfHover'); + if($li.find('ul li').size() != 0 || no_sub_menu == true) { + // we clicked on a MAIN LI + $.fn.superfish.currentActiveMenu = $li; + $li.find('>ul li:first').addClass('sfHover'); + $li.find('ul').css({'display':'block','visibility': 'visible'}); + } else { + // we are in the SUB UL LI + $.fn.superfish.currentActiveMenu = $li.parents('li'); + $li.addClass('sfHover'); + $li.parents('ul').css({'display':'block','visibility': 'visible'}); + } + $.fn.superfish.currentActiveMenu.showSuperfishUl().siblings().hideSuperfishUl(); + }, + + loadFirstSection: function() + { + var self=this; + if(broadcast.isHashExists() == false) { + $('li:first', self.param.superfish) + .click() + .each(function(){ $(this).showSuperfishUl(); }); + } + } +}; + +$(document).ready( function(){ + if($('.nav').size()) { + piwikMenu = new menu(); + piwikMenu.init(); + piwikMenu.loadFirstSection(); + broadcast.init(); + } +}); diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/menu.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/menu.tpl new file mode 100644 index 00000000..daed6eab --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/menu.tpl @@ -0,0 +1,14 @@ + diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/period_select.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/period_select.tpl new file mode 100644 index 00000000..1c704ff5 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/period_select.tpl @@ -0,0 +1,29 @@ +{loadJavascriptTranslations plugins='CoreHome'} + + + + + {$prettyDate} -  + + {$periodsNames.$period.singular} + + {foreach from=$otherPeriods item=thisPeriod} | {$periodsNames.$thisPeriod.singular}{/foreach} + + +
+ +
+ +{literal} +{/literal} + +
diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/piwik_tag.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/piwik_tag.tpl new file mode 100644 index 00000000..df1d5c43 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/piwik_tag.tpl @@ -0,0 +1,46 @@ +{if $piwikUrl == 'http://piwik.org/demo/' || $debugTrackVisitsInsidePiwikUI} +
+{literal} + + + + + + + + + + + +{/literal} +{/if} diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/sites_selection.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/sites_selection.tpl new file mode 100644 index 00000000..be60868d --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/sites_selection.tpl @@ -0,0 +1,36 @@ + diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/sparkline.js b/oss/piwik/trunk/plugins/CoreHome/templates/sparkline.js new file mode 100644 index 00000000..e9fef718 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/sparkline.js @@ -0,0 +1,36 @@ +$(document).ready( function(){ + $("a[name='evolutionGraph']").each( function() { + var graph = $(this); + if(graph && graph.size() > 0) { + //try to find sparklines and add them clickable behaviour + $(this).parent().find('div.sparkline').each( function() { + var url = ""; + //find the sparkline and get it's src attribute + $("img.sparkline", this).each(function() { + //search viewDataTable parameter and replace it with value for chart + var reg = new RegExp("(viewDataTable=sparkline)", "g"); + url = this.src.replace(reg,'viewDataTable=generateDataChartEvolution'); + }); + if(url != ""){ + //on click, reload the graph with the new url + $(this).click( function() { + //get the main page graph and reload with new data + piwikHelper.findSWFGraph(graph.attr('graphId')+"Chart_swf").reload(url); + piwikHelper.lazyScrollTo(graph[0], 400); + }); + $(this).hover( + function() { + $(this).css({ + "cursor": "pointer", + "border-bottom": "1px dashed #C3C3C3" + }); + }, + function(){ + $(this).css({"border-bottom":"1px solid white"}); + } + ); + } + }); + } + }); +}); \ No newline at end of file diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/styles.css b/oss/piwik/trunk/plugins/CoreHome/templates/styles.css new file mode 100644 index 00000000..850935ef --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/styles.css @@ -0,0 +1,101 @@ +h1 { + font-size: 2em; + color: #0F1B2E; + padding-bottom: 1em; +} + +h2 { + font-size: 1.3em; + color: #1D3256; + padding-bottom: 0.5em; + clear:both; +} + +h2 a { + text-decoration:none; +} + +h3 { + font-size: 1.3em; + margin-top: 2em; + color: #1D3256; +} + +p { + padding-bottom: 1em; + margin-right: 1em; + margin-left:1em; +} + +/* Content */ +#content { + margin-left: 10px; +} + + +/* 2 columns reports */ +#leftcolumn { + float: left; + width: 50%; + padding-left: 10px; +} +#rightcolumn { + float: right; + width: 45%; +} +/* not in widget */ +.widget #leftcolumn, .widget #rightcolumn { + float:left; + width:100%; + padding-left:10px; +} + +/* Calendar*/ +div.ui-datepicker { + font-size: 62.5%; +} + +.ui-datepicker-current-period a, .ui-datepicker-current-period a:link, .ui-datepicker-current-period a:visited { + border: 1px solid #2E85FF; + color: #2E85FF; +} + +#otherPeriods a { + text-decoration: none; +} + +#otherPeriods a:hover { + text-decoration: underline; +} + +#currentPeriod { + border-bottom: 1px dotted #520202; +} + +.hoverPeriod { + cursor: pointer; + font-weight: bold; + border-bottom: 1px solid #520202; +} + +div .sparkline { + float:left; + clear:both; + padding-bottom: 1px; + margin-top:10px; + border-bottom:1px solid white; +} + +.sparkline img { + vertical-align: middle; + padding-right: 10px; + margin-top: 0; +} + +div.pk-emptyGraph { + padding-top: 20px; + padding-bottom: 10px; + text-align: center; + font-size: 0.9em; + font-style: italic; +} diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/top_bar.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/top_bar.tpl new file mode 100644 index 00000000..53957f41 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/top_bar.tpl @@ -0,0 +1,26 @@ +{assignTopBar} + +
+ +
+{foreach from=$topBarElements item=element} + {if $element.0 == $currentModule}{else}{/if}{$element.1}{if $element.0 == $currentModule}{else}{/if} +{/foreach} +{postEvent name="template_topBar"} +
+ +
+ + +{'General_HelloUser'|translate:"$userLogin"} +{if $userLogin != 'anonymous'}| {'General_Settings'|translate}{/if} + {if $showSitesSelection && $showWebsiteSelectorInUserInterface}| {include file=CoreHome/templates/sites_selection.tpl}{/if} +| {if $userLogin == 'anonymous'}{'Login_LogIn'|translate}{else}{'Login_Logout'|translate}{/if} + + + +
+ +
+ +
diff --git a/oss/piwik/trunk/plugins/CoreHome/templates/top_screen.tpl b/oss/piwik/trunk/plugins/CoreHome/templates/top_screen.tpl new file mode 100644 index 00000000..d88d17ac --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreHome/templates/top_screen.tpl @@ -0,0 +1,7 @@ + +
diff --git a/oss/piwik/trunk/plugins/CorePluginsAdmin/Controller.php b/oss/piwik/trunk/plugins/CorePluginsAdmin/Controller.php new file mode 100644 index 00000000..a964ca2e --- /dev/null +++ b/oss/piwik/trunk/plugins/CorePluginsAdmin/Controller.php @@ -0,0 +1,73 @@ +readPluginsDirectory(); + foreach($listPlugins as $pluginName) + { + $oPlugin = Piwik_PluginsManager::getInstance()->loadPlugin($pluginName); + $plugins[$pluginName] = array( + 'activated' => Piwik_PluginsManager::getInstance()->isPluginActivated($pluginName), + 'alwaysActivated' => Piwik_PluginsManager::getInstance()->isPluginAlwaysActivated($pluginName), + ); + } + + Piwik_PluginsManager::getInstance()->loadTranslations(); + + $loadedPlugins = Piwik_PluginsManager::getInstance()->getLoadedPlugins(); + foreach($loadedPlugins as $oPlugin) + { + $pluginName = $oPlugin->getClassName(); + $plugins[$pluginName]['info'] = $oPlugin->getInformation(); + } + + $view = Piwik_View::factory('manage'); + $view->pluginsName = $plugins; + $this->setGeneralVariablesView($view); + $view->menu = Piwik_GetAdminMenu(); + if(!Zend_Registry::get('config')->isFileWritable()) + { + $view->configFileNotWritable = true; + } + + echo $view->render(); + } + + function deactivate() + { + Piwik::checkUserIsSuperUser(); + $this->checkTokenInUrl(); + $pluginName = Piwik_Common::getRequestVar('pluginName', null, 'string'); + Piwik_PluginsManager::getInstance()->deactivatePlugin($pluginName); + Piwik_Url::redirectToUrl('index.php?module=CorePluginsAdmin&action=index'); + } + + function activate() + { + Piwik::checkUserIsSuperUser(); + $this->checkTokenInUrl(); + $pluginName = Piwik_Common::getRequestVar('pluginName', null, 'string'); + Piwik_PluginsManager::getInstance()->activatePlugin($pluginName); + Piwik_Url::redirectToUrl('index.php?module=CorePluginsAdmin&action=index'); + } +} diff --git a/oss/piwik/trunk/plugins/CorePluginsAdmin/CorePluginsAdmin.php b/oss/piwik/trunk/plugins/CorePluginsAdmin/CorePluginsAdmin.php new file mode 100644 index 00000000..5e02f43b --- /dev/null +++ b/oss/piwik/trunk/plugins/CorePluginsAdmin/CorePluginsAdmin.php @@ -0,0 +1,41 @@ + Piwik_Translate('CorePluginsAdmin_PluginDescription'), + 'author' => 'Piwik', + 'author_homepage' => 'http://piwik.org/', + 'version' => Piwik_Version::VERSION, + ); + } + + function getListHooksRegistered() + { + return array('AdminMenu.add' => 'addMenu'); + } + + function addMenu() + { + Piwik_AddAdminMenu('CorePluginsAdmin_MenuPlugins', + array('module' => 'CorePluginsAdmin', 'action' => 'index'), + Piwik::isUserIsSuperUser(), + $order = 7); + } +} diff --git a/oss/piwik/trunk/plugins/CorePluginsAdmin/templates/manage.tpl b/oss/piwik/trunk/plugins/CorePluginsAdmin/templates/manage.tpl new file mode 100644 index 00000000..0c83104a --- /dev/null +++ b/oss/piwik/trunk/plugins/CorePluginsAdmin/templates/manage.tpl @@ -0,0 +1,53 @@ +{assign var=showSitesSelection value=false} +{assign var=showPeriodSelection value=false} +{include file="CoreAdminHome/templates/header.tpl"} + +
+ +

{'CorePluginsAdmin_PluginsManagement'|translate}

+

{'CorePluginsAdmin_MainDescription'|translate}

+ + + + + + + + + + + + {foreach from=$pluginsName key=name item=plugin} + {if !$plugin.alwaysActivated} + + + + + + + + + {/if} +{/foreach} + +
{'CorePluginsAdmin_Plugin'|translate}{'CorePluginsAdmin_Version'|translate}{'CorePluginsAdmin_Description'|translate}{'CorePluginsAdmin_Status'|translate}{'CorePluginsAdmin_Action'|translate}
+ {if isset($plugin.info.homepage)}{/if} + {$name} + {if isset($plugin.info.homepage)}{/if} + {$plugin.info.version} + {$plugin.info.description|nl2br} +  By + {if isset($plugin.info.author_homepage)}{/if} + {$plugin.info.author}{if isset($plugin.info.author_homepage)}{/if}. + + {if $plugin.alwaysActivated}{'CorePluginsAdmin_Active'|translate} + {elseif $plugin.activated}{'CorePluginsAdmin_Active'|translate} + {else}{'CorePluginsAdmin_Inactive'|translate}{/if} +
+ +
+{include file="CoreAdminHome/templates/footer.tpl"} diff --git a/oss/piwik/trunk/plugins/CoreUpdater/Controller.php b/oss/piwik/trunk/plugins/CoreUpdater/Controller.php new file mode 100644 index 00000000..e5d8cf23 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreUpdater/Controller.php @@ -0,0 +1,334 @@ +checkNewVersionIsAvailableOrDie(); + + $view = Piwik_View::factory('update_new_version_available'); + $view->piwik_version = Piwik_Version::VERSION; + $view->piwik_new_version = $newVersion; + echo $view->render(); + } + + public function oneClickUpdate() + { + Piwik::checkUserIsSuperUser(); + $this->checkNewVersionIsAvailableOrDie(); + + Piwik::setMaxExecutionTime(0); + + $steps = array( + array('oneClick_Download', Piwik_Translate('CoreUpdater_DownloadingUpdateFromX', self::LATEST_PIWIK_URL)), + array('oneClick_Unpack', Piwik_Translate('CoreUpdater_UnpackingTheUpdate')), + array('oneClick_Verify', Piwik_Translate('CoreUpdater_VerifyingUnpackedFiles')), + array('oneClick_CreateConfigFileBackup', Piwik_Translate('CoreUpdater_CreatingBackupOfConfigurationFile', self::CONFIG_FILE_BACKUP)), + array('oneClick_Copy', Piwik_Translate('CoreUpdater_InstallingTheLatestVersion')), + array('oneClick_Finished', Piwik_Translate('CoreUpdater_PiwikUpdatedSuccessfully')), + ); + + $errorMessage = false; + $messages = array(); + foreach($steps as $step) { + try { + $method = $step[0]; + $message = $step[1]; + $this->$method(); + $messages[] = $message; + } catch(Exception $e) { + $errorMessage = $e->getMessage(); + break; + } + } + + $view = Piwik_View::factory('update_one_click_done'); + $view->coreError = $errorMessage; + $view->feedbackMessages = $messages; + echo $view->render(); + } + + private function checkNewVersionIsAvailableOrDie() + { + $newVersion = Piwik_UpdateCheck::isNewestVersionAvailable(); + if(!$newVersion) + { + throw new Exception(Piwik_TranslateException('CoreUpdater_ExceptionAlreadyLatestVersion', Piwik_Version::VERSION)); + } + return $newVersion; + } + + private function oneClick_Download() + { + $this->pathPiwikZip = PIWIK_USER_PATH . self::PATH_TO_EXTRACT_LATEST_VERSION . '/latest.zip'; + Piwik::checkDirectoriesWritableOrDie( array(self::PATH_TO_EXTRACT_LATEST_VERSION) ); + + // we catch exceptions in the caller (i.e., oneClickUpdate) + $fetched = Piwik_Http::fetchRemoteFile(self::LATEST_PIWIK_URL, $this->pathPiwikZip); + } + + private function oneClick_Unpack() + { + require_once PIWIK_INCLUDE_PATH . '/libs/PclZip/pclzip.lib.php'; + $archive = new PclZip($this->pathPiwikZip); + + $pathExtracted = PIWIK_USER_PATH . self::PATH_TO_EXTRACT_LATEST_VERSION; + if ( false == ($archive_files = $archive->extract( + PCLZIP_OPT_PATH, $pathExtracted)) ) + { + throw new Exception(Piwik_TranslateException('CoreUpdater_ExceptionArchiveIncompatible', $archive->errorInfo(true))); + } + + if ( 0 == count($archive_files) ) + { + throw new Exception(Piwik_TranslateException('CoreUpdater_ExceptionArchiveEmpty')); + } + unlink($this->pathPiwikZip); + $this->pathRootExtractedPiwik = $pathExtracted . '/piwik'; + } + + private function oneClick_Verify() + { + $someExpectedFiles = array( + '/config/global.ini.php', + '/index.php', + '/core/Piwik.php', + '/piwik.php', + '/plugins/API/API.php' + ); + foreach($someExpectedFiles as $file) + { + if(!is_file($this->pathRootExtractedPiwik . $file)) + { + throw new Exception(Piwik_TranslateException('CoreUpdater_ExceptionArchiveIncomplete', $file)); + } + } + } + + private function oneClick_CreateConfigFileBackup() + { + $configFileBefore = PIWIK_USER_PATH . '/config/global.ini.php'; + $configFileAfter = PIWIK_USER_PATH . self::CONFIG_FILE_BACKUP; + Piwik::copy($configFileBefore, $configFileAfter); + } + + private function oneClick_Copy() + { + /* + * Overwrite the downloaded robots.txt with our local copy + */ + Piwik::copy(PIWIK_DOCUMENT_ROOT . '/robots.txt', $this->pathRootExtractedPiwik . '/robots.txt'); + + /* + * Copy all files to PIWIK_INCLUDE_PATH. + * These files are accessed through the dispatcher. + */ + Piwik::copyRecursive($this->pathRootExtractedPiwik, PIWIK_INCLUDE_PATH); + + /* + * These files are visible in the web root and are generally + * served directly by the web server. May be shared. + */ + if(PIWIK_INCLUDE_PATH !== PIWIK_DOCUMENT_ROOT) + { + /* + * Copy PHP files that expect to be in the document root + */ + $specialCases = array( + '/index.php', + '/piwik.php', + '/js/index.php', + ); + + foreach($specialCases as $file) + { + Piwik::copy($this->pathRootExtractedPiwik . $file, PIWIK_DOCUMENT_ROOT . $file); + } + + /* + * Copy the non-PHP files (e.g., images, css, javascript) + */ + Piwik::copyRecursive($this->pathRootExtractedPiwik, PIWIK_DOCUMENT_ROOT, true); + } + + /* + * Config files may be user (account) specific + */ + if(PIWIK_INCLUDE_PATH !== PIWIK_USER_PATH) + { + Piwik::copyRecursive($this->pathRootExtractedPiwik . '/config', PIWIK_USER_PATH . '/config'); + } + + Piwik::unlinkRecursive($this->pathRootExtractedPiwik, true); + } + + private function oneClick_Finished() + { + } + + public function index() + { + $language = Piwik_Common::getRequestVar('language', ''); + if(!empty($language)) + { + Piwik_LanguagesManager_API::getInstance()->setLanguageForSession($language); + } + $this->runUpdaterAndExit(); + } + + protected function runUpdaterAndExit() + { + $updater = new Piwik_Updater(); + $componentsWithUpdateFile = Piwik_CoreUpdater::getComponentUpdates($updater); + if(empty($componentsWithUpdateFile)) + { + Piwik::redirectToModule('CoreHome'); + } + + Piwik::setMaxExecutionTime(0); + + if(Piwik_Common::isPhpCliMode()) + { + $view = Piwik_View::factory('update_welcome'); + $this->doWelcomeUpdates($view, $componentsWithUpdateFile); + + if(!$this->coreError) + { + $view = Piwik_View::factory('update_database_done'); + $this->doExecuteUpdates($view, $updater, $componentsWithUpdateFile); + } + } + else if(Piwik_Common::getRequestVar('updateCorePlugins', 0, 'integer') == 1) + { + $this->warningMessages = array(); + $view = Piwik_View::factory('update_database_done'); + $this->doExecuteUpdates($view, $updater, $componentsWithUpdateFile); + } + else + { + $view = Piwik_View::factory('update_welcome'); + $view->queries = $updater->getSqlQueriesToExecute(); + $this->doWelcomeUpdates($view, $componentsWithUpdateFile); + } + exit; + } + + private function doWelcomeUpdates($view, $componentsWithUpdateFile) + { + $view->new_piwik_version = Piwik_Version::VERSION; + $view->commandUpgradePiwik = "
php ".Piwik_Common::getPathToPiwikRoot()."/index.php -- \"module=CoreUpdater\" "; + $pluginNamesToUpdate = array(); + $coreToUpdate = false; + + // handle case of existing database with no tables + if(!Piwik::isInstalled()) + { + $this->errorMessages[] = Piwik_Translate('CoreUpdater_EmptyDatabaseError', Zend_Registry::get('config')->database->dbname); + $this->coreError = true; + $currentVersion = 'N/A'; + } + else + { + $this->errorMessages = array(); + try { + $currentVersion = Piwik_GetOption('version_core'); + } catch( Exception $e) { + $currentVersion = '<= 0.2.9'; + } + + foreach($componentsWithUpdateFile as $name => $filenames) + { + if($name == 'core') + { + $coreToUpdate = true; + } + else + { + $pluginNamesToUpdate[] = $name; + } + } + } + + // check file integrity + $integrityInfo = Piwik::getFileIntegrityInformation(); + if(isset($integrityInfo[1])) + { + if($integrityInfo[0] == false) + { + $this->warningMessages[] = ''.Piwik_Translate('General_FileIntegrityWarningExplanation').''; + } + $this->warningMessages = array_merge($this->warningMessages, array_slice($integrityInfo, 1)); + } + + $view->coreError = $this->coreError; + $view->warningMessages = $this->warningMessages; + $view->errorMessages = $this->errorMessages; + $view->current_piwik_version = $currentVersion; + $view->pluginNamesToUpdate = $pluginNamesToUpdate; + $view->coreToUpdate = $coreToUpdate; + $view->clearCompiledTemplates(); + echo $view->render(); + } + + private function doExecuteUpdates($view, $updater, $componentsWithUpdateFile) + { + $this->loadAndExecuteUpdateFiles($updater, $componentsWithUpdateFile); + + $view->coreError = $this->coreError; + $view->warningMessages = $this->warningMessages; + $view->errorMessages = $this->errorMessages; + $view->deactivatedPlugins = $this->deactivatedPlugins; + $view->clearCompiledTemplates(); + echo $view->render(); + } + + private function loadAndExecuteUpdateFiles($updater, $componentsWithUpdateFile) + { + // if error in any core update, show message + help message + EXIT + // if errors in any plugins updates, show them on screen, disable plugins that errored + CONTINUE + // if warning in any core update or in any plugins update, show message + CONTINUE + // if no error or warning, success message + CONTINUE + foreach($componentsWithUpdateFile as $name => $filenames) + { + try { + $this->warningMessages = array_merge($this->warningMessages, $updater->update($name)); + } catch (Piwik_Updater_UpdateErrorException $e) { + $this->errorMessages[] = $e->getMessage(); + if($name == 'core') + { + $this->coreError = true; + break; + } + else + { + Piwik_PluginsManager::getInstance()->deactivatePlugin($name); + $this->deactivatedPlugins[] = $name; + } + } + } + } +} diff --git a/oss/piwik/trunk/plugins/CoreUpdater/CoreUpdater.php b/oss/piwik/trunk/plugins/CoreUpdater/CoreUpdater.php new file mode 100644 index 00000000..3bef77a2 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreUpdater/CoreUpdater.php @@ -0,0 +1,71 @@ + Piwik_Translate('CoreUpdater_PluginDescription'), + 'author' => 'Piwik', + 'author_homepage' => 'http://piwik.org/', + 'version' => Piwik_Version::VERSION, + ); + } + + function getListHooksRegistered() + { + $hooks = array( + 'FrontController.dispatchCoreAndPluginUpdatesScreen' => 'dispatch', + 'FrontController.checkForUpdates' => 'updateCheck', + ); + return $hooks; + } + + public static function getComponentUpdates($updater) + { + $updater->addComponentToCheck('core', Piwik_Version::VERSION); + + $plugins = Piwik_PluginsManager::getInstance()->getLoadedPlugins(); + foreach($plugins as $pluginName => $plugin) + { + $updater->addComponentToCheck($pluginName, $plugin->getVersion()); + } + + $componentsWithUpdateFile = $updater->getComponentsWithUpdateFile(); + if(count($componentsWithUpdateFile) == 0 && !$updater->hasNewVersion('core')) + { + return null; + } + + return $componentsWithUpdateFile; + } + + function dispatch() + { + $module = Piwik_Common::getRequestVar('module', '', 'string'); + $updater = new Piwik_Updater(); + if(self::getComponentUpdates($updater) !== null && $module != 'CoreUpdater') + { + Piwik::redirectToModule('CoreUpdater'); + } + } + + function updateCheck() + { + Piwik_UpdateCheck::check(); + } +} diff --git a/oss/piwik/trunk/plugins/CoreUpdater/templates/cli_update_database_done.tpl b/oss/piwik/trunk/plugins/CoreUpdater/templates/cli_update_database_done.tpl new file mode 100644 index 00000000..6cda000c --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreUpdater/templates/cli_update_database_done.tpl @@ -0,0 +1,62 @@ +{textformat} +{assign var='helpMessage' value='CoreUpdater_HelpMessageContent'|translate:'[':']':"\n\n* "|unescape} + +{if $coreError} + [X] {'CoreUpdater_CriticalErrorDuringTheUpgradeProcess'|translate|unescape} + + {foreach from=$errorMessages item=message} + * {$message} + + {/foreach} + + {'CoreUpdater_HelpMessageIntroductionWhenError'|translate|unescape} + + * {$helpMessage} + + + {'CoreUpdater_ErrorDIYHelp'|translate} + + * {'CoreUpdater_ErrorDIYHelp_1'|translate} + + * {'CoreUpdater_ErrorDIYHelp_2'|translate} + + * {'CoreUpdater_ErrorDIYHelp_3'|translate} + + * {'CoreUpdater_ErrorDIYHelp_4'|translate} + + * {'CoreUpdater_ErrorDIYHelp_5'|translate} + +{else} + {if count($warningMessages) > 0} + [!] {'CoreUpdater_WarningMessages'|translate|unescape} + + {foreach from=$warningMessages item=message} + * {$message} + + {/foreach} + {/if} + + {if count($errorMessages) > 0} + [X] {'CoreUpdater_ErrorDuringPluginsUpdates'|translate|unescape} + + {foreach from=$errorMessages item=message} + * {$message} + + {/foreach} + + {if isset($deactivatedPlugins) && count($deactivatedPlugins) > 0} + {assign var=listOfDeactivatedPlugins value=$deactivatedPlugins|@implode:', '} + [!] {'CoreUpdater_WeAutomaticallyDeactivatedTheFollowingPlugins'|translate:$listOfDeactivatedPlugins|unescape} + + {/if} + {/if} + {if count($errorMessages) > 0 || count($warningMessages) > 0} + {'CoreUpdater_HelpMessageIntroductionWhenWarning'|translate|unescape} + + * {$helpMessage} + {else} + {'CoreUpdater_PiwikHasBeenSuccessfullyUpgraded'|translate|unescape} + + {/if} +{/if} +{/textformat} diff --git a/oss/piwik/trunk/plugins/CoreUpdater/templates/cli_update_welcome.tpl b/oss/piwik/trunk/plugins/CoreUpdater/templates/cli_update_welcome.tpl new file mode 100644 index 00000000..28d60bd9 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreUpdater/templates/cli_update_welcome.tpl @@ -0,0 +1,36 @@ +{assign var='helpMessage' value='CoreUpdater_HelpMessageContent'|translate:'[':']':"\n\n* "|unescape} +{textformat} +*** {'CoreUpdater_UpdateTitle'|translate|unescape} *** + +{if $coreError} + [X] {'CoreUpdater_CriticalErrorDuringTheUpgradeProcess'|translate|unescape} + + {foreach from=$errorMessages item=message} + * {$message} + + {/foreach} + + {'CoreUpdater_HelpMessageIntroductionWhenError'|translate|unescape} + + * {$helpMessage} + +{else} + {if $coreToUpdate || count($pluginNamesToUpdate) > 0} + {'CoreUpdater_DatabaseUpgradeRequired'|translate|unescape} + + {'CoreUpdater_YourDatabaseIsOutOfDate'|translate|unescape} + + {if $coreToUpdate} + {'CoreUpdater_PiwikWillBeUpgradedFromVersionXToVersionY'|translate:$current_piwik_version:$new_piwik_version|unescape} + + {/if} + {if count($pluginNamesToUpdate) > 0} + {assign var=listOfPlugins value=$pluginNamesToUpdate|@implode:', '} + {'CoreUpdater_TheFollowingPluginsWillBeUpgradedX'|translate:$listOfPlugins|unescape} + + {/if} + {'CoreUpdater_TheUpgradeProcessMayTakeAWhilePleaseBePatient'|translate|unescape} + + {/if} +{/if} +{/textformat} diff --git a/oss/piwik/trunk/plugins/CoreUpdater/templates/footer.tpl b/oss/piwik/trunk/plugins/CoreUpdater/templates/footer.tpl new file mode 100644 index 00000000..9943ff0f --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreUpdater/templates/footer.tpl @@ -0,0 +1,3 @@ +
+ + diff --git a/oss/piwik/trunk/plugins/CoreUpdater/templates/header.tpl b/oss/piwik/trunk/plugins/CoreUpdater/templates/header.tpl new file mode 100644 index 00000000..07680177 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreUpdater/templates/header.tpl @@ -0,0 +1,32 @@ + + + + Piwik › {'CoreUpdater_UpdateTitle'|translate} + + + + + +{literal} + +{/literal} + {postEvent name="template_css_import"} + + + + + + +
+
Piwik # {'General_OpenSourceWebAnalytics'|translate}
diff --git a/oss/piwik/trunk/plugins/CoreUpdater/templates/update_database_done.tpl b/oss/piwik/trunk/plugins/CoreUpdater/templates/update_database_done.tpl new file mode 100644 index 00000000..28b0b235 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreUpdater/templates/update_database_done.tpl @@ -0,0 +1,61 @@ +{include file="CoreUpdater/templates/header.tpl"} +{assign var='helpMessage' value='CoreUpdater_HelpMessageContent'|translate:'':'':'
  • '} + +{if $coreError} +

    +
    + {'CoreUpdater_CriticalErrorDuringTheUpgradeProcess'|translate} + {foreach from=$errorMessages item=message} +
    {$message}

    + {/foreach} +
    +
    +

    {'CoreUpdater_HelpMessageIntroductionWhenError'|translate} +

    • {$helpMessage}

    + +

    {'CoreUpdater_ErrorDIYHelp'|translate} +

    • {'CoreUpdater_ErrorDIYHelp_1'|translate}
    • +
    • {'CoreUpdater_ErrorDIYHelp_2'|translate}
    • +
    • {'CoreUpdater_ErrorDIYHelp_3'|translate}
    • +
    • {'CoreUpdater_ErrorDIYHelp_4'|translate}
    • +
    • {'CoreUpdater_ErrorDIYHelp_5'|translate}

    +{else} + + {if count($warningMessages) > 0} +
    +

    {'CoreUpdater_WarningMessages'|translate}

    + {foreach from=$warningMessages item=message} +
    {$message}

    + {/foreach} +
    + {/if} + + {if count($errorMessages) > 0} +
    +

    {'CoreUpdater_ErrorDuringPluginsUpdates'|translate}

    + {foreach from=$errorMessages item=message} +
    {$message}

    + {/foreach} + + {if isset($deactivatedPlugins) && count($deactivatedPlugins) > 0} + {assign var=listOfDeactivatedPlugins value=$deactivatedPlugins|@implode:', '} +

    {'CoreUpdater_WeAutomaticallyDeactivatedTheFollowingPlugins'|translate:$listOfDeactivatedPlugins}

    + {/if} +
    + {/if} + + {if count($errorMessages) > 0 || count($warningMessages) > 0} +
    +

    {'CoreUpdater_HelpMessageIntroductionWhenWarning'|translate} +

    • {$helpMessage}
    +

    + {else} +

    {'CoreUpdater_PiwikHasBeenSuccessfullyUpgraded'|translate}

    + {/if} + +
    + +
    +{/if} + +{include file="CoreUpdater/templates/footer.tpl"} diff --git a/oss/piwik/trunk/plugins/CoreUpdater/templates/update_new_version_available.tpl b/oss/piwik/trunk/plugins/CoreUpdater/templates/update_new_version_available.tpl new file mode 100644 index 00000000..d2218175 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreUpdater/templates/update_new_version_available.tpl @@ -0,0 +1,15 @@ +{include file="CoreUpdater/templates/header.tpl"} + +

    {'CoreUpdater_ThereIsNewVersionAvailableForUpdate'|translate}

    +

    {'CoreUpdater_YouCanUpgradeAutomaticallyOrDownloadPackage'|translate:$piwik_new_version}

    +
    +
    + + + +{'CoreUpdater_DownloadX'|translate:$piwik_new_version} +
    +
    +« {'General_BackToPiwik'|translate} +{include file="CoreUpdater/templates/footer.tpl"} + diff --git a/oss/piwik/trunk/plugins/CoreUpdater/templates/update_one_click_done.tpl b/oss/piwik/trunk/plugins/CoreUpdater/templates/update_one_click_done.tpl new file mode 100644 index 00000000..f4fbde80 --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreUpdater/templates/update_one_click_done.tpl @@ -0,0 +1,18 @@ +{include file="CoreUpdater/templates/header.tpl"} + +{foreach from=$feedbackMessages item=message} +

    {$message}

    +{/foreach} + +{if $coreError} +

    +
    {$coreError}
    +

    +
    {'CoreUpdater_UpdateHasBeenCancelledExplanation'|translate:"

    ":"":""}
    +

    +{/if} + +
    + +
    +{include file="CoreUpdater/templates/footer.tpl"} diff --git a/oss/piwik/trunk/plugins/CoreUpdater/templates/update_welcome.tpl b/oss/piwik/trunk/plugins/CoreUpdater/templates/update_welcome.tpl new file mode 100644 index 00000000..f5a8da5d --- /dev/null +++ b/oss/piwik/trunk/plugins/CoreUpdater/templates/update_welcome.tpl @@ -0,0 +1,99 @@ +{include file="CoreUpdater/templates/header.tpl"} +{postEvent name="template_topBar"} +{assign var='helpMessage' value='CoreUpdater_HelpMessageContent'|translate:'':'':'
  • '} + +{if $coreError} +

    +
    + {'CoreUpdater_CriticalErrorDuringTheUpgradeProcess'|translate} + {foreach from=$errorMessages item=message} +
    {$message}
    + {/foreach} +
    +
    +

    {'CoreUpdater_HelpMessageIntroductionWhenError'|translate} +

    • {$helpMessage}

    +{else} + {if $coreToUpdate || count($pluginNamesToUpdate) > 0} +

    {'CoreUpdater_DatabaseUpgradeRequired'|translate}

    +

    {'CoreUpdater_YourDatabaseIsOutOfDate'|translate}

    + + {if $coreToUpdate} +

    {'CoreUpdater_PiwikWillBeUpgradedFromVersionXToVersionY'|translate:$current_piwik_version:$new_piwik_version}

    + {/if} + + {if count($pluginNamesToUpdate) > 0} + {assign var=listOfPlugins value=$pluginNamesToUpdate|@implode:', '} +

    {'CoreUpdater_TheFollowingPluginsWillBeUpgradedX'|translate:$listOfPlugins}

    + {/if} + +

    {'CoreUpdater_NoteForLargePiwikInstances'|translate}

    +
      +
    • {'CoreUpdater_TheUpgradeProcessMayFailExecuteCommand'|translate:$commandUpgradePiwik}
    • +
    • {'CoreUpdater_YouCouldManuallyExecuteSqlQueries'|translate}
      + › {'CoreUpdater_ClickHereToViewSqlQueries'|translate} + +

      +

      {'CoreUpdater_ReadyToGo'|translate}

      +

      {'CoreUpdater_TheUpgradeProcessMayTakeAWhilePleaseBePatient'|translate}

      + {/if} + + {if count($warningMessages) > 0} +

      {$warningMessages[0]} + {if count($warningMessages) > 1} + + {/if} +

      + {/if} + + {if $coreToUpdate || count($pluginNamesToUpdate) > 0} +
      +
      + + +
      + {else} + {if count($warningMessages) == 0} +

      {'CoreUpdater_PiwikHasBeenSuccessfullyUpgraded'|translate}

      + {/if} + +
      +
      + +
      + {/if} +{/if} + +{include file="Installation/templates/integrityDetails.tpl"} + +{literal} + + +{/literal} +{include file="CoreUpdater/templates/footer.tpl"} + diff --git a/oss/piwik/trunk/plugins/Dashboard/Controller.php b/oss/piwik/trunk/plugins/Dashboard/Controller.php new file mode 100644 index 00000000..86c4ecac --- /dev/null +++ b/oss/piwik/trunk/plugins/Dashboard/Controller.php @@ -0,0 +1,223 @@ +setGeneralVariablesView($view); + + $view->availableWidgets = json_encode(Piwik_GetWidgetsList()); + $layout = $this->getLayout(); + if(empty($layout)) { + $layout = $this->getDefaultLayout(); + } + $view->layout = $layout; + return $view; + } + + public function embeddedIndex() + { + $view = $this->getDashboardView('index'); + echo $view->render(); + } + + public function index() + { + $view = $this->getDashboardView('standalone'); + echo $view->render(); + } + + /** + * Records the layout in the DB for the given user. + * + * @param string $login + * @param int $idDashboard + * @param string $layout + */ + protected function saveLayoutForUser( $login, $idDashboard, $layout) + { + $paramsBind = array($login, $idDashboard, $layout, $layout); + Piwik_Query('INSERT INTO '.Piwik_Common::prefixTable('user_dashboard') . + ' (login, iddashboard, layout) + VALUES (?,?,?) + ON DUPLICATE KEY UPDATE layout=?', + $paramsBind); + } + + /** + * Returns the layout in the DB for the given user, or false if the layout has not been set yet. + * Parameters must be checked BEFORE this function call + * + * @param string $login + * @param int $idDashboard + * @param string|false $layout + */ + protected function getLayoutForUser( $login, $idDashboard) + { + $paramsBind = array($login, $idDashboard); + $return = Piwik_FetchAll('SELECT layout FROM '.Piwik_Common::prefixTable('user_dashboard') . + ' WHERE login = ? AND iddashboard = ?', $paramsBind); + if(count($return) == 0) + { + return false; + } + return $return[0]['layout']; + } + + /** + * Saves the layout for the current user + * anonymous = in the session + * authenticated user = in the DB + */ + public function saveLayout() + { + $this->checkTokenInUrl(); + $layout = Piwik_Common::getRequestVar('layout'); + $idDashboard = Piwik_Common::getRequestVar('idDashboard', 1, 'int' ); + $currentUser = Piwik::getCurrentUserLogin(); + if($currentUser == 'anonymous') + { + $session = new Zend_Session_Namespace("Piwik_Dashboard"); + $session->dashboardLayout = $layout; + } + else + { + $this->saveLayoutForUser($currentUser,$idDashboard, $layout); + } + } + + /** + * Get the dashboard layout for the current user (anonymous or loggued user) + * + * @return string $layout + */ + protected function getLayout() + { + $idDashboard = Piwik_Common::getRequestVar('idDashboard', 1, 'int' ); + $currentUser = Piwik::getCurrentUserLogin(); + + if($currentUser == 'anonymous') + { + $session = new Zend_Session_Namespace("Piwik_Dashboard"); + + if(!isset($session->dashboardLayout)) + { + return false; + } + $layout = $session->dashboardLayout; + } + else + { + $layout = $this->getLayoutForUser($currentUser,$idDashboard); + } + + // layout was JSON.stringified + $layout = html_entity_decode($layout); + $layout = str_replace("\\\"", "\"", $layout); + + // compatibility with the old layout format + if(!empty($layout) + && strstr($layout, '[[') == false) { + $layout = "'$layout'"; + } + $layout = $this->removeDisabledPluginFromLayout($layout); + return $layout; + } + + protected function removeDisabledPluginFromLayout($layout) + { + $layout = str_replace("\n", "", $layout); + // if the json decoding works (ie. new Json format) + // we will only return the widgets that are from enabled plugins + if($layoutObject = json_decode($layout, $assoc = false)) + { + foreach($layoutObject as &$row) + { + if(!is_array($row)) + { + $row = array(); + continue; + } + + foreach($row as $widgetId => $widget) + { + if(isset($widget->parameters->module)) { + $controllerName = $widget->parameters->module; + $controllerAction = $widget->parameters->action; + if(!Piwik_IsWidgetDefined($controllerName, $controllerAction)) + { + unset($row[$widgetId]); + } + } + } + } + $layout = json_encode($layoutObject); + } + return $layout; + } + + protected function getDefaultLayout() + { + $defaultLayout = '[ + [ + {"uniqueId":"widgetVisitsSummarygetEvolutionGraphcolumnsArray","parameters":{"module":"VisitsSummary","action":"getEvolutionGraph","columns":["nb_visits"]}}, + {"uniqueId":"widgetVisitorInterestgetNumberOfVisitsPerVisitDuration","parameters":{"module":"VisitorInterest","action":"getNumberOfVisitsPerVisitDuration"}}, + {"uniqueId":"widgetUserSettingsgetBrowser","parameters":{"module":"UserSettings","action":"getBrowser"}}, + {"uniqueId":"widgetUserCountrygetCountry","parameters":{"module":"UserCountry","action":"getCountry"}}, + {"uniqueId":"widgetExampleFeedburnerfeedburner","parameters":{"module":"ExampleFeedburner","action":"feedburner"}} + ], + [ + {"uniqueId":"widgetReferersgetKeywords","parameters":{"module":"Referers","action":"getKeywords"}}, + {"uniqueId":"widgetReferersgetWebsites","parameters":{"module":"Referers","action":"getWebsites"}} + ], + [ + {"uniqueId":"widgetReferersgetSearchEngines","parameters":{"module":"Referers","action":"getSearchEngines"}}, + {"uniqueId":"widgetVisitTimegetVisitInformationPerServerTime","parameters":{"module":"VisitTime","action":"getVisitInformationPerServerTime"}}, + {"uniqueId":"widgetExampleRssWidgetrssPiwik","parameters":{"module":"ExampleRssWidget","action":"rssPiwik"}} + ] + ]'; + $defaultLayout = $this->removeDisabledPluginFromLayout($defaultLayout); + return $defaultLayout; + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/oss/piwik/trunk/plugins/Dashboard/Dashboard.php b/oss/piwik/trunk/plugins/Dashboard/Dashboard.php new file mode 100644 index 00000000..ed02c0cf --- /dev/null +++ b/oss/piwik/trunk/plugins/Dashboard/Dashboard.php @@ -0,0 +1,86 @@ + Piwik_Translate('Dashboard_PluginDescription'), + 'author' => 'Piwik', + 'author_homepage' => 'http://piwik.org/', + 'version' => Piwik_Version::VERSION, + ); + } + + public function getListHooksRegistered() + { + return array( + 'template_js_import' => 'js', + 'template_css_import' => 'css', + 'UsersManager.deleteUser' => 'deleteDashboardLayout', + ); + } + + function js() + { + echo ' + + + + '; + } + + function css() + { + echo "\n"; + } + + function deleteDashboardLayout($notification) + { + $userLogin = $notification->getNotificationObject(); + Piwik_Query('DELETE FROM ' . Piwik_Common::prefixTable('user_dashboard') . ' WHERE login = ?', array($userLogin)); + } + + public function install() + { + // we catch the exception + try{ + $sql = "CREATE TABLE ". Piwik_Common::prefixTable('user_dashboard')." ( + login VARCHAR( 100 ) NOT NULL , + iddashboard INT NOT NULL , + layout TEXT NOT NULL, + PRIMARY KEY ( login , iddashboard ) + ) DEFAULT CHARSET=utf8 " ; + Piwik_Exec($sql); + } catch(Exception $e){ + // mysql code error 1050:table already exists + // see bug #153 http://dev.piwik.org/trac/ticket/153 + if(!Zend_Registry::get('db')->isErrNo($e, '1050')) + { + throw $e; + } + } + } + + public function uninstall() + { + $sql = "DROP TABLE ". Piwik_Common::prefixTable('user_dashboard') ; + Piwik_Exec($sql); + } + +} + +Piwik_AddMenu('Dashboard_Dashboard', '', array('module' => 'Dashboard', 'action' => 'embeddedIndex')); diff --git a/oss/piwik/trunk/plugins/Dashboard/templates/Dashboard.js b/oss/piwik/trunk/plugins/Dashboard/templates/Dashboard.js new file mode 100644 index 00000000..74d81ec3 --- /dev/null +++ b/oss/piwik/trunk/plugins/Dashboard/templates/Dashboard.js @@ -0,0 +1,253 @@ +function dashboard() +{ + this.dashboardElement = {}; + this.dashboardColumnsElement = {}; + this.layout = ''; +} + +dashboard.prototype = +{ + //function called on dashboard initialisation + init: function(layout) + { + var self = this; + + //save some often used DOM objects + self.dashboardElement = $('#dashboardWidgetsArea'); + self.dashboardColumnsElement = $('.col', self.dashDom); + + //dashboard layout + self.layout = layout; + + //generate dashboard layout and load every displayed widgets + self.generateLayout(); + + self.makeSortable(); + }, + + getWidgetsElementsInsideElement: function(elementToSearch) + { + return $('.sortable:not(.dummyItem) .widget', elementToSearch); + }, + + generateLayout: function() + { + var self = this; + + if(typeof self.layout == 'string') { + var layout = {}; + //Old dashboard layout format: a string that looks like 'Actions.getActions~Actions.getDownloads|UserCountry.getCountry|Referers.getSearchEngines'; + // '|' separate columns + // '~' separate widgets + // '.' separate plugin name from action name + var columns = self.layout.split('|'); + for(var columnNumber=0; columnNumber0) { + self.addEmptyWidget(columnNumber, uniqueId, false); + } + } + self.addDummyWidgetAtBottomOfColumn(columnNumber); + } + + self.makeSortable(); + + // load all widgets + $('.widget', self.dashboardElement).each( function() { + var uniqueId = $(this).attr('id'); + self.reloadWidget(uniqueId); + }); + }, + + reloadEnclosingWidget: function(domNodeInsideWidget) + { + var uniqueId = $(domNodeInsideWidget).parents('.widget').attr('id'); + this.reloadWidget(uniqueId); + }, + + reloadWidget: function(uniqueId) + { + function onWidgetLoadedReplaceElementWithContent(loadedContent) + { + $('#'+uniqueId+'>.widgetContent', self.dashboardElement).html(loadedContent); + } + widget = widgetsHelper.getWidgetObjectFromUniqueId(uniqueId); + if(widget == false) + { + return; + } + widgetParameters = widget["parameters"]; + $.ajax(widgetsHelper.getLoadWidgetAjaxRequest(uniqueId, widgetParameters, onWidgetLoadedReplaceElementWithContent)); + }, + + addDummyWidgetAtBottomOfColumn: function(columnNumber) + { + var self = this; + var columnElement = $(self.dashboardColumnsElement[columnNumber]); + $(columnElement).append( + '
      '+ + '
      '+ + '
      '); + }, + + addEmptyWidget: function(columnNumber, uniqueId, addWidgetOnTop) + { + var self = this; + + widgetName = widgetsHelper.getWidgetNameFromUniqueId(uniqueId); + if(widgetName == false) { + widgetName = _pk_translate('Dashboard_WidgetNotFound_js'); + } + columnElement = $(self.dashboardColumnsElement[columnNumber]); + emptyWidgetContent = '
      '+ + widgetsHelper.getEmptyWidgetHtml(uniqueId, widgetName)+ + '
      '; + if(addWidgetOnTop) { + columnElement.prepend(emptyWidgetContent); + } else { + columnElement.append(emptyWidgetContent); + } + + widgetElement = $('#'+ uniqueId); + widgetElement + .hover( function() { + $(this).addClass('widgetHover'); + $('.widgetTop', this).addClass('widgetTopHover'); + $('.button#close', this).show(); + }, function() { + $(this).removeClass('widgetHover'); + $('.widgetTop', this).removeClass('widgetTopHover'); + $('.button#close', this).hide(); + }); + $('.button#close', widgetElement) + .click( function(ev){ + self.onDeleteItem(this, ev); + }); + + widgetElement.show(); + return widgetElement; + }, + + //apply jquery sortable plugin to the dashboard layout + makeSortable: function() + { + var self = this; + + function onStart(event, ui) { + if(!jQuery.support.noCloneEvent) { + $('object', this).hide(); + } + } + + function onStop(event, ui) { + $('object', this).show(); + $('.widgetHover', this).removeClass('widgetHover'); + $('.widgetTopHover', this).removeClass('widgetTopHover'); + $('.button#close', this).hide(); + self.saveLayout(); + } + + //launch 'sortable' property on every dashboard widgets + self.dashboardElement + .sortable('destroy') + .sortable({ + items: 'div.sortable', + opacity: 0.6, + forceHelperSize: true, + forcePlaceholderSize: true, + placeholder: 'hover', + handle: '.widgetTop', + helper: 'clone', + start: onStart, + stop: onStop + }); + }, + + // on mouse click on close widget button + // we ask for confirmation and if 'yes' is clicked, we delete the widget from the dashboard + onDeleteItem: function(target, ev) + { + var self = this; + + var question = $('.dialog#confirm'); + $('#no', question).click($.unblockUI); + $('#yes', question).click(function() { + var item = $(target).parents('.sortable'); + $.unblockUI(); + item.fadeOut(200, function() { + $(this).remove(); + self.saveLayout(); + self.makeSortable(); + }); + }); + $.blockUI({ + message: question, + css: { width: '300px', border:'1px solid black' } + }); + }, + + saveLayout: function() + { + var self = this; + + // build the layout object to save + var layout = new Array; + var columnNumber = 0; + self.dashboardColumnsElement.each(function() { + layout[columnNumber] = new Array; + var items = self.getWidgetsElementsInsideElement(this); + for(var j=0; j + + + + \ No newline at end of file diff --git a/oss/piwik/trunk/plugins/Dashboard/templates/dashboard.css b/oss/piwik/trunk/plugins/Dashboard/templates/dashboard.css new file mode 100644 index 00000000..2551c8cf --- /dev/null +++ b/oss/piwik/trunk/plugins/Dashboard/templates/dashboard.css @@ -0,0 +1,183 @@ +.col { + float: left; + width: 33%; +} + +.sortable { + background: white; +} + +.hover { + border: 2px dashed #E3E3E3; +} + +.widget { + border: 1px solid #D2D9EB; + margin-top: 10px; + margin-bottom: 10px; + margin-right: 5px; + margin-left: 5px; + overflow: hidden; + -moz-border-radius:4px; + -webkit-border-radius:4px; +} + +.widgetHover { + border: 1px solid #CBD3E7; +} + +.widgetContent h2 { + font-size:1.2em; +} +.widgetTop { + background: #F0F0FA; + -moz-border-radius:4px 4px 0 0; + -webkit-border-radius:4px 4px 0 0; + border-bottom: 1px solid #D2D9EB; + width: 100%; + cursor: move; + font-size: 10pt; + font-weight: bold; + padding-bottom: 4px; +} + +.widgetTopHover { + background: #E6E6F5; +} + +.widgetName { + font-size: 14pt; + margin-left: 25px; +} + +.dummyItem { + width: 100%; + height: 150px; + display: block; + visibility:hidden; +} + +.button { + cursor: pointer; +} + +#close.button { + float: right; + display: none; + margin: 3px; +} + +.dialog { + display: none; + padding: 20px 10px; + color: #7A0101; + cursor: wait; + font-size: 1.2em; + font-weight: bold; + text-align: center; +} + +.dummyHandle { + display: none; +} + +.menu { + display: none; + border: 2px solid #FCB842; + background: white; +} + +.menuItem { +} + +.menuSelected { + border-bottom: 2px dotted #CCCCCC; + margin-bottom: -2px; +} + +.menuDisabled { + color: lightgrey; + cursor: default; +} + +.widgetLoading { + cursor: wait; + padding: 10px; + text-align: center; + font-size: 10pt; +} + +#menuTitleBar { + background: #FFC35C; + font-size: 14pt; + font-weight: bold; + padding: 2px 20px 6px 20px; + border-bottom: 2px solid #FCB842; +} + +#closeMenuIcon { + float: right; + margin: 3px; +} + +.subMenu { + float: left; + margin: 30px; +} + +#sub1.subMenu { + cursor: default; + margin-left: 50px; +} + +#sub2.subMenu { + cursor: pointer; +} + +#sub3.subMenu { + float: left; + width: 40% +} + +ol#menuList { + color: #CCCCCC; + list-style-type: lower-roman; + line-height: 25px; +} + +ul#widgetList { + list-style-type: none; + line-height: 25px; +} + +.subMenuItem span { + color: black; +} + +.menuClear { + clear: both; + height: 30px; +} + +#closeMenuIcon { + cursor: pointer; +} + +#addWidget { + font-weight: bold; +} + +.widget input { + background: #F7F7FF none repeat scroll 0% 50%; + border: 1px solid #B3B3B3; + color: #0C183A; + font-size: 0.7em; + padding: 2px; +} + +#widgetChooser { + z-index:100; +} +#widgetChooser .widgetTop { + cursor:pointer; +} diff --git a/oss/piwik/trunk/plugins/Dashboard/templates/header.tpl b/oss/piwik/trunk/plugins/Dashboard/templates/header.tpl new file mode 100644 index 00000000..45e68874 --- /dev/null +++ b/oss/piwik/trunk/plugins/Dashboard/templates/header.tpl @@ -0,0 +1,12 @@ +{* This header is for loading the dashboard in stand alone mode*} + + + +{loadJavascriptTranslations plugins='CoreHome Dashboard'} +{include file="CoreHome/templates/js_global_variables.tpl"} +{include file="CoreHome/templates/js_css_includes.tpl"} + + + + diff --git a/oss/piwik/trunk/plugins/Dashboard/templates/index.tpl b/oss/piwik/trunk/plugins/Dashboard/templates/index.tpl new file mode 100644 index 00000000..ae957e33 --- /dev/null +++ b/oss/piwik/trunk/plugins/Dashboard/templates/index.tpl @@ -0,0 +1,51 @@ +{loadJavascriptTranslations plugins='CoreHome Dashboard'} + + + +{literal} + +{/literal} +
      + +
      + +

      {'Dashboard_DeleteWidgetConfirm'|translate}

      + + +
      + +
      + {'Dashboard_AddWidget'|translate} +
      + + + +
      +
      +
      +
      +
      +
      diff --git a/oss/piwik/trunk/plugins/Dashboard/templates/standalone.tpl b/oss/piwik/trunk/plugins/Dashboard/templates/standalone.tpl new file mode 100644 index 00000000..1b1d9510 --- /dev/null +++ b/oss/piwik/trunk/plugins/Dashboard/templates/standalone.tpl @@ -0,0 +1,5 @@ + +{include file="Dashboard/templates/header.tpl"} +{include file="Dashboard/templates/index.tpl"} + + \ No newline at end of file diff --git a/oss/piwik/trunk/plugins/Dashboard/templates/widgetMenu.js b/oss/piwik/trunk/plugins/Dashboard/templates/widgetMenu.js new file mode 100644 index 00000000..997c8715 --- /dev/null +++ b/oss/piwik/trunk/plugins/Dashboard/templates/widgetMenu.js @@ -0,0 +1,319 @@ +function widgetsHelper() +{ +} + +widgetsHelper.getWidgetCategoryNameFromUniqueId = function (uniqueId) +{ + var widgets = piwik.availableWidgets; + for(var widgetCategory in widgets) { + var widgetInCategory = widgets[widgetCategory]; + for(var i in widgetInCategory) { + if(widgetInCategory[i]["uniqueId"] == uniqueId) { + return widgetCategory; + } + } + } + return false; +}; + +widgetsHelper.getWidgetObjectFromUniqueId = function (uniqueId) +{ + var widgets = piwik.availableWidgets; + for(var widgetCategory in widgets) { + var widgetInCategory = widgets[widgetCategory]; + for(var i in widgetInCategory) { + if(widgetInCategory[i]["uniqueId"] == uniqueId) { + return widgetInCategory[i]; + } + } + } + return false; +}; + +widgetsHelper.getWidgetNameFromUniqueId = function (uniqueId) +{ + widget = this.getWidgetObjectFromUniqueId(uniqueId); + if(widget == false) { + return false; + } + return widget["name"]; +}; + +widgetsHelper.getLoadWidgetAjaxRequest = function (widgetUniqueId, widgetParameters, onWidgetLoadedCallback) +{ + var ajaxRequest = + { + widgetUniqueId:widgetUniqueId, + type: 'GET', + url: 'index.php', + dataType: 'html', + async: true, + error: piwikHelper.ajaxHandleError, + success: onWidgetLoadedCallback, + data: piwikHelper.getQueryStringFromParameters(widgetParameters) + "&idSite="+piwik.idSite+"&period="+piwik.period+"&date="+piwik.currentDateString + }; + return ajaxRequest; +}; + +widgetsHelper.getEmptyWidgetHtml = function (uniqueId, widgetName) +{ + return '
      '+ + '
      '+ + '
      '+ + ''+ + '
      '+ + '
      '+widgetName+'
      '+ + '
      '+ + '
      '+ + '
      '+ + _pk_translate('Dashboard_LoadingWidget_js') + + '
      '+ + '
      '+ + '
      '; +}; + +// widgetMenu constructor +function widgetMenu(dashboard) +{ + this.menu = {}; + this.dashboard = dashboard; +} + +// widgetMenu object +widgetMenu.prototype = +{ + init: function() + { + var self = this; + self.menuElement = $('#widgetChooser'); + self.buildMenu(); + }, + + registerCallbackOnWidgetLoad: function( callbackOnWidgetLoad ) + { + this.onWidgetLoad = callbackOnWidgetLoad; + }, + + registerCallbackOnMenuHover: function( callbackOnMenuHover ) + { + this.onMenuHover = callbackOnMenuHover; + }, + + //create DOM elements of the menu + buildMenu: function() + { + var self = this; + var menuWidgetCategories = $('.subMenu#sub1', self.menuElement); + var menuWidgetNames = $('.subMenu#sub2', self.menuElement); + + menuWidgetCategories.append(''); + menuWidgetNames.append('
        '); + var lineHeight = $('ol', menuWidgetCategories).css('line-height'); + lineHeight = Number(lineHeight.substring(0, lineHeight.length-2)); + + var i=0; + for(var widgetCategory in piwik.availableWidgets) { + var widgets = piwik.availableWidgets[widgetCategory]; + for(var j in widgets) { + widgetName = widgets[j]["name"]; + widgetUniqueId = widgets[j]["uniqueId"]; + widgetParameters = widgets[j]["parameters"]; + widgetCategoryId = 'category'+i; + exist = $('.subMenuItem#'+widgetCategoryId, menuWidgetCategories); + if(exist.size() == 0) { + $('ol', menuWidgetCategories) + .append('
      • '); + $('ul', menuWidgetNames) + .append(''); + } + // we prepend the ID with "ID" to not conflict with the
        + // that contains the widget preview and that has the widgetUniqueId already + $('.subMenuItem#'+widgetCategoryId, menuWidgetNames) + .append('') + .css('padding-top', i*lineHeight+'px'); + } + i++; + } + $('.subMenuItem', menuWidgetNames).hide(); + }, + + resetMenuState: function () + { + $('.menuSelected', self.menuElement).removeClass('menuSelected'); + $('#sub2 .subMenuItem', self.menuElement).hide(); + $('#sub3').empty().html('
        '); + }, + + bindEvents: function() + { + var self = this; + if(typeof self.menuInitialized != 'undefined') { + return; + } + self.menuInitialized = true; + + // Main menu (widget categories) + $('.subMenu#sub1 .subMenuItem', self.menuElement) + .hover(function() { + self.resetMenuState(); + categoryIdHovered = $(this).attr('id'); + $('#sub2 #'+categoryIdHovered, self.menuElement).show(); + $(this).addClass('menuSelected'); + }, function() {} + ); + + // Sub menu (each widget in the middle column) + $('.menuWidgetName', self.menuElement) + .hover( function() { + if($(this).hasClass('menuDisabled')) { + return; + } + // the ID is prefixed with "ID" + widgetUniqueId = $(this).attr('id').substr(2); + + // only reload preview if necessary + if($('#sub3 .widget').attr('id') == widgetUniqueId) { + return; + } + self.expectedWidgetUniqueId = widgetUniqueId; + + widget = widgetsHelper.getWidgetObjectFromUniqueId(widgetUniqueId); + widgetParameters = widget['parameters']; + + $('.subMenu#sub2 .menuSelected').removeClass('menuSelected'); + $(this).addClass('menuSelected'); + + if(typeof self.onMenuHover != 'undefined') { + self.onMenuHover(widgetUniqueId); + } + emptyWidgetHtml = widgetsHelper.getEmptyWidgetHtml( + widgetUniqueId, + '
        '+ + _pk_translate('Dashboard_WidgetPreview_js')+ + '
        ' + ); + $('#sub3').html(emptyWidgetHtml); + + $('#sub3 .widgetTop').click(function() { + self.movePreviewToDashboard(); + }); + + var onWidgetLoadedCallback = function (response) { + if(this.widgetUniqueId != self.expectedWidgetUniqueId) { + return; + } + widgetElement = $('#'+this.widgetUniqueId); + $('.widgetContent', widgetElement).html($(response)); + if(typeof self.onWidgetLoad != 'undefined') { + self.onWidgetLoad( widgetUniqueId, + widgetElement + ); + } + }; + ajaxRequest = widgetsHelper.getLoadWidgetAjaxRequest(widgetUniqueId, widgetParameters, onWidgetLoadedCallback); + $.ajax(ajaxRequest); + }, function() {} + ); + }, + + show: function() + { + var self = this; + if(typeof self.dashboard != 'undefined') { + self.initWidgetMenuForDashboard(); + self.filterOutAlreadyLoadedWidget(); + $.blockUI({ + message: self.menuElement, + centerY: 0, + css: {width:'', top: '5%',left:'10%', right:'10%', margin:"0px", textAlign:'', cursor:'', border:'0px'} + }); + } + self.resetMenuState(); + self.bindEvents(); + }, + + hideMenu: function() + { + $.unblockUI(); + }, + + filterOutAlreadyLoadedWidget: function() + { + var self = this; + + function contains(array, searchElem) { + for(var i=0; i