This commit is contained in:
Mitsukarenai 2013-04-22 12:00:26 +02:00
parent b0fb1a8d70
commit 732c7d5f6c
12 changed files with 2906 additions and 0 deletions

908
autoblogs/autoblog.php Normal file
View file

@ -0,0 +1,908 @@
<?php
/*
VroumVroumBlog 0.3.0
This blog automatically publishes articles from an external RSS 2.0 or ATOM feed.
Requirement for the source RSS feed:
- Source feed MUST be a valid RSS 2.0, RDF 1.0 or ATOM 1.0 feed.
- Source feed MUST be valid UTF-8
- Source feed MUST contain article body
This program is public domain. COPY COPY COPY !
*/
$vvbversion = '0.3.0';
if (!version_compare(phpversion(), '5.3.0', '>='))
die("This software requires PHP version 5.3.0 at least, yours is ".phpversion());
if (!class_exists('SQLite3'))
die("This software requires the SQLite3 PHP extension, and it can't be found on this system!");
libxml_disable_entity_loader(true);
// Config and data file locations
if (file_exists(__DIR__ . '/../config.php')) {
require_once __DIR__ . '/../config.php';
}
//else die("Configuration file not found.");
if (file_exists(__DIR__ . '/../functions.php')){
require_once __DIR__ . '/../functions.php';
}
else die("Functions file not found.");
if (!defined('ROOT_DIR'))
define('ROOT_DIR', __DIR__);
if (!defined('CONFIG_FILE')) define('CONFIG_FILE', ROOT_DIR . '/vvb.ini');
if (!defined('ARTICLES_DB_FILE')) define('ARTICLES_DB_FILE', ROOT_DIR . '/articles.db');
if (!defined('MEDIA_DIR')) define('MEDIA_DIR', ROOT_DIR . '/media');
if (!defined('LOCAL_URL'))
{
// Automagic URL discover
define('LOCAL_URL', 'http' . (!empty($_SERVER['HTTPS']) ? 's' : '')."://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}");
}
if (!defined('LOCAL_URI'))
{
// filename
define('LOCAL_URI', (basename($_SERVER['SCRIPT_FILENAME']) == 'index.php' ? '' : basename($_SERVER['SCRIPT_FILENAME'])) . '?');
}
if (!function_exists('__'))
{
// Translation?
function __($str)
{
if ($str == '_date_format')
return '%A %e %B %Y at %H:%M';
else
return $str;
}
}
// ERROR MANAGEMENT
class VroumVroum_User_Exception extends Exception {}
class VroumVroum_Feed_Exception extends Exception
{
static public function getXMLErrorsAsString($errors)
{
$out = array();
foreach ($errors as $error)
{
$return = $xml[$error->line - 1] . "\n";
$return .= str_repeat('-', $error->column) . "^\n";
switch ($error->level) {
case LIBXML_ERR_WARNING:
$return .= "Warning ".$error->code.": ";
break;
case LIBXML_ERR_ERROR:
$return .= "Error ".$error->code.": ";
break;
case LIBXML_ERR_FATAL:
$return .= "Fatal Error ".$error->code.": ";
break;
}
$return .= trim($error->message) .
"\n Line: ".$error->line .
"\n Column: ".$error->column;
if ($error->file) {
$return .= "\n File: ".$error->file;
}
$out[] = $return;
}
return $out;
}
}
error_reporting(E_ALL);
function exception_error_handler($errno, $errstr, $errfile, $errline )
{
// For @ ignored errors
if (error_reporting() === 0) return;
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
function exception_handler($e)
{
if ($e instanceOf VroumVroum_User_Exception)
{
echo '<h3>'.$e->getMessage().'</h3>';
exit;
}
$error = "Error happened !\n\n".
$e->getCode()." - ".$e->getMessage()."\n\nIn: ".
$e->getFile() . ":" . $e->getLine()."\n\n";
if (!empty($_SERVER['HTTP_HOST']))
$error .= 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']."\n\n";
$error .= $e->getTraceAsString();
//$error .= print_r($_SERVER, true);
echo $error;
exit;
}
set_error_handler("exception_error_handler");
set_exception_handler("exception_handler");
// CONFIGURATION
class VroumVroum_Config
{
public $site_type = '';
public $site_title = '';
public $site_description = '';
public $site_url = '';
public $feed_url = '';
public $articles_per_page = 10;
public $update_interval = 3600;
public $update_timeout = 10;
public function __construct()
{
if (!file_exists(CONFIG_FILE))
throw new VroumVroum_User_Exception("Missing configuration file '".basename(CONFIG_FILE)."'.");
$ini = parse_ini_file(CONFIG_FILE);
foreach ($ini as $key=>$value)
{
$key = strtolower($key);
if (!property_exists($this, $key))
continue; // Unknown config
if (is_string($this->$key) || is_null($this->$key))
$this->$key = trim((string) $value);
elseif (is_int($this->$key))
$this->$key = (int) $value;
elseif (is_bool($this->$key))
$this->$key = (bool) $value;
}
// Check that all required values are filled
$check = array('site_type', 'site_title', 'site_url', 'feed_url', 'update_timeout', 'update_interval', 'articles_per_page');
foreach ($check as $c)
{
if (!trim($this->$c))
throw new VroumVroum_User_Exception("Missing or empty configuration value '".$c."' which is required!");
}
}
public function __set($key, $value)
{
return;
}
}
// BLOG
class VroumVroum_Blog
{
protected $articles = null;
protected $local = null;
public $config = null;
static public function removeHTML($str)
{
$str = strip_tags($str);
$str = html_entity_decode($str, ENT_QUOTES, 'UTF-8');
return $str;
}
static public function toURI($str)
{
$uri = self::removeHTML(trim($str));
$uri = substr($uri, 0, 70);
$uri = preg_replace('/[^\w\d()\p{L}]+/u', '-', $uri);
$uri = preg_replace('/-{2,}/', '-', $uri);
$uri = preg_replace('/^-|-$/', '', $uri);
return $uri;
}
public function __construct()
{
$this->config = new VroumVroum_Config;
$create_articles_db = file_exists(ARTICLES_DB_FILE) ? false : true;
$this->articles = new SQLite3(ARTICLES_DB_FILE);
if ($create_articles_db)
{
$this->articles->exec('
CREATE TABLE articles (
id INTEGER PRIMARY KEY,
feed_id TEXT,
title TEXT,
uri TEXT,
url TEXT,
date INT,
content TEXT
);
CREATE TABLE update_log (
date INT PRIMARY KEY,
success INT,
log TEXT
);
CREATE UNIQUE INDEX feed_id ON articles (feed_id);
CREATE INDEX date ON articles (date);
');
}
$this->articles->createFunction('countintegers', array($this, 'sql_countintegers'));
}
public function getLocalURL($in)
{
return "./?".(is_array($in) ? $in['uri'] : $in);
}
protected function log_update($success, $log = '')
{
$this->articles->exec('INSERT INTO update_log (date, success, log) VALUES (\''.time().'\', \''.(int)(bool)$success.'\',
\''.$this->articles->escapeString($log).'\');');
// Delete old log
$this->articles->exec('DELETE FROM update_log WHERE date > (SELECT date FROM update_log ORDER BY date DESC LIMIT 100,1);');
return true;
}
public function insertOrUpdateArticle($feed_id, $title, $url, $date, $content)
{
$exists = $this->articles->querySingle('SELECT date, id, title, content FROM articles WHERE feed_id = \''.$this->articles->escapeString($feed_id).'\';', true);
if (empty($exists))
{
$uri = self::toURI($title);
if ($this->articles->querySingle('SELECT 1 FROM articles WHERE uri = \''.$this->articles->escapeString($uri).'\';'))
{
$uri = date('Y-m-d-') . $uri;
}
$content = $this->mirrorMediasForArticle($content, $url);
$this->articles->exec('INSERT INTO articles (id, feed_id, title, uri, url, date, content) VALUES (NULL,
\''.$this->articles->escapeString($feed_id).'\', \''.$this->articles->escapeString($title).'\',
\''.$this->articles->escapeString($uri).'\', \''.$this->articles->escapeString($url).'\',
\''.(int)$date.'\', \''.$this->articles->escapeString($content).'\');');
$id = $this->articles->lastInsertRowId();
$title = self::removeHTML($title);
$content = self::removeHTML($content);
}
else
{
// Doesn't need update
if ($date == $exists['date'] && $content == $exists['content'] && $title == $exists['title'])
{
return false;
}
$id = $exists['id'];
if ($content != $exists['content'])
$content = $this->mirrorMediasForArticle($content, $url);
$this->articles->exec('UPDATE articles SET title=\''.$this->articles->escapeString($title).'\',
url=\''.$this->articles->escapeString($url).'\', content=\''.$this->articles->escapeString($content).'\',
date=\''.(int)$date.'\' WHERE id = \''.(int)$id.'\';');
$title = self::removeHTML($title);
$content = self::removeHTML($content);
}
return $id;
}
public function mustUpdate()
{
if (isset($_GET['update']))
return true;
$last_update = $this->articles->querySingle('SELECT date FROM update_log ORDER BY date DESC LIMIT 1;');
if (!empty($last_update) && (int) $last_update > (time() - $this->config->update_interval))
return false;
return true;
}
protected function _getStreamContext()
{
return stream_context_create(
array(
'http' => array(
'method' => 'GET',
'timeout' => $this->config->update_timeout,
'header' => "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:20.0; Autoblogs; +https://github.com/mitsukarenai/Projet-Autoblog/) Gecko/20100101 Firefox/20.0\r\n",
)
)
);
}
public function update()
{
if (!$this->mustUpdate())
return false;
try {
$body = file_get_contents($this->config->feed_url, false, $this->_getStreamContext());
}
catch (ErrorException $e)
{
$this->log_update(false, $e->getMessage() . "\n\n" . (!empty($http_response_header) ? implode("\n", $http_response_header) : ''));
throw new VroumVroum_Feed_Exception("Can't retrieve feed: ".$e->getMessage());
}
libxml_use_internal_errors(true);
$xml = @simplexml_load_string($body);
if (!$xml)
{
$errors = VroumVroum_Feed_Exception::getXMLErrorsAsString(libxml_get_errors());
$this->log_update(false, implode("\n", $errors) . "\n\n" . $body);
throw new VroumVroum_Feed_Exception("Feed is invalid - XML error: ".implode(" - ", $errors));
}
$updated = 0;
$this->articles->exec('BEGIN TRANSACTION;');
if (isset($xml->entry)) // ATOM feed
{
foreach ($xml->entry as $item)
{
$date = isset($item->published) ? (string) $item->published : (string) $item->updated;
$guid = !empty($item->id) ? (string)$item->id : (string)$item->link['href'];
$id = $this->insertOrUpdateArticle($guid, (string)$item->title,
(string)$item->link['href'], strtotime($date), (string)$item->content);
if ($id !== false)
$updated++;
}
}
elseif (isset($xml->item)) // RSS 1.0 /RDF
{
foreach ($xml->item as $item)
{
$guid = (string) $item->attributes('http://www.w3.org/1999/02/22-rdf-syntax-ns#')->about ?: (string)$item->link;
$date = (string) $item->children('http://purl.org/dc/elements/1.1/')->date;
$id = $this->insertOrUpdateArticle($guid, (string)$item->title, (string)$item->link,
strtotime($date), (string) $item->children('http://purl.org/rss/1.0/modules/content/'));
if ($id !== false)
$updated++;
}
}
elseif (isset($xml->channel->item)) // RSS 2.0
{
foreach ($xml->channel->item as $item)
{
$content = (string) $item->children('http://purl.org/rss/1.0/modules/content/');
$guid = !empty($item->guid) ? (string) $item->guid : (string) $item->link;
if (empty($content) && !empty($item->description))
$content = (string) $item->description;
$id = $this->insertOrUpdateArticle($guid, (string)$item->title, (string)$item->link,
strtotime((string) $item->pubDate), $content);
if ($id !== false)
$updated++;
}
}
else
{
throw new VroumVroum_Feed_Exception("Unknown feed type?!");
}
$this->log_update(true, $updated . " elements updated");
$this->articles->exec('END TRANSACTION;');
return $updated;
}
public function listArticlesByPage($page = 1)
{
$nb = $this->config->articles_per_page;
$begin = ($page - 1) * $nb;
$res = $this->articles->query('SELECT * FROM articles ORDER BY date DESC LIMIT '.(int)$begin.','.(int)$nb.';');
$out = array();
while ($row = $res->fetchArray(SQLITE3_ASSOC))
{
$out[] = $row;
}
return $out;
}
public function listLastArticles()
{
return array_merge($this->listArticlesByPage(1), $this->listArticlesByPage(2));
}
public function countArticles()
{
return $this->articles->querySingle('SELECT COUNT(*) FROM articles;');
}
public function getArticleFromURI($uri)
{
return $this->articles->querySingle('SELECT * FROM articles WHERE uri = \''.$this->articles->escapeString($uri).'\';', true);
}
public function sql_countintegers($in)
{
return substr_count($in, ' ');
}
public function searchArticles($query)
{
$res = $this->articles->query('SELECT id, uri, title, content
FROM articles
WHERE content LIKE \'%'.$this->articles->escapeString($query).'%\'
ORDER BY id DESC
LIMIT 0,100;');
$out = array();
while ($row = $res->fetchArray(SQLITE3_ASSOC))
{
$row['url'] = $this->getLocalURL($this->articles->querySingle('SELECT uri FROM articles WHERE id = \''.(int)$row['id'].'\';'));
$out[] = $row;
}
return $out;
}
public function mirrorMediasForArticle($content, $url)
{
if (!file_exists(MEDIA_DIR))
{
mkdir(MEDIA_DIR);
}
$schemes = array('http', 'https');
$extensions = explode(',', preg_quote('jpg,jpeg,png,apng,gif,svg,pdf,odt,ods,epub,webp,wav,mp3,ogg,aac,wma,flac,opus,mp4,webm', '!'));
$extensions = implode('|', $extensions);
$from = parse_url($url);
if( isset($from['path']) ) { // not exist if http://exemple.com
$from['path'] = preg_replace('![^/]*$!', '', $from['path']);
}else{
$from['path'] = '';
}
preg_match_all('!(src|href)\s*=\s*[\'"]?([^"\'<>\s]+\.(?:'.$extensions.'))[\'"]?!i', $content, $match, PREG_SET_ORDER);
foreach ($match as $m)
{
$url = parse_url($m[2]);
if (empty($url['scheme']))
$url['scheme'] = $from['scheme'];
if (empty($url['host']))
$url['host'] = $from['host'];
if (!in_array(strtolower($url['scheme']), $schemes))
continue;
if ($url['path'][0] != '/')
$url['path'] = $from['path'] . $url['path'];
$filename = basename($url['path']);
$url = $url['scheme'] . '://' . $url['host'] . $url['path'];
$filename = substr(sha1($url), -8) . '.' . substr(preg_replace('![^\w\d_.-]!', '', $filename), -64);
$copied = false;
if (!file_exists(MEDIA_DIR . '/' . $filename))
{
try {
$copied = $this->_copy($url, MEDIA_DIR . '/' . $filename);
}
catch (ErrorException $e)
{
// Ignore copy errors
}
}
$content = str_replace($m[0], $m[1] . '="'.'media/'.$filename.'" data-original-source="'.$url.'"', $content);
}
return $content;
}
/* copy() is buggy with http streams and safe_mode enabled (which is bad), so here's a workaround */
protected function _copy($from, $to)
{
$in = fopen($from, 'r', false, $this->_getStreamContext());
$out = fopen($to, 'w', false);
$size = stream_copy_to_stream($in, $out);
fclose($in);
fclose($out);
return $size;
}
}
// DISPLAY AND CONTROLLERS
$vvb = new VroumVroum_Blog;
$config = $vvb->config;
$site_type = escape($config->site_type);
if (isset($_GET['feed'])) // FEED
{
header('Content-Type: application/atom+xml; charset=UTF-8');
echo '<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xml:lang="fr-FR">
<title type="text">'.escape($config->site_title).'</title>
<subtitle type="text">'.escape(html_entity_decode(strip_tags($config->site_description), ENT_COMPAT, 'UTF-8')).'</subtitle>
<updated>'.date(DATE_ATOM, filemtime(ARTICLES_DB_FILE)).'</updated>
<link rel="alternate" type="text/html" href="'.str_replace('?feed./', '', LOCAL_URL).'" />
<id>'.LOCAL_URL.'</id>
<link rel="self" type="application/atom+xml" href="'.LOCAL_URL.'" />
<generator uri="https://github.com/mitsukarenai/Projet-Autoblog" version="3">Projet Autoblog</generator>';
foreach($vvb->listLastArticles() as $art)
{
echo '
<entry>
<author>
<name>'.escape($config->site_title).'</name>
<uri>'.escape($config->site_url).'</uri>
</author>
<title type="html"><![CDATA['.escape($art['title']).']]></title>
<link rel="alternate" type="text/html" href="'.str_replace('?feed', '?', LOCAL_URL).urlencode(str_replace('./?', '', $vvb->getLocalURL($art))).'" />
<id>'.str_replace('?feed', '?', LOCAL_URL).urlencode(str_replace('./?', '', $vvb->getLocalURL($art))).'</id>
<updated>'.date(DATE_ATOM, $art['date']).'</updated>
<content type="html">
<![CDATA[(<a href="'.escape($art['feed_id']).'">source</a>)<br />'.escape_content($art['content']).']]>
</content>
</entry>';
}
echo '
</feed>';
exit;
}
if (isset($_GET['opml'])) // OPML
{
//header('Content-Type: application/octet-stream');
header('Content-type: text/xml');
header('Content-Disposition: attachment; filename="'.escape($config->site_title).'.xml"');
$opmlfile = new SimpleXMLElement('<opml></opml>');
$opmlfile->addAttribute('version', '1.0');
$opmlhead = $opmlfile->addChild('head');
$opmlhead->addChild('title', escape($config->site_title));
$opmlhead->addChild('dateCreated', date('r', time()));
$opmlbody = $opmlfile->addChild('body');
$outline = $opmlbody->addChild('outline');
$outline->addAttribute('title', escape($config->site_title));
$outline->addAttribute('text', escape($config->site_type));
$outline->addAttribute('htmlUrl', escape($config->site_url));
$outline->addAttribute('xmlUrl', escape($config->feed_url));
echo $opmlfile->asXML();
exit;
}
if (isset($_GET['media'])) // MEDIA
{
header('Content-Type: application/json');
if(is_dir(MEDIA_DIR))
{
$url = str_replace('?media', 'media/', LOCAL_URL);
$files = scandir(MEDIA_DIR);
unset($files[0]); // .
unset($files[1]); // ..
echo json_encode(array("url"=> $url, "files" => $files));
}
exit;
}
if (isset($_GET['update']))
{
$_SERVER['QUERY_STRING'] = '';
}
// CONTROLLERS
$search = !empty($_GET['q']) ? trim($_GET['q']) : '';
$article = null;
if (!$search && !empty($_SERVER['QUERY_STRING']) && !is_numeric($_SERVER['QUERY_STRING']))
{
$uri = rawurldecode($_SERVER['QUERY_STRING']);
$article = $vvb->getArticleFromURI($uri);
if (!$article)
{
header('HTTP/1.1 404 Not Found', true, 404);
}
}
// common CSS
$css=' * { margin: 0; padding: 0; }
body { font-family:sans-serif; background-color: #efefef; padding: 1%; color: #333; }
img { max-width: 100%; height: auto; }
a { text-decoration: none; color: #000;font-weight:bold; }
.header a { text-decoration: none; color: #000;font-weight:bold; }
.header { text-align:center; padding: 30px 3%; max-width:70em;margin:0 auto; }
.article .title { margin-bottom: 1em; }
.article .title h2 a:hover { color:#403976; }
.article h4 { font-weight: normal; font-size: small; color: #666; }
.article .source a { color: #666; }
.searchForm { float:right; }
.searchForm input { }
.pagination { background-color:white;padding: 12px 10px 12px 10px;border:1px solid #aaa;max-width:70em;margin:1em auto;box-shadow:0px 5px 7px #aaa; }
.pagination b { font-size: 1.2em; color: #333; }
.pagination a { color:#000; margin: 0 0.5em; }
.pagination a:hover { color:#333; }
.footer a { color:#000; }
.footer a:hover { color:#333; }
.content ul, .content ol { margin-left: 2em; }
.content h1, .content h2, .content h3, .content h4, .content h5, .content h6,
.content ul, .content ol, .content p, .content object, .content div, .content blockquote,
.content dl, .content pre { margin-bottom: 0.8em; }
.content pre, .content blockquote { background: #ddd; border: 1px solid #999; padding: 0.2em; max-width: 100%; overflow: auto; }
.content h1 { font-size: 1.5em; }
.content h2 { font-size: 1.4em;color:#000; }
.result h3 a { color: darkblue; text-decoration: none; text-shadow: 1px 1px 1px #fff; }
#error { position: fixed; top: 0; left: 0; right: 0; padding: 1%; background: #fff; border-bottom: 2px solid red; color: darkred; }
';
if($site_type == 'generic') // custom CSS for generic
{
$css = $css.'.header h1 a { color: #333;font-size:40pt;text-shadow: #ccc 0px 5px 5px;text-transform:uppercase; }
.article .title h2 { margin: 0; color:#333; text-shadow: 1px 1px 1px #fff; }
.article .title h2 a { color:#000; text-decoration:none; }
.article .source { font-size: 0.8em; color: #666; }
.article { background-color:white;padding: 12px 10px 12px 10px;border:1px solid #aaa;max-width:70em;margin:1em auto;box-shadow:0px 5px 7px #aaa; }
.footer { text-align:center; font-size: small; color:#333; clear: both; }';
}
else if($site_type == 'microblog' || $site_type == 'twitter' || $site_type == 'identica') // custom CSS for microblog
{
$css = $css.'.header h1 a { color: #333;font-size:40pt;text-shadow: #ccc 0px 5px 5px; }
.article .title h2 { width: 10em;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;font-size: 0.7em;margin: 0; color:#333; text-shadow: 1px 1px 1px #fff; }
.article .title h2 a { color:#333; text-decoration:none; }
.article { background-color:white;padding: 12px 10px 12px 10px;border:1px solid #aaa;max-width:70em;margin:0 auto;box-shadow:0px 5px 7px #aaa; }
.article .source { font-size: 0.8em; color: #666; }
.footer { margin-top:1em;text-align:center; font-size: small; color:#333; clear: both; }
.content {font-size:0.9em;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;}';
}
else if($site_type == 'shaarli') // custom CSS for shaarli
{
$css = $css.'.header h1 a { color: #333;font-size:40pt;text-shadow: #ccc 0px 5px 5px; }
.article .title h2 { margin: 0; color:#333; text-shadow: 1px 1px 1px #fff; }
.article .title h2 a { color:#000; text-decoration:none; }
.article { background-color:white;padding: 12px 10px 12px 10px;border:1px solid #aaa;max-width:70em;margin:1em auto;box-shadow:0px 5px 7px #aaa; }
.article .source { margin-top:1em;font-size: 0.8em; color: #666; }
.footer { text-align:center; font-size: small; color:#333; clear: both; }';
}
// HTML HEADER
echo '
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>'.escape($config->site_title).'</title>
<link rel="canonical" href="'.escape($config->site_url).'">
<link rel="alternate" type="application/atom+xml" title="'.__('ATOM Feed').'" href="?feed">
<style type="text/css" media="screen,projection">
'.$css.'
</style>
</head>
<body>
<div class="header">
<h1><a href="../../" style="font-size:0.8em;">PROJET AUTOBLOG'. (strlen(HEAD_TITLE) > 0 ? ' ~ '. HEAD_TITLE : '') .'</a></h1>
<hr>
<h1><a href="./">'.escape($config->site_title).'</a></h1>';
if (!empty($config->site_description))
echo '<p>'.$config->site_description.'<br><a href="../../">&lArr; retour index</a></p>';
echo '
<form method="get" action="'.escape(LOCAL_URL).'" class="searchForm">
<div>
<input type="text" name="q" value="'.escape($search).'">
<input type="submit" value="'.__('Search').'">
</div>
</form>
</div>
';
if ($vvb->mustUpdate())
{
echo '
<div class="article">
<div class="title">
<h2>'.__('Update').'</h2>
</div>
<div class="content" id="update">
'.__('Updating database... Please wait.').'
</div>
</div>';
}
if (!empty($search))
{
$results = $vvb->searchArticles($search);
$text = sprintf(__('<b>%d</b> results for <i>%s</i>'), count($results), escape($search));
echo '
<div class="article">
<div class="title">
<h2>'.__('Search').'</h2>
'.$text.'
</div>
</div>';
foreach ($results as $art)
{
echo '
<div class="article result">
<h3><a href="./?'.escape($art['uri']).'">'.escape($art['title']).'</a></h3>
<p>'.$art['content'].'</p>
</div>';
}
}
elseif (!is_null($article))
{
if (!$article)
{
echo '
<div class="article">
<div class="title">
<h2>'.__('Not Found').'</h2>
'.(!empty($uri) ? '<p><tt>'.escape($vvb->getLocalURL($uri)) . '</tt></p>' : '').'
'.__('Article not found.').'
</div>
</div>';
}
else
{
display_article($article);
}
}
else
{
if (!empty($_SERVER['QUERY_STRING']) && is_numeric($_SERVER['QUERY_STRING']))
$page = (int) $_SERVER['QUERY_STRING'];
else
$page = 1;
$list = $vvb->listArticlesByPage($page);
foreach ($list as $article)
{
display_article($article);
}
$max = $vvb->countArticles();
if ($max > $config->articles_per_page)
{
echo '<div class="pagination">';
if ($page > 1)
echo '<a href="'.$vvb->getLocalURL($page - 1).'">&larr; '.__('Newer').'</a> ';
$last = ceil($max / $config->articles_per_page);
for ($i = 1; $i <= $last; $i++)
{
echo '<a href="'.$vvb->getLocalURL($i).'">'.($i == $page ? '<b>'.$i.'</b>' : $i).'</a> ';
}
if ($page < $last)
echo '<a href="'.$vvb->getLocalURL($page + 1).'">'.__('Older').' &rarr;</a> ';
echo '</div>';
}
}
echo '
<div class="footer">
<p>Propulsé par <a href="https://github.com/mitsukarenai/Projet-Autoblog">Projet Autoblog '.$vvbversion.'</a> - <a href="?feed">'.__('ATOM Feed').'</a></p>
<p>'.__('Download:').' <a href="./'.basename(CONFIG_FILE).'">'.__('configuration').'</a> (<a href="?opml">OPML</a>)
- <a href="./'.basename(ARTICLES_DB_FILE).'">'.__('articles').'</a><p/>
<p><a href="./?media">'.__('Media export').' <sup> JSON</sup></a></p>
</div>';
if ($vvb->mustUpdate())
{
try {
ob_end_flush();
flush();
}
catch (Exception $e)
{
// Silent, not critical
}
try {
$updated = $vvb->update();
}
catch (VroumVroum_Feed_Exception $e)
{
echo '
<div id="error">
'.escape($e->getMessage()).'
</div>';
$updated = 0;
}
if ($updated > 0)
{
echo '
<script type="text/javascript">
window.onload = function () {
document.getElementById("update").innerHTML = "'.__('Update complete!').' <a href=\\"#reload\\" onclick=\\"window.location.reload();\\">'.__('Click here to reload this webpage.').'</a>";
};
</script>';
}
else
{
echo '
<script type="text/javascript">
window.onload = function () {
document.body.removeChild(document.getElementById("update").parentNode);
};
</script>';
}
}
echo '
</body>
</html>';
function escape_content($str)
{
$str = preg_replace('!<\s*(style|script|link)!', '&lt;\\1', $str);
$str = str_replace('="media/', '="./media/', $str);
return $str;
}
// ARTICLE HTML CODE
function display_article($article)
{
global $vvb, $config;
echo '
<div class="article">
<div class="title">
<h2><a href="'.$vvb->getLocalURL($article).'">'.escape($article['title']).'</a></h2>
'.strftime(__('_date_format'), $article['date']).'
</div>
<div class="content">'.escape_content($article['content']).'</div>
<p class="source">'.__('Source:').' <a href="'.escape($article['url']).'">'.escape($article['url']).'</a></p>
<br style="clear: both;" />
</div>';
}
?>

277
class_rssfeed.php Executable file
View file

@ -0,0 +1,277 @@
<?php
/**
* class_rssfeed.php uses:
* RSSFeed: This class has methods for making a RSS 2.0 feed.
* RSSMerger: This class has the ability to merge different RSS feeds and sort them after the date the feed items were posted.
* @author David Laurell <david.laurell@gmail.com>
*
* + 03/2013
* Few changes, AutoblogRSS and FileRSSFeed
* @author Arthur Hoaro <http://hoa.ro>
*/
class RSSFeed {
protected $xml;
/**
* Construct a RSS feed
*/
public function __construct() {
$template = <<<END
<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
</channel>
</rss>
END;
$this->xml = new SimpleXMLElement($template);
}
/**
* Set RSS Feed headers
* @param $title the title of the feed
* @param $link link to the website where you can find the RSS feed
* @param $description a description of the RSS feed
* @param $rsslink the link to this RSS feed
*/
public function setHeaders($title, $link, $description, $rsslink) {
$atomlink = $this->xml->channel->addChild("atom:link","","http://www.w3.org/2005/Atom");
$atomlink->addAttribute("href",$rsslink);
$atomlink->addAttribute("rel","self");
$atomlink->addAttribute("type","application/rss+xml");
$this->xml->channel->title = $title;
$this->xml->channel->link = $link;
$this->xml->channel->description = $description;
}
/**
* Set the language of the RSS feed
* @param $lang the language of the RSS feed
*/
public function setLanguage($lang) {
$this->xml->channel->addChild("language",$lang);
}
/**
* Adds a picture to the RSS feed
* @param $url URL to the image
* @param $title The image title. Usually same as the RSS feed's title
* @param $link Where the image should link to. Usually same as the RSS feed's link
*/
public function setImage($url, $title, $link) {
$image = $this->xml->channel->addChild("image");
$image->url = $url;
$image->title = $title;
$image->link = $link;
}
/**
* Add a item to the RSS feed
* @param $title The title of the RSS feed
* @param $link Link to the item's url
* @param $description The description of the item
* @param $author The author who wrote this item
* @param $guid Unique ID for this post
* @param $timestamp Unix timestamp for making a date
*/
public function addItem($title, $link, $description, $author, $guid, $timestamp) {
$item = $this->xml->channel->addChild("item");
$item->title = $title;
$item->description = $description;
$item->link = $link;
$item->guid = $guid;
if( isset($guid['isPermaLink']))
$item->guid['isPermaLink'] = $guid['isPermaLink'];
if( !empty( $author) )
$item->author = $author;
$item->pubDate = date(DATE_RSS,intval($timestamp));
}
/**
* Displays the RSS feed
*/
public function displayXML() {
header('Content-type: application/rss+xml; charset=utf-8');
echo $this->xml->asXML();
exit;
}
public function getXML() {
return $this->xml;
}
}
class RSSMerger {
private $feeds = array();
/**
* Constructs a RSSmerger object
*/
function __construct() {
}
/**
* Populates the feeds array from the given url which is a rss feed
* @param $url
*/
function add($xml) {
foreach($xml->channel->item as $item) {
$item->sitetitle = $xml->channel->title;
$item->sitelink = $xml->channel->link;
preg_match("/^[A-Za-z]{3}, ([0-9]{2}) ([A-Za-z]{3}) ([0-9]{4}) ([0-9]{2}):([0-9]{2}):([0-9]{2}) ([\+|\-]?[0-9]{4})$/", $item->pubDate, $match);
$item->time = time($match[4]+($match[6]/100),$match[5],$match[6],date("m",strtotime($match[2])),$match[1],$match[3]);
$this->feeds[] = $item;
}
}
/**
* Comparing function for sorting the feeds
* @param $value1
* @param $value2
*/
function feeds_cmp($value1,$value2) {
if(intval($value1->time) == intval($value2->time))
return 0;
return (intval($value1->time) < intval($value2->time)) ? +1 : -1;
}
/**
* Sorts the feeds array using the Compare function feeds_cmp
*/
function sort() {
usort($this->feeds,Array("RssMerger","feeds_cmp"));
}
/**
* This function return the feed items.
* @param $limit how many feed items that should be returned
* @return the feeds array
*/
function getFeeds($limit) {
return array_slice($this->feeds,0,$limit);
}
}
class FileRSSFeed extends RSSFeed {
protected $filename;
public function __construct($filename) {
parent::__construct();
$this->filename = $filename;
$this->load();
}
public function load() {
if ( file_exists( $this->filename )) {
$this->xml = simplexml_load_file($this->filename);
}
}
public function create($title, $link, $description, $rsslink) {
parent::setHeaders($title, $link, $description, $rsslink);
$this->write();
}
public function addItem($title, $link, $description, $author, $guid, $timestamp) {
parent::addItem($title, $link, $description, $author, $guid, $timestamp);
$this->write();
}
private function write() {
if ( file_exists( $this->filename )) {
unlink($this->filename);
}
$outputXML = new RSSFeed();
foreach($this->xml->channel->item as $f) {
$item = $outputXML->addItem($f->title,$f->link,$f->description,$f->author,$f->guid, strtotime($f->pubDate));
}
$merger = new RssMerger();
$merger->add($outputXML->getXML());
$merger->sort();
unset($this->xml->channel->item);
foreach($merger->getFeeds(20) as $f) {
parent::addItem($f->title,$f->link,$f->description,$f->author,$f->guid,$f->time);
}
file_put_contents( $this->filename, $this->xml->asXML(), LOCK_EX );
}
}
class AutoblogRSS extends FileRSSFeed {
public function __construct($filename) {
parent::__construct($filename);
}
public function addUnavailable($title, $folder, $siteurl, $rssurl) {
$path = pathinfo( $_SERVER['PHP_SELF'] );
$autobHref = 'http'.(!empty($_SERVER['HTTPS'])?'s':'').'://'.
$_SERVER["SERVER_NAME"].':'.$_SERVER["SERVER_PORT"]. $path['dirname'].'/'.$folder;
parent::addItem( 'L\'autoblog "'. $title.'" est indisponible', $autobHref,
'Autoblog: <a href="'. $autobHref .'">'.$title.'</a><br>
Site: <a href="'. $siteurl .'">'. $siteurl .'</a><br>
RSS: <a href="'.$rssurl.'">'.$rssurl.'</a><br>
Folder: '. $folder ,
'admin@'.$_SERVER['SERVER_NAME'],
$autobHref,
time()
);
}
public function addAvailable($title, $folder, $siteurl, $rssurl) {
$path = pathinfo( $_SERVER['PHP_SELF'] );
$autobHref = 'http'.(!empty($_SERVER['HTTPS'])?'s':'').'://'.
$_SERVER["SERVER_NAME"].':'.$_SERVER["SERVER_PORT"]. $path['dirname'].'/'.$folder;
parent::addItem( 'L\'autoblog "'. $title.'" est de nouveau disponible', $autobHref,
'Autoblog : <a href="'. $autobHref .'">'.$title.'</a><br>
Site: <a href="'. $siteurl .'">'. $siteurl .'</a><br>
RSS: <a href="'.$rssurl.'">'.$rssurl.'</a><br>
Folder: '. $folder ,
'admin@'.$_SERVER['SERVER_NAME'],
$autobHref,
time()
);
}
public function addCodeChanged($title, $folder, $siteurl, $rssurl, $code) {
$path = pathinfo( $_SERVER['PHP_SELF'] );
$autobHref = 'http'.(!empty($_SERVER['HTTPS'])?'s':'').'://'.
$_SERVER["SERVER_NAME"].':'.$_SERVER["SERVER_PORT"]. $path['dirname'].'/'.$folder;
parent::addItem( 'L\'autoblog "'. $title.'" a renvoyé un code imprévu', $autobHref,
'Code: '. $code .'<br>
Autoblog : <a href="'. $autobHref .'">'.$title.'</a><br>
Site: <a href="'. $siteurl .'">'. $siteurl .'</a><br>
RSS: <a href="'.$rssurl.'">'.$rssurl.'</a><br>
Folder: '. $folder ,
'admin@'.$_SERVER['SERVER_NAME'],
$autobHref,
time()
);
}
public function addNewAutoblog($title, $folder, $siteurl, $rssurl) {
$path = pathinfo( $_SERVER['PHP_SELF'] );
$autobHref = 'http'.(!empty($_SERVER['HTTPS'])?'s':'').'://'.
$_SERVER["SERVER_NAME"].':'.$_SERVER["SERVER_PORT"]. $path['dirname'].'/'.$folder;
parent::addItem( 'L\'autoblog "'. $title.'" a été ajouté à la ferme', $autobHref,
'Autoblog : <a href="'. $autobHref .'">'.$title.'</a><br>
Site: <a href="'. $siteurl .'">'. $siteurl .'</a><br>
RSS: <a href="'.$rssurl.'">'.$rssurl.'</a><br>
Folder: '. $folder ,
'admin@'.$_SERVER['SERVER_NAME'],
$autobHref,
time()
);
}
}
?>

43
config.php Executable file
View file

@ -0,0 +1,43 @@
<?php
/**
* config.php - User configuration file
* ---
* If you uncomment a setting in this file, it will override default option
*
* See how to configure your Autoblog farm at
* https://github.com/mitsukarenai/Projet-Autoblog/wiki/Configuration
**/
// define( 'LOGO', 'icon-logo.svg' );
// define( 'HEAD_TITLE', '');
// define( 'FOOTER', 'D\'après les premières versions de <a href="http://sebsauvage.net">SebSauvage</a> et <a href="http://bohwaz.net/">Bohwaz</a>.');
// define( 'ALLOW_FULL_UPDATE', TRUE );
// define( 'ALLOW_CHECK_UPDATE', TRUE );
/**
* If you set ALLOW_NEW_AUTOBLOGS to FALSE, the following options do not matter.
**/
// define( 'ALLOW_NEW_AUTOBLOGS', TRUE );
// define( 'ALLOW_NEW_AUTOBLOGS_BY_LINKS', TRUE );
// define( 'ALLOW_NEW_AUTOBLOGS_BY_SOCIAL', TRUE );
// define( 'ALLOW_NEW_AUTOBLOGS_BY_BUTTON', TRUE );
// define( 'ALLOW_NEW_AUTOBLOGS_BY_OPML_FILE', TRUE );
// define( 'ALLOW_NEW_AUTOBLOGS_BY_OPML_LINK', TRUE );
// define( 'ALLOW_NEW_AUTOBLOGS_BY_XSAF', TRUE );
/**
* More about TwitterBridge : https://github.com/mitsukarenai/twitterbridge
**/
// define( 'API_TWITTER', FALSE );
/**
* Import autoblogs from friend's autoblog farm - Add a link to the JSON export
**/
$friends_autoblog_farm = array(
'https://raw.github.com/mitsukarenai/xsaf-bootstrap/master/3.json',
// 'https://www.ecirtam.net/autoblogs/?export',
// 'https://autoblog.suumitsu.eu/?export',
// 'http://streisand.hoa.ro/?export',
);
?>

4
docs/docs.txt Normal file
View file

@ -0,0 +1,4 @@
You can manually add files in the /docs/ directory, such as PDF, docs, images, etc.
You can also add subfolders in /docs/ for website mirroring. Be sure that your subfolder contains a file named index.html.
Delete this file to hide the 'Autres documents' block in your autoblogs homepage.

302
functions.php Executable file
View file

@ -0,0 +1,302 @@
<?php
/**
* DO NOT EDIT THESE LINES
* You can override these options by setting them in config.php
**/
if(!defined('ROOT_DIR'))
{
define('ROOT_DIR', dirname($_SERVER['SCRIPT_FILENAME']));
}
define('LOCAL_URI', '');
if (!defined('AUTOBLOGS_FOLDER')) define('AUTOBLOGS_FOLDER', './autoblogs/');
if (!defined('DOC_FOLDER')) define('DOC_FOLDER', './docs/');
if (!defined('RESOURCES_FOLDER')) define('RESOURCES_FOLDER', './resources/');
if (!defined('RSS_FILE')) define('RSS_FILE', RESOURCES_FOLDER.'rss.xml');
date_default_timezone_set('Europe/Paris');
setlocale(LC_TIME, 'fr_FR.UTF-8', 'fr_FR', 'fr');
if( !defined('ALLOW_FULL_UPDATE')) define( 'ALLOW_FULL_UPDATE', TRUE );
if( !defined('ALLOW_CHECK_UPDATE')) define( 'ALLOW_CHECK_UPDATE', TRUE );
// If you set ALLOW_NEW_AUTOBLOGS to FALSE, the following options do not matter.
if( !defined('ALLOW_NEW_AUTOBLOGS')) define( 'ALLOW_NEW_AUTOBLOGS', TRUE );
if( !defined('ALLOW_NEW_AUTOBLOGS_BY_LINKS')) define( 'ALLOW_NEW_AUTOBLOGS_BY_LINKS', TRUE );
if( !defined('ALLOW_NEW_AUTOBLOGS_BY_SOCIAL')) define( 'ALLOW_NEW_AUTOBLOGS_BY_SOCIAL', TRUE );
if( !defined('ALLOW_NEW_AUTOBLOGS_BY_BUTTON')) define( 'ALLOW_NEW_AUTOBLOGS_BY_BUTTON', TRUE );
if( !defined('ALLOW_NEW_AUTOBLOGS_BY_OPML_FILE')) define( 'ALLOW_NEW_AUTOBLOGS_BY_OPML_FILE', TRUE );
if( !defined('ALLOW_NEW_AUTOBLOGS_BY_OPML_LINK')) define( 'ALLOW_NEW_AUTOBLOGS_BY_OPML_LINK', TRUE );
if( !defined('ALLOW_NEW_AUTOBLOGS_BY_XSAF')) define( 'ALLOW_NEW_AUTOBLOGS_BY_XSAF', TRUE );
// More about TwitterBridge : https://github.com/mitsukarenai/twitterbridge
if( !defined('API_TWITTER')) define( 'API_TWITTER', FALSE );
if( !defined('LOGO')) define( 'LOGO', 'icon-logo.svg' );
if( !defined('HEAD_TITLE')) define( 'HEAD_TITLE', '');
if( !defined('FOOTER')) define( 'FOOTER', 'D\'après les premières versions de <a href="http://sebsauvage.net">SebSauvage</a> et <a href="http://bohwaz.net/">Bohwaz</a>.');
// Functions
function NoProtocolSiteURL($url) {
$protocols = array("http://", "https://");
$siteurlnoproto = str_replace($protocols, "", $url);
// Remove the / at the end of string
if ( $siteurlnoproto[strlen($siteurlnoproto) - 1] == '/' )
$siteurlnoproto = substr($siteurlnoproto, 0, -1);
// Remove index.php/html at the end of string
if( strpos($url, 'index.php') || strpos($url, 'index.html') ) {
$siteurlnoproto = preg_replace('#(.*)/index\.(html|php)$#', '$1', $siteurlnoproto);
}
return $siteurlnoproto;
}
function DetectRedirect($url)
{
if(parse_url($url, PHP_URL_HOST)==FALSE) {
//die('Not a URL');
throw new Exception('Not a URL: '. escape ($url) );
}
$response = get_headers($url, 1);
if(!empty($response['Location'])) {
$response2 = get_headers($response['Location'], 1);
if(!empty($response2['Location'])) {
//die('too much redirection');
throw new Exception('too much redirection: '. escape ($url) );
}
else { return $response['Location']; }
}
else {
return $url;
}
}
function urlToFolder($url) {
return sha1(NoProtocolSiteURL($url));
}
function urlToFolderSlash($url) {
return sha1(NoProtocolSiteURL($url).'/');
}
function folderExists($url) {
return file_exists(AUTOBLOGS_FOLDER . urlToFolder($url)) || file_exists(AUTOBLOGS_FOLDER . urlToFolderSlash($url));
}
function escape($str) {
return htmlspecialchars($str, ENT_COMPAT, 'UTF-8', false);
}
function createAutoblog($type, $sitename, $siteurl, $rssurl, $error = array()) {
if( $type == 'generic' || empty( $type )) {
$var = updateType( $siteurl );
$type = $var['type'];
if( !empty( $var['name']) ) {
if( !stripos($siteurl, $var['name'] === false) )
$sitename = ucfirst($var['name']) . ' - ' . $sitename;
}
}
if(folderExists($siteurl)) {
$error[] = 'Erreur : l\'autoblog '. $sitename .' existe déjà.';
return $error;
}
$foldername = AUTOBLOGS_FOLDER . urlToFolderSlash($siteurl);
if ( mkdir($foldername, 0755, false) ) {
/**
* RSS
**/
try { // à déplacer après la tentative de création de l'autoblog crée avec succès ?
require_once('class_rssfeed.php');
$rss = new AutoblogRSS(RSS_FILE);
$rss->addNewAutoblog($sitename, $foldername, $siteurl, $rssurl);
}
catch (Exception $e) {
;
}
$fp = fopen($foldername .'/index.php', 'w+');
if( !fwrite($fp, "<?php require_once '../autoblog.php'; ?>") )
$error[] = "Impossible d'écrire le fichier index.php";
fclose($fp);
$fp = fopen($foldername .'/vvb.ini', 'w+');
if( !fwrite($fp, '[VroumVroumBlogConfig]
SITE_TYPE="'. $type .'"
SITE_TITLE="'. $sitename .'"
SITE_DESCRIPTION="Site original : <a href=\''. $siteurl .'\'>'. $sitename .'</a>"
SITE_URL="'. $siteurl .'"
FEED_URL="'. $rssurl .'"
ARTICLES_PER_PAGE="'. getArticlesPerPage( $type ) .'"
UPDATE_INTERVAL="'. getInterval( $type ) .'"
UPDATE_TIMEOUT="'. getTimeout( $type ) .'"') )
$error[] = "Impossible d'écrire le fichier vvb.ini";
fclose($fp);
}
else
$error[] = "Impossible de créer le répertoire.";
updateXML('new_autoblog_added', 'new', $foldername, $sitename, $siteurl, $rssurl); /* éventuellement une conditionnelle ici: if(empty($error)) ? */
return $error;
}
function getArticlesPerPage( $type ) {
switch( $type ) {
case 'microblog':
return 20;
case 'shaarli':
return 20;
default:
return 5;
}
}
function getInterval( $type ) {
switch( $type ) {
case 'microblog':
return 300;
case 'shaarli':
return 1800;
default:
return 3600;
}
}
function getTimeout( $type ) {
switch( $type ) {
case 'microblog':
return 30;
case 'shaarli':
return 30;
default:
return 30;
}
}
function updateType($siteurl) {
if( strpos($siteurl, 'twitter.com') !== FALSE ) {
return array('type' => 'twitter', 'name' => 'twitter');
}
elseif ( strpos( $siteurl, 'identi.ca') !== FALSE ) {
return array('type' => 'identica', 'name' => 'identica');
}
elseif( strpos( $siteurl, 'shaarli' ) !== FALSE ) {
return array('type' => 'shaarli', 'name' => 'shaarli');
}
else
return array('type' => 'generic', 'name' => '');
}
function debug($data)
{
echo '<pre>';
var_dump($data);
echo '</pre>';
}
function __($str)
{
switch ($str)
{
case 'Search':
return 'Recherche';
case 'Update':
return 'Mise à jour';
case 'Updating database... Please wait.':
return 'Mise à jour de la base de données, veuillez patienter...';
case '<b>%d</b> results for <i>%s</i>':
return '<b>%d</b> résultats pour la recherche <i>%s</i>';
case 'Not Found':
return 'Introuvable';
case 'Article not found.':
return 'Cet article n\'a pas été trouvé.';
case 'Older':
return 'Plus anciens';
case 'Newer':
return 'Plus récents';
case 'ATOM Feed':
return 'Flux ATOM';
case 'Update complete!':
return 'Mise à jour terminée !';
case 'Click here to reload this webpage.':
return 'Cliquez ici pour recharger cette page.';
case 'Source:':
return 'Source :';
case '_date_format':