265 lines
7.3 KiB
PHP
265 lines
7.3 KiB
PHP
<?php
|
|
/**
|
|
* @brief Repository modules XML feed reader
|
|
*
|
|
* Provides an object to parse XML feed of modules from repository.
|
|
*
|
|
* @package Dotclear
|
|
* @subpackage Core
|
|
*
|
|
* @copyright Olivier Meunier & Association Dotclear
|
|
* @copyright GPL-2.0-only
|
|
*
|
|
* @since 2.6
|
|
*/
|
|
|
|
if (!defined('DC_RC_PATH')) {return;}
|
|
|
|
class dcStoreReader extends netHttp
|
|
{
|
|
/** @var string User agent used to query repository */
|
|
protected $user_agent = 'DotClear.org RepoBrowser/0.1';
|
|
/** @var integer User agent used to query repository */
|
|
protected $timeout = 5;
|
|
/** @var array HTTP Cache validators */
|
|
protected $validators = null;
|
|
/** @var string Cache temporary directory */
|
|
protected $cache_dir = null;
|
|
/** @var string Cache file prefix */
|
|
protected $cache_file_prefix = 'dcrepo';
|
|
/** @var integer Cache TTL */
|
|
protected $cache_ttl = '-30 minutes';
|
|
/** @var boolean 'Cache' TTL on server failed */
|
|
protected $cache_touch_on_fail = true;
|
|
/** @var boolean Force query server */
|
|
protected $force = false;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* Bypass first argument of clearbricks netHttp constructor.
|
|
*/
|
|
public function __construct()
|
|
{
|
|
parent::__construct('');
|
|
$this->setUserAgent(sprintf('Dotclear/%s)', DC_VERSION));
|
|
}
|
|
|
|
/**
|
|
* Parse modules feed.
|
|
*
|
|
* @param string $url XML feed URL
|
|
* @return object dcStore instance
|
|
*/
|
|
public function parse($url)
|
|
{
|
|
$this->validators = [];
|
|
|
|
if ($this->cache_dir) {
|
|
return $this->withCache($url);
|
|
} elseif (!$this->getModulesXML($url) || $this->getStatus() != '200') {
|
|
return false;
|
|
}
|
|
|
|
return new dcStoreParser($this->getContent());
|
|
}
|
|
|
|
/**
|
|
* Quick parse modules feed.
|
|
*
|
|
* @param string $url XML feed URL
|
|
* @param string $cache_dir Cache directoy or null for no cache
|
|
* @param boolean $force Force query repository
|
|
* @return object Self instance
|
|
*/
|
|
public static function quickParse($url, $cache_dir = null, $force = false)
|
|
{
|
|
$parser = new self();
|
|
if ($cache_dir) {
|
|
$parser->setCacheDir($cache_dir);
|
|
}
|
|
if ($force) {
|
|
$parser->setForce($force);
|
|
}
|
|
|
|
return $parser->parse($url);
|
|
}
|
|
|
|
/**
|
|
* Set cache directory.
|
|
*
|
|
* @param string $dir Cache directory
|
|
* @return boolean True if cache dierctory is useable
|
|
*/
|
|
public function setCacheDir($dir)
|
|
{
|
|
$this->cache_dir = null;
|
|
|
|
if (!empty($dir) && is_dir($dir) && is_writeable($dir)) {
|
|
$this->cache_dir = $dir;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Set cache TTL.
|
|
*
|
|
* @param string $str Cache TTL
|
|
*/
|
|
public function setCacheTTL($str)
|
|
{
|
|
$str = trim($str);
|
|
|
|
if (!empty($str)) {
|
|
$this->cache_ttl = substr($str, 0, 1) == '-' ? $str : '-' . $str;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set force query repository.
|
|
*
|
|
* @param boolean $force True to force query
|
|
*/
|
|
public function setForce($force)
|
|
{
|
|
$this->force = $force;
|
|
}
|
|
|
|
/**
|
|
* Get repository XML feed URL content.
|
|
*
|
|
* @param string $url XML feed URL
|
|
* @return string Feed content
|
|
*/
|
|
protected function getModulesXML($url)
|
|
{
|
|
if (!self::readURL($url, $ssl, $host, $port, $path, $user, $pass)) {
|
|
return false;
|
|
}
|
|
$this->setHost($host, $port);
|
|
$this->useSSL($ssl);
|
|
$this->setAuthorization($user, $pass);
|
|
|
|
try {
|
|
return $this->get($path);
|
|
} catch (Exception $e) {
|
|
// @todo Log error when repository query fail
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get repository modules list using cache.
|
|
*
|
|
* @param string $url XML feed URL
|
|
* @return array Feed content or False on fail
|
|
*/
|
|
protected function withCache($url)
|
|
{
|
|
$url_md5 = md5($url);
|
|
$cached_file = sprintf('%s/%s/%s/%s/%s.ser',
|
|
$this->cache_dir,
|
|
$this->cache_file_prefix,
|
|
substr($url_md5, 0, 2),
|
|
substr($url_md5, 2, 2),
|
|
$url_md5
|
|
);
|
|
|
|
$may_use_cached = false;
|
|
|
|
# Use cache file ?
|
|
if (@file_exists($cached_file) && !$this->force) {
|
|
$may_use_cached = true;
|
|
$ts = @filemtime($cached_file);
|
|
if ($ts > strtotime($this->cache_ttl)) {
|
|
# Direct cache
|
|
return unserialize(file_get_contents($cached_file));
|
|
}
|
|
$this->setValidator('IfModifiedSince', $ts);
|
|
}
|
|
|
|
# Query repository
|
|
if (!$this->getModulesXML($url)) {
|
|
if ($may_use_cached) {
|
|
# Touch cache TTL even if query failed ?
|
|
if ($this->cache_touch_on_fail) {
|
|
@files::touch($cached_file);
|
|
}
|
|
# Connection failed - fetched from cache
|
|
return unserialize(file_get_contents($cached_file));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
# Parse response
|
|
switch ($this->getStatus()) {
|
|
# Not modified, use cache
|
|
case '304':
|
|
@files::touch($cached_file);
|
|
return unserialize(file_get_contents($cached_file));
|
|
# Ok, parse feed
|
|
case '200':
|
|
if ($modules = new dcStoreParser($this->getContent())) {
|
|
try {
|
|
files::makeDir(dirname($cached_file), true);
|
|
} catch (Exception $e) {
|
|
return $modules;
|
|
}
|
|
|
|
if (($fp = @fopen($cached_file, 'wb'))) {
|
|
fwrite($fp, serialize($modules));
|
|
fclose($fp);
|
|
files::inheritChmod($cached_file);
|
|
}
|
|
return $modules;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Prepare query.
|
|
*
|
|
* @return array Query headers
|
|
*/
|
|
protected function buildRequest()
|
|
{
|
|
$headers = parent::buildRequest();
|
|
|
|
# Cache validators
|
|
if (!empty($this->validators)) {
|
|
if (isset($this->validators['IfModifiedSince'])) {
|
|
$headers[] = 'If-Modified-Since: ' . $this->validators['IfModifiedSince'];
|
|
}
|
|
if (isset($this->validators['IfNoneMatch'])) {
|
|
if (is_array($this->validators['IfNoneMatch'])) {
|
|
$etags = implode(',', $this->validators['IfNoneMatch']);
|
|
} else {
|
|
$etags = $this->validators['IfNoneMatch'];
|
|
}
|
|
$headers[] = '';
|
|
}
|
|
}
|
|
|
|
return $headers;
|
|
}
|
|
|
|
/**
|
|
* Tweak query cache validator.
|
|
*
|
|
* @param string $key Validator key
|
|
* @param string $value Validator value
|
|
*/
|
|
private function setValidator($key, $value)
|
|
{
|
|
if ($key == 'IfModifiedSince') {
|
|
$value = gmdate('D, d M Y H:i:s', $value) . ' GMT';
|
|
}
|
|
|
|
$this->validators[$key] = $value;
|
|
}
|
|
}
|