update Piwik to version 2.16 (fixes #91)

This commit is contained in:
oliver 2016-04-10 18:55:57 +02:00
commit d885a4baa9
5833 changed files with 418860 additions and 226988 deletions

View file

@ -0,0 +1,62 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl-3.0.html LGPL v3 or later
*
*/
namespace Piwik\Cache;
/**
* Backend interface
*/
interface Backend
{
/**
* Fetches an entry from the cache.
*
* @param string $id The id of the cache entry to fetch.
*
* @return mixed The cached data or FALSE, if no cache entry exists for the given id.
*/
public function doFetch($id);
/**
* Tests if an entry exists in the cache.
*
* @param string $id The cache id of the entry to check for.
*
* @return boolean TRUE if a cache entry exists for the given cache id, FALSE otherwise.
*/
public function doContains($id);
/**
* Puts data into the cache.
*
* @param string $id The cache id.
* @param mixed $data The cache entry/data.
* @param int $lifeTime The cache lifetime.
* If != 0, sets a specific lifetime for this cache entry (0 => infinite lifeTime).
*
* @return boolean TRUE if the entry was successfully stored in the cache, FALSE otherwise.
*/
public function doSave($id, $data, $lifeTime = 0);
/**
* Deletes a cache entry.
*
* @param string $id The cache id.
*
* @return boolean TRUE if the cache entry was successfully deleted, FALSE otherwise.
*/
public function doDelete($id);
/**
* Flushes all cache entries from the cache.
*
* @return boolean
*/
public function doFlush();
}

View file

@ -0,0 +1,46 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl-3.0.html LGPL v3 or later
*
*/
namespace Piwik\Cache\Backend;
use Piwik\Cache\Backend;
use Doctrine\Common\Cache\ArrayCache as DoctrineArrayCache;
class ArrayCache extends DoctrineArrayCache implements Backend
{
public function doFetch($id)
{
return parent::doFetch($id);
}
public function doContains($id)
{
return parent::doContains($id);
}
public function doSave($id, $data, $lifeTime = 0)
{
return parent::doSave($id, $data, $lifeTime);
}
public function doDelete($id)
{
if (!$this->doContains($id)) {
return false;
}
return parent::doDelete($id);
}
public function doFlush()
{
return parent::doFlush();
}
}

View file

@ -0,0 +1,103 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl-3.0.html LGPL v3 or later
*
*/
namespace Piwik\Cache\Backend;
use Piwik\Cache\Backend;
/**
* TODO: extend Doctrine ChainCache as soon as available
*/
class Chained implements Backend
{
/**
* @var Backend[]
*/
private $backends = array();
/**
* Initializes the chained backend.
*
* @param Backend[] $backends An array of backends to use. They should be ordered from fastest to slowest.
*/
public function __construct($backends = array())
{
$this->backends = array_values($backends);
}
public function getBackends()
{
return $this->backends;
}
public function doFetch($id)
{
foreach ($this->backends as $key => $backend) {
if ($backend->doContains($id)) {
$value = $backend->doFetch($id);
// EG If chain is ARRAY => REDIS => DB and we find result in DB we will update REDIS and ARRAY
for ($subKey = $key - 1 ; $subKey >= 0 ; $subKey--) {
$this->backends[$subKey]->doSave($id, $value, 300); // TODO we should use the actual TTL here
}
return $value;
}
}
return false;
}
public function doContains($id)
{
foreach ($this->backends as $backend) {
if ($backend->doContains($id)) {
return true;
}
}
return false;
}
public function doSave($id, $data, $lifeTime = 0)
{
$stored = true;
foreach ($this->backends as $backend) {
$stored = $backend->doSave($id, $data, $lifeTime) && $stored;
}
return $stored;
}
// returns true if was deleted from at least one backend, false if it was not present in any of those
public function doDelete($id)
{
$success = false;
foreach ($this->backends as $backend) {
if ($backend->doContains($id)) {
$success = $backend->doDelete($id) || $success;
}
}
return $success;
}
public function doFlush()
{
$flushed = true;
foreach ($this->backends as $backend) {
$flushed = $backend->doFlush() && $flushed;
}
return $flushed;
}
}

View file

@ -0,0 +1,111 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl-3.0.html LGPL v3 or later
*
*/
namespace Piwik\Cache\Backend;
use Piwik\Cache\Backend;
class Factory
{
public function buildArrayCache()
{
return new ArrayCache();
}
public function buildFileCache($options)
{
return new File($options['directory']);
}
public function buildNullCache()
{
return new NullCache();
}
public function buildChainedCache($options)
{
$backends = array();
foreach ($options['backends'] as $backendToBuild) {
$backendOptions = array();
if (array_key_exists($backendToBuild, $options)) {
$backendOptions = $options[$backendToBuild];
}
$backends[] = $this->buildBackend($backendToBuild, $backendOptions);
}
return new Chained($backends);
}
public function buildRedisCache($options)
{
if (empty($options['host']) || empty($options['port'])) {
throw new \InvalidArgumentException('RedisCache is not configured. Please provide at least a host and a port');
}
$timeout = 0.0;
if (array_key_exists('timeout', $options)) {
$timeout = $options['timeout'];
}
$redis = new \Redis();
$redis->connect($options['host'], $options['port'], $timeout);
if (!empty($options['password'])) {
$redis->auth($options['password']);
}
if (array_key_exists('database', $options)) {
$redis->select((int) $options['database']);
}
$redisCache = new Redis();
$redisCache->setRedis($redis);
return $redisCache;
}
/**
* Build a specific backend instance.
*
* @param string $type The type of backend you want to create. Eg 'array', 'file', 'chained', 'null', 'redis'.
* @param array $options An array of options for the backend you want to create.
* @return Backend
* @throws Factory\BackendNotFoundException In case the given type was not found.
*/
public function buildBackend($type, array $options)
{
switch ($type) {
case 'array':
return $this->buildArrayCache();
case 'file':
return $this->buildFileCache($options);
case 'chained':
return $this->buildChainedCache($options);
case 'null':
return $this->buildNullCache();
case 'redis':
return $this->buildRedisCache($options);
default:
throw new Factory\BackendNotFoundException("Cache backend $type not valid");
}
}
}

View file

@ -0,0 +1,15 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl-3.0.html LGPL v3 or later
*
*/
namespace Piwik\Cache\Backend\Factory;
use \Exception;
class BackendNotFoundException extends Exception {
}

View file

@ -0,0 +1,155 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl-3.0.html LGPL v3 or later
*
*/
namespace Piwik\Cache\Backend;
use Doctrine\Common\Cache\PhpFileCache;
use Piwik\Cache\Backend;
/**
* This class is used to cache data on the filesystem.
*
* This cache creates one file per id. Every time you try to read the value it will load the cache file again. It will
* try to invalidate the Opcache for a specific cache file if needed.
*/
class File extends PhpFileCache implements Backend
{
// for testing purposes since tests run on both CLI/FPM (changes in CLI can't invalidate
// opcache in FPM, so we have to invalidate before reading)
public static $invalidateOpCacheBeforeRead = false;
/**
* Constructor.
*
* @param string $directory The cache directory.
* @param string|null $extension The cache file extension.
*
* @throws \InvalidArgumentException
*/
public function __construct($directory, $extension = '.php')
{
if (!is_dir($directory)) {
$this->createDirectory($directory);
}
parent::__construct($directory, $extension);
}
public function doFetch($id)
{
if (self::$invalidateOpCacheBeforeRead) {
$this->invalidateCacheFile($id);
}
return parent::doFetch($id);
}
public function doContains($id)
{
return parent::doContains($id);
}
public function doSave($id, $data, $lifeTime = 0)
{
if (!is_dir($this->directory)) {
$this->createDirectory($this->directory);
}
$success = parent::doSave($id, $data, $lifeTime);
if ($success) {
$this->invalidateCacheFile($id);
}
return $success;
}
public function doDelete($id)
{
$this->invalidateCacheFile($id);
$success = parent::doDelete($id);
$this->invalidateCacheFile($id); // in case file was cached by another request between invalidate and doDelete()
return $success;
}
public function doFlush()
{
// if the directory does not exist, do not bother to continue clearing
if (!is_dir($this->directory)) {
return;
}
foreach ($this->getFileIterator() as $name => $file) {
$this->opCacheInvalidate($name);
}
parent::doFlush();
}
private function invalidateCacheFile($id)
{
$filename = $this->getFilename($id);
$this->opCacheInvalidate($filename);
}
/**
* @param string $id
*
* @return string
*/
protected function getFilename($id)
{
$path = $this->directory . DIRECTORY_SEPARATOR;
$id = preg_replace('@[\\\/:"*?<>|]+@', '', $id);
return $path . $id . $this->getExtension();
}
private function opCacheInvalidate($filepath)
{
if (is_file($filepath)) {
if (function_exists('opcache_invalidate')) {
@opcache_invalidate($filepath, $force = true);
}
if (function_exists('apc_delete_file')) {
@apc_delete_file($filepath);
}
}
}
/**
* @return \Iterator
*/
private function getFileIterator()
{
$pattern = '/^.+\\' . $this->getExtension() . '$/i';
$iterator = new \RecursiveDirectoryIterator($this->directory);
$iterator = new \RecursiveIteratorIterator($iterator);
return new \RegexIterator($iterator, $pattern);
}
private function createDirectory($path)
{
if (!is_dir($path)) {
// the mode in mkdir is modified by the current umask
@mkdir($path, 0750, $recursive = true);
}
// try to overcome restrictive umask (mis-)configuration
if (!is_writable($path)) {
@chmod($path, 0755);
if (!is_writable($path)) {
@chmod($path, 0775);
// enough! we're not going to make the directory world-writeable
}
}
}
}

View file

@ -0,0 +1,44 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl-3.0.html LGPL v3 or later
*
*/
namespace Piwik\Cache\Backend;
use Piwik\Cache\Backend;
/**
* Can be used in development to prevent caching. Does not cache anything.
*/
class NullCache implements Backend
{
public function doFetch($id)
{
return false;
}
public function doContains($id)
{
return false;
}
public function doSave($id, $data, $lifeTime = 0)
{
return true;
}
public function doDelete($id)
{
return true;
}
public function doFlush()
{
return true;
}
}

View file

@ -0,0 +1,41 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl-3.0.html LGPL v3 or later
*
*/
namespace Piwik\Cache\Backend;
use Doctrine\Common\Cache\RedisCache;
use Piwik\Cache\Backend;
class Redis extends RedisCache implements Backend
{
public function doFetch($id)
{
return parent::doFetch($id);
}
public function doContains($id)
{
return parent::doContains($id);
}
public function doSave($id, $data, $lifeTime = 0)
{
return parent::doSave($id, $data, $lifeTime);
}
public function doDelete($id)
{
return parent::doDelete($id);
}
public function doFlush()
{
return parent::doFlush();
}
}

View file

@ -0,0 +1,55 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl-3.0.html LGPL v3 or later
*
*/
namespace Piwik\Cache;
interface Cache
{
/**
* Fetches an entry from the cache.
*
* @param string $id The cache id.
* @return mixed The cached data or FALSE, if no cache entry exists for the given id.
*/
public function fetch($id);
/**
* Tests if an entry exists in the cache.
*
* @param string $id The cache id.
* @return boolean TRUE if a cache entry exists for the given cache id, FALSE otherwise.
*/
public function contains($id);
/**
* Puts data into the cache.
*
* @param string $id The cache id.
* @param mixed $data The cache entry/data.
* @param int $lifeTime The cache lifetime in seconds.
* If != 0, sets a specific lifetime for this cache entry (0 => infinite lifeTime).
*
* @return boolean TRUE if the entry was successfully stored in the cache, FALSE otherwise.
*/
public function save($id, $data, $lifeTime = 0);
/**
* Deletes a cache entry.
*
* @param string $id The cache id.
* @return boolean TRUE if the cache entry was successfully deleted, FALSE otherwise.
*/
public function delete($id);
/**
* Flushes all cache entries.
*
* @return boolean TRUE if the cache entries were successfully flushed, FALSE otherwise.
*/
public function flushAll();
}

View file

@ -0,0 +1,138 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl-3.0.html LGPL v3 or later
*
*/
namespace Piwik\Cache;
use Piwik\Cache\Backend;
/**
* This cache uses one "cache" entry for all cache entries it contains.
*
* This comes handy for things that you need very often, nearly in every request. Instead of having to read eg.
* a hundred caches from file we only load one file which contains the hundred cache ids. Should be used only for things
* that you need very often and only for cache entries that are not too large to keep loading and parsing the single
* cache entry fast.
*
* $cache = new Eager($backend, $storageId = 'eagercache');
* // $cache->fetch('my'id')
* // $cache->save('myid', 'test');
*
* // ... at some point or at the end of the request
* $cache->persistCacheIfNeeded($lifeTime = 43200);
*/
class Eager implements Cache
{
/**
* @var Backend
*/
private $storage;
private $storageId;
private $content = array();
private $isDirty = false;
/**
* Loads the cache entries from the given backend using the given storageId.
*
* @param Backend $storage
* @param $storageId
*/
public function __construct(Backend $storage, $storageId)
{
$this->storage = $storage;
$this->storageId = $storageId;
$content = $storage->doFetch($storageId);
if (is_array($content)) {
$this->content = $content;
}
}
/**
* Fetches an entry from the cache.
*
* Make sure to call the method {@link contains()} to verify whether there is actually any content saved under
* this cache id.
*
* @param string $id The cache id.
* @return int|float|string|boolean|array
*/
public function fetch($id)
{
return $this->content[$id];
}
/**
* {@inheritdoc}
*/
public function contains($id)
{
return array_key_exists($id, $this->content);
}
/**
* Puts data into the cache.
*
* @param string $id The cache id.
* @param int|float|string|boolean|array $content
* @param int $lifeTime Setting a lifetime is not supported by this cache and the parameter will be ignored.
* @return boolean
*/
public function save($id, $content, $lifeTime = 0)
{
if (is_object($content)) {
throw new \InvalidArgumentException('You cannot use this cache to cache an object, only arrays, strings and numbers. Have a look at Transient cache.');
// for performance reasons we do currently not recursively search whether any array contains an object.
}
$this->content[$id] = $content;
$this->isDirty = true;
return true;
}
/**
* {@inheritdoc}
*/
public function delete($id)
{
if ($this->contains($id)) {
$this->isDirty = true;
unset($this->content[$id]);
return true;
}
return false;
}
/**
* {@inheritdoc}
*/
public function flushAll()
{
$this->storage->doDelete($this->storageId);
$this->content = array();
$this->isDirty = false;
return true;
}
/**
* Will persist all previously made changes if there were any.
*
* @param int $lifeTime The cache lifetime in seconds.
* If != 0, sets a specific lifetime for this cache entry (0 => infinite lifeTime).
*/
public function persistCacheIfNeeded($lifeTime)
{
if ($this->isDirty) {
$this->storage->doSave($this->storageId, $this->content, $lifeTime);
}
}
}

View file

@ -0,0 +1,121 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl-3.0.html LGPL v3 or later
*
*/
namespace Piwik\Cache;
use Piwik\Cache\Backend;
class Lazy implements Cache
{
private $backend;
/**
* Initializes the cache.
*
* @param Backend $backend Any backend that should be used to store / hold the cache entries.
*/
public function __construct(Backend $backend)
{
$this->backend = $backend;
}
/**
* Fetches an entry from the cache.
*
* @param string $id The cache id.
* @return mixed The cached data or FALSE, if no cache entry exists for the given id.
*/
public function fetch($id)
{
$id = $this->getCompletedCacheIdIfValid($id);
return $this->backend->doFetch($id);
}
/**
* {@inheritdoc}
*/
public function contains($id)
{
$id = $this->getCompletedCacheIdIfValid($id);
return $this->backend->doContains($id);
}
/**
* Puts data into the cache.
*
* @param string $id The cache id.
* @param mixed $data The cache entry/data.
* @param int $lifeTime The cache lifetime in seconds.
* If != 0, sets a specific lifetime for this cache entry (0 => infinite lifeTime).
*
* @return boolean TRUE if the entry was successfully stored in the cache, FALSE otherwise.
*/
public function save($id, $data, $lifeTime = 0)
{
$id = $this->getCompletedCacheIdIfValid($id);
if (is_object($data)) {
throw new \InvalidArgumentException('You cannot use this cache to cache an object, only arrays, strings and numbers. Have a look at Transient cache.');
// for performance reasons we do currently not recursively search whether any array contains an object.
}
return $this->backend->doSave($id, $data, $lifeTime);
}
/**
* {@inheritdoc}
*/
public function delete($id)
{
$id = $this->getCompletedCacheIdIfValid($id);
return $this->backend->doDelete($id);
}
/**
* {@inheritdoc}
*/
public function flushAll()
{
return $this->backend->doFlush();
}
private function getCompletedCacheIdIfValid($id)
{
$this->checkId($id);
return 'piwikcache_' . $id;
}
private function checkId($id)
{
if (empty($id)) {
throw new \InvalidArgumentException('Empty cache id given');
}
if (!$this->isValidId($id)) {
throw new \InvalidArgumentException("Invalid cache id request $id");
}
}
/**
* Returns true if the string is a valid id.
*
* Id that start with a-Z or 0-9 and contain a-Z, 0-9, underscore(_), dash(-), and dot(.) will be accepted.
* Id beginning with anything but a-Z or 0-9 will be rejected (including .htaccess for example).
* Id containing anything other than above mentioned will also be rejected (file names with spaces won't be accepted).
*
* @param string $id
* @return bool
*/
private function isValidId($id)
{
return (0 !== preg_match('/(^[a-zA-Z0-9]+([a-zA-Z_0-9.-]*))$/D', $id));
}
}

View file

@ -0,0 +1,89 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/lgpl-3.0.html LGPL v3 or later
*
*/
namespace Piwik\Cache;
use Piwik\Cache\Backend;
/**
* This class is used to cache data during one request.
*
* Compared to the lazy cache it does not support setting any lifetime. To be a fast cache it does
* not validate any cache id etc.
*/
class Transient implements Cache
{
/**
* @var array $data
*/
private $data = array();
/**
* Fetches an entry from the cache.
*
* Make sure to call the method {@link has()} to verify whether there is actually any content set under this
* cache id.
*
* @param string $id The cache id.
* @return mixed
*/
public function fetch($id)
{
if ($this->contains($id)) {
return $this->data[$id];
}
return false;
}
/**
* {@inheritdoc}
*/
public function contains($id)
{
return isset($this->data[$id]) || array_key_exists($id, $this->data);
}
/**
* Puts data into the cache.
*
* @param string $id The cache id.
* @param mixed $content
* @param int $lifeTime Setting a lifetime is not supported by this cache and the parameter will be ignored.
* @return boolean
*/
public function save($id, $content, $lifeTime = 0)
{
$this->data[$id] = $content;
return true;
}
/**
* {@inheritdoc}
*/
public function delete($id)
{
if (!$this->contains($id)) {
return false;
}
unset($this->data[$id]);
return true;
}
/**
* {@inheritdoc}
*/
public function flushAll()
{
$this->data = array();
return true;
}
}