diff --git a/oss/piwik/trunk/plugins/VisitFrequency/API.php b/oss/piwik/trunk/plugins/VisitFrequency/API.php
new file mode 100644
index 00000000..c1906885
--- /dev/null
+++ b/oss/piwik/trunk/plugins/VisitFrequency/API.php
@@ -0,0 +1,100 @@
+getDataTableFromNumeric($toFetch);
+ if($bounceRateReturningRequested !== false)
+ {
+ $dataTable->filter('ColumnCallbackAddColumnPercentage', array('bounce_rate_returning', 'bounce_count_returning', 'nb_visits_returning', 0));
+ $dataTable->deleteColumns($toFetch);
+ }
+ return $dataTable;
+ }
+
+ protected function getNumeric( $idSite, $period, $date, $toFetch )
+ {
+ Piwik::checkUserHasViewAccess( $idSite );
+ $archive = Piwik_Archive::build($idSite, $period, $date );
+ $dataTable = $archive->getNumeric($toFetch);
+ return $dataTable;
+ }
+
+ public function getVisitsReturning( $idSite, $period, $date )
+ {
+ return $this->getNumeric( $idSite, $period, $date, 'nb_visits_returning');
+ }
+
+ public function getActionsReturning( $idSite, $period, $date )
+ {
+ return $this->getNumeric( $idSite, $period, $date, 'nb_actions_returning');
+ }
+
+ public function getMaxActionsReturning( $idSite, $period, $date )
+ {
+ return $this->getNumeric( $idSite, $period, $date, 'max_actions_returning');
+ }
+
+ public function getSumVisitsLengthReturning( $idSite, $period, $date )
+ {
+ return $this->getNumeric( $idSite, $period, $date, 'sum_visit_length_returning');
+ }
+
+ public function getBounceCountReturning( $idSite, $period, $date )
+ {
+ return $this->getNumeric( $idSite, $period, $date, 'bounce_count_returning');
+ }
+
+ public function getConvertedVisitsReturning( $idSite, $period, $date )
+ {
+ return $this->getNumeric( $idSite, $period, $date, 'nb_visits_converted_returning');
+ }
+}
diff --git a/oss/piwik/trunk/plugins/VisitFrequency/Controller.php b/oss/piwik/trunk/plugins/VisitFrequency/Controller.php
new file mode 100644
index 00000000..48c9f2e5
--- /dev/null
+++ b/oss/piwik/trunk/plugins/VisitFrequency/Controller.php
@@ -0,0 +1,78 @@
+graphEvolutionVisitFrequency = $this->getEvolutionGraph(true, array('nb_visits_returning') );
+ $this->setSparklinesAndNumbers($view);
+ echo $view->render();
+ }
+
+ public function getSparklines()
+ {
+ $view = Piwik_View::factory('sparklines');
+ $this->setSparklinesAndNumbers($view);
+ echo $view->render();
+ }
+
+ public function getEvolutionGraph( $fetch = false, $columns = false)
+ {
+ $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, "VisitFrequency.get");
+ if(empty($columns))
+ {
+ $columns = Piwik_Common::getRequestVar('columns');
+ }
+ $view->setColumnsToDisplay($columns);
+ $view->setColumnsTranslations(array(
+ 'nb_visits_returning' => Piwik_Translate('VisitFrequency_ColumnReturningVisits'),
+ 'nb_actions_returning' => Piwik_Translate('VisitFrequency_ColumnActionsByReturningVisits'),
+ 'max_actions_returning' => Piwik_Translate('VisitFrequency_ColumnMaximumActionsByAReturningVisit'),
+ 'sum_visit_length_returning' => Piwik_Translate('VisitFrequency_ColumnTotalTimeSpentByReturningVisits'),
+ 'bounce_rate_returning' => Piwik_Translate('VisitFrequency_ColumnBounceRateForReturningVisits'),
+ ));
+ return $this->renderView($view, $fetch);
+ }
+
+ protected function setSparklinesAndNumbers($view)
+ {
+ $view->urlSparklineNbVisitsReturning = $this->getUrlSparkline( 'getEvolutionGraph', array('columns' => array('nb_visits_returning')));
+ $view->urlSparklineNbActionsReturning = $this->getUrlSparkline( 'getEvolutionGraph', array('columns' => array('nb_actions_returning')));
+ $view->urlSparklineMaxActionsReturning = $this->getUrlSparkline( 'getEvolutionGraph', array('columns' => array('max_actions_returning')));
+ $view->urlSparklineSumVisitLengthReturning = $this->getUrlSparkline( 'getEvolutionGraph', array('columns' => array('sum_visit_length_returning')));
+ $view->urlSparklineBounceRateReturning = $this->getUrlSparkline( 'getEvolutionGraph', array('columns' => array('bounce_rate_returning')));
+
+ $dataTableFrequency = $this->getSummary();
+ $dataRow = $dataTableFrequency->getFirstRow();
+ $nbVisitsReturning = $dataRow->getColumn('nb_visits_returning');
+ $view->nbVisitsReturning = $nbVisitsReturning;
+ $view->nbActionsReturning = $dataRow->getColumn('nb_actions_returning');
+ $view->maxActionsReturning = $dataRow->getColumn('max_actions_returning');
+ $view->sumVisitLengthReturning = $dataRow->getColumn('sum_visit_length_returning');
+ $nbBouncedReturningVisits = $dataRow->getColumn('bounce_count_returning');
+ $view->bounceRateReturning = Piwik::getPercentageSafe($nbBouncedReturningVisits, $nbVisitsReturning);
+
+ }
+
+ protected function getSummary()
+ {
+ $requestString = "method=VisitFrequency.get&format=original";
+ $request = new Piwik_API_Request($requestString);
+ return $request->process();
+ }
+}
diff --git a/oss/piwik/trunk/plugins/VisitFrequency/VisitFrequency.php b/oss/piwik/trunk/plugins/VisitFrequency/VisitFrequency.php
new file mode 100644
index 00000000..8873a417
--- /dev/null
+++ b/oss/piwik/trunk/plugins/VisitFrequency/VisitFrequency.php
@@ -0,0 +1,102 @@
+ Piwik_Translate('VisitFrequency_PluginDescription'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => Piwik_Version::VERSION,
+ );
+ return $info;
+ }
+
+ function getListHooksRegistered()
+ {
+ $hooks = array(
+ 'ArchiveProcessing_Day.compute' => 'archiveDay',
+ 'ArchiveProcessing_Period.compute' => 'archivePeriod',
+ 'WidgetsList.add' => 'addWidgets',
+ 'Menu.add' => 'addMenu',
+ );
+ return $hooks;
+ }
+
+ function addWidgets()
+ {
+ Piwik_AddWidget( 'General_Visitors', 'VisitFrequency_WidgetOverview', 'VisitFrequency', 'getSparklines');
+ Piwik_AddWidget( 'General_Visitors', 'VisitFrequency_WidgetGraphReturning', 'VisitFrequency', 'getEvolutionGraph', array('columns' => array('nb_visits_returning')));
+ }
+
+ function addMenu()
+ {
+ Piwik_AddMenu('General_Visitors', 'VisitFrequency_SubmenuFrequency', array('module' => 'VisitFrequency', 'action' => 'index'));
+ }
+
+ function archivePeriod( $notification )
+ {
+ $archiveProcessing = $notification->getNotificationObject();
+
+ $numericToSum = array(
+ 'nb_visits_returning',
+ 'nb_actions_returning',
+ 'sum_visit_length_returning',
+ 'bounce_count_returning',
+ 'nb_visits_converted_returning',
+ );
+ $archiveProcessing->archiveNumericValuesSum($numericToSum);
+ $archiveProcessing->archiveNumericValuesMax('max_actions_returning');
+ }
+
+ function archiveDay($notification)
+ {
+ /* @var $archiveProcessing Piwik_ArchiveProcessing */
+ $archiveProcessing = $notification->getNotificationObject();
+
+ $query = "SELECT count(distinct visitor_idcookie) as nb_uniq_visitors_returning,
+ count(*) as nb_visits_returning,
+ sum(visit_total_actions) as nb_actions_returning,
+ max(visit_total_actions) as max_actions_returning,
+ sum(visit_total_time) as sum_visit_length_returning,
+ sum(case visit_total_actions when 1 then 1 else 0 end) as bounce_count_returning,
+ sum(case visit_goal_converted when 1 then 1 else 0 end) as nb_visits_converted_returning
+ FROM ".$archiveProcessing->logTable."
+ WHERE visit_last_action_time >= ?
+ AND visit_last_action_time <= ?
+ AND idsite = ?
+ AND visitor_returning = 1";
+ $row = $archiveProcessing->db->fetchRow($query, array( $archiveProcessing->getStartDatetimeUTC(), $archiveProcessing->getEndDatetimeUTC(), $archiveProcessing->idsite ) );
+
+ if($row === false || $row === null)
+ {
+ $row['nb_visits_returning'] = 0;
+ $row['nb_actions_returning'] = 0;
+ $row['max_actions_returning'] = 0;
+ $row['sum_visit_length_returning'] = 0;
+ $row['bounce_count_returning'] = 0;
+ $row['nb_visits_converted_returning'] = 0;
+ }
+
+ foreach($row as $name => $value)
+ {
+ $archiveProcessing->insertNumericRecord($name, $value);
+ }
+ }
+}
+
diff --git a/oss/piwik/trunk/plugins/VisitFrequency/templates/index.tpl b/oss/piwik/trunk/plugins/VisitFrequency/templates/index.tpl
new file mode 100644
index 00000000..1d7e4831
--- /dev/null
+++ b/oss/piwik/trunk/plugins/VisitFrequency/templates/index.tpl
@@ -0,0 +1,11 @@
+{postEvent name="template_headerVisitsFrequency"}
+
+
+
+
{'VisitFrequency_ColumnReturningVisits'|translate}
+{$graphEvolutionVisitFrequency}
+
+
+{include file=VisitFrequency/templates/sparklines.tpl}
+
+{postEvent name="template_footerVisitsFrequency"}
diff --git a/oss/piwik/trunk/plugins/VisitFrequency/templates/sparklines.tpl b/oss/piwik/trunk/plugins/VisitFrequency/templates/sparklines.tpl
new file mode 100644
index 00000000..a72ae2eb
--- /dev/null
+++ b/oss/piwik/trunk/plugins/VisitFrequency/templates/sparklines.tpl
@@ -0,0 +1,11 @@
+{sparkline src=$urlSparklineNbVisitsReturning}
+{'VisitFrequency_ReturnVisits'|translate:"$nbVisitsReturning"}
+{sparkline src=$urlSparklineNbActionsReturning}
+{'VisitFrequency_ReturnActions'|translate:"$nbActionsReturning"}
+{sparkline src=$urlSparklineMaxActionsReturning}
+ {'VisitFrequency_ReturnMaxActions'|translate:"$maxActionsReturning"}
+{sparkline src=$urlSparklineSumVisitLengthReturning}
+ {assign var=sumtimeVisitLengthReturning value=$sumVisitLengthReturning|sumtime}
+ {'VisitFrequency_ReturnTotalTime'|translate:"$sumtimeVisitLengthReturning"}
+{sparkline src=$urlSparklineBounceRateReturning}
+ {'VisitFrequency_ReturnBounceRate'|translate:"$bounceRateReturning%"}
\ No newline at end of file
diff --git a/oss/piwik/trunk/plugins/VisitorGenerator/Controller.php b/oss/piwik/trunk/plugins/VisitorGenerator/Controller.php
new file mode 100644
index 00000000..c20089e0
--- /dev/null
+++ b/oss/piwik/trunk/plugins/VisitorGenerator/Controller.php
@@ -0,0 +1,118 @@
+getSitesWithAdminAccess();
+
+ $view = Piwik_View::factory('index');
+ $this->setGeneralVariablesView($view);
+ $view->assign('sitesList', $sitesList);
+
+ $view->menu = Piwik_GetAdminMenu();
+ echo $view->render();
+ }
+
+ public function generate() {
+ // Only admin is allowed to do this!
+ Piwik::checkUserIsSuperUser();
+ $this->checkTokenInUrl();
+
+ $GET = $_GET;
+ $POST = $_POST;
+ $COOKIE = $_COOKIE;
+ $REQUEST = $_REQUEST;
+
+ if(Piwik_Common::getRequestVar('choice', 'no') != 'yes') {
+ Piwik::redirectToModule('VisitorGenerator', 'index');
+ }
+
+ $minVisitors = Piwik_Common::getRequestVar('minVisitors', 20, 'int');
+ $maxVisitors = Piwik_Common::getRequestVar('maxVisitors', 100, 'int');
+ $nbActions = Piwik_Common::getRequestVar('nbActions', 10, 'int');
+ $daysToCompute = Piwik_Common::getRequestVar('daysToCompute', 1, 'int');
+ $idSite = Piwik_Common::getRequestVar('idSite');
+ Piwik::setMaxExecutionTime(0);
+
+ $loadedPlugins = Piwik_PluginsManager::getInstance()->getLoadedPlugins();
+ $loadedPlugins = array_keys($loadedPlugins);
+ // we have to unload the Provider plugin otherwise it tries to lookup the IP for a hostname, and there is no dns server here
+ if(Piwik_PluginsManager::getInstance()->isPluginActivated('Provider')) {
+ Piwik_PluginsManager::getInstance()->unloadPlugin('Provider');
+ }
+
+ // we set the DO NOT load plugins so that the Tracker generator doesn't load the plugins we've just disabled.
+ // if for some reasons you want to load the plugins, comment this line, and disable the plugin Provider in the plugins interface
+ Piwik_PluginsManager::getInstance()->doNotLoadPlugins();
+
+ $generator = new Piwik_VisitorGenerator_Generator();
+ $generator->setMaximumUrlDepth(3);
+
+ //$generator->disableProfiler();
+ $generator->setIdSite( $idSite );
+
+ $nbActionsTotal = 0;
+ //$generator->emptyAllLogTables();
+ $generator->init();
+
+ $timer = new Piwik_Timer;
+
+ $startTime = time() - ($daysToCompute-1)*86400;
+ $dates = array();
+ while($startTime <= time()) {
+ $visitors = rand($minVisitors, $maxVisitors);
+ $actions = $nbActions;
+ $generator->setTimestampToUse($startTime);
+
+ $nbActionsTotalThisDay = $generator->generate($visitors, $actions);
+ $actionsPerVisit = round($nbActionsTotalThisDay / $visitors);
+
+ $date = array();
+ $date['visitors'] = $visitors;
+ $date['actionsPerVisit'] = $actionsPerVisit;
+ $date['startTime'] = $startTime;
+ $dates[] = $date;
+
+ $startTime += 86400;
+ $nbActionsTotal += $nbActionsTotalThisDay;
+ //sleep(1);
+ }
+
+ $generator->end();
+
+ // Recover all super globals
+ $_GET = $GET;
+ $_POST = $POST;
+ $_COOKIE = $COOKIE;
+ $_REQUEST = $REQUEST;
+
+ // Reload plugins
+ Piwik_PluginsManager::getInstance()->loadPlugins($loadedPlugins);
+
+ // Init view
+ $view = Piwik_View::factory('generate');
+ $view->menu = Piwik_GetAdminMenu();
+ $this->setGeneralVariablesView($view);
+ $view->assign('dates', $dates);
+ $view->assign('timer', $timer);
+ $view->assign('nbActionsTotal', $nbActionsTotal);
+ $view->assign('nbRequestsPerSec', round($nbActionsTotal / $timer->getTime(),0));
+ echo $view->render();
+ }
+}
diff --git a/oss/piwik/trunk/plugins/VisitorGenerator/Generator.php b/oss/piwik/trunk/plugins/VisitorGenerator/Generator.php
new file mode 100644
index 00000000..9017199b
--- /dev/null
+++ b/oss/piwik/trunk/plugins/VisitorGenerator/Generator.php
@@ -0,0 +1,623 @@
+ campaigns
+ * - campaign CPC
+ * - referer
+ * - search engine
+ * - misc site
+ * - same website
+ * - url => multiple directories, page names
+ * - multiple idsite
+ * - multiple settings configurations
+ * - action_name
+ * - HTML title
+ *
+ * @package Piwik_VisitorGenerator
+ *
+ * "Le Generator, il est trop Fort!"
+ * - Random fan
+ */
+class Piwik_VisitorGenerator_Generator
+{
+ /**
+ * GET parameters array of values to be used for the current visit
+ *
+ * @var array ('res' => '1024x768', 'urlref' => 'http://google.com/search?q=piwik', ...)
+ */
+ protected $currentget = array();
+
+ /**
+ * Array of all the potential values for the visit parameters
+ * Values of 'resolution', 'urlref', etc. will be randomly read from this array
+ *
+ * @var array (
+ * 'res' => array('1024x768','800x600'),
+ * 'urlref' => array('google.com','intel.com','amazon.com'),
+ * ....)
+ */
+ protected $allget = array();
+
+ /**
+ * See @see setMaximumUrlDepth
+ *
+ * @var int
+ */
+ protected $maximumUrlDepth = 1;
+
+ /**
+ * Unix timestamp to use for the generated visitor
+ *
+ * @var int Unix timestamp
+ */
+ protected $timestampToUse;
+
+ /**
+ * See @see disableProfiler()
+ * The profiler is enabled by default
+ *
+ * @var bool
+ */
+ protected $profiling = true;
+
+ /**
+ * If set to true, this will TRUNCATE the profiling tables at every new generated visit
+ * @see initProfiler()
+ *
+ * @var bool
+ */
+ public $reinitProfilingAtEveryRequest = true;
+
+ /**
+ * Hostname used to prefix all the generated URLs
+ * we could make this variable dynamic so that a visitor can make hit on several hosts and
+ * only the good ones should be kept (feature not yet implemented in piwik)
+ *
+ * @var string
+ */
+ public $host = 'http://localhost';
+
+ /**
+ * IdSite to generate visits for (@see setIdSite())
+ *
+ * @var int
+ */
+ public $idSite = 1;
+
+ /**
+ * Overwrite the global GET/POST/COOKIE variables and set the fake ones @see setFakeRequest()
+ * Reads the configuration file but disables write to this file
+ * Creates the database object & enable profiling by default (@see disableProfiler())
+ *
+ */
+ public function __construct()
+ {
+ $_COOKIE = $_GET = $_POST = array();
+
+ // init GET and REQUEST to the empty array
+ $this->setFakeRequest();
+
+ Piwik::createConfigObject(PIWIK_USER_PATH . '/config/config.ini.php');
+ Zend_Registry::get('config')->disableSavingConfigurationFileUpdates();
+
+ // setup database
+ Piwik::createDatabaseObject();
+
+ Piwik_Tracker_Db::enableProfiling();
+
+ $this->timestampToUse = time();
+ }
+
+ /**
+ * Sets the depth level of the generated URLs
+ * value = 1 => path OR path/page1
+ * value = 2 => path OR path/pageRand OR path/dir1/pageRand
+ *
+ * @param int Depth
+ */
+ public function setMaximumUrlDepth($value)
+ {
+ $this->maximumUrlDepth = (int)$value;
+ }
+
+ /**
+ * Set the timestamp to use as the starting time for the visitors times
+ * You have to call this method for every day you want to generate data
+ *
+ * @param int Unix timestamp
+ */
+ public function setTimestampToUse($timestamp)
+ {
+ $this->timestampToUse = $timestamp;
+ }
+
+ /**
+ * Returns the timestamp to be used as the visitor timestamp
+ *
+ * @return int Unix timestamp
+ */
+ public function getTimestampToUse()
+ {
+ return $this->timestampToUse;
+ }
+
+ /**
+ * Set the idsite to generate the visits for
+ * To be called before init()
+ *
+ * @param int idSite
+ */
+ public function setIdSite($idSite)
+ {
+ $this->idSite = $idSite;
+ }
+
+ /**
+ * Add a value to the GET global array.
+ * The generator script will then randomly read a value from this array.
+ *
+ * For example, $name = 'res' $aValue = '1024x768'
+ *
+ * @param string Name of the parameter _GET[$name]
+ * @param array|mixed Value of the parameter
+ */
+ protected function addParam( $name, $aValue)
+ {
+ if(is_array($aValue))
+ {
+ $this->allget[$name] = array_merge( $aValue,
+ (array)@$this->allget[$name]);
+ }
+ else
+ {
+ $this->allget[$name][] = $aValue;
+ }
+ }
+
+ /**
+ * TRUNCATE all logs related tables to start a fresh logging database.
+ * Be careful, any data deleted this way is deleted forever
+ */
+ public function emptyAllLogTables()
+ {
+ $db = Zend_Registry::get('db');
+ $db->query('TRUNCATE TABLE '.Piwik_Common::prefixTable('log_action'));
+ $db->query('TRUNCATE TABLE '.Piwik_Common::prefixTable('log_visit'));
+ $db->query('TRUNCATE TABLE '.Piwik_Common::prefixTable('log_link_visit_action'));
+ }
+
+ /**
+ * Call this method to disable the SQL query profiler
+ */
+ public function disableProfiler()
+ {
+ $this->profiling = false;
+ Piwik_Tracker_Db::disableProfiling();
+ }
+
+ /**
+ * This is called at the end of the Generator script.
+ * Calls the Profiler output if the profiler is enabled.
+ */
+ public function end()
+ {
+ if($this->profiling)
+ {
+ Piwik::printSqlProfilingReportTracker();
+ }
+ Piwik_Tracker::disconnectDatabase();
+ }
+
+ /**
+ * Init the Generator script:
+ * - init the SQL profiler
+ * - init the random generator
+ * - setup the different possible values for parameters such as 'resolution',
+ * 'color', 'hour', 'minute', etc.
+ * - load from DataFiles and setup values for the other parameters such as UserAgent, Referers, AcceptedLanguages, etc.
+ * @see misc/generateVisitsData/
+ */
+ public function init()
+ {
+ Piwik::createLogObject();
+
+ $this->initProfiler();
+
+ /*
+ * Init the random number generator
+ */
+ function make_seed()
+ {
+ list($usec, $sec) = explode(' ', microtime());
+ return (float) $sec + ((float) $usec * 100000);
+ }
+ mt_srand(make_seed());
+
+ // set rec=1 parameter, required as of 0.5.5 in order to force the request to be recorded
+ $this->setCurrentRequest('rec', 1);
+
+ /*
+ * Sets values for: resolutions, colors, idSite, times
+ */
+ $common = array(
+ 'res' => array('1289x800','1024x768','800x600','564x644','200x100','50x2000',),
+ 'col' => array(24,32,16),
+ 'idsite'=> $this->idSite,
+ 'h' => range(0,23),
+ 'm' => range(0,59),
+ 's' => range(0,59),
+ );
+
+ foreach($common as $label => $values)
+ {
+ $this->addParam($label,$values);
+ }
+
+ /*
+ * Sets values for: outlinks, downloads, campaigns
+ */
+ // we get the name of the Download/outlink variables
+ $downloadOrOutlink = array('download', 'link');
+
+ // we have a 20% chance to add a download or outlink variable to the URL
+ $this->addParam('piwik_downloadOrOutlink', $downloadOrOutlink);
+ $this->addParam('piwik_downloadOrOutlink', array_fill(0,8,''));
+
+ // we get the variables name for the campaign parameters
+ $campaigns = array(
+ Piwik_Tracker_Config::getInstance()->Tracker['campaign_var_name']
+ );
+ // we generate a campaign in the URL in 3/18 % of the generated URls
+ $this->addParam('piwik_vars_campaign', $campaigns);
+ $this->addParam('piwik_vars_campaign', array_fill(0,15,''));
+
+
+ /*
+ * Sets values for: Referers, user agents, accepted languages
+ */
+ // we load some real referers to be used by the generator
+ $referers = array();
+ require_once PIWIK_INCLUDE_PATH . '/plugins/VisitorGenerator/data/Referers.php';
+
+ $this->addParam('urlref',$referers);
+
+ // and we add 2000 empty referers so that some visitors don't come using a referer (direct entry)
+ $this->addParam('urlref',array_fill(0,2000,''));
+
+ // load some user agent and accept language
+ $userAgent = $acceptLanguages = array();
+ require_once PIWIK_INCLUDE_PATH . '/plugins/VisitorGenerator/data/UserAgent.php';
+ require_once PIWIK_INCLUDE_PATH . '/plugins/VisitorGenerator/data/AcceptLanguage.php';
+ $this->userAgents=$userAgent;
+ $this->acceptLanguage=$acceptLanguages;
+ }
+
+ /**
+ * If the SQL profiler is enabled and if the reinit at every request is set to true,
+ * then we TRUNCATE the profiling information so that we only profile one visitor at a time
+ */
+ protected function initProfiler()
+ {
+ /*
+ * Inits the profiler
+ */
+ if($this->profiling)
+ {
+ if($this->reinitProfilingAtEveryRequest)
+ {
+ $all = Piwik_Query('TRUNCATE TABLE '.Piwik_Common::prefixTable('log_profiling').'' );
+ }
+ }
+ }
+ /**
+ * Launches the process and generates an exact number of nbVisitors
+ * For each visit, we setup the timestamp to the common timestamp
+ * Then we generate between 1 and nbActionsMaxPerVisit actions for this visit
+ * The generated actions will have a growing timestamp so it looks like a real visit
+ *
+ * @param int The number of visits to generate
+ * @param int The maximum number of actions to generate per visit
+ *
+ * @return int The number of total actions generated
+ */
+ public function generate( $nbVisitors, $nbActionsMaxPerVisit )
+ {
+ $nbActionsTotal = 0;
+ for($i = 0; $i < $nbVisitors; $i++)
+ {
+ $nbActions = mt_rand(1, $nbActionsMaxPerVisit);
+ Piwik_VisitorGenerator_Visit::setTimestampToUse($this->getTimestampToUse());
+
+ $this->generateNewVisit();
+ for($j = 1; $j <= $nbActions; $j++)
+ {
+ $this->generateActionVisit();
+ $this->saveVisit();
+ }
+ $nbActionsTotal += $nbActions;
+ }
+ return $nbActionsTotal;
+ }
+
+ /**
+ * Generates a new visitor.
+ * Loads random values for all the necessary parameters (resolution, local time, referers, etc.) from the fake GET array.
+ * Also generates a random IP.
+ *
+ * We change the superglobal values of HTTP_USER_AGENT, HTTP_CLIENT_IP, HTTP_ACCEPT_LANGUAGE to the generated value.
+ */
+ protected function generateNewVisit()
+ {
+ $this->setCurrentRequest( 'urlref' , $this->getRandom('urlref'));
+ $this->setCurrentRequest( 'idsite', $this->getRandom('idsite'));
+ $this->setCurrentRequest( 'res' ,$this->getRandom('res'));
+ $this->setCurrentRequest( 'col' ,$this->getRandom('col'));
+ $this->setCurrentRequest( 'h' ,$this->getRandom('h'));
+ $this->setCurrentRequest( 'm' ,$this->getRandom('m'));
+ $this->setCurrentRequest( 's' ,$this->getRandom('s'));
+ $this->setCurrentRequest( 'fla' ,$this->getRandom01());
+ $this->setCurrentRequest( 'java' ,$this->getRandom01());
+ $this->setCurrentRequest( 'dir' ,$this->getRandom01());
+ $this->setCurrentRequest( 'qt' ,$this->getRandom01());
+ $this->setCurrentRequest( 'realp' ,$this->getRandom01());
+ $this->setCurrentRequest( 'pdf' ,$this->getRandom01());
+ $this->setCurrentRequest( 'wma' ,$this->getRandom01());
+ $this->setCurrentRequest( 'gears' ,$this->getRandom01());
+ $this->setCurrentRequest( 'ag' ,$this->getRandom01());
+ $this->setCurrentRequest( 'cookie',$this->getRandom01());
+
+ $_SERVER['HTTP_CLIENT_IP'] = mt_rand(0,255).".".mt_rand(0,255).".".mt_rand(0,255).".".mt_rand(0,255);
+ $_SERVER['HTTP_USER_AGENT'] = $this->userAgents[mt_rand(0,count($this->userAgents)-1)];
+ $_SERVER['HTTP_ACCEPT_LANGUAGE'] = $this->acceptLanguage[mt_rand(0,count($this->acceptLanguage)-1)];
+ }
+
+ /**
+ * Generates a new action for the current visitor.
+ * We random generate some campaigns, action names, download or outlink clicks, etc.
+ * We generate a new Referer, that would be read in the case the visit last page is older than 30 minutes.
+ *
+ * This function tries to generate actions that use the features of Piwik (campaigns, downloads, outlinks, action_name set in the JS tag, etc.)
+ */
+ protected function generateActionVisit()
+ {
+ // we don't keep the previous action values
+ // reinit them to empty string
+ $this->setCurrentRequest( 'download', '');
+ $this->setCurrentRequest( 'link', '');
+ $this->setCurrentRequest( 'action_name', '');
+
+ // generate new url referer ; case the visitor stays more than 30min
+ // (when the visit is known this value will simply be ignored)
+ $this->setCurrentRequest( 'urlref' , $this->getRandom('urlref'));
+
+ // generates the current URL
+ $url = $this->getRandomUrlFromHost($this->host);
+
+ // we generate a campaign
+ $urlVars = $this->getRandom('piwik_vars_campaign');
+
+ // if we actually generated a campaign
+ if(!empty($urlVars))
+ {
+ // campaign name
+ $urlValue = $this->getRandomString(5,3,'lower');
+
+ // add the parameter to the url
+ $url .= '?'. $urlVars . '=' . $urlValue;
+
+ // for a campaign of the CPC kind, we sometimes generate a keyword
+ if($urlVars == Piwik_Tracker_Config::getInstance()->Tracker['campaign_var_name']
+ && mt_rand(0,1)==0)
+ {
+ $url .= '&'. Piwik_Tracker_Config::getInstance()->Tracker['campaign_keyword_var_name']
+ . '=' . $this->getRandomString(6,3,'ALL');;
+ }
+ }
+ else
+ {
+ // we generate a download Or Outlink parameter in the GET request so that
+ // the current action is counted as a download action OR a outlink click action
+ $GETParamToAdd = $this->getRandom('piwik_downloadOrOutlink');
+ if(!empty($GETParamToAdd))
+ {
+
+ $possibleDownloadHosts = array('http://piwik.org/',$this->host);
+ $nameDownload = $this->getRandomUrlFromHost($possibleDownloadHosts[mt_rand(0,1)]);
+ $extensions = array('.zip','.tar.gz');
+ $nameDownload .= $extensions[mt_rand(0,1)];
+ $urlValue = $nameDownload;
+
+ // add the parameter to the url
+ $this->setCurrentRequest( $GETParamToAdd , $urlValue);
+ }
+ }
+
+ $this->setCurrentRequest( 'url' ,$url);
+
+ // setup the title of the page
+ $this->setCurrentRequest( 'action_name',$this->getRandomString(15,5));
+ }
+
+ /**
+ * Returns a random URL using the $host as the URL host.
+ * Depth level depends on @see setMaximumUrlDepth()
+ *
+ * @param string Hostname of the URL to generate, eg. http://example.com/
+ *
+ * @return string The generated URL
+ */
+ protected function getRandomUrlFromHost( $host )
+ {
+ $url = $host;
+
+ $deep = mt_rand(0,$this->maximumUrlDepth);
+ for($i=0;$i<$deep;$i++)
+ {
+ $name = $this->getRandomString(1,1,'alnum');
+
+ $url .= '/'.$name;
+ }
+ return $url;
+ }
+
+ /**
+ * Generates a random string from minLength to maxLength using a specified set of characters
+ *
+ * Taken from php.net and then badly hacked by some unknown monkey
+ *
+ * @param int (optional) Maximum length of the string to generate
+ * @param int (optional) Minimum length of the string to generate
+ * @param string (optional) Characters set to use, 'ALL' or 'lower' or 'upper' or 'numeric' or 'ALPHA' or 'ALNUM'
+ *
+ * @return string The generated random string
+ */
+ protected function getRandomString($maxLength = 15, $minLength = 5, $type = 'ALL')
+ {
+ $len = mt_rand($minLength, $maxLength);
+
+ // Register the lower case alphabet array
+ $alpha = array('a', 'd', 'e', 'f', 'g');
+
+ // Register the upper case alphabet array
+ $ALPHA = array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z');
+
+ // Register the numeric array
+ $num = array('1', '2', '3', '8', '9', '0');
+
+ // Register the strange array
+ $strange = array('/', '?', '!','"','£','$','%','^','&','*','(',')',' ');
+
+ // Initialize the keyVals array for use in the for loop
+ $keyVals = array();
+
+ // Initialize the key array to register each char
+ $key = array();
+
+ // Loop through the choices and register
+ // The choice to keyVals array
+ switch ($type)
+ {
+ case 'lower' :
+ $keyVals = $alpha;
+ break;
+ case 'upper' :
+ $keyVals = $ALPHA;
+ break;
+ case 'numeric' :
+ $keyVals = $num;
+ break;
+ case 'ALPHA' :
+ $keyVals = array_merge($alpha, $ALPHA);
+ break;
+ case 'alnum' :
+ $keyVals = array_merge($alpha, $num);
+ break;
+ case 'ALNUM' :
+ $keyVals = array_merge($alpha, $ALPHA, $num);
+ break;
+ case 'ALL' :
+ $keyVals = array_merge($alpha, $ALPHA, $num, $strange);
+ break;
+ }
+
+ // Loop as many times as specified
+ // Register each value to the key array
+ for($i = 0; $i <= $len-1; $i++)
+ {
+ $r = mt_rand(0,count($keyVals)-1);
+ $key[$i] = $keyVals[$r];
+ }
+
+ // Glue the key array into a string and return it
+ return join("", $key);
+ }
+
+ /**
+ * Sets the _GET and _REQUEST superglobal to the current generated array of values.
+ * @see setCurrentRequest()
+ * This method is called once the current action parameters array has been generated from
+ * the global parameters array
+ */
+ protected function setFakeRequest()
+ {
+ $_GET = $this->currentget;
+ }
+
+ /**
+ * Sets a value in the current action request array.
+ *
+ * @param string Name of the parameter to set
+ * @param string Value of the parameter
+ */
+ protected function setCurrentRequest($name,$value)
+ {
+ $this->currentget[$name] = $value;
+ }
+
+ /**
+ * Returns a value for the given parameter $name read randomly from the global parameter array.
+ * @see init()
+ *
+ * @param string Name of the parameter value to randomly load and return
+ * @return mixed Random value for the parameter named $name
+ * @throws Exception if the parameter asked for has never been set
+ *
+ */
+ protected function getRandom( $name )
+ {
+ if(!isset($this->allget[$name]))
+ {
+ throw new exception("You are asking for $name which doesnt exist");
+ }
+ else
+ {
+ $index = mt_rand(0,count($this->allget[$name])-1);
+ $value =$this->allget[$name][$index];
+ return $value;
+ }
+ }
+
+ /**
+ * Returns either 0 or 1
+ *
+ * @return int 0 or 1
+ */
+ protected function getRandom01()
+ {
+ return mt_rand(0,1);
+ }
+
+ /**
+ * Saves the visit
+ * - replaces GET and REQUEST by the fake generated request
+ * - load the Tracker class and call the method to launch the recording
+ *
+ * This will save the visit in the database
+ */
+ protected function saveVisit()
+ {
+ $this->setFakeRequest();
+ $process = new Piwik_VisitorGenerator_Tracker();
+ $process->main();
+ unset($process);
+ }
+}
diff --git a/oss/piwik/trunk/plugins/VisitorGenerator/Tracker.php b/oss/piwik/trunk/plugins/VisitorGenerator/Tracker.php
new file mode 100644
index 00000000..c70fde4d
--- /dev/null
+++ b/oss/piwik/trunk/plugins/VisitorGenerator/Tracker.php
@@ -0,0 +1,53 @@
+generateTimestamp();
+ return $visit;
+ }
+
+ static function disconnectDatabase()
+ {
+ return;
+ }
+}
diff --git a/oss/piwik/trunk/plugins/VisitorGenerator/Visit.php b/oss/piwik/trunk/plugins/VisitorGenerator/Visit.php
new file mode 100644
index 00000000..0ecc1b7b
--- /dev/null
+++ b/oss/piwik/trunk/plugins/VisitorGenerator/Visit.php
@@ -0,0 +1,47 @@
+getCurrentTimestamp() );
+ }
+
+ protected function getCurrentTimestamp()
+ {
+ return self::$timestampToUse;
+ }
+
+ public function generateTimestamp()
+ {
+ self::$timestampToUse = max(@$this->visitorInfo['visit_last_action_time'],self::$timestampToUse);
+ self::$timestampToUse += mt_rand(4,1840);
+ }
+
+ protected function updateCookie()
+ {
+ @parent::updateCookie();
+ }
+}
diff --git a/oss/piwik/trunk/plugins/VisitorGenerator/VisitorGenerator.php b/oss/piwik/trunk/plugins/VisitorGenerator/VisitorGenerator.php
new file mode 100644
index 00000000..d4ae66af
--- /dev/null
+++ b/oss/piwik/trunk/plugins/VisitorGenerator/VisitorGenerator.php
@@ -0,0 +1,44 @@
+ Piwik_Translate('VisitorGenerator_PluginDescription'),
+ 'author' => 'Piwik',
+ 'author_homepage' => 'http://piwik.org/',
+ 'version' => Piwik_Version::VERSION,
+ );
+ return $info;
+ }
+
+ public function getListHooksRegistered() {
+ return array(
+ 'AdminMenu.add' => 'addMenu',
+ );
+ }
+
+ public function addMenu() {
+ Piwik_AddAdminMenu(
+ 'VisitorGenerator_VisitorGenerator',
+ array('module' => 'VisitorGenerator', 'action' => 'index'),
+ Piwik::isUserIsSuperUser(),
+ $order = 10
+ );
+ }
+}
+?>
diff --git a/oss/piwik/trunk/plugins/VisitorGenerator/data/AcceptLanguage.php b/oss/piwik/trunk/plugins/VisitorGenerator/data/AcceptLanguage.php
new file mode 100644
index 00000000..c6801720
--- /dev/null
+++ b/oss/piwik/trunk/plugins/VisitorGenerator/data/AcceptLanguage.php
@@ -0,0 +1,544 @@
+{'VisitorGenerator_VisitorGenerator'|translate}
+
+
+
+
+ | {'VisitorGenerator_Visitors'|translate} |
+ {'VisitorGenerator_ActionsPerVisit'|translate} |
+ {'VisitorGenerator_Date'|translate} |
+
+
+
+{foreach from=$dates item=date}
+
+ | {$date.visitors} |
+ {$date.actionsPerVisit} |
+ {$date.startTime|date_format:"%Y-%m-%d"} |
+
+{/foreach}
+
+
+
+{'VisitorGenerator_NbActions'|translate}: {$nbActionsTotal}
+{'VisitorGenerator_NbRequestsPerSec'|translate}: {$nbRequestsPerSec}
+{$timer}
+
+{include file="CoreAdminHome/templates/footer.tpl"}
\ No newline at end of file
diff --git a/oss/piwik/trunk/plugins/VisitorGenerator/templates/index.tpl b/oss/piwik/trunk/plugins/VisitorGenerator/templates/index.tpl
new file mode 100644
index 00000000..f54349b4
--- /dev/null
+++ b/oss/piwik/trunk/plugins/VisitorGenerator/templates/index.tpl
@@ -0,0 +1,45 @@
+{assign var=showSitesSelection value=false}
+{assign var=showPeriodSelection value=false}
+{include file="CoreAdminHome/templates/header.tpl"}
+
+{'VisitorGenerator_VisitorGenerator'|translate}
+{'VisitorGenerator_PluginDescription'|translate}
+
+
+
+{include file="CoreAdminHome/templates/footer.tpl"}
\ No newline at end of file