add icons for Character groups

This commit is contained in:
coderkun 2014-04-29 14:18:04 +02:00
commit 2d9a41a5fe
3461 changed files with 594457 additions and 0 deletions

View file

@ -0,0 +1,242 @@
<?php
/**
* Piwik - Open source web analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome;
use Exception;
use Piwik\API\Request;
use Piwik\Common;
use Piwik\Date;
use Piwik\FrontController;
use Piwik\Menu\MenuMain;
use Piwik\Notification\Manager as NotificationManager;
use Piwik\Piwik;
use Piwik\Plugins\CoreHome\DataTableRowAction\MultiRowEvolution;
use Piwik\Plugins\CoreHome\DataTableRowAction\RowEvolution;
use Piwik\Plugins\CorePluginsAdmin\MarketplaceApiClient;
use Piwik\Plugins\Dashboard\DashboardManagerControl;
use Piwik\Plugins\UsersManager\API;
use Piwik\Site;
use Piwik\UpdateCheck;
use Piwik\Url;
use Piwik\View;
/**
*
*/
class Controller extends \Piwik\Plugin\Controller
{
function getDefaultAction()
{
return 'redirectToCoreHomeIndex';
}
function redirectToCoreHomeIndex()
{
$defaultReport = API::getInstance()->getUserPreference(Piwik::getCurrentUserLogin(), API::PREFERENCE_DEFAULT_REPORT);
$module = 'CoreHome';
$action = 'index';
// User preference: default report to load is the All Websites dashboard
if ($defaultReport == 'MultiSites'
&& \Piwik\Plugin\Manager::getInstance()->isPluginActivated('MultiSites')
) {
$module = 'MultiSites';
}
if ($defaultReport == Piwik::getLoginPluginName()) {
$module = Piwik::getLoginPluginName();
}
$idSite = Common::getRequestVar('idSite', false, 'int');
parent::redirectToIndex($module, $action, $idSite);
}
public function showInContext()
{
$controllerName = Common::getRequestVar('moduleToLoad');
$actionName = Common::getRequestVar('actionToLoad', 'index');
if ($actionName == 'showInContext') {
throw new Exception("Preventing infinite recursion...");
}
$view = $this->getDefaultIndexView();
$view->content = FrontController::getInstance()->fetchDispatch($controllerName, $actionName);
return $view->render();
}
public function markNotificationAsRead()
{
$notificationId = Common::getRequestVar('notificationId');
NotificationManager::cancel($notificationId);
}
protected function getDefaultIndexView()
{
$view = new View('@CoreHome/getDefaultIndexView');
$this->setGeneralVariablesView($view);
$view->menu = MenuMain::getInstance()->getMenu();
$view->dashboardSettingsControl = new DashboardManagerControl();
$view->content = '';
return $view;
}
protected function setDateTodayIfWebsiteCreatedToday()
{
$date = Common::getRequestVar('date', false);
if ($date == 'today'
|| Common::getRequestVar('period', false) == 'range'
) {
return;
}
$websiteId = Common::getRequestVar('idSite', false, 'int');
if ($websiteId) {
$website = new Site($websiteId);
$datetimeCreationDate = $website->getCreationDate()->getDatetime();
$creationDateLocalTimezone = Date::factory($datetimeCreationDate, $website->getTimezone())->toString('Y-m-d');
$todayLocalTimezone = Date::factory('now', $website->getTimezone())->toString('Y-m-d');
if ($creationDateLocalTimezone == $todayLocalTimezone) {
Piwik::redirectToModule('CoreHome', 'index',
array('date' => 'today',
'idSite' => $websiteId,
'period' => Common::getRequestVar('period'))
);
}
}
}
public function index()
{
$this->setDateTodayIfWebsiteCreatedToday();
$view = $this->getDefaultIndexView();
return $view->render();
}
// --------------------------------------------------------
// ROW EVOLUTION
// The following methods render the popover that shows the
// evolution of a singe or multiple rows in a data table
// --------------------------------------------------------
/** Render the entire row evolution popover for a single row */
public function getRowEvolutionPopover()
{
$rowEvolution = $this->makeRowEvolution($isMulti = false);
$view = new View('@CoreHome/getRowEvolutionPopover');
return $rowEvolution->renderPopover($this, $view);
}
/** Render the entire row evolution popover for multiple rows */
public function getMultiRowEvolutionPopover()
{
$rowEvolution = $this->makeRowEvolution($isMulti = true);
$view = new View('@CoreHome/getMultiRowEvolutionPopover');
return $rowEvolution->renderPopover($this, $view);
}
/** Generic method to get an evolution graph or a sparkline for the row evolution popover */
public function getRowEvolutionGraph($fetch = false, $rowEvolution = null)
{
if (empty($rowEvolution)) {
$label = Common::getRequestVar('label', '', 'string');
$isMultiRowEvolution = strpos($label, ',') !== false;
$rowEvolution = $this->makeRowEvolution($isMultiRowEvolution, $graphType = 'graphEvolution');
$rowEvolution->useAvailableMetrics();
}
$view = $rowEvolution->getRowEvolutionGraph();
return $this->renderView($view);
}
/** Utility function. Creates a RowEvolution instance. */
private function makeRowEvolution($isMultiRowEvolution, $graphType = null)
{
if ($isMultiRowEvolution) {
return new MultiRowEvolution($this->idSite, $this->date, $graphType);
} else {
return new RowEvolution($this->idSite, $this->date, $graphType);
}
}
/**
* Forces a check for updates and re-renders the header message.
*
* This will check piwik.org at most once per 10s.
*/
public function checkForUpdates()
{
Piwik::checkUserHasSomeAdminAccess();
$this->checkTokenInUrl();
// perform check (but only once every 10s)
UpdateCheck::check($force = false, UpdateCheck::UI_CLICK_CHECK_INTERVAL);
MarketplaceApiClient::clearAllCacheEntries();
$view = new View('@CoreHome/checkForUpdates');
$this->setGeneralVariablesView($view);
return $view->render();
}
/**
* Renders and echo's the in-app donate form w/ slider.
*/
public function getDonateForm()
{
$view = new View('@CoreHome/getDonateForm');
if (Common::getRequestVar('widget', false)
&& Piwik::hasUserSuperUserAccess()
) {
$view->footerMessage = Piwik::translate('CoreHome_OnlyForSuperUserAccess');
}
return $view->render();
}
/**
* Renders and echo's HTML that displays the Piwik promo video.
*/
public function getPromoVideo()
{
$view = new View('@CoreHome/getPromoVideo');
$view->shareText = Piwik::translate('CoreHome_SharePiwikShort');
$view->shareTextLong = Piwik::translate('CoreHome_SharePiwikLong');
$view->promoVideoUrl = 'http://www.youtube.com/watch?v=OslfF_EH81g';
return $view->render();
}
/**
* Redirects the user to a paypal so they can donate to Piwik.
*/
public function redirectToPaypal()
{
$parameters = Request::getRequestArrayFromString($request = null);
foreach ($parameters as $name => $param) {
if ($name == 'idSite'
|| $name == 'module'
|| $name == 'action'
) {
unset($parameters[$name]);
}
}
$url = "https://www.paypal.com/cgi-bin/webscr?" . Url::getQueryStringFromParameters($parameters);
header("Location: $url");
exit;
}
public function getSiteSelector()
{
return "<div piwik-siteselector class=\"sites_autocomplete\" switch-site-on-select=\"false\"></div>";
}
public function getPeriodSelector()
{
$view = new View("@CoreHome/_periodSelect");
$this->setGeneralVariablesView($view);
return $view->render();
}
}

View file

@ -0,0 +1,201 @@
<?php
/**
* Piwik - Open source web analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome;
use Piwik\WidgetsList;
/**
*
*/
class CoreHome extends \Piwik\Plugin
{
/**
* @see Piwik\Plugin::getListHooksRegistered
*/
public function getListHooksRegistered()
{
return array(
'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
'AssetManager.getJavaScriptFiles' => 'getJsFiles',
'WidgetsList.addWidgets' => 'addWidgets',
'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys'
);
}
/**
* Adds the donate form widget.
*/
public function addWidgets()
{
WidgetsList::add('Example Widgets', 'CoreHome_SupportPiwik', 'CoreHome', 'getDonateForm');
WidgetsList::add('Example Widgets', 'Installation_Welcome', 'CoreHome', 'getPromoVideo');
}
public function getStylesheetFiles(&$stylesheets)
{
$stylesheets[] = "libs/jquery/themes/base/jquery-ui.css";
$stylesheets[] = "libs/jquery/stylesheets/jquery.jscrollpane.css";
$stylesheets[] = "libs/jquery/stylesheets/scroll.less";
$stylesheets[] = "plugins/Zeitgeist/stylesheets/base.less";
$stylesheets[] = "plugins/CoreHome/stylesheets/coreHome.less";
$stylesheets[] = "plugins/CoreHome/stylesheets/menu.less";
$stylesheets[] = "plugins/CoreHome/stylesheets/dataTable.less";
$stylesheets[] = "plugins/CoreHome/stylesheets/cloud.less";
$stylesheets[] = "plugins/CoreHome/stylesheets/jquery.ui.autocomplete.css";
$stylesheets[] = "plugins/CoreHome/stylesheets/jqplotColors.less";
$stylesheets[] = "plugins/CoreHome/stylesheets/sparklineColors.less";
$stylesheets[] = "plugins/CoreHome/stylesheets/promo.less";
$stylesheets[] = "plugins/CoreHome/stylesheets/color_manager.css";
$stylesheets[] = "plugins/CoreHome/stylesheets/sparklineColors.less";
$stylesheets[] = "plugins/CoreHome/stylesheets/notification.less";
$stylesheets[] = "plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.less";
}
public function getJsFiles(&$jsFiles)
{
$jsFiles[] = "libs/jquery/jquery.js";
$jsFiles[] = "libs/jquery/jquery-ui.js";
$jsFiles[] = "libs/jquery/jquery.browser.js";
$jsFiles[] = "libs/jquery/jquery.truncate.js";
$jsFiles[] = "libs/jquery/jquery.scrollTo.js";
$jsFiles[] = "libs/jquery/jquery.history.js";
$jsFiles[] = "libs/jquery/jquery.jscrollpane.js";
$jsFiles[] = "libs/jquery/jquery.mousewheel.js";
$jsFiles[] = "libs/jquery/mwheelIntent.js";
$jsFiles[] = "libs/javascript/sprintf.js";
$jsFiles[] = "libs/angularjs/angular.min.js";
$jsFiles[] = "libs/angularjs/angular-sanitize.min.js";
$jsFiles[] = "libs/angularjs/angular-animate.min.js";
$jsFiles[] = "plugins/Zeitgeist/javascripts/piwikHelper.js";
$jsFiles[] = "plugins/Zeitgeist/javascripts/ajaxHelper.js";
$jsFiles[] = "plugins/CoreHome/javascripts/require.js";
$jsFiles[] = "plugins/CoreHome/javascripts/uiControl.js";
$jsFiles[] = "plugins/CoreHome/javascripts/dataTable.js";
$jsFiles[] = "plugins/CoreHome/javascripts/dataTable_rowactions.js";
$jsFiles[] = "plugins/CoreHome/javascripts/popover.js";
$jsFiles[] = "plugins/CoreHome/javascripts/broadcast.js";
$jsFiles[] = "plugins/CoreHome/javascripts/menu.js";
$jsFiles[] = "plugins/CoreHome/javascripts/menu_init.js";
$jsFiles[] = "plugins/CoreHome/javascripts/calendar.js";
$jsFiles[] = "plugins/CoreHome/javascripts/sparkline.js";
$jsFiles[] = "plugins/CoreHome/javascripts/corehome.js";
$jsFiles[] = "plugins/CoreHome/javascripts/top_controls.js";
$jsFiles[] = "plugins/CoreHome/javascripts/donate.js";
$jsFiles[] = "libs/jqplot/jqplot-custom.min.js";
$jsFiles[] = "plugins/CoreHome/javascripts/promo.js";
$jsFiles[] = "plugins/CoreHome/javascripts/color_manager.js";
$jsFiles[] = "plugins/CoreHome/javascripts/notification.js";
$jsFiles[] = "plugins/CoreHome/javascripts/notification_parser.js";
$jsFiles[] = "plugins/CoreHome/angularjs/piwikAppConfig.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/services/service.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/services/piwik.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/services/piwik-api.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/filters/filter.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/filters/translate.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/filters/startfrom.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/filters/evolution.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/autocomplete-matched.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/focus-anywhere-but-here.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/ignore-click.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/onenter.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/focusif.js";
$jsFiles[] = "plugins/CoreHome/angularjs/common/directives/dialog.js";
$jsFiles[] = "plugins/CoreHome/angularjs/piwikApp.js";
$jsFiles[] = "plugins/CoreHome/angularjs/anchorLinkFix.js";
$jsFiles[] = "plugins/CoreHome/angularjs/siteselector/siteselector-model.js";
$jsFiles[] = "plugins/CoreHome/angularjs/siteselector/siteselector-controller.js";
$jsFiles[] = "plugins/CoreHome/angularjs/siteselector/siteselector-directive.js";
$jsFiles[] = "plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline-directive.js";
}
public function getClientSideTranslationKeys(&$translationKeys)
{
$translationKeys[] = 'General_InvalidDateRange';
$translationKeys[] = 'General_Loading';
$translationKeys[] = 'General_Show';
$translationKeys[] = 'General_Hide';
$translationKeys[] = 'General_YearShort';
$translationKeys[] = 'General_MultiSitesSummary';
$translationKeys[] = 'CoreHome_YouAreUsingTheLatestVersion';
$translationKeys[] = 'CoreHome_IncludeRowsWithLowPopulation';
$translationKeys[] = 'CoreHome_ExcludeRowsWithLowPopulation';
$translationKeys[] = 'CoreHome_DataTableIncludeAggregateRows';
$translationKeys[] = 'CoreHome_DataTableExcludeAggregateRows';
$translationKeys[] = 'CoreHome_Default';
$translationKeys[] = 'CoreHome_PageOf';
$translationKeys[] = 'CoreHome_FlattenDataTable';
$translationKeys[] = 'CoreHome_UnFlattenDataTable';
$translationKeys[] = 'CoreHome_ExternalHelp';
$translationKeys[] = 'SitesManager_NotFound';
$translationKeys[] = 'Annotations_ViewAndAddAnnotations';
$translationKeys[] = 'General_RowEvolutionRowActionTooltipTitle';
$translationKeys[] = 'General_RowEvolutionRowActionTooltip';
$translationKeys[] = 'Annotations_IconDesc';
$translationKeys[] = 'Annotations_IconDescHideNotes';
$translationKeys[] = 'Annotations_HideAnnotationsFor';
$translationKeys[] = 'General_LoadingPopover';
$translationKeys[] = 'General_LoadingPopoverFor';
$translationKeys[] = 'General_ShortMonth_1';
$translationKeys[] = 'General_ShortMonth_2';
$translationKeys[] = 'General_ShortMonth_3';
$translationKeys[] = 'General_ShortMonth_4';
$translationKeys[] = 'General_ShortMonth_5';
$translationKeys[] = 'General_ShortMonth_6';
$translationKeys[] = 'General_ShortMonth_7';
$translationKeys[] = 'General_ShortMonth_8';
$translationKeys[] = 'General_ShortMonth_9';
$translationKeys[] = 'General_ShortMonth_10';
$translationKeys[] = 'General_ShortMonth_11';
$translationKeys[] = 'General_ShortMonth_12';
$translationKeys[] = 'General_LongMonth_1';
$translationKeys[] = 'General_LongMonth_2';
$translationKeys[] = 'General_LongMonth_3';
$translationKeys[] = 'General_LongMonth_4';
$translationKeys[] = 'General_LongMonth_5';
$translationKeys[] = 'General_LongMonth_6';
$translationKeys[] = 'General_LongMonth_7';
$translationKeys[] = 'General_LongMonth_8';
$translationKeys[] = 'General_LongMonth_9';
$translationKeys[] = 'General_LongMonth_10';
$translationKeys[] = 'General_LongMonth_11';
$translationKeys[] = 'General_LongMonth_12';
$translationKeys[] = 'General_ShortDay_1';
$translationKeys[] = 'General_ShortDay_2';
$translationKeys[] = 'General_ShortDay_3';
$translationKeys[] = 'General_ShortDay_4';
$translationKeys[] = 'General_ShortDay_5';
$translationKeys[] = 'General_ShortDay_6';
$translationKeys[] = 'General_ShortDay_7';
$translationKeys[] = 'General_LongDay_1';
$translationKeys[] = 'General_LongDay_2';
$translationKeys[] = 'General_LongDay_3';
$translationKeys[] = 'General_LongDay_4';
$translationKeys[] = 'General_LongDay_5';
$translationKeys[] = 'General_LongDay_6';
$translationKeys[] = 'General_LongDay_7';
$translationKeys[] = 'General_DayMo';
$translationKeys[] = 'General_DayTu';
$translationKeys[] = 'General_DayWe';
$translationKeys[] = 'General_DayTh';
$translationKeys[] = 'General_DayFr';
$translationKeys[] = 'General_DaySa';
$translationKeys[] = 'General_DaySu';
$translationKeys[] = 'General_Search';
$translationKeys[] = 'General_MoreDetails';
$translationKeys[] = 'General_Help';
}
}

View file

@ -0,0 +1,71 @@
<?php
/**
* Piwik - Open source web analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\DataTableRowAction;
use Piwik\Common;
use Piwik\Piwik;
/**
* MULTI ROW EVOLUTION
* The class handles the popover that shows the evolution of a multiple rows in a data table
*/
class MultiRowEvolution extends RowEvolution
{
/** The requested metric */
protected $metric;
/** Show all metrics in the evolution graph when the popover opens */
protected $initiallyShowAllMetrics = true;
/** The metrics available in the metrics select */
protected $metricsForSelect;
/**
* The constructor
* @param int $idSite
* @param \Piwik\Date $date ($this->date from controller)
*/
public function __construct($idSite, $date)
{
$this->metric = Common::getRequestVar('column', '', 'string');
parent::__construct($idSite, $date);
}
protected function loadEvolutionReport($column = false)
{
// set the "column" parameter for the API.getRowEvolution call
parent::loadEvolutionReport($this->metric);
}
protected function extractEvolutionReport($report)
{
$this->metric = $report['column'];
$this->dataTable = $report['reportData'];
$this->availableMetrics = $report['metadata']['metrics'];
$this->metricsForSelect = $report['metadata']['columns'];
$this->dimension = $report['metadata']['dimension'];
}
/**
* Render the popover
* @param \Piwik\Plugins\CoreHome\Controller $controller
* @param View (the popover_rowevolution template)
*/
public function renderPopover($controller, $view)
{
// add data for metric select box
$view->availableMetrics = $this->metricsForSelect;
$view->selectedMetric = $this->metric;
$view->availableRecordsText = $this->dimension . ': '
. Piwik::translate('RowEvolution_ComparingRecords', array(count($this->availableMetrics)));
return parent::renderPopover($controller, $view);
}
}

View file

@ -0,0 +1,342 @@
<?php
/**
* Piwik - Open source web analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreHome\DataTableRowAction;
use Exception;
use Piwik\API\Request;
use Piwik\API\ResponseBuilder;
use Piwik\Common;
use Piwik\DataTable;
use Piwik\Date;
use Piwik\Metrics;
use Piwik\Piwik;
use Piwik\Plugins\CoreVisualizations\Visualizations\JqplotGraph\Evolution as EvolutionViz;
use Piwik\Url;
use Piwik\ViewDataTable\Factory;
/**
* ROW EVOLUTION
* The class handles the popover that shows the evolution of a singe row in a data table
*/
class RowEvolution
{
/** The current site id */
protected $idSite;
/** The api method to get the data. Format: Plugin.apiAction */
protected $apiMethod;
/** The label of the requested row */
protected $label;
/** The requested period */
protected $period;
/** The requested date */
protected $date;
/** The request segment */
protected $segment;
/** The metrics that are available for the requested report and period */
protected $availableMetrics;
/** The name of the dimension of the current report */
protected $dimension;
/**
* The data
* @var \Piwik\DataTable
*/
protected $dataTable;
/** The label of the current record */
protected $rowLabel;
/** The icon of the current record */
protected $rowIcon;
/** The type of graph that has been requested last */
protected $graphType;
/** The metrics for the graph that has been requested last */
protected $graphMetrics;
/** Whether or not to show all metrics in the evolution graph when to popover opens */
protected $initiallyShowAllMetrics = false;
/**
* The constructor
* Initialize some local variables from the request
* @param int $idSite
* @param Date $date ($this->date from controller)
* @param null|string $graphType
* @throws Exception
*/
public function __construct($idSite, $date, $graphType = null)
{
$this->apiMethod = Common::getRequestVar('apiMethod', '', 'string');
if (empty($this->apiMethod)) throw new Exception("Parameter apiMethod not set.");
$this->label = ResponseBuilder::getLabelFromRequest($_GET);
$this->label = $this->label[0];
if ($this->label === '') throw new Exception("Parameter label not set.");
$this->period = Common::getRequestVar('period', '', 'string');
if (empty($this->period)) throw new Exception("Parameter period not set.");
$this->idSite = $idSite;
$this->graphType = $graphType;
if ($this->period != 'range') {
// handle day, week, month and year: display last X periods
$end = $date->toString();
list($this->date, $lastN) = EvolutionViz::getDateRangeAndLastN($this->period, $end);
}
$this->segment = \Piwik\API\Request::getRawSegmentFromRequest();
$this->loadEvolutionReport();
}
/**
* Render the popover
* @param \Piwik\Plugins\CoreHome\Controller $controller
* @param View (the popover_rowevolution template)
*/
public function renderPopover($controller, $view)
{
// render main evolution graph
$this->graphType = 'graphEvolution';
$this->graphMetrics = $this->availableMetrics;
$view->graph = $controller->getRowEvolutionGraph($fetch = true, $rowEvolution = $this);
// render metrics overview
$view->metrics = $this->getMetricsToggles();
// available metrics text
$metricsText = Piwik::translate('RowEvolution_AvailableMetrics');
$popoverTitle = '';
if ($this->rowLabel) {
$icon = $this->rowIcon ? '<img src="' . $this->rowIcon . '" alt="">' : '';
$metricsText = sprintf(Piwik::translate('RowEvolution_MetricsFor'), $this->dimension . ': ' . $icon . ' ' . $this->rowLabel);
$popoverTitle = $icon . ' ' . $this->rowLabel;
}
$view->availableMetricsText = $metricsText;
$view->popoverTitle = $popoverTitle;
return $view->render();
}
protected function loadEvolutionReport($column = false)
{
list($apiModule, $apiAction) = explode('.', $this->apiMethod);
$parameters = array(
'method' => 'API.getRowEvolution',
'label' => $this->label,
'apiModule' => $apiModule,
'apiAction' => $apiAction,
'idSite' => $this->idSite,
'period' => $this->period,
'date' => $this->date,
'format' => 'original',
'serialize' => '0'
);
if (!empty($this->segment)) {
$parameters['segment'] = $this->segment;
}
if ($column !== false) {
$parameters['column'] = $column;
}
$url = Url::getQueryStringFromParameters($parameters);
$request = new Request($url);
$report = $request->process();
$this->extractEvolutionReport($report);
}
protected function extractEvolutionReport($report)
{
$this->dataTable = $report['reportData'];
$this->rowLabel = $this->extractPrettyLabel($report);
$this->rowIcon = !empty($report['logo']) ? $report['logo'] : false;
$this->availableMetrics = $report['metadata']['metrics'];
$this->dimension = $report['metadata']['dimension'];
}
/**
* Generic method to get an evolution graph or a sparkline for the row evolution popover.
* Do as much as possible from outside the controller.
* @param string|bool $graphType
* @param array|bool $metrics
* @return Factory
*/
public function getRowEvolutionGraph($graphType = false, $metrics = false)
{
// set up the view data table
$view = Factory::build($graphType ? : $this->graphType, $this->apiMethod,
$controllerAction = 'CoreHome.getRowEvolutionGraph', $forceDefault = true);
$view->setDataTable($this->dataTable);
if (!empty($this->graphMetrics)) { // In row Evolution popover, this is empty
$view->config->columns_to_display = array_keys($metrics ? : $this->graphMetrics);
}
$view->config->show_goals = false;
$view->config->show_all_views_icons = false;
$view->config->show_active_view_icon = false;
$view->config->show_related_reports = false;
$view->config->show_series_picker = false;
$view->config->show_footer_message = false;
foreach ($this->availableMetrics as $metric => $metadata) {
$view->config->translations[$metric] = $metadata['name'];
}
$view->config->external_series_toggle = 'RowEvolutionSeriesToggle';
$view->config->external_series_toggle_show_all = $this->initiallyShowAllMetrics;
return $view;
}
/**
* Prepare metrics toggles with spark lines
* @return array
*/
protected function getMetricsToggles()
{
$i = 0;
$metrics = array();
foreach ($this->availableMetrics as $metric => $metricData) {
$unit = Metrics::getUnit($metric, $this->idSite);
$change = isset($metricData['change']) ? $metricData['change'] : false;
list($first, $last) = $this->getFirstAndLastDataPointsForMetric($metric);
$details = Piwik::translate('RowEvolution_MetricBetweenText', array($first, $last));
if ($change !== false) {
$lowerIsBetter = Metrics::isLowerValueBetter($metric);
if (substr($change, 0, 1) == '+') {
$changeClass = $lowerIsBetter ? 'bad' : 'good';
$changeImage = $lowerIsBetter ? 'arrow_up_red' : 'arrow_up';
} else if (substr($change, 0, 1) == '-') {
$changeClass = $lowerIsBetter ? 'good' : 'bad';
$changeImage = $lowerIsBetter ? 'arrow_down_green' : 'arrow_down';
} else {
$changeClass = 'neutral';
$changeImage = false;
}
$change = '<span class="' . $changeClass . '">'
. ($changeImage ? '<img src="plugins/MultiSites/images/' . $changeImage . '.png" /> ' : '')
. $change . '</span>';
$details .= ', ' . Piwik::translate('RowEvolution_MetricChangeText', $change);
}
// set metric min/max text (used as tooltip for details)
$max = isset($metricData['max']) ? $metricData['max'] : 0;
$min = isset($metricData['min']) ? $metricData['min'] : 0;
$min .= $unit;
$max .= $unit;
$minmax = Piwik::translate('RowEvolution_MetricMinMax', array($metricData['name'], $min, $max));
$newMetric = array(
'label' => $metricData['name'],
'details' => $details,
'minmax' => $minmax,
'sparkline' => $this->getSparkline($metric),
);
// Multi Rows, each metric can be for a particular row and display an icon
if (!empty($metricData['logo'])) {
$newMetric['logo'] = $metricData['logo'];
}
$metrics[] = $newMetric;
$i++;
}
return $metrics;
}
/** Get the img tag for a sparkline showing a single metric */
protected function getSparkline($metric)
{
// sparkline is always echoed, so we need to buffer the output
$view = $this->getRowEvolutionGraph($graphType = 'sparkline', $metrics = array($metric => $metric));
ob_start();
$view->render();
$spark = ob_get_contents();
ob_end_clean();
// undo header change by sparkline renderer
header('Content-type: text/html');
// base64 encode the image and put it in an img tag
$spark = base64_encode($spark);
return '<img src="data:image/png;base64,' . $spark . '" />';
}
/** Use the available metrics for the metrics of the last requested graph. */
public function useAvailableMetrics()
{
$this->graphMetrics = $this->availableMetrics;
}
private function getFirstAndLastDataPointsForMetric($metric)
{
$first = 0;
$firstTable = $this->dataTable->getFirstRow();
if (!empty($firstTable)) {
$row = $firstTable->getFirstRow();
if (!empty($row)) {
$first = floatval($row->getColumn($metric));
}
}
$last = 0;
$lastTable = $this->dataTable->getLastRow();
if (!empty($lastTable)) {
$row = $lastTable->getFirstRow();
if (!empty($row)) {
$last = floatval($row->getColumn($metric));
}
}
return array($first, $last);
}
/**
* @param $report
* @return string
*/
protected function extractPrettyLabel($report)
{
// By default, use the specified label
$rowLabel = Common::sanitizeInputValue($report['label']);
$rowLabel = str_replace('/', '<wbr>/', str_replace('&', '<wbr>&', $rowLabel ));
// If the dataTable specifies a label_html, use this instead
/** @var $dataTableMap \Piwik\DataTable\Map */
$dataTableMap = $report['reportData'];
$labelPretty = $dataTableMap->getColumn('label_html');
$labelPretty = array_filter($labelPretty, 'strlen');
$labelPretty = current($labelPretty);
if(!empty($labelPretty)) {
return $labelPretty;
}
return $rowLabel;
}
}

View file

@ -0,0 +1,108 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* See http://dev.piwik.org/trac/ticket/4795 "linking to #hash tag does not work after merging AngularJS"
*/
(function () {
function scrollToAnchorNode($node)
{
$.scrollTo($node, 20);
}
function preventDefaultIfEventExists(event)
{
if (event) {
event.preventDefault();
}
}
function scrollToAnchorIfPossible(hash, event)
{
if (!hash) {
return;
}
if (-1 !== hash.indexOf('&')) {
return;
}
var $node = $('#' + hash);
if ($node && $node.length) {
scrollToAnchorNode($node);
preventDefaultIfEventExists(event);
return;
}
$node = $('a[name='+ hash + ']');
if ($node && $node.length) {
scrollToAnchorNode($node);
preventDefaultIfEventExists(event);
}
}
function isLinkWithinSamePage(location, newUrl)
{
if (location && location.origin && -1 === newUrl.indexOf(location.origin)) {
// link to different domain
return false;
}
if (location && location.pathname && -1 === newUrl.indexOf(location.pathname)) {
// link to different path
return false;
}
if (location && location.search && -1 === newUrl.indexOf(location.search)) {
// link with different search
return false;
}
return true;
}
function handleScrollToAnchorIfPresentOnPageLoad()
{
if (location.hash.substr(0, 2) == '#/') {
var hash = location.hash.substr(2);
scrollToAnchorIfPossible(hash, null);
}
}
function handleScrollToAnchorAfterPageLoad()
{
angular.module('piwikApp').run(['$rootScope', function ($rootScope) {
$rootScope.$on('$locationChangeStart', function (event, newUrl, oldUrl, $location) {
if (!newUrl) {
return;
}
var hashPos = newUrl.indexOf('#/');
if (-1 === hashPos) {
return;
}
if (!isLinkWithinSamePage(this.location, newUrl)) {
return;
}
var hash = newUrl.substr(hashPos + 2);
scrollToAnchorIfPossible(hash, event);
});
}]);
}
handleScrollToAnchorAfterPageLoad();
$(handleScrollToAnchorIfPresentOnPageLoad);
})();

View file

@ -0,0 +1,42 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* If the given text or resolved expression matches any text within the element, the matching text will be wrapped
* with a class.
*
* Example:
* <div piwik-autocomplete-matched="'text'">My text</div> ==> <div>My <span class="autocompleteMatched">text</span></div>
*
* <div piwik-autocomplete-matched="searchTerm">{{ name }}</div>
* <input type="text" ng-model="searchTerm">
*/
angular.module('piwikApp.directive').directive('piwikAutocompleteMatched', function() {
return function(scope, element, attrs) {
var searchTerm;
scope.$watch(attrs.piwikAutocompleteMatched, function(value) {
searchTerm = value;
updateText();
});
function updateText () {
if (!searchTerm || !element) {
return;
}
var content = element.html();
var startTerm = content.toLowerCase().indexOf(searchTerm.toLowerCase());
if (-1 !== startTerm) {
var word = content.substr(startTerm, searchTerm.length);
content = content.replace(word, '<span class="autocompleteMatched">' + word + '</span>');
element.html(content);
}
}
};
});

View file

@ -0,0 +1,43 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
describe('piwikAutocompleteMatchedDirective', function() {
var $compile;
var $rootScope;
beforeEach(module('piwikApp.directive'));
beforeEach(inject(function(_$compile_, _$rootScope_){
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
function assertRenderedContentIs(query, expectedResult) {
var template = '<div piwik-autocomplete-matched="\'' + query + '\'">My Content</div>';
var element = $compile(template)($rootScope);
$rootScope.$digest();
expect(element.html()).to.eql(expectedResult);
}
describe('#piwikAutocompleteMatched()', function() {
it('should not change anything if query does not match the text', function() {
assertRenderedContentIs('Whatever', 'My Content');
});
it('should wrap the matching part and find case insensitive', function() {
assertRenderedContentIs('y cont', 'M<span class="autocompleteMatched">y Cont</span>ent');
});
it('should be able to wrap the whole content', function() {
assertRenderedContentIs('my content', '<span class="autocompleteMatched">My Content</span>');
});
it('should find matching content case sensitive', function() {
assertRenderedContentIs('My Co', '<span class="autocompleteMatched">My Co</span>ntent');
});
});
});

View file

@ -0,0 +1,41 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Usage:
* <div piwik-dialog="showDialog">...</div>
* Will show dialog once showDialog evaluates to true.
*
* <div piwik-dialog="showDialog" yes="executeMyFunction();">
* ... <input type="button" role="yes" value="button">
* </div>
* Will execute the "executeMyFunction" function in the current scope once the yes button is pressed.
*/
angular.module('piwikApp.directive').directive('piwikDialog', function(piwik) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
element.css('display', 'none');
element.on( "dialogclose", function() {
scope.$eval(attrs.piwikDialog+'=false');
});
scope.$watch(attrs.piwikDialog, function(newValue, oldValue) {
if (newValue) {
piwik.helper.modalConfirm(element, {yes: function() {
if (attrs.yes) {
scope.$eval(attrs.yes);
}
}});
}
});
}
};
});

View file

@ -0,0 +1,8 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
angular.module('piwikApp.directive', []);

View file

@ -0,0 +1,40 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* The given expression will be executed when the user presses either escape or presses something outside
* of this element
*
* Example:
* <div piwik-focus-anywhere-but-here="closeDialog()">my dialog</div>
*/
angular.module('piwikApp.directive').directive('piwikFocusAnywhereButHere', function($document){
return {
restrict: 'A',
link: function(scope, element, attr, ctrl) {
function onClickOutsideElement (event) {
if (element.has(event.target).length === 0) {
scope.$apply(attr.piwikFocusAnywhereButHere);
}
}
function onEscapeHandler (event) {
if (event.which === 27) {
scope.$apply(attr.piwikFocusAnywhereButHere);
}
}
$document.on('keyup', onEscapeHandler);
$document.on('mouseup', onClickOutsideElement);
scope.$on('$destroy', function() {
$document.off('mouseup', onClickOutsideElement);
$document.off('keyup', onEscapeHandler);
});
}
};
});

View file

@ -0,0 +1,27 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* If the given expression evaluates to true the element will be focussed
*
* Example:
* <input type="text" piwik-focus-if="view.editName">
*/
angular.module('piwikApp.directive').directive('piwikFocusIf', function($timeout) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
scope.$watch(attrs.piwikFocusIf, function(newValue, oldValue) {
if (newValue) {
$timeout(function () {
element[0].focus();
}, 5);
}
});
}
};
});

View file

@ -0,0 +1,21 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Prevents the default behavior of the click. For instance useful if a link should only work in case the user
* does a "right click open in new window".
*
* Example
* <a piwik-ignore-click ng-click="doSomething()" href="/">my link</a>
*/
angular.module('piwikApp.directive').directive('piwikIgnoreClick', function() {
return function(scope, element, attrs) {
$(element).click(function(event) {
event.preventDefault();
});
};
});

View file

@ -0,0 +1,27 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Allows you to define any expression to be executed in case the user presses enter
*
* Example
* <div piwik-onenter="save()">
* <div piwik-onenter="showList=false">
*/
angular.module('piwikApp.directive').directive('piwikOnenter', function() {
return function(scope, element, attrs) {
element.bind("keydown keypress", function(event) {
if(event.which === 13) {
scope.$apply(function(){
scope.$eval(attrs.piwikOnenter, {'event': event});
});
event.preventDefault();
}
});
};
});

View file

@ -0,0 +1,44 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
angular.module('piwikApp.filter').filter('evolution', function() {
function calculateEvolution(currentValue, pastValue)
{
pastValue = parseInt(pastValue, 10);
currentValue = parseInt(currentValue, 10) - pastValue;
if (currentValue === 0 || isNaN(currentValue)) {
evolution = 0;
} else if (pastValue === 0 || isNaN(pastValue)) {
evolution = 100;
} else {
evolution = (currentValue / pastValue) * 100;
}
return evolution;
}
function formatEvolution(evolution)
{
evolution = Math.round(evolution);
if (evolution > 0) {
evolution = '+' + evolution;
}
evolution += '%';
return evolution;
}
return function(currentValue, pastValue) {
var evolution = calculateEvolution(currentValue, pastValue);
return formatEvolution(evolution);
};
});

View file

@ -0,0 +1,7 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
angular.module('piwikApp.filter', []);

View file

@ -0,0 +1,13 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
angular.module('piwikApp.filter').filter('startFrom', function() {
return function(input, start) {
start = +start; //parse to int
return input.slice(start);
};
});

View file

@ -0,0 +1,40 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
describe('startFromFilter', function() {
var startFrom;
beforeEach(module('piwikApp.filter'));
beforeEach(inject(function($injector) {
var $filter = $injector.get('$filter');
startFrom = $filter('startFrom');
}));
describe('#startFrom()', function() {
it('should return all entries if index is zero', function() {
var result = startFrom([1,2,3], 0);
expect(result).to.eql([1,2,3]);
});
it('should return only partial entries if filter is higher than zero', function() {
var result = startFrom([1,2,3], 2);
expect(result).to.eql([3]);
});
it('should return no entries if start is higher than input length', function() {
var result = startFrom([1,2,3], 11);
expect(result).to.eql([]);
});
});
});

View file

@ -0,0 +1,19 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
angular.module('piwikApp.filter').filter('translate', function() {
return function(key, value1, value2, value3) {
var values = [];
if (arguments && arguments.length > 1) {
for (var index = 1; index < arguments.length; index++) {
values.push(arguments[index]);
}
}
return _pk_translate(key, values);
};
});

View file

@ -0,0 +1,186 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
angular.module('piwikApp.service').factory('piwikApi', function ($http, $q, $rootScope, piwik, $window) {
var url = 'index.php';
var format = 'json';
var getParams = {};
var postParams = {};
var requestHandle = null;
var piwikApi = {};
/**
* Adds params to the request.
* If params are given more then once, the latest given value is used for the request
*
* @param {object} params
* @return {void}
*/
function addParams (params) {
if (typeof params == 'string') {
params = piwik.broadcast.getValuesFromUrl(params);
}
for (var key in params) {
getParams[key] = params[key];
}
}
function reset () {
getParams = {};
postParams = {};
}
/**
* Send the request
* @return $promise
*/
function send () {
var deferred = $q.defer();
var requestHandle = deferred;
var onError = function (message) {
deferred.reject(message);
requestHandle = null;
};
var onSuccess = function (response) {
if (response && response.result == 'error') {
if (response.message) {
onError(response.message);
var UI = require('piwik/UI');
var notification = new UI.Notification();
notification.show(response.message, {
context: 'error',
type: 'toast',
id: 'ajaxHelper'
});
notification.scrollToNotification();
} else {
onError(null);
}
} else {
deferred.resolve(response);
}
requestHandle = null;
};
var headers = {
'Content-Type': 'application/x-www-form-urlencoded',
// ie 8,9,10 caches ajax requests, prevent this
'cache-control': 'no-cache'
};
var ajaxCall = {
method: 'POST',
url: url,
responseType: format,
params: _mixinDefaultGetParams(getParams),
data: $.param(getPostParams(postParams)),
timeout: deferred.promise,
headers: headers
};
$http(ajaxCall).success(onSuccess).error(onError);
return deferred.promise;
}
/**
* Get the parameters to send as POST
*
* @param {object} params parameter object
* @return {object}
* @private
*/
function getPostParams () {
return {
token_auth: piwik.token_auth
};
}
/**
* Mixin the default parameters to send as GET
*
* @param {object} getParamsToMixin parameter object
* @return {object}
* @private
*/
function _mixinDefaultGetParams (getParamsToMixin) {
var defaultParams = {
idSite: piwik.idSite || piwik.broadcast.getValueFromUrl('idSite'),
period: piwik.period || piwik.broadcast.getValueFromUrl('period'),
segment: piwik.broadcast.getValueFromHash('segment', $window.location.href.split('#')[1])
};
// never append token_auth to url
if (getParamsToMixin.token_auth) {
getParamsToMixin.token_auth = null;
delete getParamsToMixin.token_auth;
}
for (var key in defaultParams) {
if (!getParamsToMixin[key] && !postParams[key] && defaultParams[key]) {
getParamsToMixin[key] = defaultParams[key];
}
}
// handle default date & period if not already set
if (!getParamsToMixin.date && !postParams.date) {
getParamsToMixin.date = piwik.currentDateString || piwik.broadcast.getValueFromUrl('date');
if (getParamsToMixin.period == 'range' && piwik.currentDateString) {
getParamsToMixin.date = piwik.startDateString + ',' + getParamsToMixin.date;
}
}
return getParamsToMixin;
}
piwikApi.abort = function () {
reset();
if (requestHandle) {
requestHandle.resolve();
requestHandle = null;
}
};
/**
* Perform a reading API request.
* @param getParams
*/
piwikApi.fetch = function (getParams) {
getParams.module = 'API';
getParams.format = 'JSON';
addParams(getParams, 'GET');
var promise = send();
reset();
return promise;
};
piwikApi.post = function (getParams, _postParams_) {
if (_postParams_) {
postParams = _postParams_;
}
return this.fetch(getParams);
};
return piwikApi;
});

View file

@ -0,0 +1,13 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
angular.module('piwikApp.service').service('piwik', function () {
piwik.helper = piwikHelper;
piwik.broadcast = broadcast;
return piwik;
});

View file

@ -0,0 +1,37 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
describe('piwikService', function() {
var piwikService;
beforeEach(module('piwikApp.service'));
beforeEach(inject(function($injector) {
piwikService = $injector.get('piwik');
}));
describe('#piwikService', function() {
it('should be the same as piwik global var', function() {
piwik.should.equal(piwikService);
});
it('should mixin broadcast', function() {
expect(piwikService.broadcast).to.be.an('object');
});
it('should mixin piwikHelper', function() {
expect(piwikService.helper).to.be.an('object');
});
});
describe('#piwik_url', function() {
it('should contain the piwik url', function() {
expect(piwikService.piwik_url).to.eql('http://localhost/');
});
});
});

View file

@ -0,0 +1,8 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
angular.module('piwikApp.service', []);

View file

@ -0,0 +1,66 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Usage:
*
* <h2 piwik-enriched-headline>All Websites Dashboard</h2>
* -> uses "All Websites Dashboard" as featurename
*
* <h2 piwik-enriched-headline feature-name="All Websites Dashboard">All Websites Dashboard (Total: 309 Visits)</h2>
* -> custom featurename
*
* <h2 piwik-enriched-headline help-url="http://piwik.org/guide">All Websites Dashboard</h2>
* -> shows help icon and links to external url
*
* <h2 piwik-enriched-headline>All Websites Dashboard
* <div class="inlineHelp>My <strong>inline help</strong></div>
* </h2>
* -> shows help icon to display inline help on click. Note: You can combine inlinehelp and help-url
*/
angular.module('piwikApp').directive('piwikEnrichedHeadline', function($document, piwik, $filter){
var defaults = {
helpUrl: ''
};
return {
transclude: true,
restrict: 'A',
scope: {
helpUrl: '@',
featureName: '@'
},
templateUrl: 'plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.html?cb=' + piwik.cacheBuster,
compile: function (element, attrs) {
for (var index in defaults) {
if (!attrs[index]) { attrs[index] = defaults[index]; }
}
return function (scope, element, attrs) {
var helpNode = $('[ng-transclude] .inlineHelp', element);
if ((!helpNode || !helpNode.length) && element.next()) {
// hack for reports :(
helpNode = element.next().find('.reportDocumentation');
}
if (helpNode && helpNode.length) {
if ($.trim(helpNode.text())) {
scope.inlineHelp = $.trim(helpNode.html());
}
helpNode.remove();
}
if (!attrs.featureName) {
attrs.featureName = $.trim(element.text());
}
};
}
};
});

View file

@ -0,0 +1,29 @@
<div class="enrichedHeadline"
ng-mouseenter="view.showIcons=true" ng-mouseleave="view.showIcons=false">
<span ng-transclude></span>
<span ng-show="view.showIcons">
<a ng-if="helpUrl && !inlineHelp"
target="_blank"
href="{{ helpUrl }}"
title="{{ 'CoreHome_ExternalHelp'|translate }}"
class="helpIcon"></a>
<a ng-if="inlineHelp"
title="{{ 'General_Help'|translate }}"
ng-click="view.showInlineHelp=!view.showInlineHelp"
class="helpIcon"></a>
<div class="ratingIcons"
piwik-rate-feature
title="{{ featureName }}"></div>
</span>
<div class="inlineHelp" ng-show="view.showIcons && view.showInlineHelp">
<div ng-bind-html="inlineHelp"></div>
<a ng-if="helpUrl"
target="_blank"
href="{{ helpUrl }}"
class="readMore">{{ 'General_MoreDetails'|translate }}</a>
</div>
</div>

View file

@ -0,0 +1,44 @@
.inlineHelp {
display: none;
}
.enrichedHeadline {
min-height: 22px;
.inlineHelp {
display:block;
background: #F7F7F7;
font-size: 12px;
font-weight: normal;
border: 1px solid #E4E5E4;
margin: 10px 0 10px 0;
padding: 10px;
border-radius: 4px;
max-width: 500px;
.readMore {
margin-top: 10px;
display: inline-block;
font-weight: bold;
}
}
.ratingIcons {
display:inline-block;
vertical-align: bottom;
}
.helpIcon:hover {
opacity: 0.9;
}
.helpIcon {
cursor: pointer;
display:inline-block;
margin: 0px 0px -1px 4px;
width: 16px;
opacity: 0.3;
height: 16px;
background: url(plugins/CoreHome/angularjs/enrichedheadline/help.png) no-repeat;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

View file

@ -0,0 +1,16 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
angular.module('piwikApp', [
'ngSanitize',
'ngAnimate',
'piwikApp.config',
'piwikApp.service',
'piwikApp.directive',
'piwikApp.filter'
]);
angular.module('app', []);

View file

@ -0,0 +1,9 @@
angular.module('piwikApp.config', []);
(function () {
var piwikAppConfig = angular.module('piwikApp.config');
// we probably want this later as a separate config file, till then it serves as a "bridge"
for (var index in piwik.config) {
piwikAppConfig.constant(index.toUpperCase(), piwik.config[index]);
}
})();

View file

@ -0,0 +1,49 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
angular.module('piwikApp').controller('SiteSelectorController', function($scope, siteSelectorModel, piwik, AUTOCOMPLETE_MIN_SITES){
$scope.model = siteSelectorModel;
$scope.autocompleteMinSites = AUTOCOMPLETE_MIN_SITES;
$scope.selectedSite = {id: '', name: ''};
$scope.activeSiteId = piwik.idSite;
$scope.switchSite = function (site) {
$scope.selectedSite.id = site.idsite;
if (site.name === $scope.allSitesText) {
$scope.selectedSite.name = $scope.allSitesText;
} else {
$scope.selectedSite.name = site.name.replace(/[\u0000-\u2666]/g, function(c) {
return '&#'+c.charCodeAt(0)+';';
});
}
if (!$scope.switchSiteOnSelect || $scope.activeSiteId == site.idsite) {
return;
}
if (site.idsite == 'all') {
piwik.broadcast.propagateNewPage('module=MultiSites&action=index');
} else {
piwik.broadcast.propagateNewPage('segment=&idSite=' + site.idsite, false);
}
};
$scope.getUrlAllSites = function () {
var newParameters = 'module=MultiSites&action=index';
return piwik.helper.getCurrentQueryStringWithParametersModified(newParameters);
};
$scope.getUrlForSiteId = function (idSite) {
var idSiteParam = 'idSite=' + idSite;
var newParameters = 'segment=&' + idSiteParam;
var hash = piwik.broadcast.isHashExists() ? piwik.broadcast.getHashFromUrl() : "";
return piwik.helper.getCurrentQueryStringWithParametersModified(newParameters) +
'#' + piwik.helper.getQueryStringWithParametersModified(hash.substring(1), newParameters);
};
});

View file

@ -0,0 +1,80 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Usage:
* <div piwik-siteselector>
*
* More advanced example
* <div piwik-siteselector
* show-selected-site="true" show-all-sites-item="true" switch-site-on-select="true"
* all-sites-location="top|bottom" all-sites-text="test" show-selected-site="true"
* show-all-sites-item="true">
*
* Within a form
* <div piwik-siteselector input-name="siteId">
*
* Events:
* Triggers a `change` event on any change
* <div piwik-siteselector id="mySelector">
* $('#mySelector').on('change', function (event) { event.id/event.name })
*/
angular.module('piwikApp').directive('piwikSiteselector', function($document, piwik, $filter){
var defaults = {
name: '',
siteid: piwik.idSite,
sitename: piwik.siteName,
allSitesLocation: 'bottom',
allSitesText: $filter('translate')('General_MultiSitesSummary'),
showSelectedSite: 'false',
showAllSitesItem: 'true',
switchSiteOnSelect: 'true'
};
return {
restrict: 'A',
scope: {
showSelectedSite: '=',
showAllSitesItem: '=',
switchSiteOnSelect: '=',
inputName: '@name',
allSitesText: '@',
allSitesLocation: '@'
},
templateUrl: 'plugins/CoreHome/angularjs/siteselector/siteselector.html?cb=' + piwik.cacheBuster,
controller: 'SiteSelectorController',
compile: function (element, attrs) {
for (var index in defaults) {
if (!attrs[index]) { attrs[index] = defaults[index]; }
}
return function (scope, element, attrs) {
// selectedSite.id|.name + model is hard-coded but actually the directive should not know about this
scope.selectedSite.id = attrs.siteid;
scope.selectedSite.name = attrs.sitename;
if (!attrs.siteid || !attrs.sitename) {
scope.model.loadInitialSites();
}
scope.$watch('selectedSite.id', function (newValue, oldValue, scope) {
if (newValue != oldValue) {
element.attr('siteid', newValue);
element.trigger('change', scope.selectedSite);
}
});
/** use observe to monitor attribute changes
attrs.$observe('maxsitenamewidth', function(val) {
// for instance trigger a function or whatever
}) */
};
}
};
});

View file

@ -0,0 +1,75 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
angular.module('piwikApp').factory('siteSelectorModel', function (piwikApi, $filter) {
var model = {};
model.sites = [];
model.hasMultipleWebsites = false;
model.isLoading = false;
model.firstSiteName = '';
var initialSites = null;
model.updateWebsitesList = function (sites) {
if (!sites || !sites.length) {
model.sites = [];
return [];
}
angular.forEach(sites, function (site) {
if (site.group) site.name = '[' + site.group + '] ' + site.name;
});
model.sites = $filter('orderBy')(sites, '+name');
if (!model.firstSiteName) {
model.firstSiteName = model.sites[0].name;
}
model.hasMultipleWebsites = model.hasMultipleWebsites || sites.length > 1;
return model.sites;
};
model.searchSite = function (term) {
if (!term) {
model.loadInitialSites();
return;
}
if (model.isLoading) {
piwikApi.abort();
}
model.isLoading = true;
return piwikApi.fetch({
method: 'SitesManager.getPatternMatchSites',
pattern: term
}).then(function (response) {
return model.updateWebsitesList(response);
})['finally'](function () { // .finally() is not IE8 compatible see https://github.com/angular/angular.js/commit/f078762d48d0d5d9796dcdf2cb0241198677582c
model.isLoading = false;
});
};
model.loadInitialSites = function () {
if (initialSites) {
model.sites = initialSites;
return;
}
this.searchSite('%').then(function (websites) {
initialSites = websites;
});
};
return model;
});

View file

@ -0,0 +1,61 @@
<div piwik-focus-anywhere-but-here="view.showSitesList=false" class="custom_select"
ng-class="{'sites_autocomplete--dropdown': (model.hasMultipleWebsites || showAllSitesItem || !model.sites.length)}">
<script type="text/ng-template" id="siteselector_allsiteslink.html">
<div ng-click="switchSite({idsite: 'all', name: allSitesText});view.showSitesList=false;"
class="custom_select_all">
<a href="{{ getUrlAllSites() }}"
piwik-ignore-click
ng-bind-html="allSitesText"></a>
</div>
</script>
<input ng-if="inputName" type="hidden" name="{{ inputName }}" ng-value="selectedSite.id"/>
<a ng-click="view.showSitesList=!view.showSitesList; view.showSitesList && model.loadInitialSites()"
href="javascript:void(0)"
class="custom_select_main_link"
ng-class="{'loading': model.isLoading}">
<span ng-bind-html="selectedSite.name || model.firstSiteName">?</span>
</a>
<div ng-show="view.showSitesList" class="custom_select_block">
<div ng-if="allSitesLocation=='top' && showAllSitesItem"
ng-include="'siteselector_allsiteslink.html'"></div>
<div class="custom_select_container">
<ul class="custom_select_ul_list" ng-click="view.showSitesList=false;">
<li ng-click="switchSite(site)"
ng-repeat="site in model.sites"
ng-hide="!showSelectedSite && activeSiteId==site.idsite">
<a piwik-ignore-click href="{{ getUrlForSiteId(site.idsite) }}"
piwik-autocomplete-matched="view.searchTerm">{{ site.name }}</a>
</li>
</ul>
<ul ng-show="!model.sites.length && view.searchTerm" class="ui-autocomplete ui-front ui-menu ui-widget ui-widget-content ui-corner-all siteSelect">
<li class="ui-menu-item">
<a class="ui-corner-all" tabindex="-1">{{ ('SitesManager_NotFound' | translate) + ' ' + view.searchTerm }}</a>
</li>
</ul>
</div>
<div ng-if="allSitesLocation=='bottom' && showAllSitesItem"
ng-include="'siteselector_allsiteslink.html'"></div>
<div class="custom_select_search" ng-show="autocompleteMinSites <= model.sites.length || view.searchTerm">
<input type="text"
ng-click="view.searchTerm=''"
ng-model="view.searchTerm"
ng-change="model.searchSite(view.searchTerm)"
class="websiteSearch inp"/>
<input type="submit"
ng-click="model.searchSite(view.searchTerm)"
value="{{ 'General_Search' | translate }}" class="but"/>
<img title="Clear"
ng-show="view.searchTerm"
ng-click="view.searchTerm=''; model.loadInitialSites()"
class="reset"
src="plugins/CoreHome/images/reset_search.png"/>
</div>
</div>
</div>

View file

@ -0,0 +1,177 @@
/*sites_autocomplete*/
.sites_autocomplete {
position: absolute;
font-size: 12px;
display: inline-block;
height: 30px; /* Hack to not push the dashboard widget below */
}
.sites_selector_in_dashboard {
margin-top:10px;
}
.top_bar_sites_selector {
float: right
}
.top_bar_sites_selector > label {
display: inline-block;
padding: 7px 0 6px 0;
float: left;
font-size: 12px;
}
.top_bar_sites_selector > .sites_autocomplete {
position: static;
padding-left: 12px;
}
.autocompleteMatched {
color: #5256BE;
font-weight: bold;
}
.sites_autocomplete .custom_select {
float: left;
position: relative;
z-index: 19;
background: #fff url(plugins/Zeitgeist/images/sites_selection.png) repeat-x 0 0;
border: 1px solid #d4d4d4;
color: #255792;
border-radius: 4px;
cursor: pointer;
min-width: 165px;
padding: 5px 6px 4px;
}
.sites_autocomplete .custom_select_main_link {
display: block;
text-decoration: none;
background: none;
cursor: default;
height:1.4em;
}
.sites_autocomplete .custom_select_ul_list li a,
.sites_autocomplete .custom_select_all a,
.sites_autocomplete .custom_select_main_link > span {
display: inline-block;
max-width: 140px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 20px 0 4px;
}
.sites_autocomplete--dropdown .custom_select_main_link:not(.loading):before {
content: " \25BC";
position: absolute;
right: 0;
font-size: 0.8em;
margin-top: 0.2em;
color: #444;
}
.sites_autocomplete--dropdown .custom_select_main_link {
cursor: pointer;
position: relative;
}
.sites_autocomplete .custom_select_main_link.loading {
background: url(plugins/Zeitgeist/images/loading-blue.gif) no-repeat right 3px;
}
.sites_autocomplete .custom_select_ul_list,
.sites_autocomplete ul.ui-autocomplete {
position: relative;
list-style: none;
line-height: 18px;
padding: 0 0 15px 0;
}
.sites_autocomplete .custom_select_ul_list li a,
.sites_autocomplete .custom_select_all a {
line-height: 18px;
height: auto;
display: block;
text-decoration: none;
}
.sites_autocomplete .custom_select_ul_list li a:hover,
.sites_autocomplete .custom_select_all a:hover {
background: #ebeae6;
}
.sites_autocomplete .custom_select_all a {
text-decoration: none;
margin: 0 0 5px 0;
}
.sites_autocomplete .custom_select_search {
margin: 0 0 0 4px;
height: 26px;
display: block;
white-space: nowrap;
background: url(plugins/Zeitgeist/images/search_bg.png) no-repeat 0 0;
}
.sites_autocomplete .custom_select_search .inp {
vertical-align: top;
width: 114px;
padding: 2px 6px;
border: 0;
background: transparent;
font-size: 10px;
color: #454545;
}
.sites_autocomplete {
width: 165px;
}
.sites_autocomplete .custom_select_search .but {
vertical-align: top;
font-size: 10px;
border: 0;
background: transparent;
width: 21px;
height: 17px;
overflow: hidden;
opacity: 0;
cursor: pointer;
}
.sites_selector_container>.sites_autocomplete {
padding-left: 12px;
}
.custom_selector_container .ui-menu-item,
.custom_selector_container .ui-menu-item a {
float:none;position:static
}
.custom_select_search .reset {
position: relative; top: 4px; left: -44px; cursor: pointer;
}
.custom_select_block {
overflow: hidden;
max-width: inherit;
visibility: visible;
}
.custom_select_block_show {
height: auto;
overflow: visible;
max-width:inherit;
}
.sites_selector_container {
padding-top: 5px;
}
.siteSelect a {
white-space: normal;
text-align: left;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,021 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

View file

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

View file

@ -0,0 +1,545 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function ($) {
Date.prototype.getWeek = function () {
var onejan = new Date(this.getFullYear(), 0, 1), // needed for getDay()
// use UTC times since getTime() can differ based on user's timezone
onejan_utc = Date.UTC(this.getFullYear(), 0, 1),
this_utc = Date.UTC(this.getFullYear(), this.getMonth(), this.getDate()),
daysSinceYearStart = (this_utc - onejan_utc) / 86400000; // constant is millisecs in one day
return Math.ceil((daysSinceYearStart + onejan.getDay()) / 7);
};
var currentYear, currentMonth, currentDay, currentDate, currentWeek;
function setCurrentDate(dateStr) {
var splitDate = dateStr.split("-");
currentYear = splitDate[0];
currentMonth = splitDate[1] - 1;
currentDay = splitDate[2];
currentDate = new Date(currentYear, currentMonth, currentDay);
currentWeek = currentDate.getWeek();
}
if(!piwik.currentDateString) {
// eg. Login form
return;
}
setCurrentDate(piwik.currentDateString);
var todayDate = new Date;
var todayMonth = todayDate.getMonth();
var todayYear = todayDate.getFullYear();
var todayDay = todayDate.getDate();
// min/max date for picker
var piwikMinDate = new Date(piwik.minDateYear, piwik.minDateMonth - 1, piwik.minDateDay),
piwikMaxDate = new Date(piwik.maxDateYear, piwik.maxDateMonth - 1, piwik.maxDateDay);
// we start w/ the current period
var selectedPeriod = piwik.period;
function isDateInCurrentPeriod(date) {
// if the selected period isn't the current period, don't highlight any dates
if (selectedPeriod != piwik.period) {
return [true, ''];
}
var valid = false;
var dateMonth = date.getMonth();
var dateYear = date.getFullYear();
var dateDay = date.getDate();
// 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() == currentWeek
&& 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, ''];
}
piwik.getBaseDatePickerOptions = function (defaultDate) {
return {
showOtherMonths: false,
dateFormat: 'yy-mm-dd',
firstDay: 1,
minDate: piwikMinDate,
maxDate: piwikMaxDate,
prevText: "",
nextText: "",
currentText: "",
defaultDate: defaultDate,
changeMonth: true,
changeYear: true,
stepMonths: 1,
// jquery-ui-i18n 1.7.2 lacks some translations, so we use our own
dayNamesMin: [
_pk_translate('General_DaySu'),
_pk_translate('General_DayMo'),
_pk_translate('General_DayTu'),
_pk_translate('General_DayWe'),
_pk_translate('General_DayTh'),
_pk_translate('General_DayFr'),
_pk_translate('General_DaySa')],
dayNamesShort: [
_pk_translate('General_ShortDay_7'), // start with sunday
_pk_translate('General_ShortDay_1'),
_pk_translate('General_ShortDay_2'),
_pk_translate('General_ShortDay_3'),
_pk_translate('General_ShortDay_4'),
_pk_translate('General_ShortDay_5'),
_pk_translate('General_ShortDay_6')],
dayNames: [
_pk_translate('General_LongDay_7'), // start with sunday
_pk_translate('General_LongDay_1'),
_pk_translate('General_LongDay_2'),
_pk_translate('General_LongDay_3'),
_pk_translate('General_LongDay_4'),
_pk_translate('General_LongDay_5'),
_pk_translate('General_LongDay_6')],
monthNamesShort: [
_pk_translate('General_ShortMonth_1'),
_pk_translate('General_ShortMonth_2'),
_pk_translate('General_ShortMonth_3'),
_pk_translate('General_ShortMonth_4'),
_pk_translate('General_ShortMonth_5'),
_pk_translate('General_ShortMonth_6'),
_pk_translate('General_ShortMonth_7'),
_pk_translate('General_ShortMonth_8'),
_pk_translate('General_ShortMonth_9'),
_pk_translate('General_ShortMonth_10'),
_pk_translate('General_ShortMonth_11'),
_pk_translate('General_ShortMonth_12')],
monthNames: [
_pk_translate('General_LongMonth_1'),
_pk_translate('General_LongMonth_2'),
_pk_translate('General_LongMonth_3'),
_pk_translate('General_LongMonth_4'),
_pk_translate('General_LongMonth_5'),
_pk_translate('General_LongMonth_6'),
_pk_translate('General_LongMonth_7'),
_pk_translate('General_LongMonth_8'),
_pk_translate('General_LongMonth_9'),
_pk_translate('General_LongMonth_10'),
_pk_translate('General_LongMonth_11'),
_pk_translate('General_LongMonth_12')]
};
};
var updateDate;
function getDatePickerOptions() {
var result = piwik.getBaseDatePickerOptions(currentDate);
result.beforeShowDay = isDateInCurrentPeriod;
result.stepMonths = selectedPeriod == 'year' ? 12 : 1;
result.onSelect = function () { updateDate.apply(this, arguments); };
return result;
}
$(function () {
var datepickerElem = $('#datepicker').datepicker(getDatePickerOptions()),
periodLabels = $('#periodString').find('.period-type label'),
periodTooltip = $('#periodString').find('.period-click-tooltip').html();
var toggleWhitespaceHighlighting = function (klass, toggleTop, toggleBottom) {
var viewedYear = $('.ui-datepicker-year', datepickerElem).val(),
viewedMonth = +$('.ui-datepicker-month', datepickerElem).val(), // convert to int w/ '+'
firstOfViewedMonth = new Date(viewedYear, viewedMonth, 1),
lastOfViewedMonth = new Date(viewedYear, viewedMonth + 1, 0);
// only highlight dates between piwik.minDate... & piwik.maxDate...
// we select the cells to highlight by checking whether the first & last of the
// currently viewed month are within the min/max dates.
if (firstOfViewedMonth >= piwikMinDate) {
$('tbody>tr:first-child td.ui-datepicker-other-month', datepickerElem).toggleClass(klass, toggleTop);
}
if (lastOfViewedMonth < piwikMaxDate) {
$('tbody>tr:last-child td.ui-datepicker-other-month', datepickerElem).toggleClass(klass, toggleBottom);
}
};
// 'this' is the table cell
var highlightCurrentPeriod = function () {
switch (selectedPeriod) {
case 'day':
// highlight this link
$('a', $(this)).addClass('ui-state-hover');
break;
case 'week':
var row = $(this).parent();
// highlight parent row (the week)
$('a', row).addClass('ui-state-hover');
// toggle whitespace if week goes into previous or next month. we check if week is on
// top or bottom row.
var toggleTop = row.is(':first-child'),
toggleBottom = row.is(':last-child');
toggleWhitespaceHighlighting('ui-state-hover', toggleTop, toggleBottom);
break;
case 'month':
// highlight all parent rows (the month)
$('a', $(this).parent().parent()).addClass('ui-state-hover');
break;
case 'year':
// highlight table (month + whitespace)
$('a', $(this).parent().parent()).addClass('ui-state-hover');
toggleWhitespaceHighlighting('ui-state-hover', true, true);
break;
}
};
var unhighlightAllDates = function () {
// make sure nothing is highlighted
$('.ui-state-active,.ui-state-hover', datepickerElem).removeClass('ui-state-active ui-state-hover');
// color whitespace
if (piwik.period == 'year') {
var viewedYear = $('.ui-datepicker-year', datepickerElem).val(),
toggle = selectedPeriod == 'year' && currentYear == viewedYear;
toggleWhitespaceHighlighting('ui-datepicker-current-period', toggle, toggle);
}
else if (piwik.period == 'week') {
var toggleTop = $('tr:first-child a', datepickerElem).parent().hasClass('ui-datepicker-current-period'),
toggleBottom = $('tr:last-child a', datepickerElem).parent().hasClass('ui-datepicker-current-period');
toggleWhitespaceHighlighting('ui-datepicker-current-period', toggleTop, toggleBottom);
}
};
updateDate = function (dateText) {
piwikHelper.showAjaxLoading('ajaxLoadingCalendar');
// select new dates in calendar
setCurrentDate(dateText);
piwik.period = selectedPeriod;
// make sure it's called after jquery-ui is done, otherwise everything we do will
// be undone.
setTimeout(unhighlightAllDates, 1);
datepickerElem.datepicker('refresh');
// Let broadcast do its job:
// It will replace date value to both search query and hash and load the new page.
broadcast.propagateNewPage('date=' + dateText + '&period=' + selectedPeriod);
};
var toggleMonthDropdown = function (disable) {
if (typeof disable === 'undefined') {
disable = selectedPeriod == 'year';
}
// enable/disable month dropdown based on period == year
$('.ui-datepicker-month', datepickerElem).attr('disabled', disable);
};
var togglePeriodPickers = function (showSingle) {
$('#periodString').find('.period-date').toggle(showSingle);
$('#periodString').find('.period-range').toggle(!showSingle);
$('#calendarRangeApply').toggle(!showSingle);
};
//
// setup datepicker
//
unhighlightAllDates();
//
// hook up event slots
//
// highlight current period when mouse enters date
datepickerElem.on('mouseenter', 'tbody td', function () {
if ($(this).hasClass('ui-state-hover')) // if already highlighted, do nothing
{
return;
}
// unhighlight if cell is disabled/blank, unless the period is year
if ($(this).hasClass('ui-state-disabled') && selectedPeriod != 'year') {
unhighlightAllDates();
// if period is week, then highlight the current week
if (selectedPeriod == 'week') {
highlightCurrentPeriod.call(this);
}
}
else {
highlightCurrentPeriod.call(this);
}
});
// make sure cell stays highlighted when mouse leaves cell (overrides jquery-ui behavior)
datepickerElem.on('mouseleave', 'tbody td', function () {
$('a', this).addClass('ui-state-hover');
});
// unhighlight everything when mouse leaves table body (can't do event on tbody, for some reason
// that fails, so we do two events, one on the table & one on thead)
datepickerElem.on('mouseleave', 'table', unhighlightAllDates)
.on('mouseenter', 'thead', unhighlightAllDates);
// make sure whitespace is clickable when the period makes it appropriate
datepickerElem.on('click', 'tbody td.ui-datepicker-other-month', function () {
if ($(this).hasClass('ui-state-hover')) {
var row = $(this).parent(), tbody = row.parent();
if (row.is(':first-child')) {
// click on first of the month
$('a', tbody).first().click();
}
else {
// click on last of month
$('a', tbody).last().click();
}
}
});
// Hack to get around firefox bug. When double clicking a label in firefox, the 'click'
// event of its associated input will not be fired twice. We want to change the period
// if clicking the select period's label OR input, so we catch the click event on the
// label & the input.
var reloading = false;
var changePeriodOnClick = function (periodInput) {
if (reloading) // if a click event resulted in reloading, don't reload again
{
return;
}
var url = periodInput.val(),
period = broadcast.getValueFromUrl('period', url);
// if clicking on the selected period, change the period but not the date
if (selectedPeriod == period && selectedPeriod != 'range') {
// only reload if current period is different from selected
if (piwik.period != selectedPeriod && !reloading) {
reloading = true;
selectedPeriod = period;
updateDate(piwik.currentDateString);
}
return true;
}
return false;
};
$("#otherPeriods").find("label").on('click', function (e) {
var id = $(e.target).attr('for');
changePeriodOnClick($('#' + id));
});
// when non-range period is clicked, change the period & refresh the date picker
$("#otherPeriods").find("input").on('click', function (e) {
var request_URL = $(e.target).val(),
period = broadcast.getValueFromUrl('period', request_URL),
lastPeriod = selectedPeriod;
if (changePeriodOnClick($(e.target))) {
return true;
}
// switch the selected period
selectedPeriod = period;
// remove tooltips from the period inputs
periodLabels.each(function () { $(this).attr('title', '').removeClass('selected-period-label'); });
// range periods are handled in an event handler below
if (period == 'range') {
return true;
}
// set the tooltip of the current period
if (period != piwik.period) // don't add tooltip for current period
{
$(this).parent().find('label[for=period_id_' + period + ']')
.attr('title', periodTooltip).addClass('selected-period-label');
}
// toggle the right selector controls (show period selector datepicker & hide 'apply range' button)
togglePeriodPickers(true);
// set months step to 12 for year period (or set back to 1 if leaving year period)
if (selectedPeriod == 'year' || lastPeriod == 'year') {
// setting stepMonths will change the month in view back to the selected date. to avoid
// we set the selected date to the month in view.
var currentMonth = $('.ui-datepicker-month', datepickerElem).val(),
currentYear = $('.ui-datepicker-year', datepickerElem).val();
datepickerElem
.datepicker('option', 'stepMonths', selectedPeriod == 'year' ? 12 : 1)
.datepicker('setDate', new Date(currentYear, currentMonth));
}
datepickerElem.datepicker('refresh'); // must be last datepicker call, otherwise cells get highlighted
unhighlightAllDates();
toggleMonthDropdown();
return true;
});
// clicking left/right re-enables the month dropdown, so we disable it again
$(datepickerElem).on('click', '.ui-datepicker-next,.ui-datepicker-prev', function () {
unhighlightAllDates(); // make sure today's date isn't highlighted & toggle extra year highlighting
toggleMonthDropdown(selectedPeriod == 'year');
});
// reset date/period when opening calendar
$("#periodString").on('click', "#date,.calendar-icon", function () {
var periodMore = $("#periodMore").toggle();
if (periodMore.is(":visible")) {
periodMore.find(".ui-state-highlight").removeClass('ui-state-highlight');
}
});
$('body').on('click', function(e) {
var target = $(e.target);
if (target.closest('html').length && !target.closest('#periodString').length && !target.is('option') && $("#periodMore").is(":visible")) {
$("#periodMore").hide();
}
});
function onDateRangeSelect(dateText, inst) {
var toOrFrom = inst.id == 'calendarFrom' ? 'From' : 'To';
$('#inputCalendar' + toOrFrom).val(dateText);
}
// this will trigger to change only the period value on search query and hash string.
$("#period_id_range").on('click', function (e) {
togglePeriodPickers(false);
var options = getDatePickerOptions();
// Custom Date range callback
options.onSelect = onDateRangeSelect;
// Do not highlight the period
options.beforeShowDay = '';
// Create both calendars
options.defaultDate = piwik.startDateString;
$('#calendarFrom').datepicker(options).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', piwik.startDateString));
// Technically we should trigger the onSelect event on the calendar, but I couldn't find how to do that
// So calling the onSelect bind function manually...
//$('#calendarFrom').trigger('dateSelected'); // or onSelect
onDateRangeSelect(piwik.startDateString, { "id": "calendarFrom" });
// Same code for the other calendar
options.defaultDate = piwik.endDateString;
$('#calendarTo').datepicker(options).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', piwik.endDateString));
onDateRangeSelect(piwik.endDateString, { "id": "calendarTo" });
// If not called, the first date appears light brown instead of dark brown
$('.ui-state-hover').removeClass('ui-state-hover');
// Apply date range button will reload the page with the selected range
$('#calendarRangeApply')
.on('click', function () {
var request_URL = $(e.target).val();
var dateFrom = $('#inputCalendarFrom').val(),
dateTo = $('#inputCalendarTo').val(),
oDateFrom = $.datepicker.parseDate('yy-mm-dd', dateFrom),
oDateTo = $.datepicker.parseDate('yy-mm-dd', dateTo);
if (!isValidDate(oDateFrom)
|| !isValidDate(oDateTo)
|| oDateFrom > oDateTo) {
$('#alert').find('h2').text(_pk_translate('General_InvalidDateRange'));
piwikHelper.modalConfirm('#alert', {});
return false;
}
piwikHelper.showAjaxLoading('ajaxLoadingCalendar');
broadcast.propagateNewPage('period=range&date=' + dateFrom + ',' + dateTo);
})
.show();
// Bind the input fields to update the calendar's date when date is manually changed
$('#inputCalendarFrom, #inputCalendarTo')
.keyup(function (e) {
var fromOrTo = this.id == 'inputCalendarFrom' ? 'From' : 'To';
var dateInput = $(this).val();
try {
var newDate = $.datepicker.parseDate('yy-mm-dd', dateInput);
} catch (e) {
return;
}
$("#calendar" + fromOrTo).datepicker("setDate", newDate);
if (e.keyCode == 13) {
$('#calendarRangeApply').click();
}
});
return true;
});
function isValidDate(d) {
if (Object.prototype.toString.call(d) !== "[object Date]")
return false;
return !isNaN(d.getTime());
}
if (piwik.period == 'range') {
$("#period_id_range").click();
}
});
}(jQuery));

View file

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

View file

@ -0,0 +1,160 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function ($) {
$(function () {
//
// 'check for updates' behavior
//
var headerMessageParent = $('#header_message').parent();
// when 'check for updates...' link is clicked, force a check & display the result
headerMessageParent.on('click', '#updateCheckLinkContainer', function (e) {
e.preventDefault();
var headerMessage = $(this).closest('#header_message');
var ajaxRequest = new ajaxHelper();
ajaxRequest.setLoadingElement('#header_message .loadingPiwik');
ajaxRequest.addParams({
module: 'CoreHome',
action: 'checkForUpdates'
}, 'get');
ajaxRequest.setCallback(function (response) {
headerMessage.fadeOut('slow', function () {
response = $(response);
var newVersionAvailable = response.hasClass('header_alert');
if (newVersionAvailable) {
headerMessage.replaceWith(response);
}
else {
headerMessage.html(_pk_translate('CoreHome_YouAreUsingTheLatestVersion')).show();
setTimeout(function () {
headerMessage.fadeOut('slow', function () {
headerMessage.replaceWith(response);
});
}, 4000);
}
});
});
ajaxRequest.setFormat('html');
ajaxRequest.send(false);
return false;
});
// when clicking the header message, show the long message w/o needing to hover
headerMessageParent.on('click', '#header_message', function (e) {
if (e.target.tagName.toLowerCase() != 'a') {
$(this).toggleClass('active');
}
});
//
// section toggler behavior
//
var handleSectionToggle = function (self, showType, doHide) {
var sectionId = $(self).attr('data-section-id'),
section = $('#' + sectionId),
showText = _pk_translate('General_Show'),
hideText = _pk_translate('General_Hide');
if (typeof(doHide) == 'undefined') {
doHide = section.is(':visible');
}
if (doHide) {
var newText = $(self).text().replace(hideText, showText),
afterHide = function () { $(self).text(newText); };
if (showType == 'slide') {
section.slideUp(afterHide);
}
else if (showType == 'inline') {
section.hide();
afterHide();
}
else {
section.hide(afterHide);
}
}
else {
var newText = $(self).text().replace(showText, hideText);
$(self).text(newText);
if (showType == 'slide') {
section.slideDown();
}
else if (showType == 'inline') {
section.css('display', 'inline-block');
}
else {
section.show();
}
}
};
// when click section toggler link, toggle the visibility of the associated section
$('body').on('click', 'a.section-toggler-link', function (e) {
e.preventDefault();
handleSectionToggle(this, 'slide');
return false;
});
$('body').on('change', 'input.section-toggler-link', function (e) {
handleSectionToggle(this, 'inline', !$(this).is(':checked'));
});
//
// reports by dimension list behavior
//
// when a report dimension is clicked, load the appropriate report
var currentWidgetLoading = null;
$('body').on('click', '.reportDimension', function (e) {
var view = $(this).closest('.reportsByDimensionView'),
report = $('.dimensionReport', view),
loading = $('.loadingPiwik', view);
// make this dimension the active one
$('.activeDimension', view).removeClass('activeDimension');
$(this).addClass('activeDimension');
// hide the visible report & show the loading elem
report.hide();
loading.show();
// load the report using the data-url attribute (which holds the URL to the report)
var widgetParams = broadcast.getValuesFromUrl($(this).attr('data-url'));
for (var key in widgetParams) {
widgetParams[key] = decodeURIComponent(widgetParams[key]);
}
var widgetUniqueId = widgetParams.module + widgetParams.action;
currentWidgetLoading = widgetUniqueId;
widgetsHelper.loadWidgetAjax(widgetUniqueId, widgetParams, function (response) {
// if the widget that was loaded was not for the latest clicked link, do nothing w/ the response
if (widgetUniqueId != currentWidgetLoading) {
return;
}
loading.hide();
report.html($(response)).css('display', 'inline-block');
// scroll to report
piwikHelper.lazyScrollTo(report, 400);
});
});
});
}(jQuery));

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -0,0 +1,110 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* @constructor
*/
function menu() {
this.param = {};
}
menu.prototype =
{
resetTimer: null,
adaptSubMenuHeight: function() {
var subNavHeight = $('.sfHover > ul').outerHeight();
$('.nav_sep').height(subNavHeight);
},
overMainLI: function () {
var $this = $(this);
$this.siblings().removeClass('sfHover');
$this.addClass('sfHover');
menu.prototype.adaptSubMenuHeight();
clearTimeout(menu.prototype.resetTimer);
},
outMainLI: function () {
clearTimeout(menu.prototype.resetTimer);
menu.prototype.resetTimer = setTimeout(function() {
$('.Menu-tabList > .sfHover', this.menuNode).removeClass('sfHover');
$('.Menu-tabList > .sfActive', this.menuNode).addClass('sfHover');
menu.prototype.adaptSubMenuHeight();
}, 2000);
},
onItemClick: function (item) {
$('.Menu--dashboard').trigger('piwikSwitchPage', item);
broadcast.propagateAjax( $(item).attr('href').substr(1) );
return false;
},
init: function () {
this.menuNode = $('.Menu--dashboard');
this.menuNode.find("li:has(ul)").hover(this.overMainLI, this.outMainLI);
// add id to all li menu to support 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.menuNode.find('li').each(function () {
var url = $(this).find('a').attr('href').substr(1);
var module = broadcast.getValueFromUrl("module", url);
var action = broadcast.getValueFromUrl("action", url);
var moduleId = broadcast.getValueFromUrl("idGoal", url) || broadcast.getValueFromUrl("idDashboard", url);
var main_menu = $(this).parent().hasClass('Menu-tabList') ? true : false;
if (main_menu) {
$(this).attr({id: module});
}
// if there's a idGoal or idDashboard, use this in the ID
else if (moduleId != '') {
$(this).attr({id: module + '_' + action + '_' + moduleId});
}
else {
$(this).attr({id: module + '_' + action});
}
});
menu.prototype.adaptSubMenuHeight();
},
activateMenu: function (module, action, id) {
this.menuNode.find('li').removeClass('sfHover').removeClass('sfActive');
var $li = this.getSubmenuID(module, id, action);
var mainLi = $("#" + module);
if (!mainLi.length) {
mainLi = $li.parents('li');
}
mainLi.addClass('sfActive').addClass('sfHover');
$li.addClass('sfHover');
},
// getting the right li is a little tricky since goals uses idGoal, and overview is index.
getSubmenuID: function (module, id, action) {
var $li = '';
// So, if module is Goals, id is present, and action is not Index, must be one of the goals
if (module == 'Goals' && id != '' && (action != 'index')) {
$li = $("#" + module + "_" + action + "_" + id);
// if module is Dashboard and id is present, must be one of the dashboards
} else if (module == 'Dashboard') {
if (!id) id = 1;
$li = $("#" + module + "_" + action + "_" + id);
} else {
$li = $("#" + module + "_" + action);
}
return $li;
},
loadFirstSection: function () {
if (broadcast.isHashExists() == false) {
$('li:first a:first', this.menuNode).click().addClass('sfHover').addClass('sfActive');
}
}
};

View file

@ -0,0 +1,19 @@
$(function () {
var isPageHasMenu = $('.Menu--dashboard').size();
var isPageIsAdmin = $('#content.admin').size();
if (isPageHasMenu) {
piwikMenu = new menu();
piwikMenu.init();
piwikMenu.loadFirstSection();
}
if(isPageIsAdmin) {
// don't use broadcast in admin pages
return;
}
if(isPageHasMenu) {
broadcast.init();
} else {
broadcast.init(true);
}
});

View file

@ -0,0 +1,198 @@
/**
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function ($, require) {
var exports = require('piwik/UI');
/**
* Creates a new notifications.
*
* Example:
* var UI = require('piwik/UI');
* var notification = new UI.Notification();
* notification.show('My Notification Message', {title: 'Low space', context: 'warning'});
*/
var Notification = function () {
this.$node = null;
};
/**
* Makes the notification visible.
*
* @param {string} message The actual message that will be displayed. Must be set.
* @param {Object} [options]
* @param {string} [options.id] Only needed for persistent notifications. The id will be sent to the
* frontend once the user closes the notifications. The notification has to
* be registered/notified under this name
* @param {string} [options.title] The title of the notification. For instance the plugin name.
* @param {bool} [options.animate=true] If enabled, the notification will be faded in.
* @param {string} [options.context=warning] Context of the notification: 'info', 'warning', 'success' or
* 'error'
* @param {string} [options.type=transient] The type of the notification: Either 'toast' or 'transitent'
* @param {bool} [options.noclear=false] If set, the close icon is not displayed.
* @param {object} [options.style] Optional style/css dictionary. For instance {'display': 'inline-block'}
* @param {string} [options.placeat] By default, the notification will be displayed in the "stats bar".
* You can specify any other CSS selector to place the notifications
* whereever you want.
*/
Notification.prototype.show = function (message, options) {
if (!message) {
throw new Error('No message given, cannot display notification');
}
if (options && !$.isPlainObject(options)) {
throw new Error('Options has the wrong format, cannot display notification');
} else if (!options) {
options = {};
}
if ('persistent' == options.type) {
// otherwise it is never possible to dismiss the notification
options.noclear = false;
}
closeExistingNotificationHavingSameIdIfNeeded(options);
var template = generateNotificationHtmlMarkup(options, message);
var $notificationNode = placeNotification(template, options);
this.$node = $notificationNode;
if ('persistent' == options.type) {
addPersistentEvent($notificationNode);
} else if ('toast' == options.type) {
addToastEvent($notificationNode);
}
if (!options.noclear) {
addCloseEvent($notificationNode);
}
};
Notification.prototype.scrollToNotification = function () {
if (this.$node) {
piwikHelper.lazyScrollTo(this.$node, 250);
}
};
exports.Notification = Notification;
function closeExistingNotificationHavingSameIdIfNeeded(options)
{
if (!options.id) {
return;
}
var $existingNode = $('.system.notification[data-id=' + options.id + ']');
if ($existingNode && $existingNode.length) {
$existingNode.remove();
}
}
function generateNotificationHtmlMarkup(options, message) {
var template = buildNotificationStart(options);
if (!options.noclear) {
template += buildClearButton();
}
if (options.title) {
template += buildTitle(options);
}
template += message;
template += buildNotificationEnd();
return template;
}
function buildNotificationStart(options) {
var template = '<div class="notification system';
if (options.context) {
template += ' notification-' + options.context;
}
template += '"';
if (options.id) {
template += ' data-id="' + options.id + '"';
}
template += '>';
return template;
}
function buildNotificationEnd() {
return '</div>';
}
function buildClearButton() {
return '<button type="button" class="close" data-dismiss="alert">&times;</button>';
}
function buildTitle(options) {
return '<strong>' + options.title + '</strong> ';
}
function placeNotification(template, options) {
var $notificationNode = $(template);
if (options.style) {
$notificationNode.css(options.style);
}
$notificationNode = $notificationNode.hide();
$(options.placeat || '#notificationContainer').append($notificationNode);
if (false === options.animate) {
$notificationNode.show();
} else {
$notificationNode.fadeIn(1000);
}
return $notificationNode;
}
function addToastEvent($notificationNode)
{
setTimeout(function () {
$notificationNode.fadeOut( 'slow', function() {
$notificationNode.remove();
$notificationNode = null;
});
}, 12 * 1000);
}
function addCloseEvent($notificationNode) {
$notificationNode.on('click', '.close', function (event) {
if (event && event.delegateTarget) {
$(event.delegateTarget).remove();
}
});
};
function addPersistentEvent($notificationNode) {
var notificationId = $notificationNode.data('id');
if (!notificationId) {
return;
}
$notificationNode.on('click', '.close', function (event) {
var ajaxHandler = new ajaxHelper();
ajaxHandler.addParams({
module: 'CoreHome',
action: 'markNotificationAsRead'
}, 'GET');
ajaxHandler.addParams({notificationId: notificationId}, 'POST');
ajaxHandler.send(true);
});
};
})(jQuery, require);

View file

@ -0,0 +1,31 @@
/**
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
$(document).ready((function ($, require) {
return function () {
var UI = require('piwik/UI');
var $notificationNodes = $('[data-role="notification"]');
$notificationNodes.each(function (index, notificationNode) {
$notificationNode = $(notificationNode);
var attributes = $notificationNode.data();
var message = $notificationNode.html();
if (message) {
var notification = new UI.Notification();
attributes.animate = false;
notification.show(message, attributes);
}
$notificationNodes.remove();
});
}
})(jQuery, require));

View file

@ -0,0 +1,254 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
var Piwik_Popover = (function () {
var container = false;
var isOpen = false;
var closeCallback = false;
var createContainer = function () {
if (container === false) {
container = $(document.createElement('div')).attr('id', 'Piwik_Popover');
}
};
var openPopover = function (title, dialogClass) {
createContainer();
var options =
{
title: title,
modal: true,
width: '950px',
position: ['center', 'center'],
resizable: false,
autoOpen: true,
open: function (event, ui) {
if (dialogClass) {
$(this).parent().addClass(dialogClass).attr('style', '');
}
$('.ui-widget-overlay').on('click.popover', function () {
container.dialog('close');
});
},
close: function (event, ui) {
container.find('div.jqplot-target').trigger('piwikDestroyPlot');
container[0].innerHTML = ''; // IE8 fix
container.dialog('destroy').remove();
globalAjaxQueue.abort();
$('.ui-widget-overlay').off('click.popover');
isOpen = false;
broadcast.propagateNewPopoverParameter(false);
require('piwik/UI').UIControl.cleanupUnusedControls();
if (typeof closeCallback == 'function') {
closeCallback();
closeCallback = false;
}
}
};
container.dialog(options);
// override the undocumented _title function to ensure that the title attribute is not escaped (according to jQueryUI bug #6016)
container.data( "uiDialog" )._title = function(title) {
title.html( this.options.title );
};
isOpen = true;
};
var centerPopover = function () {
if (container !== false) {
container.dialog({position: ['center', 'center']});
}
};
return {
/**
* Open the popover with a loading message
*
* @param {string} popoverName name of the popover
* @param {string} [popoverSubject] subject of the popover (e.g. url, optional)
* @param {int} [height] height of the popover in px (optional)
* @param {string} [dialogClass] css class to add to dialog
*/
showLoading: function (popoverName, popoverSubject, height, dialogClass) {
var loading = $(document.createElement('div')).addClass('Piwik_Popover_Loading');
var loadingMessage = popoverSubject ? translations.General_LoadingPopoverFor :
translations.General_LoadingPopover;
loadingMessage = loadingMessage.replace(/%s/, popoverName);
var p1 = $(document.createElement('p')).addClass('Piwik_Popover_Loading_Name');
loading.append(p1.text(loadingMessage));
var p2;
if (popoverSubject) {
popoverSubject = piwikHelper.addBreakpointsToUrl(popoverSubject);
p1.addClass('Piwik_Popover_Loading_NameWithSubject');
p2 = $(document.createElement('p')).addClass('Piwik_Popover_Loading_Subject');
loading.append(p2.html(popoverSubject));
}
if (height) {
loading.height(height);
}
if (!isOpen) {
openPopover(null, dialogClass);
}
this.setContent(loading);
this.setTitle('');
if (height) {
var offset = loading.height() - p1.outerHeight();
if (popoverSubject) {
offset -= p2.outerHeight();
}
var spacingEl = $(document.createElement('div'));
spacingEl.height(Math.round(offset / 2));
loading.prepend(spacingEl);
}
return container;
},
/**
* Add a help button to the current popover
*
* @param {string} helpUrl
*/
addHelpButton: function (helpUrl) {
if (!isOpen) {
return;
}
var titlebar = container.parent().find('.ui-dialog-titlebar');
var button = $(document.createElement('a')).addClass('ui-dialog-titlebar-help');
button.attr({href: helpUrl, target: '_blank'});
titlebar.append(button);
},
/** Set the title of the popover */
setTitle: function (titleHtml) {
container.dialog('option', 'title', titleHtml);
},
/** Set inner HTML of the popover */
setContent: function (html) {
if (typeof closeCallback == 'function') {
closeCallback();
closeCallback = false;
}
container[0].innerHTML = ''; // IE8 fix
container.html(html);
centerPopover();
},
/**
* Show an error message. All params are HTML!
*
* @param {string} title
* @param {string} [message]
* @param {string} [backLabel]
*/
showError: function (title, message, backLabel) {
var error = $(document.createElement('div')).addClass('Piwik_Popover_Error');
var p = $(document.createElement('p')).addClass('Piwik_Popover_Error_Title');
error.append(p.html(title));
if (message) {
p = $(document.createElement('p')).addClass('Piwik_Popover_Error_Message');
error.append(p.html(message));
}
if (backLabel) {
var back = $(document.createElement('a')).addClass('Piwik_Popover_Error_Back');
back.attr('href', '#').click(function () {
history.back();
return false;
});
error.append(back.html(backLabel));
}
if (!isOpen) {
openPopover();
}
this.setContent(error);
},
/**
* Add a callback for the next time the popover is closed or the content changes
*
* @param {function} callback
*/
onClose: function (callback) {
closeCallback = callback;
},
/** Close the popover */
close: function () {
if (isOpen) {
container.dialog('close');
}
},
/**
* Create a Popover and load the specified URL in it.
*
* Note: If you want the popover to be persisted in the URL (so if the URL is copy/pasted
* to a new window/tab it will be opened there), use broadcast.propagateNewPopoverParameter
* with a popover handler function that calls this one.
*
* @param {string} url
* @param {string} loadingName
* @param {string} [dialogClass] css class to add to dialog
*/
createPopupAndLoadUrl: function (url, loadingName, dialogClass) {
// make sure the minimum top position of the popover is 15px
var ensureMinimumTop = function () {
var popoverContainer = $('#Piwik_Popover').parent();
if (popoverContainer.position().top < 106) {
popoverContainer.css('top', '15px');
}
};
// open the popover
var box = Piwik_Popover.showLoading(loadingName, null, null, dialogClass);
ensureMinimumTop();
var callback = function (html) {
function setPopoverTitleIfOneFoundInContainer() {
var title = $('h1,h2', container);
if (title.length == 1) {
Piwik_Popover.setTitle(title.text());
$(title).hide();
}
}
Piwik_Popover.setContent(html);
setPopoverTitleIfOneFoundInContainer();
ensureMinimumTop();
};
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams(piwikHelper.getArrayFromQueryString(url), 'get');
ajaxRequest.setCallback(callback);
ajaxRequest.setFormat('html');
ajaxRequest.send(false);
}
};
})();

View file

@ -0,0 +1,12 @@
$(function () {
$('#piwik-promo-thumbnail').click(function () {
var promoEmbed = $('#piwik-promo-embed'),
widgetWidth = $(this).closest('.widgetContent').width(),
height = (266 * widgetWidth) / 421,
embedHtml = '<iframe width="100%" height="' + height + '" src="http://www.youtube.com/embed/OslfF_EH81g?autoplay=1&vq=hd720&wmode=transparent" frameborder="0" wmode="Opaque"></iframe>';
$(this).hide();
promoEmbed.height(height).html(embedHtml);
promoEmbed.show();
});
});

View file

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

View file

@ -0,0 +1,83 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function ($) {
var sparklineColorNames = ['backgroundColor', 'lineColor', 'minPointColor', 'maxPointColor', 'lastPointColor'];
piwik.getSparklineColors = function () {
return piwik.ColorManager.getColors('sparkline-colors', sparklineColorNames);
};
// initializes each sparkline so they use colors defined in CSS
piwik.initSparklines = function() {
$('.sparkline > img').each(function () {
var $self = $(this);
if ($self.attr('src')) {
return;
}
var colors = JSON.stringify(piwik.getSparklineColors());
var appendToSparklineUrl = '&colors=' + encodeURIComponent(colors);
// Append the token_auth to the URL if it was set (eg. embed dashboard)
var token_auth = broadcast.getValueFromUrl('token_auth');
if (token_auth.length) {
appendToSparklineUrl += '&token_auth=' + token_auth;
}
$self.attr('src', $self.attr('data-src') + appendToSparklineUrl);
});
};
window.initializeSparklines = function () {
var sparklineUrlParamsToIgnore = ['module', 'action', 'idSite', 'period', 'date', 'viewDataTable'];
$("[data-graph-id]").each(function () {
var graph = $(this);
// try to find sparklines and add them clickable behaviour
graph.parent().find('div.sparkline').each(function () {
// find the sparkline and get it's src attribute
var sparklineUrl = $('img', this).attr('data-src');
if (sparklineUrl != "") {
var params = broadcast.getValuesFromUrl(sparklineUrl);
for (var i = 0; i != sparklineUrlParamsToIgnore.length; ++i) {
delete params[sparklineUrlParamsToIgnore[i]];
}
for (var key in params) {
if (typeof params[key] == 'undefined') {
// this happens for example with an empty segment parameter
delete params[key];
} else {
params[key] = decodeURIComponent(params[key]);
}
}
// on click, reload the graph with the new url
$(this).click(function () {
var reportId = graph.attr('data-graph-id'),
dataTable = $(require('piwik/UI').DataTable.getDataTableByReport(reportId));
// when the metrics picker is used, the id of the data table might be updated (which is correct behavior).
// for example, in goal reports it might change from GoalsgetEvolutionGraph to GoalsgetEvolutionGraph1.
// if this happens, we can't find the graph using $('#'+idDataTable+"Chart");
// instead, we just use the first evolution graph we can find.
if (dataTable.length == 0) {
dataTable = $('div.dataTableVizEvolution');
}
// reload the datatable w/ a new column & scroll to the graph
dataTable.trigger('reload', params);
});
}
});
});
};
}(jQuery));

View file

@ -0,0 +1,27 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
function initTopControls() {
var $topControlsContainer = $('.top_controls'),
left = 0;
if ($topControlsContainer.length) {
$('.piwikTopControl').each(function () {
var $control = $(this);
if ($control.css('display') == 'none') {
return;
}
$control.css('left', left);
if (!$.contains($topControlsContainer[0], this)) {
$control.detach().appendTo($topControlsContainer);
}
left += $control.outerWidth(true);
});
}
}

View file

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

View file

@ -0,0 +1,125 @@
.piwik-donate-call {
padding: 1em;
border: 1px solid #CCC;
border-radius: 4px;
max-width: 432px;
position: relative;
}
#piwik-worth {
font-size: 1.2em;
font-weight: bold;
font-style: italic;
display: block;
margin: 0 1em 0 1em;
}
.piwik-donate-slider {
margin: 1em 0 1em 1em;
}
.piwik-donate-slider > .slider-range {
vertical-align: top;
position: relative;
display: inline-block;
border: 1px solid #999;
background-color: #f7f7f7;
border-radius: 6px;
height: 14px;
width: 270px;
margin: 22px 8px 0 0;
cursor: pointer;
}
.piwik-donate-slider .slider-position {
border: 1px solid #999;
background-color: #CCC;
border-radius: 3px;
height: 18px;
width: 10px;
position: absolute;
top: -3px;
left: -1px;
}
.piwik-donate-slider .slider-donate-amount {
display: inline-block;
padding: .3em .5em .3em .5em;
margin: 16px 8px 0 0;
vertical-align: top;
width: 48px;
text-align: center;
background-color: #CCC;
color: #333;
cursor: pointer;
}
.piwik-donate-slider .slider-smiley-face {
margin: 8px 0 8px 0;
display: inline-block;
cursor: pointer;
}
.piwik-donate-call .donate-submit {
min-height: 55px;
position: relative;
}
.piwik-donate-call .donate-submit input {
margin-left: 13px;
border-style: none;
background-image: none;
padding: 0;
}
.piwik-donate-call .donate-submit a {
display: inline-block;
margin-left: 1.2em;
font-size: 1em;
font-style: italic;
}
.piwik-donate-call .donate-submit a.donate-spacer {
margin-bottom:.5em;
visibility:hidden;
}
.piwik-donate-call .donate-submit a.donate-one-time {
position: absolute;
bottom: .5em;
right: 1.2em;
}
.piwik-donate-call > .piwik-donate-message {
margin-bottom: .5em;
}
.piwik-donate-call > .piwik-donate-message p {
margin-left: 1em;
}
.piwik-donate-call > .form-description {
margin-top: 1.25em;
}
.donate-form-instructions {
font-size: .8em;
margin: 0 1.25em 0 1.25em;
color: #666;
font-style: italic;
}
.widget .piwik-donate-call {
border-style: none;
}
.widget .piwik-donate-slider > .slider-range {
width: 205px;
}

View file

@ -0,0 +1,55 @@
.tagCloud {
padding: 10px;
img {
border: 0;
}
.word a {
text-decoration: none;
}
.word {
padding: 4px 8px 4px 0;
white-space: nowrap;
}
.valueIsZero {
text-decoration: line-through;
}
span.size0, span.size0 a {
color: #255792;
font-size: 28px;
}
span.size1, span.size1 a {
color: #255792;
font-size: 24px;
}
span.size2, span.size2 a {
color: #255792;
font-size: 20px;
}
span.size3, span.size3 a {
color: #255792;
font-size: 16px;
}
span.size4, span.size4 a {
color: #255792;
font-size: 15px;
}
span.size5, span.size5 a {
color: #255792;
font-size: 14px;
}
span.size6, span.size6 a {
color: #255792;
font-size: 11px;
}
}

View file

@ -0,0 +1,3 @@
.color-manager {
color: transparent;
}

View file

@ -0,0 +1,260 @@
h1 {
font-size: 18px;
font-weight: bold;
color: #7e7363;
padding: 3px 0 7px 0;
clear: both;
}
h2 {
font-size: 18px;
font-weight: normal;
color: #7e7363;
padding: 12px 0 7px 0;
clear: both;
}
h2 a {
text-decoration: none;
color: #7e7363;
}
h3 {
font-size: 1.3em;
margin-top: 2em;
color: #1D3256;
}
.home p {
padding-bottom: 1em;
margin-right: 1em;
margin-left: 1em;
}
.nav_sep {
height: 39px;
border-radius: 0 4px 0 0;
border: 1px solid #DDD;
}
.pageWrap {
border-left: 1px solid #DDDDDD;
border-right: 1px solid #DDDDDD;
min-height: 10px;
overflow: visible;
padding: 15px 15px 0;
position: relative;
font-size: 13px;
}
/* Content */
#content.home {
padding-top: 5px;
font-size: 14px;
}
/* 2 columns reports */
#leftcolumn {
float: left;
width: 50%;
}
#rightcolumn {
float: right;
width: 45%;
}
/* not in widget */
.widget #leftcolumn, .widget #rightcolumn {
float: left;
padding: 0 10px;
width: auto;
}
/* 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;
}
#calendarRangeTo {
float: left;
margin-left: 20px;
}
#calendarRangeFrom {
float: left;
}
#inputCalendarFrom, #inputCalendarTo {
margin-left: 10px;
width: 95px;
}
#calendarRangeApply {
display: none;
margin-top: 10px;
margin-left: 10px;
}
#invalidDateRange {
display: none;
}
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-style: italic;
}
/**
* Popover
* @see popover.js
*/
#Piwik_Popover {
font-family: Arial, Helvetica, sans-serif;
}
.Piwik_Popover_Loading_Name {
padding: 50px 0 65px 0;
font-size: 16px;
line-height: 20px;
font-weight: normal;
text-align: center;
background: url(plugins/Zeitgeist/images/loading-blue.gif) no-repeat center 20px;
}
.Piwik_Popover_Loading_NameWithSubject {
padding-bottom: 30px;
}
.Piwik_Popover_Loading_Subject {
padding: 0 70px 55px 70px;
color: #7e7363;
text-align: center;
font-size: 14px;
}
.Piwik_Popover_Error {
padding: 50px 20px 65px 20px;
text-align: center;
}
.Piwik_Popover_Error_Title {
color: #E87500;
font-weight: bold;
font-size: 16px;
}
.Piwik_Popover_Error_Title span {
color: #222;
font-weight: normal;
font-size: 16px;
}
.Piwik_Popover_Error_Message {
color: #7e7363;
padding: 20px 0 0 0;
font-size: 14px;
}
a.Piwik_Popover_Error_Back {
display: block;
margin: 20px 0 0 0;
color: #1D3256;
font-size: 14px;
}
#alert.ui-confirm input {
display: block;
margin: 10px auto 5px !important;
}
.header_short #updateCheckLinkContainer .icon {
display: none;
}
.header_full #updateCheckLinkContainer {
margin-top: -2px;
}
@-moz-document url-prefix() {
.header_full #updateCheckLinkContainer {
margin-top: -3px;
}
}
#updateCheckLinkContainer {
opacity: 0.7;
}
#updateCheckLinkContainer:hover {
opacity: 1;
}
#updateCheckLinkContainer {
float: right;
cursor: pointer;
}
#updateCheckLinkContainer>* {
vertical-align: middle;
}
#header_message #checkForUpdates {
font-size: 10px;
line-height: 16px;
display: inline-block;
vertical-align: middle;
text-transform: uppercase;
}
#header_message #checkForUpdates {
text-decoration: none;
}
#header_message #checkForUpdates:hover {
text-decoration: underline;
}
/* Used to link within content text, without adding visual clutter */
.linkContent { color:#333; text-decoration:none}
.linkContent:hover { text-decoration:underline;}

View file

@ -0,0 +1,10 @@
@dataTable-link-color: #255792;
@dataTable-header-background: #e4e2d7;
@dataTable-headerActive-background: #D5D3C8;
@import "dataTable/_dataTable.less";
@import "dataTable/_limitSelection.less";
@import "dataTable/_reportDocumentation.less";
@import "dataTable/_rowActions.less";
@import "dataTable/_subDataTable.less";
@import "dataTable/_tableConfiguration.less";

View file

@ -0,0 +1,613 @@
/* main data table */
.dataTable {
border: 0;
width: 100%;
padding: 0;
border-spacing: 0;
margin: 0;
td .ratio {
color: #999999;
font-size: 12px;
display:none;
text-align: right;
min-width: 45px;
margin-left: 4px;
font-weight: normal;
}
td.highlight > .ratio {
display: inline-block;
line-height: 15px;
}
}
div.dataTable {
position:relative;
}
table.dataTable td.label,
table.subDataTable td.label {
width: 100%;
white-space: nowrap;
}
table.dataTable img,
table.subDataTable img {
vertical-align: middle;
}
table.dataTable img {
border: 0;
margin-right: 1em;
margin-left: 0.5em;
}
table.dataTable tr.subDataTable {
cursor: pointer;
td.label span.label {
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
width: inherit;
display: inline-block;
}
}
table.dataTable th {
margin: 0;
color: @dataTable-link-color;
text-align: left;
padding: 6px 6px 6px 12px;
background: @dataTable-header-background;
font-size: 12px;
font-weight: normal;
border-left: 1px solid #d4d0c4;
vertical-align: top;
}
table.dataTable th.sortable {
cursor: pointer;
}
table.dataTable th.columnSorted {
font-weight: bold;
padding-right: 20px;
background: @dataTable-headerActive-background;
}
table.dataTable td {
padding: 5px 5px 5px 12px;
background: #fff;
border-left: 1px solid #e7e7e7;
}
table.dataTable td,
table.dataTable td a {
margin: 0;
text-decoration: none;
color: #444;
}
table.dataTable tr:hover > td,
table.dataTable tr:hover > td .dataTableRowActions {
background-color: #FFFFF7;
}
table.dataTable tr.subDataTable:hover > td,
table.dataTable tr.subDataTable:hover > td .dataTableRowActions {
background-color: #ffffcb;
}
table.dataTable tr:hover > td.cellSubDataTable
table.dataTable tr:hover > td.cellSubDataTable .dataTableRowActions {
background-color: #fff;
}
td.clean {
background-color: #fff;
}
table.dataTable td.column {
white-space: nowrap;
}
table.dataTable td.columneven {
background: #efeeec;
}
table.dataTable td.columnodd {
background: #f6f5f3;
}
.dataTable tr.highlight td {
background-color: #ECF9DD;
font-weight: bold;
}
table.dataTable td.label,
table.subActionsDataTable td.label,
table.actionsDataTable td.label {
border-top: 0;
border-left: 0;
}
table.dataTable th.label {
border-left: 0;
}
.dataTableActions table.dataTable th.label {
/* Ensures tables have enough space to display subtable on click, and prevent the jumping effect */
min-width: 250px;
}
table.dataTable span.label.highlighted {
font-style: italic;
}
/* the cell containing the subdatatable */
table.dataTable .cellSubDataTable {
margin: 0;
border-left: 0;
padding: 6px 12px 6px;
}
.cellSubDataTable > .dataTable {
padding: 6px 0 0;
}
/* A link in a column in the DataTable */
table.dataTable td #urlLink {
display: none;
}
table.dataTable img {
margin-left: 0;
}
.dataTable > .dataTableWrapper {
width: 450px;
}
.subDataTable > .dataTableWrapper {
width: 95%;
}
.sortIconContainer {
float: right;
position: relative;
}
.sortIcon {
margin: 0;
position: absolute;
}
.datatableFooterMessage {
color: #888;
text-align: left;
margin: 10px;
}
.dataTablePages {
color: #BFBFBF;
font-weight: bold;
margin: 10px;
font-size: 12px;
}
.dataTableSearchPattern {
margin: 5px 0 2px 0;
height: 20px;
display: block;
white-space: nowrap;
background: url(plugins/Zeitgeist/images/search_bg.png) no-repeat center 0;
text-align: center;
}
.dataTableSearchPattern input {
vertical-align: top;
font-size: 10px;
color: #454545;
border: 0;
background: transparent;
width: 21px;
height: 17px;
overflow: hidden;
opacity: 0;
filter: Alpha(opacity=0);
cursor: pointer;
}
.dataTableSearchPattern .searchInput {
width: 114px;
height: auto;
overflow: visible;
padding: 2px 6px;
opacity: 1;
cursor: text;
filter: Alpha(opacity=100);
}
.dataTableNext,
.dataTablePrevious {
font-size: 12px;
color: #184A83;
cursor: pointer;
}
.datatableRelatedReports {
color: #888;
font-size: 11px;
padding-bottom: 5px;
margin-top: 6px;
}
#dashboard .datatableRelatedReports {
margin-top: 0px;
}
.datatableRelatedReports span {
cursor: pointer;
font-weight: bold;
}
.dataTableFeatures {
text-align: center;
}
.dataTableFooterNavigation {
padding: 5px 0;
}
.dataTableNext,
.dataTablePrevious,
.dataTableSearchPattern {
display: none;
}
.dataTableFeatures .loadingPiwik {
font-size: 0.9em;
}
.subDataTable .dataTableFooterIcons {
height: 0;
}
.dataTable .loadingPiwikBelow {
padding-bottom: 5px;
display: block;
text-align: center;
}
.dataTableFooterIcons div {
padding-bottom: 4px;
}
#dashboard {
.dataTableFeatures {
&.expanded {
.dataTableFooterIcons {
display: block;
}
.expandDataTableFooterDrawer {
display: none;
}
}
&.hasEvolution {
.dataTableFooterIcons {
margin-top: 17px;
}
.expandDataTableFooterDrawer {
margin-top: 20px;
}
}
.expandDataTableFooterDrawer {
display: block;
margin-top: 5px;
margin-bottom: 0px;
margin-left: auto;
margin-right: auto;
background-color: #D9D9D9;
height: 7px;
width: 70px;
-webkit-border-top-left-radius: 10px;
-webkit-border-top-right-radius: 10px;
-moz-border-radius-topleft: 10px;
-moz-border-radius-topright: 10px;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
line-height: 0px;
img {
margin-bottom: 0px;
line-height: 0px;
}
}
}
.dataTableFooterIcons {
display: none;
margin-top: 5px;
height: auto;
div {
padding-bottom: 2px;
}
.foldDataTableFooterDrawer {
display: block;
padding-bottom: 0px;
margin-top: 0px;
margin-left: auto;
margin-right: auto;
background-color: #D9D9D9;
-webkit-border-top-left-radius: 10px;
-webkit-border-top-right-radius: 10px;
-moz-border-radius-topleft: 10px;
-moz-border-radius-topright: 10px;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
line-height: 0px;
height: 7px;
width: 70px;
clear: both;
img {
margin-top: 2px;
margin-bottom: 0px;
line-height: 0px;
}
}
.controls {
padding: 15px 0px;
text-align: left;
color: #333;
}
}
}
.dataTableFooterIcons .foldDataTableFooterDrawer,
.dataTableFeatures .expandDataTableFooterDrawer {
display: none;
cursor: pointer;
}
@-moz-document url-prefix() {
#dashboard .dataTableFeatures .expandDataTableFooterDrawer {
line-height: 1px;
img {
margin-bottom: 1px;
line-height: 1px;
}
}
}
.dataTableFooterIcons {
height: 20px;
white-space: nowrap;
font-size: 10px;
padding: 6px 5px;
border-top: 1px solid #B6B0A6;
}
.dataTableFooterWrap {
position: relative;
float: left;
}
.dataTableFooterWrap select {
float: left;
margin: 1px 0 1px 10px;
}
.tableIcon {
background: #f2f1ed;
display: inline-block;
float: left;
margin: 0 1px 0 0;
padding: 2px;
border-radius: 2px;
}
.tableIcon:hover {
background: #e9e8e1;
}
.activeIcon {
background: #e9e8e1;
}
.tableIconsGroup > span > span {
position:relative;
float:left;
}
.dataTableFooterActiveItem {
position: absolute;
top: -6px;
left: 0;
}
.exportToFormatItems {
background: #dcdacf;
float: left;
margin: 0 1px 0 0;
padding: 4px 6px 3px 6px;
color: #968d7f;
border-radius: 2px;
}
.exportToFormatItems img {
vertical-align: middle;
margin: -4px -3px -2px 2px;
}
.tableIconsGroup {
float: left;
padding-right: 4px;
}
.tableIconsGroup .tableIcon span {
margin-right: 5px;
margin-left: 5px;
}
.tableIconsGroup img {
vertical-align: bottom;
}
.tableIconsGroupActive {
display: block;
float: left;
background: #dcdacf;
border-radius: 2px;
}
.tableIconsGroupActive .tableIcon {
background: none;
}
.tableIconsGroupActive .tableIcon:hover {
background: #e9e8e1;
}
.exportToFormatIcons,
.dataTableFooterIconsShow {
float: left;
}
.dataTableFooterIcons,
.dataTableFooterIcons a {
text-decoration: none;
color: @dataTable-link-color;
}
.dataTableSpacer {
clear: both;
}
/* Actions table */
.dataTableActions table.dataTable tr td.labelodd {
background-image: none;
}
/* levels higher than 4 have a default padding left */
.actionsDataTable tr 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;
}
tr.level5 td.label {
padding-left: 5.5em;
}
tr.level6 td.label {
padding-left: 6em;
}
tr.level7 td.label {
padding-left: 6.5em;
}
tr.level8 td.label {
padding-left: 7em;
}
tr.level9 td.label {
padding-left: 7.5em;
}
tr.level10 td.label {
padding-left: 8em;
}
tr.level11 td.label {
padding-left: 8.5em;
}
tr.level12 td.label {
padding-left: 9em;
}
/* less right margins for the link image in the Pa*/
.dataTableActions table.dataTable img.link {
margin-right: 0.5em;
margin-left: -0.5em;
margin-top: -8px;
}
tr td.label img.plusMinus {
margin-left: -1em;
margin-right: 3px;
margin-top: -5px;
}
.pk-emptyDataTable {
padding-top: 20px;
padding-bottom: 10px;
text-align: center;
font-style: italic;
}
.helpDate {
color: #777777;
font-size: 11px;
font-style: italic;
padding: 4px;
text-align: right;
display: block;
}
body .ui-tooltip.rowActionTooltip {
font-size: 11px;
padding: 3px 5px 3px 6px;
}
table.dataTable span.cell-tooltip {
cursor: default;
}
.dataTable .jqplot-graph {
padding-left: 6px;
> div {
position: relative;
}
}
td.cellSubDataTable .loadingPiwik {
padding:0;
}
.dataTable .searchReset {
position:relative;
img {
position: absolute;
top: 4px;
left: -15px;
cursor: pointer;
display: inline;
}
}

View file

@ -0,0 +1,68 @@
.limitSelection {
float: right;
position: relative;
margin-left: 5px;
min-height: 20px;
z-index: 1;
}
.limitSelection.hidden {
display: none;
}
.limitSelection > div {
border: 1px solid #DFDFDF;
border-radius: 4px;
background: url(plugins/Zeitgeist/images/sort_subtable_desc_light.png) no-repeat right 2px;
padding: 0 14px 0 4px;
display: block;
width: 24px;
height: 20px;
cursor: pointer;
}
.limitSelection.disabled > div {
opacity: 0.5;
cursor: not-allowed;
filter: Alpha(opacity=50);
}
.limitSelection.visible > div {
border-radius: 0 0 4px 4px;
background-image: url(plugins/Zeitgeist/images/sort_subtable_asc_light.png)
}
.limitSelection > ul {
margin-top: 1px;
overflow: visible;
background-color: #fff;
}
.limitSelection > ul > li {
cursor: pointer;
width: 28px;
padding: 0 10px 0 4px;
font-size: 1.1em;
font-weight: bold;
height: 20px;
margin-top: -40px;
background-color: #fff;
border-left: 1px solid #DFDFDF;
border-right: 1px solid #DFDFDF;
vertical-align: middle;
text-align: right;
}
.limitSelection > ul > li.last {
border-top: 1px solid #DFDFDF;
border-radius: 4px 4px 0 0;
}
.limitSelection > ul > li:hover {
background-color: #EBEAE6;
}
.limitSelection span {
padding-top: 3px;
display: inline-block;
}

View file

@ -0,0 +1,61 @@
/* Documentation */
table.dataTable th .columnDocumentation {
display: none;
width: 165px;
text-align: left;
background: #f7f7f7;
color: #444;
font-size: 11px;
font-weight: normal;
border: 1px solid #e4e5e4;
padding: 5px 10px 6px 10px;
border-radius: 4px;
z-index: 125;
position: absolute;
box-shadow: 0 0 4px #e4e5e4;
cursor: default;
}
table.dataTable th .columnDocumentationTitle {
background: url(plugins/Zeitgeist/images/help.png) no-repeat;
line-height: 14px;
padding: 2px 0 3px 21px;
font-weight: bold;
}
.reportDocumentation {
display: none;
background: #f7f7f7;
font-size: 12px;
font-weight: normal;
border: 1px solid #e4e5e4;
margin: 0 0 10px 0;
padding: 0;
border-radius: 4px;
max-width: 500px;
}
.reportDocumentation p {
padding: 5px 10px 6px 10px;
margin: 0;
color: #444;
font-size: 12px;
}
.reportDocumentationIcon {
display: block;
width: 16px;
height: 16px;
margin: 10px 0;
background: url(plugins/Zeitgeist/images/help.png) no-repeat;
}
h2 .reportDocumentationIcon {
position: absolute;
margin: 4px 0 0 0;
display: none;
}
h2 .reportDocumentationIcon.hidden {
background: none;
}

View file

@ -0,0 +1,36 @@
table.dataTable .dataTableRowActions {
position: absolute;
display: none;
overflow: hidden;
margin-top: -5px;
z-index: 1000; /* Work around FF bug to make sure it displays over ellipsis */
}
*+html table.dataTable .dataTableRowActions {
margin-top: -7px;
}
table.dataTable .dataTableRowActions a {
display: block;
float: left;
padding: 6px 4px 6px 0;
margin: 0;
}
table.dataTable .dataTableRowActions a.leftmost {
padding-left: 4px;
}
table.dataTable .dataTableRowActions a.rightmost {
padding-right: 8px;
}
table.dataTable .dataTableRowActions a img {
margin: 0;
padding: 0;
border: 0;
width: 20px;
height: 17px;
}

View file

@ -0,0 +1,46 @@
/* SUBDATATABLE */
/* a datatable inside another datatable */
table.subDataTable td {
border: 0;
}
table.subDataTable thead th {
font-weight: normal;
font-size: 12px;
text-align: left;
padding: .3em 1em;
border: 0;
border-top: 1px solid #e7e7e7;
border-bottom: 1px solid #e7e7e7;
}
table.subDataTable td.labeleven, table.subDataTable td.labelodd {
background-image: none;
}
table.subDataTable td {
border-bottom: 1px solid #e7e7e7;
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.label {
padding: 5px;
}
/* are the following two supposed to be together? */
.subDataTable.dataTableFeatures {
padding-top: 0;
padding-bottom: 5px;
}

View file

@ -0,0 +1,82 @@
.tableConfiguration {
float: right;
position: relative;
margin-left: 5px;
min-height: 20px;
min-width: 25px;
}
a.tableConfigurationIcon {
display: block;
width: 30px;
height: 22px;
background: url(plugins/Zeitgeist/images/configure.png) no-repeat center 2px;
position: absolute;
z-index: 9;
right: 0;
}
a.tableConfigurationIcon.highlighted {
display: block;
width: 30px;
height: 22px;
background-image: url(plugins/Zeitgeist/images/configure-highlight.png);
position: absolute;
z-index: 9;
right: 0;
}
.tableConfiguration ul {
overflow: visible;
display: none;
position: relative;
z-index: 8;
text-align: left;
}
.tableConfiguration ul.open {
display: block;
}
.tableConfiguration ul li {
padding: 0;
font-size: 1.1em;
height: 40px;
margin-top: -80px;
background-color: #fff;
border: 1px solid #DFDFDF;
border-width: 0 1px;
vertical-align: middle;
}
.tableConfiguration ul li.firstDummy {
border-bottom-width: 1px;
border-radius: 0 0 4px 4px;
height: 25px;
cursor: default;
margin-top: -4px;
}
.tableConfiguration ul li.first {
margin-top: -65px;
}
.tableConfiguration ul li.last {
border-top-width: 1px;
border-radius: 4px 4px 0 0;
}
.tableConfiguration div.configItem {
cursor: pointer;
padding: 5px 10px;
line-height: 15px;
color: #444;
}
.tableConfiguration div.configItem:hover {
background-color: #EBEAE6;
}
.tableConfiguration div.configItem span.action {
color: @dataTable-link-color;
}

View file

@ -0,0 +1,150 @@
// evolution graph colors
.evolution-graph-colors[data-name=grid-background] {
color: #fff;
}
.evolution-graph-colors[data-name=grid-border] {
color: #f00;
}
.evolution-graph-colors[data-name=series1] {
color: #5170AE;
}
.evolution-graph-colors[data-name=series2] {
color: #F29007;
}
.evolution-graph-colors[data-name=series3] {
color: #CC3399;
}
.evolution-graph-colors[data-name=series4] {
color: #9933CC;
}
.evolution-graph-colors[data-name=series5] {
color: #80a033;
}
.evolution-graph-colors[data-name=series6] {
color: #246AD2;
}
.evolution-graph-colors[data-name=series7] {
color: #FD16EA;
}
.evolution-graph-colors[data-name=series8] {
color: #49C100;
}
.evolution-graph-colors[data-name=ticks] {
color: #ccc;
}
.evolution-graph-colors[data-name=single-metric-label] {
color: #666;
}
// bar graph colors
.bar-graph-colors[data-name=grid-background] {
color: #fff;
}
.bar-graph-colors[data-name=grid-border] {
color: #f00;
}
.bar-graph-colors[data-name=series1] {
color: #5170AE;
}
.bar-graph-colors[data-name=series2] {
color: #F3A010;
}
.bar-graph-colors[data-name=series3] {
color: #CC3399;
}
.bar-graph-colors[data-name=series4] {
color: #9933CC;
}
.bar-graph-colors[data-name=series5] {
color: #80a033;
}
.bar-graph-colors[data-name=series6] {
color: #246AD2;
}
.bar-graph-colors[data-name=series7] {
color: #FD16EA;
}
.bar-graph-colors[data-name=series8] {
color: #49C100;
}
.bar-graph-colors[data-name=ticks] {
color: #ccc;
}
.bar-graph-colors[data-name=single-metric-label] {
color: #666;
}
// pie graph colors
.pie-graph-colors[data-name=grid-background] {
color: #fff;
}
.pie-graph-colors[data-name=grid-border] {
color: #f00;
}
.pie-graph-colors[data-name=series1] {
color: #59727F;
}
.pie-graph-colors[data-name=series2] {
color: #7DAAC0;
}
.pie-graph-colors[data-name=series3] {
color: #7F7259;
}
.pie-graph-colors[data-name=series4] {
color: #C09E7D;
}
.pie-graph-colors[data-name=series5] {
color: #9BB39B;
}
.pie-graph-colors[data-name=series6] {
color: #B1D8B3;
}
.pie-graph-colors[data-name=series7] {
color: #B39BA7;
}
.pie-graph-colors[data-name=series8] {
color: #D8B1C5;
}
.pie-graph-colors[data-name=series9] {
color: #A5A5A5;
}
.pie-graph-colors[data-name=ticks] {
color: #ccc;
}
.pie-graph-colors[data-name=single-metric-label] {
color: #666;
}

View file

@ -0,0 +1,70 @@
/* Autocomplete
----------------------------------*/
.ui-autocomplete {
position: absolute;
cursor: default;
}
.ui-autocomplete-loading {
background: white;
}
/* workarounds */
* html .ui-autocomplete {
/* without this, the menu expands to 100% in IE6 */
width: 1px;
}
/* Menu
----------------------------------*/
.ui-menu {
list-style: none;
padding: 6px;
margin: 0;
display: block;
position: relative;
font-family: Arial, Verdana, Arial, Helvetica, sans-serif;
}
.ui-menu .ui-menu {
margin-top: -3px;
margin-bottom: 8px;
}
.ui-menu .ui-menu-item {
line-height: 18px;
padding: 0;
height: auto;
display: block;
text-decoration: none;
white-space: nowrap;
}
.ui-menu .ui-menu-item a {
line-height: 18px;
color: #255792;
font-size: 12px;
padding: 0 5px 0 5px;
position: relative;
}
.ui-menu .ui-menu-item a.ui-state-focus,
.ui-menu .ui-menu-item a.ui-state-active {
font-weight: normal;
margin: 0;
}
.ui-widget-content {
border: 0;
}
.ui-corner-all {
border-radius: 4px;
}
.ui-menu .ui-menu-item a.ui-state-focus {
background: #ebeae6;
border: 0;
border-radius: 0;
}

View file

@ -0,0 +1,131 @@
.Menu--dashboard {
position: relative;
}
.Menu--dashboard > .Menu-tabList {
line-height: 1;
display: table; // The nav has the height og his children
margin-bottom: -1px; // Allow tabs to merge with the submenu
}
.Menu--dashboard > .Menu-tabList ul {
background: #fff; /*IE6 needs this*/
float: left;
position: relative;
}
/* LEVEL1 NORMAL */
.Menu--dashboard > .Menu-tabList > li {
background: #f1f1f1;
float: left;
list-style: none;
z-index: 49;
margin-right: -1px;
border: 1px solid #ddd;
border-bottom: 0;
border-radius: 4px 4px 0 0;
}
.Menu--dashboard > .Menu-tabList a {
color: #444;
font-size: 18px;
display: block;
float: left;
padding: 8px 27px 0;
height: 25px;
text-decoration: none;
font-weight: normal;
}
/* LEVEL1 HOVER */
.Menu--dashboard > .Menu-tabList > li:hover,
.Menu--dashboard > .Menu-tabList > li.sfHover {
background: #fff;
}
.Menu--dashboard > .Menu-tabList > li:hover > a,
.Menu--dashboard > .Menu-tabList > li.sfHover > a,
.Menu--dashboard > .Menu-tabList > li.sfActive > a,
.Menu--dashboard > .Menu-tabList a:hover {
color: #e87500;
}
.Menu--dashboard > .Menu-tabList > li:hover > a {
text-decoration: underline;
}
.Menu--dashboard > .Menu-tabList > li.sfHover > a {
border-bottom: 1px solid #fff;
}
/* LEVEL2 NORMAL */
.Menu--dashboard > .Menu-tabList > li > ul {
padding: 9px 0 5px 0;
left: 0;
top: -999em;
position: absolute;
min-height: 25px;
width: 100%;
background: none;
}
.Menu--dashboard > .Menu-tabList > li li {
float: left;
background: none;
border: 0;
text-align: center;
}
.Menu--dashboard > .Menu-tabList > li li > a {
padding: 5px 15px;
font-size: 14px;
border: 0;
float: none;
display: inline-block;
height: auto;
background: none;
color: #444;
text-decoration: none;
}
/* LEVEL2 HOVER */
.Menu--dashboard > .Menu-tabList > li.sfHover > ul,
.Menu--dashboard > .Menu-tabList > li:hover > ul {
z-index: 1;
top: 100%;
opacity: 1;
-webkit-transition: opacity 300ms ease-out 10ms; /* property duration timing-function delay */
-moz-transition: opacity 300ms ease-out 10ms;
-o-transition: opacity 300ms ease-out 10ms;
transition: opacity 300ms ease-out 10ms;
}
.Menu--dashboard > .Menu-tabList > li li:hover > a,
.Menu--dashboard > .Menu-tabList > li li.sfHover > a {
color: #e87500;
}
.Menu--dashboard > .Menu-tabList > li li.sfHover > a {
font-weight: bold;
text-decoration: none !important;
}
@media all and (max-width: 949px) {
.nav {
clear: right;
}
}
@media all and (max-width: 749px) {
.Menu--dashboard > .Menu-tabList a {
padding-left: 8px;
padding-right: 8px;
}
}
@media all and (max-width: 549px) {
.Menu--dashboard > ul.Menu-tabList > li.sfHover > a,
.Menu--dashboard > ul.Menu-tabList > li.sfActive.sfHover > a {
border-bottom: 0;
}
}

View file

@ -0,0 +1,93 @@
#content.admin #notificationContainer {
width: 750px;
display: table-header-group; /* no overlap with About Piwik box */
.notification {
margin: 10px;
}
}
.system.notification {
color: #9b7a44;
float: none;
padding: 15px 35px 15px 15px;
text-shadow: 0 1px 0 rgba(255,255,255,.5);
background-color: #ffffe0;
border: 1px solid #e6db55;
border-radius: 3px;
font-size: 14px;
margin: 0px;
margin-bottom: 16px;
a {
color: #9b7a44;
text-decoration: underline;
}
.close {
position: relative;
top: -5px;
right: -28px;
line-height: 20px;
}
button.close {
padding: 0;
cursor: pointer;
background: transparent;
border: 0;
-webkit-appearance: none;
}
.close {
float: right;
font-size: 20px;
font-weight: bold;
line-height: 20px;
color: #000000;
text-shadow: 0 1px 0 #ffffff;
opacity: 0.2;
filter: alpha(opacity=20);
}
&.notification-success {
background-color: #dff0d8;
border-color: #c3d6b7;
color: #468847;
a {
color: #468847
}
}
&.notification-danger,
&.notification-error {
background-color: #f2dede;
border-color: #d5bfc4;
color: #b94a48;
a {
color: #b94a48
}
}
&.notification-info {
background-color: #d9edf7;
border-color: #a7d3e3;
color: #3a87ad;
a {
color: #3a87ad
}
}
&.notification-block {
padding-top: 14px;
padding-bottom: 14px;
}
&.notification-block > p,
&.notification-block > ul {
margin-bottom: 0;
}
&.notification-block p + p {
margin-top: 5px;
}
}

View file

@ -0,0 +1,72 @@
#piwik-promo-thumbnail {
background: #fff url(plugins/CoreHome/images/promo_splash.png) no-repeat 0 0;
background-position: center;
width: 321px;
margin: 0 auto 0 auto;
}
#piwik-promo-embed {
margin-left: 1px;
}
#piwik-promo-embed>iframe {
z-index: 0;
}
#piwik-promo-thumbnail {
height: 178px;
}
#piwik-promo-thumbnail:hover {
opacity: .75;
cursor: pointer;
}
#piwik-promo-thumbnail>img {
display: block;
position: relative;
top: 53px;
left: 125px;
}
#piwik-promo-video {
margin: 2em 0 2em 0;
}
#piwik-widget-footer {
margin: 0 1em 1em 1em;
}
#piwik-promo-share {
margin: 0 2em 1em 0;
background-color: #CCC;
border: 1px solid #CCC;
border-radius: 6px;
display: inline-block;
padding: 0 .5em 0 .5em;
float: right;
}
#piwik-promo-share > a {
margin-left: .5em;
margin-top: 4px;
display: inline-block;
}
#piwik-promo-share>span {
display: inline-block;
vertical-align: top;
margin-top: 4px;
}
#piwik-promo-videos-link {
font-size: .8em;
font-style: italic;
margin: 1em 0 0 1.25em;
color: #666;
display: inline-block;
}
#piwik-promo-videos-link:hover {
text-decoration: none;
}

View file

@ -0,0 +1,30 @@
// sparkline styles
div.sparkline {
border-bottom: 1px solid white;
}
div.sparkline:hover {
cursor: pointer;
border-bottom: 1px dashed #c3c3c3;
}
// sparkline colors
.sparkline-colors[data-name=backgroundColor] {
color: white;
}
.sparkline-colors[data-name=lineColor] {
color: rgb(22, 44, 74);
}
.sparkline-colors[data-name=minPointColor] {
color: #ff7f7f;
}
.sparkline-colors[data-name=lastPointColor] {
color: #55AAFF;
}
.sparkline-colors[data-name=maxPointColor] {
color: #75BF7C;
}

View file

@ -0,0 +1,80 @@
<h2 id="{{ reportId }}" style="color: rgb({{ reportTitleTextColor }}); font-size: {{ reportTitleTextSize }}pt;">
{{ reportName }}
</h2>
{% if reportRows is empty %}
{{ 'CoreHome_ThereIsNoDataForThisReport'|translate }}
{% else %}
{% if displayGraph %}
<img alt=""
{% if renderImageInline %}
src="data:image/png;base64,{{ generatedImageGraph }}"
{% else %}
src="cid:{{ reportId }}"
{% endif %}
height="{{ graphHeight }}"
width="{{ graphWidth }}"/>
{% endif %}
{% if displayGraph and displayTable %}
<br/>
<br/>
{% endif %}
{% if displayTable %}
<table style="border-collapse:collapse; margin-left: 5px;">
<thead style="background-color: rgb({{ tableHeaderBgColor }}); color: rgb({{ tableHeaderTextColor }}); font-size: {{ reportTableHeaderTextSize }}pt;">
{% for columnName in reportColumns %}
<th style="padding: 6px 0;">
&nbsp;{{ columnName }}&nbsp;&nbsp;
</th>
{% endfor %}
</thead>
<tbody>
{% set cycleValues=['','background-color: rgb('~tableBgColor~')'] %}
{% set cycleIndex=0 %}
{% for rowId,row in reportRows %}
{% set rowMetrics=row.columns %}
{% if reportRowsMetadata[rowId] is defined %}
{% set rowMetadata=reportRowsMetadata[rowId].columns %}
{% else %}
{% set rowMetadata=null %}
{% endif %}
<tr style="{{ cycle(cycleValues, cycleIndex) }}">
{% set cycleIndex=cycleIndex+1 %}
{% for columnId, columnName in reportColumns %}
<td style="font-size: {{ reportTableRowTextSize }}pt; border-bottom: 1px solid rgb({{ tableCellBorderColor }}); padding: 5px 0 5px 5px;">
{% if columnId == 'label' %}
{% if rowMetrics[columnId] is defined %}
{% if rowMetadata.logo is defined %}
<img src='{{ currentPath }}{{ rowMetadata.logo }}'>
&nbsp;
{% endif %}
{% if rowMetadata.url is defined %}
<a style="color: rgb({{ reportTextColor }});" href='{% if rowMetadata.url|slice(0,4) not in ['http','ftp:'] %}http://{% endif %}{{ rowMetadata.url }}'>
{% endif %}
{{ rowMetrics[columnId] | raw }}{# labels are escaped by SafeDecodeLabel filter in core/API/Response.php #}
{% if rowMetadata.url is defined %}
</a>
{% endif %}
{% endif %}
{% else %}
{% if rowMetrics[columnId] is empty %}
0
{% else %}
{{ rowMetrics[columnId] }}
{% endif %}
{% endif %}
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
<br/>
<a style="text-decoration:none; color: rgb({{ reportTitleTextColor }}); font-size: {{ reportBackToTopTextSize }}pt;" href="#reportTop">
{{ 'ScheduledReports_TopOfReport'|translate }}
</a>
{% endif %}

View file

@ -0,0 +1,2 @@
</body>
</html>

View file

@ -0,0 +1,36 @@
<html>
<head>
<meta charset="utf-8">
</head>
<body style="color: rgb({{ reportTextColor }});">
<a id="reportTop" target="_blank" href="{{ currentPath }}"><img title="{{ 'General_GoTo'|translate("Piwik") }}" border="0" alt="Piwik" src='{{ logoHeader }}'/></a>
<h1 style="color: rgb({{ reportTitleTextColor }}); font-size: {{ reportTitleTextSize }}pt;">
{{ reportTitle }}
</h1>
<p>
{{ description }} - {{ 'General_DateRange'|translate }} {{ prettyDate }}
</p>
{% if displaySegment %}
<p style="color: rgb({{ reportTitleTextColor }});">
{{ 'ScheduledReports_CustomVisitorSegment'|translate("Piwik") }} {{ segmentName }}
</p>
{% endif %}
{% if reportMetadata|length > 1 %}
<h2 style="color: rgb({{ reportTitleTextColor }}); font-size: {{ reportTitleTextSize }}pt;">
{{ 'ScheduledReports_TableOfContent'|translate }}
</h2>
<ul>
{% for metadata in reportMetadata %}
<li>
<a href="#{{ metadata.uniqueId }}" style="text-decoration:none; color: rgb({{ reportTextColor }});">
{{ metadata.name }}
</a>
</li>
{% endfor %}
</ul>
{% endif %}

View file

@ -0,0 +1,29 @@
<div class="reportsByDimensionView">
<div class="entityList">
{% for category, dimensions in dimensionCategories %}
{% set firstCategory = (loop.index0 == 0) %}
<div class='dimensionCategory'>
{{ category|translate }}
<ul class='listCircle'>
{% for idx, dimension in dimensions %}
<li class="reportDimension {% if idx == 0 and firstCategory %}activeDimension{% endif %}"
data-url="{{ dimension.url }}">
<span class='dimension'>{{ dimension.title|translate }}</span>
</li>
{% endfor %}
</ul>
</div>
{% endfor %}
</div>
<div style="float:left;max-width:900px;">
<div class="loadingPiwik" style="display:none;">
<img src="plugins/Zeitgeist/images/loading-blue.gif" alt=""/>{{ 'General_LoadingData'|translate }}
</div>
<div class="dimensionReport">{{ firstReport|raw }}</div>
</div>
<div class="clear"></div>
</div>

View file

@ -0,0 +1,40 @@
{% if properties.show_visualization_only %}
{% include visualizationTemplate %}
{%- else -%}
{% set summaryRowId = constant('Piwik\\DataTable::ID_SUMMARY_ROW') %}{# ID_SUMMARY_ROW #}
{% set isSubtable = javascriptVariablesToSet.idSubtable is defined and javascriptVariablesToSet.idSubtable != 0 %}
<div class="dataTable {{ visualizationCssClass }} {{ properties.datatable_css_class|default('') }} {% if isSubtable %}subDataTable{% endif %}"
data-table-type="{{ properties.datatable_js_type }}"
data-report="{{ properties.report_id }}"
data-props="{% if clientSideProperties is empty %}{}{% else %}{{ clientSideProperties|json_encode }}{% endif %}"
data-params="{% if clientSideParameters is empty %}{}{% else %}{{ clientSideParameters|json_encode }}{% endif %}">
<div class="reportDocumentation">
{% if properties.documentation|default is not empty %}<p>{{ properties.documentation|raw }}</p>{% endif %}
{% if reportLastUpdatedMessage is defined %}<span class='helpDate'>{{ reportLastUpdatedMessage }}</span>{% endif %}
</div>
<div class="dataTableWrapper">
{% if error is defined %}
{{ error.message }}
{% else %}
{% if dataTable is empty or dataTableHasNoData|default(false) %}
<div class="pk-emptyDataTable">
{% if showReportDataWasPurgedMessage is defined and showReportDataWasPurgedMessage %}
{{ 'CoreHome_DataForThisReportHasBeenPurged'|translate(deleteReportsOlderThan) }}
{% else %}
{{ 'CoreHome_ThereIsNoDataForThisReport'|translate }}
{% endif %}
</div>
{% else %}
{% include visualizationTemplate %}
{% endif %}
{% if properties.show_footer %}
{% include "@CoreHome/_dataTableFooter.twig" %}
{% endif %}
{% include "@CoreHome/_dataTableJS.twig" %}
{% endif %}
</div>
</div>
{%- endif %}

View file

@ -0,0 +1,49 @@
{% spaceless %}
{% set tooltipIndex = column ~ '_tooltip' %}
{% if row.getMetadata(tooltipIndex) %}<span class="cell-tooltip" data-tooltip="{{ row.getMetadata(tooltipIndex) }}">{% endif %}
{% if not row.getIdSubDataTable() and column=='label' and row.getMetadata('url') %}
<a target="_blank" href='{% if row.getMetadata('url')|slice(0,4) not in ['http','ftp:'] %}http://{% endif %}{{ row.getMetadata('url')|raw }}'>
{% if not row.getMetadata('logo') %}
<img class="link" width="10" height="9" src="plugins/Zeitgeist/images/link.gif"/>
{% endif %}
{% endif %}
{% if column=='label' %}
{% import 'macros.twig' as piwik %}
<span class='label{% if row.getMetadata('is_aggregate') %} highlighted{% endif %}'
{% if properties is defined and properties.tooltip_metadata_name is not empty %}title="{{ row.getMetadata(properties.tooltip_metadata_name) }}"{% endif %}>
{{ piwik.logoHtml(row.getMetadata(), row.getColumn('label')) }}
{% if row.getMetadata('html_label_prefix') %}<span class='label-prefix'>{{ row.getMetadata('html_label_prefix') | raw }}&nbsp;</span>{% endif -%}
{%- if row.getMetadata('html_label_suffix') %}<span class='label-suffix'>{{ row.getMetadata('html_label_suffix') | raw }}</span>{% endif -%}
{% endif %}<span class="value">{% if row.getColumn(column) %}{{- row.getColumn(column)|raw -}}{% else %}-{% endif %}</span>
{% if column=='label' %}</span>{% endif %}
{% if not row.getIdSubDataTable() and column=='label' and row.getMetadata('url') %}
</a>
{% endif %}
{% if row.getMetadata(tooltipIndex) %}</span>{% endif %}
{% set totals = dataTable.getMetadata('totals') %}
{% if column in totals|keys -%}
{% set labelColumn = columns_to_display|first %}
{% set reportTotal = totals[column] %}
{% if siteSummary is defined and siteSummary is not empty and siteSummary.getFirstRow %}
{% set siteTotal = siteSummary.getFirstRow.getColumn(column) %}
{% else %}
{% set siteTotal = 0 %}
{% endif %}
{% set rowPercentage = row.getColumn(column)|percentage(reportTotal, 1) %}
{% set metricTitle = translations[column]|default(column) %}
{% set reportLabel = row.getColumn(labelColumn)|truncate(40)|raw %}
{% set reportRatioTooltip = 'General_ReportRatioTooltip'|translate(reportLabel, rowPercentage|e('html_attr'), reportTotal|e('html_attr'), metricTitle|e('html_attr'), translations[labelColumn]|default(labelColumn)|e('html_attr')) %}
{% if siteTotal and siteTotal > reportTotal %}
{% set totalPercentage = row.getColumn(column)|percentage(siteTotal, 1) %}
{% set totalRatioTooltip = 'General_TotalRatioTooltip'|translate(totalPercentage, siteTotal, metricTitle) %}
{% else %}
{% set totalRatioTooltip = '' %}
{% endif %}
<span class="ratio" title="{{ reportRatioTooltip|raw }} {{ totalRatioTooltip|e('html_attr') }}">&nbsp;{{ rowPercentage }}</span>
{%- endif %}
{% endspaceless %}

View file

@ -0,0 +1,140 @@
<div class="dataTableFeatures">
<div class="dataTableFooterNavigation">
{% if properties.show_offset_information %}
<span>
<span class="dataTablePages"></span>
</span>
{% endif %}
{% if properties.show_pagination_control %}
<span>
<span class="dataTablePrevious">&lsaquo; {% if clientSideParameters.dataTablePreviousIsFirst is defined %}{{ 'General_First'|translate }}{% else %}{{ 'General_Previous'|translate }}{% endif %} </span>
<span class="dataTableNext">{{ 'General_Next'|translate }} &rsaquo;</span>
</span>
{% endif %}
{% if properties.show_search %}
<span class="dataTableSearchPattern">
<input type="text" class="searchInput" length="15" />
<input type="submit" value="{{ 'General_Search'|translate }}" />
</span>
{% endif %}
</div>
<span class="loadingPiwik" style="display:none;"><img src="plugins/Zeitgeist/images/loading-blue.gif"/> {{ 'General_LoadingData'|translate }}</span>
{% if properties.show_footer_icons %}
<div class="dataTableFooterIcons">
<div class="dataTableFooterWrap">
{% for footerIconGroup in footerIcons %}
<div class="tableIconsGroup">
<span class="{{ footerIconGroup.class }}">
{% for footerIcon in footerIconGroup.buttons %}
<span>
{% if properties.show_active_view_icon and clientSideParameters.viewDataTable == footerIcon.id %}
<img src="plugins/Zeitgeist/images/data_table_footer_active_item.png" class="dataTableFooterActiveItem"/>
{% endif %}
<a class="tableIcon {% if clientSideParameters.viewDataTable == footerIcon.id %}activeIcon{% endif %}" data-footer-icon-id="{{ footerIcon.id }}">
<img width="16" height="16" title="{{ footerIcon.title }}" src="{{ footerIcon.icon }}"/>
{% if footerIcon.text is defined %}<span>{{ footerIcon.text }}</span>{% endif %}
</a>
</span>
{% endfor %}
</span>
</div>
{% endfor %}
<div class="tableIconsGroup">
{% if footerIcons is empty %}
<img src="plugins/Zeitgeist/images/data_table_footer_active_item.png" class="dataTableFooterActiveItem"/>
{% endif %}
<span class="exportToFormatIcons">
<a class="tableIcon" var="export">
<img width="16" height="16" src="plugins/Zeitgeist/images/export.png" title="{{ 'General_ExportThisReport'|translate }}"/>
</a>
</span>
<span class="exportToFormatItems" style="display:none;">
{{ 'General_Export'|translate }}:
<a target="_blank" methodToCall="{{ properties.apiMethodToRequestDataTable }}" format="CSV" filter_limit="{{ properties.export_limit }}">CSV</a> |
<a target="_blank" methodToCall="{{ properties.apiMethodToRequestDataTable }}" format="TSV" filter_limit="{{ properties.export_limit }}">TSV (Excel)</a> |
<a target="_blank" methodToCall="{{ properties.apiMethodToRequestDataTable }}" format="XML" filter_limit="{{ properties.export_limit }}">XML</a> |
<a target="_blank" methodToCall="{{ properties.apiMethodToRequestDataTable }}" format="JSON" filter_limit="{{ properties.export_limit }}">Json</a> |
<a target="_blank" methodToCall="{{ properties.apiMethodToRequestDataTable }}" format="PHP" filter_limit="{{ properties.export_limit }}">Php</a>
{% if properties.show_export_as_rss_feed %}
|
<a target="_blank" methodToCall="{{ properties.apiMethodToRequestDataTable }}" format="RSS" filter_limit="{{ properties.export_limit }}" date="last10">
<img border="0" src="plugins/Zeitgeist/images/feed.png"/>
</a>
{% endif %}
</span>
{% if properties.show_export_as_image_icon %}
<span id="dataTableFooterExportAsImageIcon">
<a class="tableIcon" href="#" onclick="$(this).closest('.dataTable').find('div.jqplot-target').trigger('piwikExportAsImage'); return false;">
<img title="{{ 'General_ExportAsImage'|translate }}" src="plugins/Zeitgeist/images/image.png"/>
</a>
</span>
{% endif %}
</div>
</div>
<div class="limitSelection {% if not properties.show_pagination_control and not properties.show_limit_control %} hidden{% endif %}"
title="{{ 'General_RowsToDisplay'|translate }}"></div>
<div class="tableConfiguration">
<a class="tableConfigurationIcon" href="#"></a>
<ul>
{% if properties.show_flatten_table %}
{% if clientSideParameters.flat is defined and clientSideParameters.flat == 1 %}
<li>
<div class="configItem dataTableIncludeAggregateRows"></div>
</li>
{% endif %}
<li>
<div class="configItem dataTableFlatten"></div>
</li>
{% endif %}
{% if properties.show_exclude_low_population %}
<li>
<div class="configItem dataTableExcludeLowPopulation"></div>
</li>
{% endif %}
</ul>
</div>
{% if isPluginLoaded('Annotations') and not properties.hide_annotations_view %}
<div class="annotationView" title="{{ 'Annotations_IconDesc'|translate }}">
<a class="tableIcon">
<img width="16" height="16" src="plugins/Zeitgeist/images/annotations.png"/>
</a>
<span>{{ 'Annotations_Annotations'|translate }}</span>
</div>
{% endif %}
<div class="foldDataTableFooterDrawer" title="{{ 'General_Close'|translate|e('html_attr') }}"
><img width="7" height="4" src="plugins/Morpheus/images/sortasc_dark.png"></div>
</div>
<div class="expandDataTableFooterDrawer" title="{{ 'General_ExpandDataTableFooter'|translate|e('html_attr') }}"
><img width="7" height="4" src="plugins/Morpheus/images/sortdesc_dark.png" style=""></div>
{% endif %}
<div class="datatableRelatedReports">
{% if (properties.related_reports is not empty) and properties.show_related_reports %}
{{ properties.related_reports_title|raw }}
<ul style="list-style:none;{% if properties.related_reports|length == 1 %}display:inline-block;{% endif %}}">
<li><span href="{{ properties.self_url }}" style="display:none;">{{ properties.title }}</span></li>
{% for reportUrl,reportTitle in properties.related_reports %}
<li><span href="{{ reportUrl }}">{{ reportTitle }}</span></li>
{% endfor %}
</ul>
{% endif %}
</div>
{% if properties.show_footer_message is defined and properties.show_footer_message is not empty %}
<div class='datatableFooterMessage'>{{ properties.show_footer_message | raw }}</div>
{% endif %}
</div>
<span class="loadingPiwikBelow" style="display:none;"><img src="plugins/Zeitgeist/images/loading-blue.gif"/> {{ 'General_LoadingData'|translate }}</span>
<div class="dataTableSpacer"></div>

View file

@ -0,0 +1,17 @@
<thead>
<tr>
{% for column in properties.columns_to_display %}
<th class="{% if properties.enable_sort %}sortable{% endif %} {% if loop.first %}first{% elseif loop.last %}last{% endif %}" id="{{ column }}">
{% if properties.metrics_documentation[column]|default is not empty %}
<div class="columnDocumentation">
<div class="columnDocumentationTitle">
{{ properties.translations[column]|default(column)|raw }}
</div>
{{ properties.metrics_documentation[column]|raw }}
</div>
{% endif %}
<div id="thDIV">{{ properties.translations[column]|default(column)|raw }}</div>
</th>
{% endfor %}
</tr>
</thead>

View file

@ -0,0 +1,5 @@
<script type="text/javascript" defer="defer">
$(document).ready(function () {
require('piwik/UI/DataTable').initNewDataTables();
});
</script>

View file

@ -0,0 +1,63 @@
<div class="piwik-donate-call">
<div class="piwik-donate-message">
{% if msg is defined %}
{{ msg }}
{% else %}
<p>{{ 'CoreHome_DonateCall1'|translate }}</p>
<p><strong><em>{{ 'CoreHome_DonateCall2'|translate }}</em></strong></p>
<p>{{ 'CoreHome_DonateCall3'|translate('<em><strong>','</strong></em>')|raw }}</p>
{% endif %}
</div>
<span id="piwik-worth">{{ 'CoreHome_HowMuchIsPiwikWorth'|translate }}</span>
<div class="donate-form-instructions">({{ 'CoreHome_DonateFormInstructions'|translate }})</div>
<form action="index.php?module=CoreHome&action=redirectToPaypal&idSite=1" method="post" target="_blank">
<input type="hidden" name="cmd" value="_s-xclick"/>
<input type="hidden" name="hosted_button_id" value="DVKLY73RS7JTE"/>
<input type="hidden" name="currency_code" value="USD"/>
<input type="hidden" name="on0" value="Piwik Supporter"/>
<div class="piwik-donate-slider">
<div class="slider-range">
<div class="slider-position"></div>
</div>
<div style="display:inline-block;">
<div class="slider-donate-amount">$30/{{ 'General_YearShort'|translate }}</div>
<img class="slider-smiley-face" width="40" height="40" src="plugins/Zeitgeist/images/smileyprog_1.png"/>
</div>
<input type="hidden" name="os0" value="Option 1"/>
</div>
<div class="donate-submit">
<input type="image" src="plugins/Zeitgeist/images/paypal_subscribe.gif" border="0" name="submit"
title="{{ 'CoreHome_SubscribeAndBecomePiwikSupporter'|translate }}"/>
<a class="donate-spacer">{{ 'CoreHome_MakeOneTimeDonation'|translate }}</a>
<a href="index.php?module=CoreHome&action=redirectToPaypal&idSite=1&cmd=_s-xclick&hosted_button_id=RPL23NJURMTFA&bb2_screener_=1357583494+83.233.186.82"
target="_blank" class="donate-one-time">{{ 'CoreHome_MakeOneTimeDonation'|translate }}</a>
</div>
<!-- to cache images -->
<img style="display:none;" src="plugins/Zeitgeist/images/smileyprog_0.png"/>
<img style="display:none;" src="plugins/Zeitgeist/images/smileyprog_1.png"/>
<img style="display:none;" src="plugins/Zeitgeist/images/smileyprog_2.png"/>
<img style="display:none;" src="plugins/Zeitgeist/images/smileyprog_3.png"/>
<img style="display:none;" src="plugins/Zeitgeist/images/smileyprog_4.png"/>
</form>
{% if footerMessage is defined %}
<div class="form-description">
{{ footerMessage }}
</div>
{% endif %}
</div>
<script type="text/javascript">
$(document).ready(function () {
// Note: this will cause problems if more than one donate form is on the page
$('.piwik-donate-slider').each(function () {
$(this).trigger('piwik:changePosition', {position: 1});
});
});
</script>

View file

@ -0,0 +1,47 @@
{# testing, remove test_ from var names #}
{% set test_latest_version_available="3.0" %}
{% set test_piwikUrl='http://demo.piwik.org/' %}
{% set isPiwikDemo %}{{ piwikUrl == 'http://demo.piwik.org/' or piwikUrl == 'https://demo.piwik.org/'}}{% endset %}
{% set updateCheck %}
<div id="updateCheckLinkContainer">
<span class='loadingPiwik' style="display:none;"><img src='plugins/Zeitgeist/images/loading-blue.gif'/></span>
<img class="icon" src="plugins/Zeitgeist/images/reload.png"/>
<a href="#" id="checkForUpdates"><em>{{ 'CoreHome_CheckForUpdates'|translate }}</em></a>
</div>
{% endset %}
{% if isPiwikDemo or (latest_version_available and hasSomeViewAccess and not isUserIsAnonymous) or (isSuperUser and adminMenu is defined and adminMenu) %}
<span id="header_message" class="{% if isPiwikDemo or not latest_version_available %}header_info{% else %}header_alert{% endif %}">
<span class="header_short">
{% if isPiwikDemo %}
{{ 'General_YouAreViewingDemoShortMessage'|translate }}
{% elseif latest_version_available %}
{{ 'General_NewUpdatePiwikX'|translate(latest_version_available) }}
{% elseif isSuperUser and adminMenu is defined and adminMenu %}
{{ updateCheck|raw }}
{% endif %}
</span>
<span class="header_full">
{% if isPiwikDemo %}
{{ 'General_YouAreViewingDemoShortMessage'|translate }}
<br />
{{ 'General_DownloadFullVersion'|translate("<a href='http://piwik.org/'>","</a>","<a href='http://piwik.org'>piwik.org</a>")|raw }}
<br/>
{% endif %}
{% if latest_version_available and isSuperUser %}
{{ 'General_PiwikXIsAvailablePleaseUpdateNow'|translate(latest_version_available,"<br /><a href='index.php?module=CoreUpdater&amp;action=newVersionAvailable'>","</a>","<a href='?module=Proxy&amp;action=redirect&amp;url=http://piwik.org/changelog/' target='_blank'>","</a>")|raw }}
<br/>
{{ 'General_YouAreCurrentlyUsing'|translate(piwik_version) }}
{% elseif latest_version_available and not isPiwikDemo and hasSomeViewAccess and not isUserIsAnonymous %}
{% set updateSubject = 'General_NewUpdatePiwikX'|translate(latest_version_available)|e('url') %}
{{ 'General_PiwikXIsAvailablePleaseNotifyPiwikAdmin'|translate("<a href='?module=Proxy&action=redirect&url=http://piwik.org/' target='_blank'>Piwik</a> <a href='?module=Proxy&action=redirect&url=http://piwik.org/changelog/' target='_blank'>" ~ latest_version_available ~ "</a>", "<a href='mailto:" ~ superUserEmails ~ "?subject=" ~ updateSubject ~ "'>", "</a>")|raw }}
{% elseif isSuperUser and adminMenu is defined and adminMenu %}
{{ updateCheck|raw }}
<br />
{{ 'General_YouAreCurrentlyUsing'|translate(piwik_version) }}
{% endif %}
</span>
</span>
{% endif %}

View file

@ -0,0 +1,20 @@
{% import 'ajaxMacros.twig' as ajax %}
<div class="pageWrap">
{% include "@CoreHome/_notifications.twig" %}
<div class="top_controls">
{% include "@CoreHome/_periodSelect.twig" %}
{{ postEvent("Template.nextToCalendar") }}
{% render dashboardSettingsControl %}
{% include "@CoreHome/_headerMessage.twig" %}
{{ ajax.requestErrorDiv }}
</div>
{{ ajax.loadingDiv() }}
<div id="content" class="home">
{% if content %}{{ content }}{% endif %}
</div>
<div class="clear"></div>
</div>
<br/><br/>

View file

@ -0,0 +1,3 @@
<noscript>
<div id="javascriptDisabled">{{ 'CoreHome_JavascriptDisabled'|translate('<a href="">','</a>')|raw }}</div>
</noscript>

View file

@ -0,0 +1,10 @@
<span id="logo">
<a href="index.php" title="{% if isCustomLogo %}{{ 'General_PoweredBy'|translate }} {% endif %}Piwik # {{ 'General_OpenSourceWebAnalytics'|translate }}">
{% if hasSVGLogo %}
<img src='{{ logoSVG }}' alt="{% if isCustomLogo %}{{ 'General_PoweredBy'|translate }} {% endif %}Piwik" class="ie-hide {% if not isCustomLogo %}default-piwik-logo{% endif %}" />
<!--[if lt IE 9]>
{% endif %}
<img src='{{ logoHeader }}' alt="{% if isCustomLogo %}{{ 'General_PoweredBy'|translate }} {% endif %}Piwik" />
{% if hasSVGLogo %}<![endif]-->{% endif %}
</a>
</span>

View file

@ -0,0 +1,23 @@
<div class="Menu--dashboard">
<ul class="Menu-tabList">
{% for level1,level2 in menu %}
<li id="{{ level2._url|urlRewriteWithParameters }}">
<a href="#{{ level2._url|urlRewriteWithParameters|slice(1) }}"
onclick="return piwikMenu.onItemClick(this);">{{ level1|translate }}</a>
<ul>
{% for name,urlParameters in level2 %}
{% if name|slice(0,1) != '_' %}
<li>
<a href='#{{ urlParameters._url|urlRewriteWithParameters|slice(1) }}'
onclick='return piwikMenu.onItemClick(this);'>
{{ name|translate }}
</a>
</li>
{% endif %}
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
</div>
<div class="nav_sep"></div>

View file

@ -0,0 +1,9 @@
<div id="notificationContainer">
{% if notifications|length %}
{% for notificationId, n in notifications %}
{{ n.message|notification({'id': notificationId, 'type': n.type, 'title': n.title, 'noclear': n.hasNoClear, 'context': n.context, 'raw': n.raw}, false) }}
{% endfor %}
{% endif %}
</div>

View file

@ -0,0 +1,37 @@
<div id="periodString" class="piwikTopControl periodSelector">
<div id="date">{{ 'General_DateRange'|translate }} <strong>{{ prettyDate }}</strong></div>
<div class="calendar-icon"></div>
<div id="periodMore">
<div class="period-date">
<h6>{{ 'General_Date'|translate }}</h6>
<div id="datepicker"></div>
</div>
<div class="period-range" style="display:none;">
<div id="calendarRangeFrom">
<h6>{{ 'General_DateRangeFrom'|translate }}<input tabindex="1" type="text" id="inputCalendarFrom" name="inputCalendarFrom"/></h6>
<div id="calendarFrom"></div>
</div>
<div id="calendarRangeTo">
<h6>{{ 'General_DateRangeTo'|translate }}<input tabindex="2" type="text" id="inputCalendarTo" name="inputCalendarTo"/></h6>
<div id="calendarTo"></div>
</div>
</div>
<div class="period-type">
<h6>{{ 'General_Period'|translate }}</h6>
<span id="otherPeriods">
{% for label,thisPeriod in periodsNames %}
<input type="radio" name="period" id="period_id_{{ label }}" value="{{ linkTo( { 'period': label} ) }}"{% if label==period %} checked="checked"{% endif %} />
<label for="period_id_{{ label }}">{{ thisPeriod.singular }}</label>
<br/>
{% endfor %}
</span>
<input tabindex="3" type="submit" value="{{ 'General_ApplyDateRange'|translate }}" id="calendarRangeApply"/>
{% import 'ajaxMacros.twig' as ajax %}
{{ ajax.loadingDiv('ajaxLoadingCalendar') }}
</div>
</div>
<div class="period-click-tooltip" style="display:none;">{{ 'General_ClickToChangePeriod'|translate }}</div>
</div>

View file

@ -0,0 +1,2 @@
<h2 piwik-enriched-headline>{{ title }}</h2>
{{ report|raw }}

View file

@ -0,0 +1,5 @@
<div class="top_bar_sites_selector {% if currentModule == 'CoreHome' %}sites_selector_in_dashboard{% endif %}">
<label>{{ 'General_Website'|translate }}</label>
<div piwik-siteselector class="sites_autocomplete"></div>
</div>

View file

@ -0,0 +1,5 @@
{{ postEvent("Template.beforeTopBar", userAlias, userLogin, topMenu) }}
<div id="topBars">
{% include "@CoreHome/_topBarHelloMenu.twig" %}
{% include "@CoreHome/_topBarTopMenu.twig" %}
</div>

View file

@ -0,0 +1,25 @@
<div id="topRightBar">
{% set helloAlias %}
{% if userAlias is not empty %}
<strong>{{ userAlias }}</strong>
{% else %}
<strong>{{ userLogin }}</strong>
{% endif %}
{% endset %}
<span class="topBarElem">{{ 'General_HelloUser'|translate(helloAlias|trim)|raw }}</span>
{% if userLogin != 'anonymous' %}
|
{% if isAdminLayout is defined %}
<span class="topBarElem topBarElemActive">{{ 'General_Settings'|translate }}</span>
{% else %}
<span class="topBarElem"><a href='index.php?module=CoreAdminHome'>{{ 'General_Settings'|translate }}</a></span>
{% endif %}
{% endif %}
| <span class="topBarElem">
{% if userLogin == 'anonymous' %}
<a href='index.php?module={{ loginModule }}'>{{ 'Login_LogIn'|translate }}</a>
{% else %}
<a href='index.php?module={{ loginModule }}&amp;action=logout'>{{ 'General_Logout'|translate }}</a>
{% endif %}
</span>
</div>

View file

@ -0,0 +1,13 @@
<div id="topLeftBar">
{% for label,menu in topMenu %}
{% if menu._html is defined %}
{{ menu._html|raw }}
{% elseif (menu._url.module == currentModule and (menu._url.action is empty or menu._url.action == currentAction)) %}
<span class="topBarElem topBarElemActive"><strong>{{ label|translate }}</strong></span>{% if not loop.last %} |{% endif %}
{% else %}
<span class="topBarElem" {% if menu._tooltip is defined %}title="{{ menu._tooltip }}"{% endif %}>
<a id="topmenu-{{ menu._url.module|lower }}" href="index.php{{ menu._url|urlRewriteWithParameters }}">{{ label|translate }}</a>
</span>{% if not loop.last %} | {% endif %}
{% endif %}
{% endfor %}
</div>

Some files were not shown because too many files have changed in this diff Show more