diff --git a/oss/piwik/trunk/core/Db/Adapter.php b/oss/piwik/trunk/core/Db/Adapter.php new file mode 100644 index 00000000..d7b6aa15 --- /dev/null +++ b/oss/piwik/trunk/core/Db/Adapter.php @@ -0,0 +1,166 @@ +getConnection(); + + Zend_Db_Table::setDefaultAdapter($adapter); + + // we don't want the connection information to appear in the logs + $adapter->resetConfig(); + + return $adapter; + } + + /** + * Get adapter class name + * + * @param string $adapterName + * @return string + */ + private static function getAdapterClassName($adapterName) + { + return 'Piwik_Db_Adapter_' . str_replace(' ', '_', ucwords(str_replace('_', ' ', strtolower($adapterName)))); + } + + /** + * Get default port for named adapter + * + * @param string $adapterName + * @return int + */ + public static function getDefaultPortForAdapter($adapterName) + { + $className = self::getAdapterClassName($adapterName); + return call_user_func(array($className, 'getDefaultPort')); + } + + /** + * Get list of adapters + * + * @return array + */ + public static function getAdapters() + { + static $adapterNames = array( + // currently supported by Piwik + 'Pdo_Mysql', + 'Mysqli', + + // other adapters supported by Zend_Db +// 'Pdo_Pgsql', +// 'Pdo_Mssql', +// 'Sqlsrv', +// 'Pdo_Ibm', +// 'Db2', +// 'Pdo_Oci', +// 'Oracle', + ); + + $adapters = array(); + + foreach($adapterNames as $adapterName) + { + $className = 'Piwik_Db_Adapter_'.$adapterName; + if(call_user_func(array($className, 'isEnabled'))) + { + $adapters[strtoupper($adapterName)] = call_user_func(array($className, 'getDefaultPort')); + } + } + + return $adapters; + } +} + +/** + * @package Piwik + * @subpackage Piwik_Db + */ +interface Piwik_Db_Adapter_Interface +{ + /** + * Reset the configuration variables in this adapter. + */ + public function resetConfig(); + + /** + * Return default port. + * + * @return int + */ + public static function getDefaultPort(); + + /** + * Check database server version + * + * @throws Exception if database version is less than required version + */ + public function checkServerVersion(); + + /** + * Returns true if this adapter's required extensions are enabled + * + * @return bool + */ + public static function isEnabled(); + + /** + * Returns true if this adapter supports blobs as fields + * + * @return bool + */ + public function hasBlobDataType(); + + /** + * Test error number + * + * @param Exception $e + * @param string $errno + * @return bool + */ + public function isErrNo($e, $errno); + + /** + * Is the connection character set equal to utf8? + * + * @return bool + */ + public function isConnectionUTF8(); +} diff --git a/oss/piwik/trunk/core/Db/Adapter/Mysqli.php b/oss/piwik/trunk/core/Db/Adapter/Mysqli.php new file mode 100644 index 00000000..0c7fe220 --- /dev/null +++ b/oss/piwik/trunk/core/Db/Adapter/Mysqli.php @@ -0,0 +1,156 @@ +_config = array(); + } + + /** + * Return default port. + * + * @return int + */ + public static function getDefaultPort() + { + return 3306; + } + + /** + * Check MySQL version + */ + public function checkServerVersion() + { + $serverVersion = $this->getServerVersion(); + $requiredVersion = Zend_Registry::get('config')->General->minimum_mysql_version; + if(version_compare($serverVersion, $requiredVersion) === -1) + { + throw new Exception(Piwik_TranslateException('General_ExceptionDatabaseVersion', array('MySQL', $serverVersion, $requiredVersion))); + } + } + + /** + * Check client version compatibility against database server + */ + public function checkClientVersion() + { + $serverVersion = $this->getServerVersion(); + $clientVersion = $this->getClientVersion(); + if(version_compare($serverVersion, '5') >= 0 + && version_compare($clientVersion, '5') < 0) + { + throw new Exception(Piwik_TranslateException('General_ExceptionIncompatibleClientServerVersions', array('MySQL', $clientVersion, $serverVersion))); + } + } + + /** + * Returns true if this adapter's required extensions are enabled + * + * @return bool + */ + public static function isEnabled() + { + $extensions = @get_loaded_extensions(); + return in_array('mysqli', $extensions); + } + + /** + * Returns true if this adapter supports blobs as fields + * + * @return bool + */ + public function hasBlobDataType() + { + return true; + } + + /** + * Test error number + * + * @param Exception $e + * @param string $errno + * @return bool + */ + public function isErrNo($e, $errno) + { + if(is_null($this->_connection)) + { + if(preg_match('/(?:\[|\s)([0-9]{4})(?:\]|\s)/', $e->getMessage(), $match)) + { + return $match[1] == $errno; + } + return mysqli_connect_errno() == $errno; + } + + return mysqli_errno($this->_connection) == $errno; + } + + /** + * Execute unprepared SQL query and throw away the result + * + * Workaround some SQL statements not compatible with prepare(). + * See http://framework.zend.com/issues/browse/ZF-1398 + * + * @param string $sqlQuery + * @return int Number of rows affected (SELECT/INSERT/UPDATE/DELETE) + */ + public function exec( $sqlQuery ) + { + $rc = mysqli_query($this->_connection, $sqlQuery); + $rowsAffected = mysqli_affected_rows($this->_connection); + if(!is_bool($rc)) + { + mysqli_free_result($rc); + } + return $rowsAffected; + } + + /** + * Is the connection character set equal to utf8? + * + * @return bool + */ + public function isConnectionUTF8() + { + $charset = mysqli_character_set_name($this->_connection); + return $charset === 'utf8'; + } + + /** + * Get client version + * + * @return string + */ + public function getClientVersion() + { + $this->_connect(); + $version = $this->_connection->server_version; + $major = (int) ($version / 10000); + $minor = (int) ($version % 10000 / 100); + $revision = (int) ($version % 100); + return $major . '.' . $minor . '.' . $revision; + } +} diff --git a/oss/piwik/trunk/core/Db/Adapter/Pdo/Mssql.php b/oss/piwik/trunk/core/Db/Adapter/Pdo/Mssql.php new file mode 100644 index 00000000..7246c383 --- /dev/null +++ b/oss/piwik/trunk/core/Db/Adapter/Pdo/Mssql.php @@ -0,0 +1,253 @@ +_connection) + { + return $this->_connection; + } + + $this->_pdoType = "sqlsrv"; + // get the dsn first, because some adapters alter the $_pdoType + //$dsn = $this->_dsn(); + + // check for PDO extension + if (!extension_loaded('pdo')) + { + /** + * @see Zend_Db_Adapter_Exception + */ + throw new Zend_Db_Adapter_Exception('The PDO extension is required for this adapter but the extension is not loaded'); + } + + // check the PDO driver is available + if (!in_array($this->_pdoType, PDO::getAvailableDrivers())) + { + /** + * @see Zend_Db_Adapter_Exception + */ + throw new Zend_Db_Adapter_Exception('The ' . $this->_pdoType . ' driver is not currently installed'); + } + + // create PDO connection + $q = $this->_profiler->queryStart('connect', Zend_Db_Profiler::CONNECT); + + // add the persistence flag if we find it in our config array + if (isset($this->_config['persistent']) && ($this->_config['persistent'] == true)) + { + $this->_config['driver_options'][PDO::ATTR_PERSISTENT] = true; + } + + try { + $serverName = $this->_config["host"]; + $database = $this->_config["dbname"]; + if(is_null($database)) + { + $database = 'master'; + } + $uid = $this->_config['username']; + $pwd = $this->_config['password']; + if($this->_config["port"] != "") + { + $serverName = $serverName.",".$this->_config["port"]; + } + + $this->_connection = new PDO( "sqlsrv:$serverName", $uid, $pwd, array( 'Database' => $database )); + + + if( $this->_connection === false ) + { + die( self::FormatErrors( sqlsrv_errors() ) ); + } + + /* + $this->_connection = new PDO( + $dsn, + $this->_config['username'], + $this->_config['password'], + $this->_config['driver_options'] + ); + */ + + $this->_profiler->queryEnd($q); + + // set the PDO connection to perform case-folding on array keys, or not + $this->_connection->setAttribute(PDO::ATTR_CASE, $this->_caseFolding); + $this->_connection->setAttribute(PDO::SQLSRV_ENCODING_UTF8, true); + + + // always use exceptions. + $this->_connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + return $this->_connection; + } catch (PDOException $e) { + /** + * @see Zend_Db_Adapter_Exception + */ + throw new Zend_Db_Adapter_Exception($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * Reset the configuration variables in this adapter. + */ + public function resetConfig() + { + $this->_config = array(); + } + + /** + * Return default port. + * + * @return int + */ + public static function getDefaultPort() + { + return 1433; + } + + /** + * Check MSSQL version + */ + public function checkServerVersion() + { + $serverVersion = $this->getServerVersion(); + $requiredVersion = Zend_Registry::get('config')->General->minimum_mssql_version; + if(version_compare($serverVersion, $requiredVersion) === -1) + { + throw new Exception(Piwik_TranslateException('General_ExceptionDatabaseVersion', array('MSSQL', $serverVersion, $requiredVersion))); + } + + } + + public function getServerVersion() + { + try + { + $stmt = $this->query("SELECT CAST(SERVERPROPERTY('productversion') as VARCHAR) as productversion"); + $result = $stmt->fetchAll(Zend_Db::FETCH_NUM); + if (count($result)) + { + return $result[0][0]; + } + } + catch (PDOException $e) + { + } + + return null; + } + + /** + * Check client version compatibility against database server + */ + public function checkClientVersion() + { + $serverVersion = $this->getServerVersion(); + $clientVersion = $this->getClientVersion(); + if(version_compare($serverVersion, '10') >= 0 + && version_compare($clientVersion, '10') < 0) + { + throw new Exception(Piwik_TranslateException('General_ExceptionIncompatibleClientServerVersions', array('MSSQL', $clientVersion, $serverVersion))); + } + } + + /** + * Returns true if this adapter's required extensions are enabled + * + * @return bool + */ + public static function isEnabled() + { + $extensions = @get_loaded_extensions(); + return in_array('PDO', $extensions) && in_array('pdo_sqlsrv', $extensions); + } + + /** + * Returns true if this adapter supports blobs as fields + * + * @return bool + */ + public function hasBlobDataType() + { + return true; + } + + /** + * Test error number + * + * @param Exception $e + * @param string $errno + * @return bool + */ + public function isErrNo($e, $errno) + { + if(preg_match('/(?:\[|\s)([0-9]{4})(?:\]|\s)/', $e->getMessage(), $match)) + { + return $match[1] == $errno; + } + return false; + } + + /** + * Is the connection character set equal to utf8? + * + * @return bool + */ + public function isConnectionUTF8() + { + //check the getconnection, it's specified on the connection string. + return true; + } + + /** + * Retrieve client version in PHP style + * + * @return string + */ + public function getClientVersion() + { + $this->_connect(); + try + { + $version = $this->_connection->getAttribute(PDO::ATTR_CLIENT_VERSION); + $requiredVersion = Zend_Registry::get('config')->General->minimum_mssql_client_version; + if(version_compare($version['DriverVer'], $requiredVersion) === -1) + { + throw new Exception(Piwik_TranslateException('General_ExceptionDatabaseVersion', array('MSSQL', $serverVersion, $requiredVersion))); + } + else + { + return $version['DriverVer']; + } + } + catch (PDOException $e) + { + // In case of the driver doesn't support getting attributes + } + + return null; + } +} diff --git a/oss/piwik/trunk/core/Db/Adapter/Pdo/Mysql.php b/oss/piwik/trunk/core/Db/Adapter/Pdo/Mysql.php new file mode 100644 index 00000000..cce07214 --- /dev/null +++ b/oss/piwik/trunk/core/Db/Adapter/Pdo/Mysql.php @@ -0,0 +1,161 @@ +_connection) + { + return $this->_connection; + } + + $this->_connect(); + + /** + * Before MySQL 5.1.17, server-side prepared statements + * do not use the query cache. + * @see http://dev.mysql.com/doc/refman/5.1/en/query-cache-operation.html + * + * MySQL also does not support preparing certain DDL and SHOW + * statements. + * @see http://framework.zend.com/issues/browse/ZF-1398 + */ + $this->_connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); + $this->_connection->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true); + + return $this->_connection; + } + + /** + * Reset the configuration variables in this adapter. + */ + public function resetConfig() + { + $this->_config = array(); + } + + /** + * Return default port. + * + * @return int + */ + public static function getDefaultPort() + { + return 3306; + } + + /** + * Check MySQL version + */ + public function checkServerVersion() + { + $serverVersion = $this->getServerVersion(); + $requiredVersion = Zend_Registry::get('config')->General->minimum_mysql_version; + if(version_compare($serverVersion, $requiredVersion) === -1) + { + throw new Exception(Piwik_TranslateException('General_ExceptionDatabaseVersion', array('MySQL', $serverVersion, $requiredVersion))); + } + } + + /** + * Check client version compatibility against database server + */ + public function checkClientVersion() + { + $serverVersion = $this->getServerVersion(); + $clientVersion = $this->getClientVersion(); + if(version_compare($serverVersion, '5') >= 0 + && version_compare($clientVersion, '5') < 0) + { + throw new Exception(Piwik_TranslateException('General_ExceptionIncompatibleClientServerVersions', array('MySQL', $clientVersion, $serverVersion))); + } + } + + /** + * Returns true if this adapter's required extensions are enabled + * + * @return bool + */ + public static function isEnabled() + { + $extensions = @get_loaded_extensions(); + return in_array('PDO', $extensions) && in_array('pdo_mysql', $extensions) && in_array('mysql', PDO::getAvailableDrivers()); + } + + /** + * Returns true if this adapter supports blobs as fields + * + * @return bool + */ + public function hasBlobDataType() + { + return true; + } + + /** + * Test error number + * + * @param Exception $e + * @param string $errno + * @return bool + */ + public function isErrNo($e, $errno) + { + if(preg_match('/(?:\[|\s)([0-9]{4})(?:\]|\s)/', $e->getMessage(), $match)) + { + return $match[1] == $errno; + } + return false; + } + + /** + * Is the connection character set equal to utf8? + * + * @return bool + */ + public function isConnectionUTF8() + { + $charsetInfo = $this->fetchAll('SHOW VARIABLES LIKE ?', array('character_set_connection')); + $charset = $charsetInfo[0]['Value']; + return $charset === 'utf8'; + } + + /** + * Retrieve client version in PHP style + * + * @return string + */ + public function getClientVersion() + { + $this->_connect(); + try { + $version = $this->_connection->getAttribute(PDO::ATTR_CLIENT_VERSION); + $matches = null; + if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $version, $matches)) { + return $matches[1]; + } + } catch (PDOException $e) { + // In case of the driver doesn't support getting attributes + } + return null; + } +} diff --git a/oss/piwik/trunk/core/Db/Adapter/Pdo/Pgsql.php b/oss/piwik/trunk/core/Db/Adapter/Pdo/Pgsql.php new file mode 100644 index 00000000..c5d4320a --- /dev/null +++ b/oss/piwik/trunk/core/Db/Adapter/Pdo/Pgsql.php @@ -0,0 +1,163 @@ +_config = array(); + } + + /** + * Return default port. + * + * @return int + */ + public static function getDefaultPort() + { + return 5432; + } + + /** + * Check PostgreSQL version + */ + public function checkServerVersion() + { + $databaseVersion = $this->getServerVersion(); + $requiredVersion = Zend_Registry::get('config')->General->minimum_pgsql_version; + if(version_compare($databaseVersion, $requiredVersion) === -1) + { + throw new Exception(Piwik_TranslateException('General_ExceptionDatabaseVersion', array('PostgreSQL', $databaseVersion, $requiredVersion))); + } + } + + /** + * Check client version compatibility against database server + */ + public function checkClientVersion() + { + } + + /** + * Returns true if this adapter's required extensions are enabled + * + * @return bool + */ + public static function isEnabled() + { + $extensions = @get_loaded_extensions(); + return in_array('PDO', $extensions) && in_array('pdo_pgsql', $extensions); + } + + /** + * Returns true if this adapter supports blobs as fields + * + * @return bool + */ + public function hasBlobDataType() + { + // large objects must be loaded from a file using a non-SQL API + // and then referenced by the object ID (oid); + // the alternative, bytea fields, incur a space and time + // penalty for encoding/decoding + return false; + } + + /** + * Test error number + * + * @param Exception $e + * @param string $errno + * @return bool + */ + public function isErrNo($e, $errno) + { + // map MySQL driver-specific error codes to PostgreSQL SQLSTATE + $map = array( + // MySQL: Unknown database '%s' + // PostgreSQL: database "%s" does not exist + '1049' => '08006', + + // MySQL: Table '%s' already exists + // PostgreSQL: relation "%s" already exists + '1050' => '42P07', + + // MySQL: Unknown column '%s' in '%s' + // PostgreSQL: column "%s" does not exist + '1054' => '42703', + + // MySQL: Duplicate column name '%s' + // PostgreSQL: column "%s" of relation "%s" already exists + '1060' => '42701', + + // MySQL: Duplicate key name '%s' + // PostgreSQL: relation "%s" already exists + '1061' => '42P07', + + // MySQL: Duplicate entry '%s' for key '%s' + // PostgreSQL: duplicate key violates unique constraint + '1062' => '23505', + + // MySQL: Can't DROP '%s'; check that column/key exists + // PostgreSQL: index "%s" does not exist + '1091' => '42704', + + // MySQL: Table '%s.%s' doesn't exist + // PostgreSQL: relation "%s" does not exist + '1146' => '42P01', + ); + + if(preg_match('/([0-9]{2}[0-9P][0-9]{2})/', $e->getMessage(), $match)) + { + return $match[1] == $map[$errno]; + } + return false; + } + + /** + * Is the connection character set equal to utf8? + * + * @return bool + */ + public function isConnectionUTF8() + { + $charset = $this->fetchOne('SHOW client_encoding'); + return strtolower($charset) === 'utf8'; + } + + /** + * Retrieve client version in PHP style + * + * @return string + */ + public function getClientVersion() + { + $this->_connect(); + try { + $version = $this->_connection->getAttribute(PDO::ATTR_CLIENT_VERSION); + $matches = null; + if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $version, $matches)) { + return $matches[1]; + } + } catch (PDOException $e) { + // In case of the driver doesn't support getting attributes + } + return null; + } +} diff --git a/oss/piwik/trunk/core/Db/Schema.php b/oss/piwik/trunk/core/Db/Schema.php new file mode 100644 index 00000000..12d7c568 --- /dev/null +++ b/oss/piwik/trunk/core/Db/Schema.php @@ -0,0 +1,282 @@ + array( + 'Myisam', +// 'Innodb', +// 'Infinidb', + ), + + // Microsoft SQL Server +// 'MSSQL' => array( 'Mssql' ), + + // PostgreSQL +// 'PDO_PGSQL' => array( 'Pgsql' ), + + // IBM DB2 +// 'IBM' => array( 'Ibm' ), + + // Oracle +// 'OCI' => array( 'Oci' ), + ); + + $adapterName = strtoupper($adapterName); + switch($adapterName) + { + case 'PDO_MYSQL': + case 'MYSQLI': + $adapterName = 'MYSQL'; + break; + + case 'PDO_MSSQL': + case 'SQLSRV': + $adapterName = 'MSSQL'; + break; + + case 'PDO_IBM': + case 'DB2': + $adapterName = 'IBM'; + break; + + case 'PDO_OCI': + case 'ORACLE': + $adapterName = 'OCI'; + break; + } + $schemaNames = $allSchemaNames[$adapterName]; + + $schemas = array(); + + foreach($schemaNamess as $schemaName) + { + $className = 'Piwik_Db_Schema_'.$schemaName; + if(call_user_func(array($className, 'isAvailable'))) + { + $schemas[] = $schemaName; + } + } + + return $schemas; + } + + /** + * Load schema + */ + private function loadSchema() + { + $schema = null; + Piwik_PostEvent('Schema.loadSchema', $schema); + if($schema === null) + { + $config = Zend_Registry::get('config'); + $dbInfos = $config->database->toArray(); + if(isset($dbInfos['schema'])) + { + $schemaName = $dbInfos['schema']; + } + else + { + $schemaName = 'Myisam'; + } + $className = self::getSchemaClassName($schemaName); + $schema = new $className(); + } + $this->schema = $schema; + } + + /** + * Returns an instance that subclasses Piwik_Db_Schema + * + * @return Piwik_Db_Schema_Interface + */ + private function getSchema() + { + if ($this->schema === null) + { + $this->loadSchema(); + } + return $this->schema; + } + + /** + * Get the SQL to create a specific Piwik table + * + * @return string SQL + */ + public function getTableCreateSql( $tableName ) + { + return $this->getSchema()->getTableCreateSql($tableName); + } + + /** + * Get the SQL to create Piwik tables + * + * @return array of strings containing SQL + */ + public function getTablesCreateSql() + { + return $this->getSchema()->getTablesCreateSql(); + } + + /** + * Create database + */ + public function createDatabase( $dbName = null ) + { + $this->getSchema()->createDatabase($dbName); + } + + /** + * Drop database + */ + public function dropDatabase() + { + $this->getSchema()->dropDatabase(); + } + + /** + * Create all tables + */ + public function createTables() + { + $this->getSchema()->createTables(); + } + + /** + * Creates an entry in the User table for the "anonymous" user. + */ + public function createAnonymousUser() + { + $this->getSchema()->createAnonymousUser(); + } + + /** + * Truncate all tables + */ + public function truncateAllTables() + { + $this->getSchema()->truncateAllTables(); + } + + /** + * Drop specific tables + */ + public function dropTables( $doNotDelete = array() ) + { + $this->getSchema()->dropTables($doNotDelete); + } + + /** + * Names of all the prefixed tables in piwik + * Doesn't use the DB + * + * @return array Table names + */ + public function getTablesNames() + { + return $this->getSchema()->getTablesNames(); + } + + /** + * Get list of tables installed + * + * @param bool $forceReload Invalidate cache + * @param string $idSite + * @return array Tables installed + */ + public function getTablesInstalled($forceReload = true, $idSite = null) + { + return $this->getSchema()->getTablesInstalled($forceReload, $idSite); + } + + /** + * Returns true if Piwik tables exist + * + * @return bool True if tables exist; false otherwise + */ + public function hasTables() + { + return $this->getSchema()->hasTables(); + } +} + +/** + * @package Piwik + */ +interface Piwik_Db_Schema_Interface +{ + static public function isAvailable(); + + public function getTableCreateSql($tableName); + public function getTablesCreateSql(); + + public function createDatabase( $dbName = null ); + public function dropDatabase(); + + public function createTables(); + public function createAnonymousUser(); + public function truncateAllTables(); + public function dropTables( $doNotDelete = array() ); + + public function getTablesNames(); + public function getTablesInstalled($forceReload = true, $idSite = null); + public function hasTables(); +} diff --git a/oss/piwik/trunk/core/Db/Schema/Myisam.php b/oss/piwik/trunk/core/Db/Schema/Myisam.php new file mode 100644 index 00000000..d68b2340 --- /dev/null +++ b/oss/piwik/trunk/core/Db/Schema/Myisam.php @@ -0,0 +1,484 @@ +fetchAssoc('SHOW ENGINES'); + if(key_exists($engineName, $allEngines)) + { + $support = $allEngines[$engineName]['Support']; + return $support == 'DEFAULT' || $support == 'YES'; + } + return false; + } + + /** + * Is this schema available? + * + * @return bool True if schema is available; false otherwise + */ + static public function isAvailable() + { + return self::hasStorageEngine('MyISAM'); + } + + /** + * Get the SQL to create Piwik tables + * + * @return array of strings containing SQL + */ + public function getTablesCreateSql() + { + $config = Zend_Registry::get('config'); + $prefixTables = $config->database->tables_prefix; + $tables = array( + 'user' => "CREATE TABLE {$prefixTables}user ( + login VARCHAR(100) NOT NULL, + password CHAR(32) NOT NULL, + alias VARCHAR(45) NOT NULL, + email VARCHAR(100) NOT NULL, + token_auth CHAR(32) NOT NULL, + date_registered TIMESTAMP NULL, + PRIMARY KEY(login), + UNIQUE KEY uniq_keytoken(token_auth) + ) DEFAULT CHARSET=utf8 + ", + + 'access' => "CREATE TABLE {$prefixTables}access ( + login VARCHAR(100) NOT NULL, + idsite INTEGER UNSIGNED NOT NULL, + access VARCHAR(10) NULL, + PRIMARY KEY(login, idsite) + ) DEFAULT CHARSET=utf8 + ", + + 'site' => "CREATE TABLE {$prefixTables}site ( + idsite INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, + name VARCHAR(90) NOT NULL, + main_url VARCHAR(255) NOT NULL, + ts_created TIMESTAMP NULL, + timezone VARCHAR( 50 ) NOT NULL, + currency CHAR( 3 ) NOT NULL, + excluded_ips TEXT NOT NULL, + excluded_parameters VARCHAR ( 255 ) NOT NULL, + PRIMARY KEY(idsite) + ) DEFAULT CHARSET=utf8 + ", + + 'site_url' => "CREATE TABLE {$prefixTables}site_url ( + idsite INTEGER(10) UNSIGNED NOT NULL, + url VARCHAR(255) NOT NULL, + PRIMARY KEY(idsite, url) + ) DEFAULT CHARSET=utf8 + ", + + 'goal' => " CREATE TABLE `{$prefixTables}goal` ( + `idsite` int(11) NOT NULL, + `idgoal` int(11) NOT NULL, + `name` varchar(50) NOT NULL, + `match_attribute` varchar(20) NOT NULL, + `pattern` varchar(255) NOT NULL, + `pattern_type` varchar(10) NOT NULL, + `case_sensitive` tinyint(4) NOT NULL, + `revenue` float NOT NULL, + `deleted` tinyint(4) NOT NULL default '0', + PRIMARY KEY (`idsite`,`idgoal`) + ) DEFAULT CHARSET=utf8 + ", + + 'logger_message' => "CREATE TABLE {$prefixTables}logger_message ( + idlogger_message INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, + timestamp TIMESTAMP NULL, + message TEXT NULL, + PRIMARY KEY(idlogger_message) + ) DEFAULT CHARSET=utf8 + ", + + 'logger_api_call' => "CREATE TABLE {$prefixTables}logger_api_call ( + idlogger_api_call INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, + class_name VARCHAR(255) NULL, + method_name VARCHAR(255) NULL, + parameter_names_default_values TEXT NULL, + parameter_values TEXT NULL, + execution_time FLOAT NULL, + caller_ip INT UNSIGNED NULL, + timestamp TIMESTAMP NULL, + returned_value TEXT NULL, + PRIMARY KEY(idlogger_api_call) + ) DEFAULT CHARSET=utf8 + ", + + 'logger_error' => "CREATE TABLE {$prefixTables}logger_error ( + idlogger_error INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, + timestamp TIMESTAMP NULL, + message TEXT NULL, + errno INTEGER UNSIGNED NULL, + errline INTEGER UNSIGNED NULL, + errfile VARCHAR(255) NULL, + backtrace TEXT NULL, + PRIMARY KEY(idlogger_error) + ) DEFAULT CHARSET=utf8 + ", + + 'logger_exception' => "CREATE TABLE {$prefixTables}logger_exception ( + idlogger_exception INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, + timestamp TIMESTAMP NULL, + message TEXT NULL, + errno INTEGER UNSIGNED NULL, + errline INTEGER UNSIGNED NULL, + errfile VARCHAR(255) NULL, + backtrace TEXT NULL, + PRIMARY KEY(idlogger_exception) + ) DEFAULT CHARSET=utf8 + ", + + 'log_action' => "CREATE TABLE {$prefixTables}log_action ( + idaction INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, + name TEXT, + hash INTEGER(10) UNSIGNED NOT NULL, + type TINYINT UNSIGNED NULL, + PRIMARY KEY(idaction), + INDEX index_type_hash (type, hash) + ) DEFAULT CHARSET=utf8 + ", + + 'log_visit' => "CREATE TABLE {$prefixTables}log_visit ( + idvisit INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, + idsite INTEGER(10) UNSIGNED NOT NULL, + visitor_localtime TIME NOT NULL, + visitor_idcookie CHAR(32) NOT NULL, + visitor_returning TINYINT(1) NOT NULL, + visit_first_action_time DATETIME NOT NULL, + visit_last_action_time DATETIME NOT NULL, + visit_server_date DATE NOT NULL, + visit_exit_idaction_url INTEGER(11) NOT NULL, + visit_entry_idaction_url INTEGER(11) NOT NULL, + visit_total_actions SMALLINT(5) UNSIGNED NOT NULL, + visit_total_time SMALLINT(5) UNSIGNED NOT NULL, + visit_goal_converted TINYINT(1) NOT NULL, + referer_type INTEGER UNSIGNED NULL, + referer_name VARCHAR(70) NULL, + referer_url TEXT NOT NULL, + referer_keyword VARCHAR(255) NULL, + config_md5config CHAR(32) NOT NULL, + config_os CHAR(3) NOT NULL, + config_browser_name VARCHAR(10) NOT NULL, + config_browser_version VARCHAR(20) NOT NULL, + config_resolution VARCHAR(9) NOT NULL, + config_pdf TINYINT(1) NOT NULL, + config_flash TINYINT(1) NOT NULL, + config_java TINYINT(1) NOT NULL, + config_director TINYINT(1) NOT NULL, + config_quicktime TINYINT(1) NOT NULL, + config_realplayer TINYINT(1) NOT NULL, + config_windowsmedia TINYINT(1) NOT NULL, + config_gears TINYINT(1) NOT NULL, + config_silverlight TINYINT(1) NOT NULL, + config_cookie TINYINT(1) NOT NULL, + location_ip INT UNSIGNED NOT NULL, + location_browser_lang VARCHAR(20) NOT NULL, + location_country CHAR(3) NOT NULL, + location_continent CHAR(3) NOT NULL, + PRIMARY KEY(idvisit), + INDEX index_idsite_idvisit (idsite, idvisit), + INDEX index_idsite_date_config (idsite, visit_server_date, config_md5config(8)), + INDEX index_idsite_datetime_config (idsite, visit_last_action_time, config_md5config(8)) + ) DEFAULT CHARSET=utf8 + ", + + 'log_conversion' => "CREATE TABLE `{$prefixTables}log_conversion` ( + idvisit int(10) unsigned NOT NULL, + idsite int(10) unsigned NOT NULL, + visitor_idcookie char(32) NOT NULL, + server_time datetime NOT NULL, + idaction_url int(11) default NULL, + idlink_va int(11) default NULL, + referer_idvisit int(10) unsigned default NULL, + referer_visit_server_date date default NULL, + referer_type int(10) unsigned default NULL, + referer_name varchar(70) default NULL, + referer_keyword varchar(255) default NULL, + visitor_returning tinyint(1) NOT NULL, + location_country char(3) NOT NULL, + location_continent char(3) NOT NULL, + url text NOT NULL, + idgoal int(10) unsigned NOT NULL, + revenue float default NULL, + PRIMARY KEY (idvisit, idgoal), + INDEX index_idsite_datetime ( idsite, server_time ) + ) DEFAULT CHARSET=utf8 + ", + + 'log_link_visit_action' => "CREATE TABLE {$prefixTables}log_link_visit_action ( + idlink_va INTEGER(11) NOT NULL AUTO_INCREMENT, + idvisit INTEGER(10) UNSIGNED NOT NULL, + idaction_url INTEGER(10) UNSIGNED NOT NULL, + idaction_url_ref INTEGER(10) UNSIGNED NOT NULL, + idaction_name INTEGER(10) UNSIGNED, + time_spent_ref_action INTEGER(10) UNSIGNED NOT NULL, + PRIMARY KEY(idlink_va), + INDEX index_idvisit(idvisit) + ) DEFAULT CHARSET=utf8 + ", + + 'log_profiling' => "CREATE TABLE {$prefixTables}log_profiling ( + query TEXT NOT NULL, + count INTEGER UNSIGNED NULL, + sum_time_ms FLOAT NULL, + UNIQUE KEY query(query(100)) + ) DEFAULT CHARSET=utf8 + ", + + 'option' => "CREATE TABLE `{$prefixTables}option` ( + option_name VARCHAR( 64 ) NOT NULL, + option_value LONGTEXT NOT NULL, + autoload TINYINT NOT NULL DEFAULT '1', + PRIMARY KEY ( option_name ) + ) DEFAULT CHARSET=utf8 + ", + + 'archive_numeric' => "CREATE TABLE {$prefixTables}archive_numeric ( + idarchive INTEGER UNSIGNED NOT NULL, + name VARCHAR(255) NOT NULL, + idsite INTEGER UNSIGNED NULL, + date1 DATE NULL, + date2 DATE NULL, + period TINYINT UNSIGNED NULL, + ts_archived DATETIME NULL, + value FLOAT NULL, + PRIMARY KEY(idarchive, name), + INDEX index_idsite_dates_period(idsite, date1, date2, period, ts_archived), + INDEX index_period_archived(period, ts_archived) + ) DEFAULT CHARSET=utf8 + ", + + 'archive_blob' => "CREATE TABLE {$prefixTables}archive_blob ( + idarchive INTEGER UNSIGNED NOT NULL, + name VARCHAR(255) NOT NULL, + idsite INTEGER UNSIGNED NULL, + date1 DATE NULL, + date2 DATE NULL, + period TINYINT UNSIGNED NULL, + ts_archived DATETIME NULL, + value MEDIUMBLOB NULL, + PRIMARY KEY(idarchive, name), + INDEX index_period_archived(period, ts_archived) + ) DEFAULT CHARSET=utf8 + ", + ); + return $tables; + } + + /** + * Get the SQL to create a specific Piwik table + * + * @param string $tableName + * @return string SQL + */ + public function getTableCreateSql( $tableName ) + { + $tables = Piwik::getTablesCreateSql(); + + if(!isset($tables[$tableName])) + { + throw new Exception("The table '$tableName' SQL creation code couldn't be found."); + } + + return $tables[$tableName]; + } + + /** + * Names of all the prefixed tables in piwik + * Doesn't use the DB + * + * @return array Table names + */ + public function getTablesNames() + { + $aTables = array_keys($this->getTablesCreateSql()); + $config = Zend_Registry::get('config'); + $prefixTables = $config->database->tables_prefix; + $return = array(); + foreach($aTables as $table) + { + $return[] = $prefixTables.$table; + } + return $return; + } + + private $tablesInstalled = null; + + /** + * Get list of tables installed + * + * @param bool $forceReload Invalidate cache + * @param string $idSite + * @return array Tables installed + */ + public function getTablesInstalled($forceReload = true, $idSite = null) + { + if(is_null($this->tablesInstalled) + || $forceReload === true) + { + $db = Zend_Registry::get('db'); + $config = Zend_Registry::get('config'); + $prefixTables = $config->database->tables_prefix; + + $allTables = $db->fetchCol("SHOW TABLES"); + + // all the tables to be installed + $allMyTables = $this->getTablesNames(); + + // we get the intersection between all the tables in the DB and the tables to be installed + $tablesInstalled = array_intersect($allMyTables, $allTables); + + // at this point we have only the piwik tables which is good + // but we still miss the piwik generated tables (using the class Piwik_TablePartitioning) + $idSiteInSql = "no"; + if(!is_null($idSite)) + { + $idSiteInSql = $idSite; + } + $allArchiveNumeric = $db->fetchCol("/* SHARDING_ID_SITE = ".$idSiteInSql." */ + SHOW TABLES LIKE '".$prefixTables."archive_numeric%'"); + $allArchiveBlob = $db->fetchCol("/* SHARDING_ID_SITE = ".$idSiteInSql." */ + SHOW TABLES LIKE '".$prefixTables."archive_blob%'"); + + $allTablesReallyInstalled = array_merge($tablesInstalled, $allArchiveNumeric, $allArchiveBlob); + + $this->tablesInstalled = $allTablesReallyInstalled; + } + return $this->tablesInstalled; + } + + /** + * Do tables exist? + * + * @return bool True if tables exist; false otherwise + */ + public function hasTables() + { + return count($this->getTablesInstalled()) != 0; + } + + /** + * Create database + * + * @param string $dbName + */ + public function createDatabase( $dbName = null ) + { + if(is_null($dbName)) + { + $dbName = Zend_Registry::get('config')->database->dbname; + } + Piwik_Exec("CREATE DATABASE IF NOT EXISTS ".$dbName); + } + + /** + * Drop database + */ + public function dropDatabase() + { + $dbName = Zend_Registry::get('config')->database->dbname; + Piwik_Exec("DROP DATABASE IF EXISTS " . $dbName); + + } + + /** + * Create all tables + */ + public function createTables() + { + $db = Zend_Registry::get('db'); + $config = Zend_Registry::get('config'); + $prefixTables = $config->database->tables_prefix; + + $tablesAlreadyInstalled = $this->getTablesInstalled(); + $tablesToCreate = $this->getTablesCreateSql(); + unset($tablesToCreate['archive_blob']); + unset($tablesToCreate['archive_numeric']); + + foreach($tablesToCreate as $tableName => $tableSql) + { + $tableName = $prefixTables . $tableName; + if(!in_array($tableName, $tablesAlreadyInstalled)) + { + $db->query( $tableSql ); + } + } + } + + /** + * Creates an entry in the User table for the "anonymous" user. + */ + public function createAnonymousUser() + { + // The anonymous user is the user that is assigned by default + // note that the token_auth value is anonymous, which is assigned by default as well in the Login plugin + $db = Zend_Registry::get('db'); + $db->query("INSERT INTO ". Piwik_Common::prefixTable("user") . " + VALUES ( 'anonymous', '', 'anonymous', 'anonymous@example.org', 'anonymous', '".Piwik_Date::factory('now')->getDatetime()."' );" ); + } + + /** + * Truncate all tables + */ + public function truncateAllTables() + { + $tablesAlreadyInstalled = $this->getTablesInstalled($forceReload = true); + foreach($tablesAlreadyInstalled as $table) + { + Piwik_Query("TRUNCATE `$table`"); + } + } + + /** + * Drop specific tables + * + * @param array $doNotDelete Names of tables to not delete + */ + public function dropTables( $doNotDelete = array() ) + { + $tablesAlreadyInstalled = $this->getTablesInstalled(); + $db = Zend_Registry::get('db'); + + $doNotDeletePattern = '/('.implode('|',$doNotDelete).')/'; + + foreach($tablesAlreadyInstalled as $tableName) + { + if( count($doNotDelete) == 0 + || (!in_array($tableName,$doNotDelete) + && !preg_match($doNotDeletePattern,$tableName) + ) + ) + { + $db->query("DROP TABLE `$tableName`"); + } + } + } +} diff --git a/oss/piwik/trunk/core/Log/APICall.php b/oss/piwik/trunk/core/Log/APICall.php new file mode 100644 index 00000000..178f2378 --- /dev/null +++ b/oss/piwik/trunk/core/Log/APICall.php @@ -0,0 +1,117 @@ +setEventItem('caller_ip', Piwik_Common::getIp() ); + } + + public function logEvent($className, $methodName, $parameterNames, $parameterValues, $executionTime, $returnedValue) + { + $event = array(); + $event['class_name'] = $className; + $event['method_name'] = $methodName; + $event['parameter_names_default_values'] = serialize($parameterNames); + $event['parameter_values'] = serialize($parameterValues); + $event['execution_time'] = $executionTime; + $event['returned_value'] = is_array($returnedValue) ? serialize($returnedValue) : $returnedValue; + + parent::log($event, Piwik_Log::INFO, null); + } +} + +/** + * Class used to format the API Call log on the screen. + * + * @package Piwik + * @subpackage Piwik_Log + */ +class Piwik_Log_APICall_Formatter_ScreenFormatter extends Piwik_Log_Formatter_ScreenFormatter +{ + /** + * Formats data into a single line to be written by the writer. + * + * @param array $event event data + * @return string formatted line to write to the log + */ + public function format($event) + { + $str = "\n
"; + $str .= "Called: {$event['class_name']}.{$event['method_name']} (took {$event['execution_time']}ms)\n
"; + $str .= "Parameters: "; + $parameterNamesAndDefault = unserialize($event['parameter_names_default_values']); + $parameterValues = unserialize($event['parameter_values']); + + $i = 0; + foreach($parameterNamesAndDefault as $pName => $pDefault) + { + if(isset($parameterValues[$i])) + { + $currentValue = $parameterValues[$i]; + } + else + { + $currentValue = $pDefault; + } + + $currentValue = $this->formatValue($currentValue); + $str .= "$pName = $currentValue, "; + + $i++; + } + $str .= "\n
"; + +// $str .= "Returned: ".$this->formatValue($event['returned_value']); + $str .= "\n
"; + return parent::format($str); + } + + private function formatValue( $value ) + { + if(is_string($value)) + { + $value = "'$value'"; + } + if(is_null($value)) + { + $value= 'null'; + } + if(is_array($value)) + { + $value = "array( ".implode(", ", $value). ")"; + } + return $value; + + } +} diff --git a/oss/piwik/trunk/core/Log/Error.php b/oss/piwik/trunk/core/Log/Error.php new file mode 100644 index 00000000..912430de --- /dev/null +++ b/oss/piwik/trunk/core/Log/Error.php @@ -0,0 +1,116 @@ +setFormatter( $this->screenFormatter ); + $this->addWriter($writerScreen); + } + + public function logEvent($errno, $errstr, $errfile, $errline, $backtrace) + { + $event = array(); + $event['errno'] = $errno; + $event['message'] = $errstr; + $event['errfile'] = $errfile; + $event['errline'] = $errline; + $event['backtrace'] = $backtrace; + + parent::log($event, Piwik_Log::ERR, null); + } +} + +/** + * Format an error event to be displayed on the screen. + * + * @package Piwik + * @subpackage Piwik_Log + */ +class Piwik_Log_Error_Formatter_ScreenFormatter extends Piwik_Log_Formatter_ScreenFormatter +{ + /** + * Formats data into a single line to be written by the writer. + * + * @param array $event event data + * @return string formatted line to write to the log + */ + public function format($event) + { + $event = parent::formatEvent($event); + + $errno = $event['errno'] ; + $errstr = $event['message'] ; + $errfile = $event['errfile'] ; + $errline = $event['errline'] ; + $backtrace = $event['backtrace'] ; + + $strReturned = ''; + $errno = $errno & error_reporting(); + + // problem when using error_reporting with the @ silent fail operator + // it gives an errno 0, and in this case the objective is to NOT display anything on the screen! + // is there any other case where the errno is zero at this point? + if($errno == 0) return ''; + $strReturned .= "\n
"; + switch($errno) + { + case E_ERROR: $strReturned .= "Error"; break; + case E_WARNING: $strReturned .= "Warning"; break; + case E_PARSE: $strReturned .= "Parse Error"; break; + case E_NOTICE: $strReturned .= "Notice"; break; + case E_CORE_ERROR: $strReturned .= "Core Error"; break; + case E_CORE_WARNING: $strReturned .= "Core Warning"; break; + case E_COMPILE_ERROR: $strReturned .= "Compile Error"; break; + case E_COMPILE_WARNING: $strReturned .= "Compile Warning"; break; + case E_USER_ERROR: $strReturned .= "User Error"; break; + case E_USER_WARNING: $strReturned .= "User Warning"; break; + case E_USER_NOTICE: $strReturned .= "User Notice"; break; + case E_STRICT: $strReturned .= "Strict Notice"; break; + case E_RECOVERABLE_ERROR: $strReturned .= "Recoverable Error"; break; + case E_EXCEPTION: $strReturned .= "Exception"; break; + default: $strReturned .= "Unknown error ($errno)"; break; + } + $strReturned .= ": $errstr in $errfile on line $errline\n"; + $strReturned .= "

Backtrace -->
"; + $strReturned .= str_replace("\n", "
\n", $backtrace); + $strReturned .= "


"; + $strReturned .= "\n

"; + + return parent::format($strReturned); + } +} diff --git a/oss/piwik/trunk/core/Log/Exception.php b/oss/piwik/trunk/core/Log/Exception.php new file mode 100644 index 00000000..3fa6d5cd --- /dev/null +++ b/oss/piwik/trunk/core/Log/Exception.php @@ -0,0 +1,87 @@ +setFormatter( $this->screenFormatter ); + $this->addWriter($writerScreen); + } + + public function logEvent($exception) + { + $event = array(); + $event['errno'] = $exception->getCode(); + $event['message'] = $exception->getMessage(); + $event['errfile'] = $exception->getFile(); + $event['errline'] = $exception->getLine(); + $event['backtrace'] = $exception->getTraceAsString(); + + parent::log($event, Piwik_Log::CRIT, null); + } +} + +/** + * Format an exception event to be displayed on the screen. + * + * @package Piwik + * @subpackage Piwik_Log + */ +class Piwik_Log_Exception_Formatter_ScreenFormatter extends Piwik_Log_Formatter_ScreenFormatter +{ + /** + * Formats data into a single line to be written by the writer. + * + * @param array $event event data + * @return string formatted line to write to the log + */ + public function format($event) + { + $event = parent::formatEvent($event); + $errno = $event['errno'] ; + $errstr = $event['message'] ; + $errfile = $event['errfile'] ; + $errline = $event['errline'] ; + $backtrace = $event['backtrace'] ; + + $outputFormat = strtolower(Piwik_Common::getRequestVar('format', 'html', 'string')); + $response = new Piwik_API_ResponseBuilder($outputFormat); + $message = $response->getResponseException(new Exception($errstr)); + return parent::format($message); + } +} diff --git a/oss/piwik/trunk/core/Log/Message.php b/oss/piwik/trunk/core/Log/Message.php new file mode 100644 index 00000000..c22973ad --- /dev/null +++ b/oss/piwik/trunk/core/Log/Message.php @@ -0,0 +1,73 @@ +".var_export($event['message'], true).""; + } + else + { + $message = $event['message']; + } + + return parent::format($message); + } +} diff --git a/oss/piwik/trunk/core/Period/Day.php b/oss/piwik/trunk/core/Period/Day.php new file mode 100644 index 00000000..90ee4764 --- /dev/null +++ b/oss/piwik/trunk/core/Period/Day.php @@ -0,0 +1,62 @@ +getDateStart()->toString() ; + return $out; + } + + public function getLocalizedShortString() + { + //"Mon 15 Aug" + $date = $this->getDateStart(); + $template = "%shortDay% %day% %shortMonth%"; + $out = $date->getLocalized($template); + return $out; + } + public function getLocalizedLongString() + { + //"Mon 15 Aug" + $date = $this->getDateStart(); + $template = "%longDay% %day% %longMonth% %longYear%"; + $out = $date->getLocalized($template); + return $out; + } + + public function getNumberOfSubperiods() + { + return 0; + } + + public function addSubperiod( $date ) + { + throw new Exception("Adding a subperiod is not supported for Piwik_Period_Day"); + } + + public function toString() + { + return $this->date->toString("Y-m-d"); + } + public function __toString() + { + return $this->toString(); + } +} diff --git a/oss/piwik/trunk/core/Period/Month.php b/oss/piwik/trunk/core/Period/Month.php new file mode 100644 index 00000000..55a4a1a9 --- /dev/null +++ b/oss/piwik/trunk/core/Period/Month.php @@ -0,0 +1,58 @@ +getDateStart()->getLocalized("%shortMonth% %shortYear%"); + return $out; + } + + public function getLocalizedLongString() + { + //"August 2009" + $out = $this->getDateStart()->getLocalized("%longMonth% %longYear%"); + return $out; + } + public function getPrettyString() + { + $out = $this->getDateStart()->toString('Y-m'); + return $out; + } + + protected function generate() + { + if($this->subperiodsProcessed) + { + return; + } + parent::generate(); + + $date = $this->date; + + $startMonth = $date->setDay(1); + $currentDay = clone $startMonth; + while($currentDay->compareMonth($startMonth) == 0) + { + $this->addSubperiod(new Piwik_Period_Day($currentDay)); + $currentDay = $currentDay->addDay(1); + } + } +} diff --git a/oss/piwik/trunk/core/Period/Range.php b/oss/piwik/trunk/core/Period/Range.php new file mode 100644 index 00000000..adc80e60 --- /dev/null +++ b/oss/piwik/trunk/core/Period/Range.php @@ -0,0 +1,186 @@ +strPeriod = $strPeriod; + $this->strDate = $strDate; + $this->defaultEndDate = null; + $this->timezone = $timezone; + } + public function getLocalizedShortString() + { + //"30 Dec 08 - 26 Feb 09" + $dateStart = $this->getDateStart(); + $dateEnd = $this->getDateEnd(); + $template = "%day% %shortMonth% %shortYear%"; + $shortDateStart = $dateStart->getLocalized($template); + $shortDateEnd = $dateEnd->getLocalized($template); + $out = "$shortDateStart - $shortDateEnd"; + return $out; + } + + public function getLocalizedLongString() + { + return $this->getLocalizedShortString(); + } + public function getPrettyString() + { + $out = "From ".$this->getDateStart()->toString() . " to " . $this->getDateEnd()->toString(); + return $out; + } + + /** + * + * @param Piwik_Date $date + * @param int $n + * @return Piwik_Date + */ + protected function removePeriod( $date, $n ) + { + switch($this->strPeriod) + { + case 'day': + $startDate = $date->subDay( $n ); + break; + + case 'week': + $startDate = $date->subDay( $n * 7 ); + break; + + case 'month': + $startDate = $date->subMonth( $n ); + break; + + case 'year': + $startDate = $date->subMonth( 12 * $n ); + break; + } + return $startDate; + } + + protected function getMaxN($lastN) + { + switch($this->strPeriod) + { + case 'day': + $lastN = min( $lastN, 5*365 ); + break; + + case 'week': + $lastN = min( $lastN, 5*52 ); + break; + + case 'month': + $lastN = min( $lastN, 5*12 ); + break; + + case 'year': + $lastN = min( $lastN, 10 ); + break; + } + return $lastN; + } + + public function setDefaultEndDate( Piwik_Date $oDate) + { + $this->defaultEndDate = $oDate; + } + + protected function generate() + { + if($this->subperiodsProcessed) + { + return; + } + parent::generate(); + + if(preg_match('/(last|previous)([0-9]*)/', $this->strDate, $regs)) + { + $lastN = $regs[2]; + $lastOrPrevious = $regs[1]; + if(!is_null($this->defaultEndDate)) + { + $defaultEndDate = $this->defaultEndDate; + } + else + { + $defaultEndDate = Piwik_Date::factory('now', $this->timezone); + } + if($lastOrPrevious == 'last') + { + $endDate = $defaultEndDate; + } + elseif($lastOrPrevious == 'previous') + { + $endDate = $this->removePeriod($defaultEndDate, 1); + } + + // last1 means only one result ; last2 means 2 results so we remove only 1 to the days/weeks/etc + $lastN--; + $lastN = abs($lastN); + + $lastN = $this->getMaxN($lastN); + + $startDate = $this->removePeriod($endDate, $lastN); + } + elseif(preg_match('/([0-9]{4}-[0-9]{1,2}-[0-9]{1,2}),([0-9]{4}-[0-9]{1,2}-[0-9]{1,2})/', $this->strDate, $regs)) + { + $strDateStart = $regs[1]; + $strDateEnd = $regs[2]; + + $startDate = Piwik_Date::factory($strDateStart); + $endDate = Piwik_Date::factory($strDateEnd); + } + else + { + throw new Exception(Piwik_TranslateException('General_ExceptionInvalidDateRange', array($this->strDate, ' \'lastN\', \'previousN\', \'YYYY-MM-DD,YYYY-MM-DD\''))); + } + $endSubperiod = Piwik_Period::factory($this->strPeriod, $endDate); + + $arrayPeriods= array(); + $arrayPeriods[] = $endSubperiod; + while($endDate->isLater($startDate) ) + { + $endDate = $this->removePeriod($endDate, 1); + $subPeriod = Piwik_Period::factory($this->strPeriod, $endDate); + $arrayPeriods[] = $subPeriod ; + } + $arrayPeriods = array_reverse($arrayPeriods); + foreach($arrayPeriods as $period) + { + $this->addSubperiod($period); + } + } + + function toString() + { + if(!$this->subperiodsProcessed) + { + $this->generate(); + } + $range = array(); + foreach($this->subperiods as $element) + { + $range[] = $element->toString(); + } + return $range; + } +} diff --git a/oss/piwik/trunk/core/Period/Week.php b/oss/piwik/trunk/core/Period/Week.php new file mode 100644 index 00000000..772491f2 --- /dev/null +++ b/oss/piwik/trunk/core/Period/Week.php @@ -0,0 +1,65 @@ +getDateStart(); + $dateEnd = $this->getDateEnd(); + $shortDateStart = $dateStart->getLocalized("%day% %shortMonth%"); + $shortDateEnd = $dateEnd->getLocalized("%day% %shortMonth% %shortYear%"); + $out = "$shortDateStart - $shortDateEnd"; + return $out; + } + + public function getLocalizedLongString() + { + return Piwik_Translate('CoreHome_PeriodWeek') . " ". $this->getLocalizedShortString(); + } + public function getPrettyString() + { + $out = $this->getDateStart()->toString() . " to " . $this->getDateEnd()->toString(); + return $out; + } + + protected function generate() + { + if($this->subperiodsProcessed) + { + return; + } + parent::generate(); + $date = $this->date; + + if( $date->toString('N') > 1) + { + $date = $date->subDay($date->toString('N')-1); + } + + $startWeek = $date; + + $currentDay = clone $startWeek; + while($currentDay->compareWeek($startWeek) == 0) + { + $this->addSubperiod(new Piwik_Period_Day($currentDay) ); + $currentDay = $currentDay->addDay(1); + } + } +} diff --git a/oss/piwik/trunk/core/Period/Year.php b/oss/piwik/trunk/core/Period/Year.php new file mode 100644 index 00000000..cbea923e --- /dev/null +++ b/oss/piwik/trunk/core/Period/Year.php @@ -0,0 +1,69 @@ +getLocalizedLongString(); + } + + public function getLocalizedLongString() + { + //"2009" + $out = $this->getDateStart()->getLocalized("%longYear%"); + return $out; + } + public function getPrettyString() + { + $out = $this->getDateStart()->toString('Y'); + return $out; + } + + protected function generate() + { + if($this->subperiodsProcessed) + { + return; + } + parent::generate(); + + $year = $this->date->toString("Y"); + for($i=1; $i<=12; $i++) + { + $this->addSubperiod( new Piwik_Period_Month( + Piwik_Date::factory("$year-$i-01") + ) + ); + } + } + + function toString() + { + if(!$this->subperiodsProcessed) + { + $this->generate(); + } + $stringMonth = array(); + foreach($this->subperiods as $month) + { + $stringMonth[] = $month->get("Y")."-".$month->get("m")."-01"; + } + return $stringMonth; + } +} diff --git a/oss/piwik/trunk/core/PluginsFunctions/AdminMenu.php b/oss/piwik/trunk/core/PluginsFunctions/AdminMenu.php new file mode 100644 index 00000000..b52e7e32 --- /dev/null +++ b/oss/piwik/trunk/core/PluginsFunctions/AdminMenu.php @@ -0,0 +1,113 @@ +adminMenuOrdered)) + { + return $this->adminMenuOrdered; + } + + Piwik_PostEvent('AdminMenu.add'); + + $this->adminMenuOrdered = array(); + ksort($this->adminMenu); + foreach($this->adminMenu as $order => $menu) + { + foreach($menu as $key => &$element) + { + if(!is_null($element)) + { + $this->adminMenuOrdered[$key] = $element; + } + } + } + return $this->adminMenuOrdered; + } + + /* + * + */ + public function add($adminMenuName, $url, $displayedForCurrentUser, $order) + { + if($displayedForCurrentUser + && !isset($this->adminMenu[$adminMenuName])) + { + $this->adminMenu[$order][$adminMenuName] = $url; + } + } + + /* + * + */ + public function rename($adminMenuOriginal, $adminMenuRenamed) + { + $save = $this->adminMenu[$adminMenuOriginal]; + unset($this->adminMenu[$adminMenuOriginal]); + $this->adminMenu[$adminMenuRenamed] = $save; + } +} +function Piwik_GetCurrentAdminMenuName() +{ + $menu = Piwik_GetAdminMenu(); + $currentModule = Piwik::getModule(); + $currentAction = Piwik::getAction(); + foreach($menu as $name => $parameters) + { + if($parameters['module'] == $currentModule + && $parameters['action'] == $currentAction) + { + return $name; + } + } + return false; +} + +function Piwik_GetAdminMenu() +{ + return Piwik_AdminMenu::getInstance()->get(); +} + +function Piwik_AddAdminMenu( $adminMenuName, $url, $displayedForCurrentUser = true, $order = 10 ) +{ + return Piwik_AdminMenu::getInstance()->add($adminMenuName, $url, $displayedForCurrentUser, $order); +} + +function Piwik_RenameAdminMenuEntry($adminMenuOriginal, $adminMenuRenamed) +{ + Piwik_AdminMenu::getInstance()->rename($adminMenuOriginal, $adminMenuRenamed); +} diff --git a/oss/piwik/trunk/core/PluginsFunctions/Menu.php b/oss/piwik/trunk/core/PluginsFunctions/Menu.php new file mode 100644 index 00000000..2767b923 --- /dev/null +++ b/oss/piwik/trunk/core/PluginsFunctions/Menu.php @@ -0,0 +1,205 @@ +menu['Dashboard_Dashboard'] = null; + $this->menu['General_Visitors'] = null; + $this->menu['Actions_Actions'] = null; + $this->menu['Referers_Referers'] = null; + } + + /* + * + */ + function add( $mainMenuName, $subMenuName, $url ) + { + if(!isset($this->menu[$mainMenuName])) + { + $this->menu[$mainMenuName]['_url'] = $url; + } + if(!empty($subMenuName)) + { + $this->menu[$mainMenuName][$subMenuName] = $url; + } + } + + function isUrlFound($url) + { + $menu = Piwik_Menu::getInstance()->get(); + foreach($menu as $mainMenuName => $subMenus) + { + foreach($subMenus as $subMenuName => $menuUrl) + { + if($menuUrl == $url) + { + return true; + } + } + } + return false; + } + /* + * + */ + function rename($mainMenuOriginal, $subMenuOriginal, $mainMenuRenamed, $subMenuRenamed) + { + $this->renames[] = array($mainMenuOriginal, $subMenuOriginal, + $mainMenuRenamed, $subMenuRenamed); + } + + /* + * + */ + function editUrl($mainMenuToEdit, $subMenuToEdit, $newUrl ) + { + $this->edits[] = array($mainMenuToEdit, $subMenuToEdit, $newUrl); + + } + + /* + * @return array + */ + function get() + { + Piwik_PostEvent('Menu.add'); + $this->applyEdits(); + $this->applyRenames(); + $this->applyOrdering(); + return $this->menu; + } + + /* + * + */ + private function applyEdits() + { + foreach($this->edits as $edit) + { + $mainMenuToEdit = $edit[0]; + $subMenuToEdit = $edit[1]; + $newUrl = $edit[2]; + if(!isset($this->menu[$mainMenuToEdit][$subMenuToEdit])) + { + Piwik_AddMenu($mainMenuToEdit, $subMenuToEdit, $newUrl); + } + else + { + $this->menu[$mainMenuToEdit][$subMenuToEdit] = $newUrl; + } + } + } + + /* + * + */ + private function applyRenames() + { + foreach($this->renames as $rename) + { + $mainMenuOriginal = $rename[0]; + $subMenuOriginal = $rename[1]; + $mainMenuRenamed = $rename[2]; + $subMenuRenamed = $rename[3]; + if(isset($this->menu[$mainMenuOriginal][$subMenuOriginal])) + { + $save = $this->menu[$mainMenuOriginal][$subMenuOriginal]; + unset($this->menu[$mainMenuOriginal][$subMenuOriginal]); + $this->menu[$mainMenuRenamed][$subMenuRenamed] = $save; + } + } + } + + /* + * + */ + private function applyOrdering() + { + // we now do some cleaning on the menu + foreach($this->menu as $key => &$element) + { + if(is_null($element)) + { + unset($this->menu[$key]); + } + else + { + // we want to move some submenus in the first position + foreach($element as $nameSubmenu => $submenu) + { + if(preg_match('/Evolution/', $nameSubmenu) + || preg_match('/Overview/', $nameSubmenu)) + { + $newElement = array($nameSubmenu => $submenu); + unset($element[$nameSubmenu]); + $element = $newElement + $element; + break; + } + } + $element['_url'] = reset($element); + } + } + } +} + +function Piwik_IsMenuUrlFound($url) +{ + return Piwik_Menu::getInstance()->isUrlFound($url); +} + +function Piwik_GetMenu() +{ + return Piwik_Menu::getInstance()->get(); +} + +function Piwik_AddMenu( $mainMenuName, $subMenuName, $url ) +{ + Piwik_Menu::getInstance()->add($mainMenuName, $subMenuName, $url); +} + +function Piwik_RenameMenuEntry($mainMenuOriginal, $subMenuOriginal, + $mainMenuRenamed, $subMenuRenamed) +{ + Piwik_Menu::getInstance()->rename($mainMenuOriginal, $subMenuOriginal, $mainMenuRenamed, $subMenuRenamed); +} + +function Piwik_EditMenuUrl( $mainMenuToEdit, $subMenuToEdit, $newUrl ) +{ + Piwik_Menu::getInstance()->editUrl($mainMenuToEdit, $subMenuToEdit, $newUrl); +} diff --git a/oss/piwik/trunk/core/PluginsFunctions/Sql.php b/oss/piwik/trunk/core/PluginsFunctions/Sql.php new file mode 100644 index 00000000..c73ae687 --- /dev/null +++ b/oss/piwik/trunk/core/PluginsFunctions/Sql.php @@ -0,0 +1,123 @@ +exec($sql); + } + + static public function query($sql, $parameters = array()) + { + return self::getDb()->query($sql, $parameters); + } + + static public function fetchAll($sql, $parameters = array()) + { + return self::getDb()->fetchAll($sql, $parameters); + } + + static public function fetchRow($sql, $parameters = array()) + { + return self::getDb()->fetchRow($sql, $parameters); + } + + static public function fetchOne($sql, $parameters = array()) + { + return self::getDb()->fetchOne($sql, $parameters); + } +} + +/** + * Executes an unprepared SQL query on the DB. Recommended for DDL statements, e.g., CREATE/DROP/ALTER. + * The return result is DBMS-specific. For MySQLI, it returns the number of rows affected. For PDO, it returns the Zend_Db_Statement object + * If you want to fetch data from the DB you should use the function Piwik_FetchAll() + * + * @param string $sqlQuery + * @param array Parameters to bind in the query, array( param1 => value1, param2 => value2) + * @return integer|Zend_Db_Statement + */ +function Piwik_Exec($sqlQuery) +{ + return Piwik_Sql::exec($sqlQuery); +} + +/** + * Executes a SQL query on the DB and returns the Zend_Db_Statement object + * If you want to fetch data from the DB you should use the function Piwik_FetchAll() + * + * See also http://framework.zend.com/manual/en/zend.db.statement.html + * + * @param string $sqlQuery + * @param array Parameters to bind in the query, array( param1 => value1, param2 => value2) + * @return Zend_Db_Statement + */ +function Piwik_Query($sqlQuery, $parameters = array()) +{ + return Piwik_Sql::query($sqlQuery, $parameters); +} + +/** + * Executes the SQL Query and fetches all the rows from the database query + * + * @param string $sqlQuery + * @param array $parameters Parameters to bind in the query, array( param1 => value1, param2 => value2) + * @return array (one row in the array per row fetched in the DB) + */ +function Piwik_FetchAll( $sqlQuery, $parameters = array()) +{ + return Piwik_Sql::fetchAll($sqlQuery, $parameters); +} + +/** + * Fetches first row of result from the database query + * + * @param string $sqlQuery + * @param array $parameters Parameters to bind in the query, array( param1 => value1, param2 => value2) + * @return array + */ +function Piwik_FetchRow($sqlQuery, $parameters = array()) +{ + return Piwik_Sql::fetchRow($sqlQuery, $parameters); +} + +/** + * Fetches first column of first row of result from the database query + * + * @param string $sqlQuery + * @param array $parameters Parameters to bind in the query, array( param1 => value1, param2 => value2) + * @return string + */ +function Piwik_FetchOne( $sqlQuery, $parameters = array()) +{ + return Piwik_Sql::fetchOne($sqlQuery, $parameters); +} diff --git a/oss/piwik/trunk/core/PluginsFunctions/WidgetsList.php b/oss/piwik/trunk/core/PluginsFunctions/WidgetsList.php new file mode 100644 index 00000000..46a03a49 --- /dev/null +++ b/oss/piwik/trunk/core/PluginsFunctions/WidgetsList.php @@ -0,0 +1,80 @@ + $value) + { + $widgetUniqueId .= $name . $value; + } + self::$widgets[$widgetCategory][] = array( + 'name' => $widgetName, + 'uniqueId' => $widgetUniqueId, + 'parameters' => array ( 'module' => $controllerName, + 'action' => $controllerAction + ) + $customParameters + ); + } + + static function isDefined($controllerName, $controllerAction) + { + $widgetsList = self::get(); + foreach($widgetsList as $widgetCategory => $widgets) + { + foreach($widgets as $widget) + { + if($widget['parameters']['module'] == $controllerName + && $widget['parameters']['action'] == $controllerAction) + { + return true; + } + } + } + return false; + } +} + +function Piwik_GetWidgetsList() +{ + return Piwik_WidgetsList::get(); +} + +function Piwik_AddWidget( $widgetCategory, $widgetName, $controllerName, $controllerAction, $customParameters = array()) +{ + Piwik_WidgetsList::add($widgetCategory, $widgetName, $controllerName, $controllerAction, $customParameters); +} + +function Piwik_IsWidgetDefined($controllerName, $controllerAction) +{ + return Piwik_WidgetsList::isDefined($controllerName, $controllerAction); +}