├── .gitignore ├── LICENSE.md ├── README.md ├── autoblogs └── autoblog.php ├── config.php.example ├── docs └── docs.txt ├── functions.php ├── index.php ├── resources ├── autoblog.css ├── icon-err.svg ├── icon-generic.svg ├── icon-logo.png ├── icon-logo.svg ├── icon-microblog.svg ├── icon-mv.svg ├── icon-ok.svg ├── icon-shaarli.svg ├── icon-twitter.svg ├── icon-youtube.svg ├── rss.png └── user.css.example ├── version └── xsaf3.php /.gitignore: -------------------------------------------------------------------------------- 1 | config.php 2 | .versionlock 3 | .xsaflock 4 | .updatealllock 5 | resources/rss.xml 6 | resources/rss.json 7 | .project 8 | resources/user.css 9 | autoblogs/* 10 | !autoblogs/autoblog.php 11 | docs/* 12 | !docs/docs.txt 13 | robots.txt 14 | .idea 15 | cache_* 16 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Creative Commons 2 | 3 | ## CC0 1.0 Universal 4 | 5 | ``` 6 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. 7 | ``` 8 | 9 | ### Statement of Purpose 10 | 11 | The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). 12 | 13 | Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. 14 | 15 | For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 16 | 17 | 1. __Copyright and Related Rights.__ A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: 18 | 19 | i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; 20 | 21 | ii. moral rights retained by the original author(s) and/or performer(s); 22 | 23 | iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; 24 | 25 | iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; 26 | 27 | v. rights protecting the extraction, dissemination, use and reuse of data in a Work; 28 | 29 | vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and 30 | 31 | vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 32 | 33 | 2. __Waiver.__ To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 34 | 35 | 3. __Public License Fallback.__ Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 36 | 37 | 4. __Limitations and Disclaimers.__ 38 | 39 | a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. 40 | 41 | b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. 42 | 43 | c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. 44 | 45 | d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Projet Autoblog 3 | =============== 4 | 5 | > This project is historically in French. See [our english README](https://github.com/mitsukarenai/Projet-Autoblog/wiki/Autoblog-Project). 6 | 7 | Réplication automatique de contenu à partir de flux RSS/Atom, avec partage des ajouts entre les fermes d'Autoblog. 8 | 9 | L'objectif premier du projet Autoblog est de lutter contre la censure et toute autre forme de pression allant à l'encontre de la liberté d'expression en favorisant l'[effet Streisand](http://fr.wikipedia.org/wiki/Effet_Streisand). 10 | 11 | Le projet a été initialement lancé par Sébastien Sauvage : [plus d'info par ici](http://sebsauvage.net/streisand.me/fr/). 12 | 13 | Exemples d'instances : 14 | - [autoblog.suumitsu.eu](http://autoblog.suumitsu.eu/) 15 | - [streisand.hoa.ro](http://streisand.hoa.ro/) 16 | - [ecirtam.net](https://ecirtam.net/autoblogs/) 17 | - [autoblog.ohax.fr](http://autoblog.ohax.fr/) 18 | - [kaelsitoo.fr](http://kaelsitoo.fr/autoblog/) 19 | - [autoblog.postblue.info](http://autoblog.postblue.info/) 20 | - [arche.depotoi.re](http://arche.depotoi.re/) 21 | 22 | 23 | Serie 0.3 par [Mitsu](https://github.com/mitsukarenai/), [Oros](https://github.com/Oros42), [Arthur Hoaro](https://github.com/ArthurHoaro). 24 | 25 | ![logo](https://raw.github.com/mitsukarenai/Projet-Autoblog/master/resources/icon-logo.png) 26 | Fonctionnalités majeures 27 | =================== 28 | 29 | - Ferme d'autoblogs avec ajout facile par différents formulaires (générique, microblogging, OPML, marque-pages). 30 | - Échange de références entre fermes avec XSAF ([Cross-Site Autoblog Farming](https://github.com/mitsukarenai/Projet-Autoblog/wiki/XSAF---Cross-Site-Autoblog-Farming)). 31 | - Vérification du statut des sites distants, et flux de suivi des changements. 32 | - Export des références, articles et médias. 33 | - Contrôle de version de la ferme et alerte de mise à jour. 34 | - Identification du type d'autoblog. 35 | - CSS utilisateur personnalisable. 36 | - Hébergement de documents divers (PDF, images, réplique de site web, etc.). 37 | 38 | Branches 39 | =================== 40 | 41 | - [master](https://github.com/mitsukarenai/Projet-Autoblog/tree/master/) _(développement)_ : Autoblog Project serie 0.3 par Mitsu, Oros, Arthur Hoaro 42 | - [legacy-0.2](https://github.com/mitsukarenai/Projet-Autoblog/tree/legacy-0.2) : version VroumVroumBlog 0.2.11 par BohwaZ (VVB) & Arthur Hoaro, Mitsukarenai, Oros (index ferme d'autoblogs) 43 | - [legacy-0.1](https://github.com/mitsukarenai/Projet-Autoblog/tree/legacy-0.1) : version VroumVroumBlog 0.1.32 par Sebastien Sauvage 44 | - [legacy-0.2to0.3](https://github.com/mitsukarenai/Projet-Autoblog/tree/legacy-0.2to0.3) : script de migration 0.2 to 0.3 par Oros et Arthur Hoaro 45 | 46 | Pré-requis techniques 47 | ===================== 48 | 49 | - Serveur web (Apache, nginx, etc.) 50 | - PHP 5.3 ou supérieur 51 | - Support SQLite 3 pour PHP 52 | 53 | Documentation 54 | ===================== 55 | 56 | La documentation du projet est sur le [Wiki du repo](https://github.com/mitsukarenai/Projet-Autoblog/wiki). 57 | 58 | Accès hors ligne : `git clone https://github.com/mitsukarenai/Projet-Autoblog.wiki.git` 59 | 60 | Licence 61 | ===================== 62 | 63 | Domaine public. 64 | 65 | Changelog 66 | ===================== 67 | - 2014-09-18 MILESTONE 0.3.3 68 | - Drop support of twitter2feed - use [RSS Bridge](https://github.com/sebsauvage/rss-bridge) instead 69 | - Switch licence to CC-0 70 | - Add 'reset cache' button 71 | - Remove cache when an autoblog is added 72 | - Set .png as default logo 73 | - Add user-agent at autoblog creation 74 | - 2014-02-12 MILESTONE 0.3.2 75 | - separate type icons 76 | - cache added 77 | - pagination fixes 78 | - SVG fixes 79 | - fix date() warnings 80 | - bugfixes 81 | - 2013-10-14 MILESTONE 0.3.1 82 | - code semantics 83 | - "docs" filesize 84 | - robots.txt 85 | - bugfixes 86 | - 2013-07-30 87 | - twitter2feed.php fixed (regex on class "avatar"; ``````) 88 | - 2013-07-22 MILESTONE 0.3 89 | -------------------------------------------------------------------------------- /autoblogs/autoblog.php: -------------------------------------------------------------------------------- 1 | =')) 15 | die("This software requires PHP version 5.3.0 at least, yours is ".phpversion()); 16 | 17 | if (!class_exists('SQLite3')) 18 | die("This software requires the SQLite3 PHP extension, and it can't be found on this system!"); 19 | 20 | libxml_disable_entity_loader(true); 21 | 22 | // Config and data file locations 23 | 24 | if (file_exists(__DIR__ . '/../config.php')) { 25 | require_once __DIR__ . '/../config.php'; 26 | } 27 | //else die("Configuration file not found."); 28 | 29 | if (file_exists(__DIR__ . '/../functions.php')){ 30 | require_once __DIR__ . '/../functions.php'; 31 | } 32 | else die("Functions file not found."); 33 | 34 | if (!defined('ROOT_DIR')) 35 | define('ROOT_DIR', __DIR__); 36 | 37 | if (!defined('CONFIG_FILE')) define('CONFIG_FILE', ROOT_DIR . '/vvb.ini'); 38 | if (!defined('ARTICLES_DB_FILE')) define('ARTICLES_DB_FILE', ROOT_DIR . '/articles.db'); 39 | if (!defined('MEDIA_DIR')) define('MEDIA_DIR', ROOT_DIR . '/media'); 40 | 41 | if (!defined('LOCAL_URL')) 42 | { 43 | // Automagic URL discover 44 | define('LOCAL_URL', 'http' . (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 's' : '')."://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}"); 45 | } 46 | 47 | if (!defined('LOCAL_URI')) 48 | { 49 | // filename 50 | define('LOCAL_URI', (basename($_SERVER['SCRIPT_FILENAME']) == 'index.php' ? '' : basename($_SERVER['SCRIPT_FILENAME'])) . '?'); 51 | } 52 | 53 | if (!function_exists('__')) 54 | { 55 | // Translation? 56 | function __($str) 57 | { 58 | if ($str == '_date_format') 59 | return '%A %e %B %Y at %H:%M'; 60 | else 61 | return $str; 62 | } 63 | } 64 | 65 | // ERROR MANAGEMENT 66 | 67 | class VroumVroum_User_Exception extends Exception {} 68 | 69 | class VroumVroum_Feed_Exception extends Exception 70 | { 71 | static public function getXMLErrorsAsString($errors) 72 | { 73 | $out = array(); 74 | 75 | foreach ($errors as $error) 76 | { 77 | $return = $xml[$error->line - 1] . "\n"; 78 | $return .= str_repeat('-', $error->column) . "^\n"; 79 | 80 | switch ($error->level) { 81 | case LIBXML_ERR_WARNING: 82 | $return .= "Warning ".$error->code.": "; 83 | break; 84 | case LIBXML_ERR_ERROR: 85 | $return .= "Error ".$error->code.": "; 86 | break; 87 | case LIBXML_ERR_FATAL: 88 | $return .= "Fatal Error ".$error->code.": "; 89 | break; 90 | } 91 | 92 | $return .= trim($error->message) . 93 | "\n Line: ".$error->line . 94 | "\n Column: ".$error->column; 95 | 96 | if ($error->file) { 97 | $return .= "\n File: ".$error->file; 98 | } 99 | 100 | $out[] = $return; 101 | } 102 | 103 | return $out; 104 | } 105 | } 106 | 107 | //error_reporting(E_ALL); 108 | error_reporting(0); 109 | function exception_error_handler($errno, $errstr, $errfile, $errline ) 110 | { 111 | // For @ ignored errors 112 | if (error_reporting() === 0) return; 113 | throw new ErrorException($errstr, 0, $errno, $errfile, $errline); 114 | } 115 | 116 | function exception_handler($e) 117 | { 118 | if ($e instanceOf VroumVroum_User_Exception) 119 | { 120 | echo '

'.$e->getMessage().'

'; 121 | exit; 122 | } 123 | 124 | $error = "Error happened!\n\n". 125 | $e->getCode()." - ".$e->getMessage()."\n\nIn: ". 126 | $e->getFile() . ":" . $e->getLine()."\n\n"; 127 | 128 | if (!empty($_SERVER['HTTP_HOST'])) 129 | $error .= 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']."\n\n"; 130 | 131 | $error .= $e->getTraceAsString(); 132 | //$error .= print_r($_SERVER, true); 133 | 134 | echo $error; 135 | exit; 136 | } 137 | 138 | set_error_handler("exception_error_handler"); 139 | set_exception_handler("exception_handler"); 140 | 141 | // CONFIGURATION 142 | 143 | class VroumVroum_Config 144 | { 145 | public $site_type = ''; 146 | public $site_title = ''; 147 | public $site_description = ''; 148 | public $site_url = ''; 149 | public $feed_url = ''; 150 | public $articles_per_page = 10; 151 | public $update_interval = 3600; 152 | public $update_timeout = 10; 153 | public $disabled = false; 154 | 155 | public function __construct() 156 | { 157 | if (!file_exists(CONFIG_FILE)) 158 | throw new VroumVroum_User_Exception("Missing configuration file '".basename(CONFIG_FILE)."'."); 159 | 160 | $ini = parse_ini_file(CONFIG_FILE); 161 | 162 | foreach ($ini as $key=>$value) 163 | { 164 | $key = strtolower($key); 165 | 166 | if (!property_exists($this, $key)) 167 | continue; // Unknown config 168 | 169 | if (is_string($this->$key) || is_null($this->$key)) 170 | $this->$key = trim((string) $value); 171 | elseif (is_int($this->$key)) 172 | $this->$key = (int) $value; 173 | elseif (is_bool($this->$key)) 174 | $this->$key = (bool) $value; 175 | } 176 | 177 | if(empty($this->feed_url)) { 178 | $this->disabled = true; 179 | } 180 | 181 | if(!$this->disabled) { 182 | // Check that all required values are filled 183 | $check = array('site_type', 'site_title', 'site_url', 'feed_url', 'update_timeout', 'update_interval', 'articles_per_page'); 184 | foreach ($check as $c) 185 | { 186 | if (!trim($this->$c)) 187 | throw new VroumVroum_User_Exception("Missing or empty configuration value '".$c."' which is required!"); 188 | } 189 | } 190 | 191 | } 192 | 193 | public function __set($key, $value) 194 | { 195 | return; 196 | } 197 | 198 | public function setDisabled() 199 | { 200 | $res = $this->articles->query('SELECT success FROM update_log ORDER BY date DESC LIMIT 10;'); 201 | $haveSuccess = true; 202 | while ($row = $res->fetchArray(SQLITE3_ASSOC)) { 203 | $haveSuccess &= $row; 204 | } 205 | if ($haveSuccess) { 206 | $res = $this->articles->query('SELECT date FROM update_log ORDER BY date DESC LIMIT 1;'); 207 | $haveSuccess = $res + 63115200 < time(); //31557600 = 1 year 208 | } 209 | if (!$haveSuccess) { 210 | if (defined('MAIL_ADMIN')) { 211 | $title = sprintf("Autoblogs : %s", $this->site_title); 212 | $msg = sprintf("%s is down!\n%s\n%s\n%s", $this->site_title, $this->site_url, $this->feed_url, ROOT_DIR); 213 | mail(MAIL_ADMIN, $title, $msg); 214 | } 215 | $f = file_get_contents(CONFIG_FILE); 216 | if (strpos($f, 'DISABLED="1"') === false) { 217 | file_put_contents(CONFIG_FILE, $f."\nDISABLED=\"1\""); 218 | } 219 | } 220 | return; 221 | } 222 | } 223 | 224 | // BLOG 225 | 226 | class VroumVroum_Blog 227 | { 228 | protected $articles = null; 229 | protected $local = null; 230 | 231 | public $config = null; 232 | 233 | static public function removeHTML($str) 234 | { 235 | $str = strip_tags($str); 236 | $str = html_entity_decode($str, ENT_QUOTES, 'UTF-8'); 237 | return $str; 238 | } 239 | 240 | static public function toURI($str) 241 | { 242 | $uri = self::removeHTML(trim($str)); 243 | $uri = substr($uri, 0, 70); 244 | $uri = preg_replace('/[^\w\d()\p{L}]+/u', '-', $uri); 245 | $uri = preg_replace('/-{2,}/', '-', $uri); 246 | $uri = preg_replace('/^-|-$/', '', $uri); 247 | return $uri; 248 | } 249 | 250 | public function __construct() 251 | { 252 | $this->config = new VroumVroum_Config; 253 | 254 | $create_articles_db = file_exists(ARTICLES_DB_FILE) ? false : true; 255 | 256 | $this->articles = new SQLite3(ARTICLES_DB_FILE); 257 | 258 | if ($create_articles_db) 259 | { 260 | $this->articles->exec(' 261 | CREATE TABLE articles ( 262 | id INTEGER PRIMARY KEY, 263 | feed_id TEXT, 264 | title TEXT, 265 | uri TEXT, 266 | url TEXT, 267 | date INT, 268 | content TEXT 269 | ); 270 | CREATE TABLE update_log ( 271 | date INT PRIMARY KEY, 272 | success INT, 273 | log TEXT 274 | ); 275 | CREATE UNIQUE INDEX feed_id ON articles (feed_id); 276 | CREATE INDEX date ON articles (date); 277 | '); 278 | } 279 | 280 | $this->articles->createFunction('countintegers', array($this, 'sql_countintegers')); 281 | } 282 | 283 | public function getLocalURL($in) 284 | { 285 | return "./?".(is_array($in) ? $in['uri'] : $in); 286 | } 287 | 288 | protected function log_update($success, $log = '') 289 | { 290 | $this->articles->exec('INSERT INTO update_log (date, success, log) VALUES (\''.time().'\', \''.(int)(bool)$success.'\', 291 | \''.$this->articles->escapeString($log).'\');'); 292 | 293 | // Delete old log 294 | $this->articles->exec('DELETE FROM update_log WHERE date > (SELECT date FROM update_log ORDER BY date DESC LIMIT 100,1);'); 295 | 296 | return true; 297 | } 298 | 299 | public function insertOrUpdateArticle($feed_id, $title, $url, $date, $content) 300 | { 301 | $exists = $this->articles->querySingle('SELECT date, id, title, content FROM articles WHERE feed_id = \''.$this->articles->escapeString($feed_id).'\';', true); 302 | 303 | if (empty($exists)) 304 | { 305 | $uri = self::toURI($title); 306 | 307 | if ($this->articles->querySingle('SELECT 1 FROM articles WHERE uri = \''.$this->articles->escapeString($uri).'\';')) 308 | { 309 | $uri = date('Y-m-d-') . $uri; 310 | } 311 | 312 | $content = $this->mirrorMediasForArticle($content, $url); 313 | 314 | $this->articles->exec('INSERT INTO articles (id, feed_id, title, uri, url, date, content) VALUES (NULL, 315 | \''.$this->articles->escapeString($feed_id).'\', \''.$this->articles->escapeString($title).'\', 316 | \''.$this->articles->escapeString($uri).'\', \''.$this->articles->escapeString($url).'\', 317 | \''.(int)$date.'\', \''.$this->articles->escapeString($content).'\');'); 318 | 319 | $id = $this->articles->lastInsertRowId(); 320 | 321 | $title = self::removeHTML($title); 322 | $content = self::removeHTML($content); 323 | 324 | } 325 | else 326 | { 327 | // Doesn't need update 328 | if ($date == $exists['date'] && $content == $exists['content'] && $title == $exists['title']) 329 | { 330 | return false; 331 | } 332 | 333 | // $id = $exists['id']; 334 | 335 | if ($content != $exists['content']) 336 | $content = $this->mirrorMediasForArticle($content, $url); 337 | /* 338 | $this->articles->exec('UPDATE articles SET title=\''.$this->articles->escapeString($title).'\', 339 | url=\''.$this->articles->escapeString($url).'\', content=\''.$this->articles->escapeString($content).'\', 340 | date=\''.(int)$date.'\' WHERE id = \''.(int)$id.'\';'); 341 | */ 342 | $uri = self::toURI($title); 343 | $this->articles->exec('INSERT INTO articles (id, feed_id, title, uri, url, date, content) VALUES (NULL, 344 | \''.$this->articles->escapeString($feed_id).'\', \'[Update] '.$this->articles->escapeString($title).'\', 345 | \''.$this->articles->escapeString($uri).'\', \''.$this->articles->escapeString($url).'\', 346 | \''.(int)$date.'\', \''.$this->articles->escapeString($content).'\');'); 347 | $id = $this->articles->lastInsertRowId(); 348 | 349 | $title = self::removeHTML($title); 350 | $content = self::removeHTML($content); 351 | 352 | } 353 | 354 | return $id; 355 | } 356 | 357 | public function mustUpdate() 358 | { 359 | if ($this->config->disabled) { 360 | return false; 361 | } 362 | if (isset($_GET['update'])) 363 | return true; 364 | 365 | $last_update = $this->articles->querySingle('SELECT date FROM update_log ORDER BY date DESC LIMIT 1;'); 366 | 367 | if (!empty($last_update) && (int) $last_update > (time() - $this->config->update_interval)) 368 | return false; 369 | 370 | return true; 371 | } 372 | 373 | public function mustUpdateXsaf() 374 | { 375 | return file_exists('import.json'); 376 | } 377 | 378 | protected function _getStreamContext() 379 | { 380 | return stream_context_create( 381 | array( 382 | 'http' => array( 383 | 'method' => 'GET', 384 | 'timeout' => $this->config->update_timeout, 385 | '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", 386 | ) 387 | ) 388 | ); 389 | } 390 | 391 | public function update() 392 | { 393 | if (!$this->mustUpdate() || $this->config->disabled) { 394 | return false; 395 | } 396 | 397 | try { 398 | $body = file_get_contents($this->config->feed_url, false, $this->_getStreamContext()); 399 | } 400 | catch (ErrorException $e) 401 | { 402 | $this->log_update(false, $e->getMessage() . "\n\n" . (!empty($http_response_header) ? implode("\n", $http_response_header) : '')); 403 | $this->config->setDisabled(); 404 | throw new VroumVroum_Feed_Exception("Can't retrieve feed: ".$e->getMessage()); 405 | } 406 | 407 | libxml_use_internal_errors(true); 408 | $xml = @simplexml_load_string($body); 409 | 410 | if (!$xml) 411 | { 412 | $errors = VroumVroum_Feed_Exception::getXMLErrorsAsString(libxml_get_errors()); 413 | $this->log_update(false, implode("\n", $errors) . "\n\n" . $body); 414 | $this->config->setDisabled(); 415 | throw new VroumVroum_Feed_Exception("Feed is invalid - XML error: ".implode(" - ", $errors)); 416 | } 417 | 418 | $updated = 0; 419 | $this->articles->exec('BEGIN TRANSACTION;'); 420 | 421 | if (isset($xml->entry)) // ATOM feed 422 | { 423 | foreach ($xml->entry as $item) 424 | { 425 | $date = isset($item->published) ? (string) $item->published : (string) $item->updated; 426 | $guid = !empty($item->id) ? (string)$item->id : (string)$item->link['href']; 427 | 428 | if( count($item->content->children()) > 0 ) $content = (string)$item->content->asXML(); 429 | else $content = (string)$item->content; 430 | 431 | $id = $this->insertOrUpdateArticle($guid, (string)$item->title, 432 | (string)$item->link['href'], strtotime($date), $content ); 433 | 434 | if ($id !== false) 435 | $updated++; 436 | } 437 | } 438 | elseif (isset($xml->item)) // RSS 1.0 /RDF 439 | { 440 | foreach ($xml->item as $item) 441 | { 442 | $guid = (string) $item->attributes('http://www.w3.org/1999/02/22-rdf-syntax-ns#')->about ?: (string)$item->link; 443 | $date = (string) $item->children('http://purl.org/dc/elements/1.1/')->date; 444 | 445 | $id = $this->insertOrUpdateArticle($guid, (string)$item->title, (string)$item->link, 446 | strtotime($date), (string) $item->children('http://purl.org/rss/1.0/modules/content/')); 447 | 448 | if ($id !== false) 449 | $updated++; 450 | } 451 | } 452 | elseif (isset($xml->channel->item)) // RSS 2.0 453 | { 454 | foreach ($xml->channel->item as $item) 455 | { 456 | $content = (string) $item->children('http://purl.org/rss/1.0/modules/content/'); 457 | $guid = !empty($item->guid) ? (string) $item->guid : (string) $item->link; 458 | 459 | if (empty($content) && !empty($item->description)) 460 | $content = (string) $item->description; 461 | 462 | $id = $this->insertOrUpdateArticle($guid, (string)$item->title, (string)$item->link, 463 | strtotime((string) $item->pubDate), $content); 464 | 465 | if ($id !== false) 466 | $updated++; 467 | } 468 | } 469 | else 470 | { 471 | throw new VroumVroum_Feed_Exception("Unknown feed type?!"); 472 | } 473 | 474 | $this->log_update(true, $updated . " elements updated"); 475 | 476 | $this->articles->exec('END TRANSACTION;'); 477 | 478 | return $updated; 479 | } 480 | 481 | public function updateXsaf() { 482 | if($this->mustUpdateXsaf() && !$this->config->disabled) { 483 | $json = json_decode(file_get_contents('import.json'), true); 484 | $count = count($json['files']); 485 | file_put_contents('import.lock', $count); /* one-process locking */ 486 | $remoteurl = $json['url']; 487 | if (!file_exists('media')) { 488 | mkdir('media'); 489 | } 490 | if(!file_exists('media/.htaccess')){ 491 | file_put_contents('media/.htaccess', 492 | "Options -ExecCGI 493 | RemoveHandler .php .phtml .php3 .php4 .php5 .html .htm .js 494 | RemoveType .php .phtml .php3 .php4 .php5 .html .htm .js 495 | php_flag engine off 496 | AddType text/plain .php .phtml .php3 .php4 .php5 .html .htm .js 497 | "); 498 | } 499 | 500 | $time = time(); 501 | $maxtime = $time + 3; /* max exec time: 3 seconds */ 502 | 503 | while ($time <= $maxtime) { 504 | $file = array_shift($json['files']); /* get first element while unstacking */ 505 | if(!empty($file)) { 506 | $this->_copy($remoteurl.$file, "media/$file"); 507 | file_put_contents('import.json', json_encode($json)); 508 | } 509 | else { 510 | unlink('import.json'); 511 | break; 512 | } /* first element empty: import finished */ 513 | $time = time(); 514 | } 515 | unlink('import.lock'); 516 | } 517 | } 518 | 519 | public function listArticlesByPage($page = 1) 520 | { 521 | $nb = $this->config->articles_per_page; 522 | $begin = ($page - 1) * $nb; 523 | $res = $this->articles->query('SELECT * FROM articles ORDER BY date DESC LIMIT '.(int)$begin.','.(int)$nb.';'); 524 | 525 | $out = array(); 526 | 527 | while ($row = $res->fetchArray(SQLITE3_ASSOC)) 528 | { 529 | $out[] = $row; 530 | } 531 | 532 | return $out; 533 | } 534 | 535 | public function listLastArticles() 536 | { 537 | return array_merge($this->listArticlesByPage(1), $this->listArticlesByPage(2)); 538 | } 539 | 540 | public function countArticles() 541 | { 542 | return $this->articles->querySingle('SELECT COUNT(*) FROM articles;'); 543 | } 544 | 545 | public function getArticleFromURI($uri) 546 | { 547 | return $this->articles->querySingle('SELECT * FROM articles WHERE uri = \''.$this->articles->escapeString($uri).'\';', true); 548 | } 549 | 550 | public function sql_countintegers($in) 551 | { 552 | return substr_count($in, ' '); 553 | } 554 | 555 | public function searchArticles($query) 556 | { 557 | $res = $this->articles->query('SELECT id, uri, title, content 558 | FROM articles 559 | WHERE content LIKE \'%'.$this->articles->escapeString($query).'%\' 560 | OR title LIKE \'%'.$this->articles->escapeString($query).'%\' 561 | ORDER BY id DESC 562 | LIMIT 0,100;'); 563 | 564 | $out = array(); 565 | 566 | while ($row = $res->fetchArray(SQLITE3_ASSOC)) 567 | { 568 | $row['url'] = $this->getLocalURL($this->articles->querySingle('SELECT uri FROM articles WHERE id = \''.(int)$row['id'].'\';')); 569 | $out[] = $row; 570 | } 571 | 572 | return $out; 573 | } 574 | 575 | public function mirrorMediasForArticle($content, $url) 576 | { 577 | if (!file_exists(MEDIA_DIR)) 578 | { 579 | mkdir(MEDIA_DIR); 580 | } 581 | if(!file_exists(MEDIA_DIR.'/.htaccess')){ 582 | file_put_contents(MEDIA_DIR.'/.htaccess', 583 | "Options -ExecCGI 584 | RemoveHandler .php .phtml .php3 .php4 .php5 .html .htm .js 585 | RemoveType .php .phtml .php3 .php4 .php5 .html .htm .js 586 | php_flag engine off 587 | AddType text/plain .php .phtml .php3 .php4 .php5 .html .htm .js 588 | "); 589 | } 590 | 591 | $schemes = array('http', 'https'); 592 | $extensions = explode(',', preg_quote('jpg,jpeg,png,apng,gif,svg,pdf,odt,ods,epub,webp,wav,mp3,ogg,aac,wma,flac,opus,mp4,webm', '!')); 593 | $extensions = implode('|', $extensions); 594 | 595 | $from = parse_url($url); 596 | if( isset($from['path']) ) { // not exist if http://exemple.com 597 | $from['path'] = preg_replace('![^/]*$!', '', $from['path']); 598 | }else{ 599 | $from['path'] = ''; 600 | } 601 | 602 | preg_match_all('!(src|href)\s*=\s*[\'"]?([^"\'<>\s]+\.(?:'.$extensions.')[\'"])[\'"]?!i', $content, $match, PREG_SET_ORDER); 603 | 604 | foreach ($match as $m) 605 | { 606 | $url = parse_url(substr($m[2], 0, -1)); 607 | 608 | if (empty($url['scheme'])) 609 | $url['scheme'] = $from['scheme']; 610 | 611 | if (empty($url['host'])) 612 | $url['host'] = $from['host']; 613 | 614 | if (!in_array(strtolower($url['scheme']), $schemes)) 615 | continue; 616 | 617 | if ($url['path'][0] != '/') 618 | $url['path'] = $from['path'] . $url['path']; 619 | 620 | $filename = basename($url['path']); 621 | $url = $url['scheme'] . '://' . $url['host'] . $url['path']; 622 | 623 | $filename = substr(sha1($url), -8) . '.' . substr(preg_replace('![^\w\d_.-]!', '', $filename), -64); 624 | $copied = false; 625 | 626 | if (!file_exists(MEDIA_DIR . '/' . $filename)) 627 | { 628 | try { 629 | $copied = $this->_copy($url, MEDIA_DIR . '/' . $filename); 630 | } 631 | catch (ErrorException $e) 632 | { 633 | // Ignore copy errors 634 | } 635 | } 636 | $content = str_replace($m[0], $m[1] . '="'.'media/'.$filename.'" data-original-source="'.$url.'"', $content); 637 | } 638 | return $content; 639 | } 640 | 641 | public function getXsafCounter() { 642 | if($this->mustUpdateXsaf()) { 643 | $json = json_decode(file_get_contents('import.json'), true); 644 | return count($json['files']); 645 | } 646 | } 647 | 648 | /* copy() is buggy with http streams and safe_mode enabled (which is bad), so here's a workaround */ 649 | protected function _copy($from, $to) 650 | { 651 | $in = fopen($from, 'r', false, $this->_getStreamContext()); 652 | $out = fopen($to, 'w', false); 653 | $size = stream_copy_to_stream($in, $out); 654 | fclose($in); 655 | fclose($out); 656 | return $size; 657 | } 658 | } 659 | 660 | // DISPLAY AND CONTROLLERS 661 | 662 | $vvb = new VroumVroum_Blog; 663 | $config = $vvb->config; 664 | $site_type = escape($config->site_type); 665 | 666 | if (isset($_GET['feed'])) // FEED 667 | { 668 | header('Content-Type: application/atom+xml; charset=UTF-8'); 669 | echo ' 670 | 671 | '.escape($config->site_title).' 672 | '.escape(html_entity_decode(strip_tags($config->site_description), ENT_COMPAT, 'UTF-8')).' 673 | '.date(DATE_ATOM, filemtime(ARTICLES_DB_FILE)).' 674 | 675 | '.LOCAL_URL.' 676 | 677 | Projet Autoblog'; 678 | 679 | foreach($vvb->listLastArticles() as $art) 680 | { 681 | echo ' 682 | 683 | 684 | '.escape($config->site_title).' 685 | '.escape($config->site_url).' 686 | 687 | <![CDATA['.escape($art['title']).']]> 688 | 689 | '.str_replace('?feed', '?', LOCAL_URL).urlencode(str_replace('./?', '', $vvb->getLocalURL($art))).' 690 | '.date(DATE_ATOM, $art['date']).' 691 | 692 | 693 | source)
'.escape_content($art['content']).']]> 694 |
695 |
'; 696 | } 697 | 698 | echo ' 699 |
'; 700 | exit; 701 | } 702 | 703 | if (isset($_GET['opml'])) // OPML 704 | { 705 | //header('Content-Type: application/octet-stream'); 706 | header('Content-type: text/xml'); 707 | header('Content-Disposition: attachment; filename="'.escape($config->site_title).'.xml"'); 708 | $opmlfile = new SimpleXMLElement(''); 709 | $opmlfile->addAttribute('version', '1.0'); 710 | $opmlhead = $opmlfile->addChild('head'); 711 | $opmlhead->addChild('title', escape($config->site_title)); 712 | $opmlhead->addChild('dateCreated', date('r', time())); 713 | $opmlbody = $opmlfile->addChild('body'); 714 | $outline = $opmlbody->addChild('outline'); 715 | $outline->addAttribute('title', escape($config->site_title)); 716 | $outline->addAttribute('text', escape($config->site_type)); 717 | $outline->addAttribute('htmlUrl', escape($config->site_url)); 718 | $outline->addAttribute('xmlUrl', escape($config->feed_url)); 719 | 720 | echo $opmlfile->asXML(); 721 | exit; 722 | } 723 | 724 | if (isset($_GET['media'])) // MEDIA 725 | { 726 | header('Content-Type: application/json'); 727 | if(is_dir(MEDIA_DIR)) 728 | { 729 | $url = str_replace('?media', 'media/', LOCAL_URL); 730 | $files = scandir(MEDIA_DIR); 731 | unset($files[0]); // . 732 | unset($files[1]); // .. 733 | echo json_encode(array("url"=> $url, "files" => $files)); 734 | } 735 | exit; 736 | } 737 | 738 | if (isset($_GET['update'])) 739 | { 740 | $_SERVER['QUERY_STRING'] = ''; 741 | } 742 | 743 | // CONTROLLERS 744 | $search = !empty($_GET['q']) ? trim($_GET['q']) : ''; 745 | $article = null; 746 | 747 | if (!$search && !empty($_SERVER['QUERY_STRING']) && !is_numeric($_SERVER['QUERY_STRING'])) 748 | { 749 | $uri = rawurldecode($_SERVER['QUERY_STRING']); 750 | $article = $vvb->getArticleFromURI($uri); 751 | 752 | if (!$article) 753 | { 754 | header('HTTP/1.1 404 Not Found', true, 404); 755 | } 756 | } 757 | 758 | // common CSS 759 | $css='* { margin: 0; padding: 0; } 760 | body { font-family:sans-serif; background-color: #efefef; padding: 1%; color: #333; } 761 | img { max-width: 100%; height: auto; } 762 | a { text-decoration: none; color: #000;font-weight:bold; } 763 | body > header a { text-decoration: none; color: #000;font-weight:bold; } 764 | body > header { text-align:center; padding: 30px 3%; max-width:70em;margin:0 auto; } 765 | body > article > header { margin-bottom: 1em; } 766 | body > article > header h2 a:hover { color:#403976; } 767 | body > article h4 { font-weight: normal; font-size: small; color: #666; } 768 | body > article .source a { color: #666; } 769 | body > header > form { float:right; } 770 | body > header > form input { } 771 | body > nav { background-color:white;padding: 12px 10px 12px 10px;border:1px solid #aaa;max-width:70em;margin:1em auto;box-shadow:0px 5px 7px #aaa; } 772 | body > nav strong { font-size: 1.2em; color: #333; } 773 | body > nav a { color:#000; margin: 0 0.5em; } 774 | body > nav a:hover { color:#333; } 775 | body > footer a { color:#000; } 776 | body > footer a:hover { color:#333; } 777 | .content ul, .content ol { margin-left: 2em; } 778 | .content h1, .content h2, .content h3, .content h4, .content h5, .content h6, 779 | .content ul, .content ol, .content p, .content object, .content div, .content blockquote, 780 | .content dl, .content pre { margin-bottom: 0.8em; } 781 | .content pre, .content blockquote { background: #ddd; border: 1px solid #999; padding: 0.2em; max-width: 100%; overflow: auto; } 782 | .content h1 { font-size: 1.5em; } 783 | .content h2 { font-size: 1.4em;color:#000; } 784 | .result h3 a { color: darkblue; text-decoration: none; text-shadow: 1px 1px 1px #fff; } 785 | #error { position: fixed; top: 0; left: 0; right: 0; padding: 1%; background: #fff; border-bottom: 2px solid red; color: darkred; } 786 | '; 787 | 788 | switch($site_type) { 789 | case 'microblog': 790 | case 'twitter': 791 | $css .= "\n".' /* twitter/microblog style */ 792 | body > header h1 a { color: #333;font-size:40pt;text-shadow: #ccc 0px 5px 5px; } 793 | body > article > header 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; } 794 | body > article > header h2 a { color:#333; text-decoration:none; } 795 | body > article { background-color:white;padding: 12px 10px 33px;border:1px solid #aaa;max-width:70em;margin:0 auto;box-shadow:0px 5px 7px #aaa; } 796 | body > article .source { font-size: 0.8em; color: #666; } 797 | body > footer { margin-top:1em;text-align:center; font-size: small; color:#333; clear: both; } 798 | .content {font-size:0.9em;overflow: hidden;text-overflow: ellipsis;}'; 799 | //.content {font-size:0.9em;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;}'; 800 | break; 801 | case 'shaarli': 802 | $css .= "\n".' /* shaarli style */ 803 | body > header h1 a { color: #333;font-size:40pt;text-shadow: #ccc 0px 5px 5px; } 804 | body > article > header title h2 { margin: 0; color:#333; text-shadow: 1px 1px 1px #fff; } 805 | body > article > header h2 a { color:#000; text-decoration:none; } 806 | body > article { background-color:white;padding: 12px 10px 33px;border:1px solid #aaa;max-width:70em;margin:1em auto;box-shadow:0px 5px 7px #aaa; } 807 | body > article footer.source { margin-top:1em;font-size: 0.8em; color: #666; } 808 | body > footer { text-align:center; font-size: small; color:#333; clear: both; }'; 809 | break; 810 | case 'generic': 811 | case 'youtube': 812 | default: 813 | $css .= "\n".' /* youtube style */ 814 | body > header h1 a { color: #333;font-size:40pt;text-shadow: #ccc 0px 5px 5px;text-transform:uppercase; } 815 | body > article > header h2 { margin: 0; color:#333; text-shadow: 1px 1px 1px #fff; } 816 | body > article > header h2 a { color:#000; text-decoration:none; } 817 | body > article footer.source { font-size: 0.8em; color: #666; } 818 | body > article { background-color:white;padding: 12px 10px 33px;border:1px solid #aaa;max-width:70em;margin:1em auto;box-shadow:0px 5px 7px #aaa; } 819 | body > footer { text-align:center; font-size: small; color:#333; clear: both; }'; 820 | } 821 | 822 | // HTML HEADER 823 | echo ' 824 | 825 | 826 | 827 | '.escape($config->site_title).' 828 | 829 | 830 | '; 833 | if( $vvb->mustUpdateXsaf()) { 834 | echo ' '; 835 | } 836 | echo ' 837 | 838 | 839 |
840 |

PROJET AUTOBLOG'. (strlen(HEAD_TITLE) > 0 ? ' ~ '. HEAD_TITLE : '') .'

841 |
842 |

'.escape($config->site_title).'

'; 843 | if ($config->disabled) { 844 | echo "Archivé"; 845 | } 846 | if (!empty($config->site_description)) 847 | echo ' 848 |

'.$config->site_description.'

849 |

⇐ retour index

'; 850 | 851 | echo ' 852 |
853 | 854 | 855 |
856 |
857 | '; 858 | 859 | if( $vvb->mustUpdateXsaf()) { 860 | echo ' 861 |
862 |
863 |

'.__('Update').'

864 |
865 |
866 | '.__('Import running: '). $vvb->getXsafCounter() . __(' files remaining').'
867 | '.__('The page should refresh every second. If not, refresh manually.').' 868 |
869 |
'; 870 | } 871 | elseif ($vvb->mustUpdate()) 872 | { 873 | echo ' 874 |
875 |
876 |

'.__('Update').'

877 |
878 |
879 | '.__('Updating database... Please wait.').' 880 |
881 |
'; 882 | } 883 | 884 | if (!empty($search)) 885 | { 886 | $results = $vvb->searchArticles($search); 887 | $text = sprintf(__('%d results for %s'), count($results), escape($search)); 888 | echo ' 889 |
890 |
891 |

'.__('Search').'

892 | '.$text.' 893 |
894 |
'; 895 | 896 | foreach ($results as $art) 897 | { 898 | echo ' 899 | '; 905 | } 906 | } 907 | elseif (!is_null($article)) 908 | { 909 | if (!$article) 910 | { 911 | echo ' 912 |
913 |
914 |

'.__('Not Found').'

915 | '.(!empty($uri) ? '

'.escape($vvb->getLocalURL($uri)) . '

' : '').' 916 | '.__('Article not found.').' 917 |
918 |
'; 919 | } 920 | else 921 | { 922 | display_article($article); 923 | } 924 | } 925 | else 926 | { 927 | if (!empty($_SERVER['QUERY_STRING']) && is_numeric($_SERVER['QUERY_STRING'])) 928 | $page = (int) $_SERVER['QUERY_STRING']; 929 | else 930 | $page = 1; 931 | 932 | $list = $vvb->listArticlesByPage($page); 933 | 934 | foreach ($list as $article) 935 | { 936 | display_article($article); 937 | } 938 | 939 | $max = $vvb->countArticles(); 940 | if ($max > $config->articles_per_page) { 941 | echo "\n".' '; 954 | } 955 | } 956 | 957 | echo ' 958 | '; 964 | 965 | if( $vvb->mustUpdateXsaf() ) { 966 | try { 967 | ob_end_flush(); 968 | flush(); 969 | } 970 | catch (Exception $e) 971 | { 972 | // Silent, not critical 973 | } 974 | 975 | try { 976 | $updated = $vvb->updateXsaf(); 977 | } 978 | catch (VroumVroum_Feed_Exception $e) 979 | { 980 | echo ' 981 |
982 | '.escape($e->getMessage()).' 983 |
'; 984 | $updated = 0; 985 | } 986 | } 987 | elseif ($vvb->mustUpdate()) 988 | { 989 | try { 990 | ob_end_flush(); 991 | flush(); 992 | } 993 | catch (Exception $e) 994 | { 995 | // Silent, not critical 996 | } 997 | 998 | try { 999 | $updated = $vvb->update(); 1000 | } 1001 | catch (VroumVroum_Feed_Exception $e) 1002 | { 1003 | echo ' 1004 |
1005 | '.escape($e->getMessage()).' 1006 |
'; 1007 | $updated = 0; 1008 | } 1009 | 1010 | if ($updated > 0) 1011 | { 1012 | echo ' 1013 | '; 1018 | } 1019 | else 1020 | { 1021 | echo ' 1022 | '; 1027 | } 1028 | } 1029 | 1030 | echo ' 1031 | 1032 | '; 1033 | 1034 | 1035 | function escape_content($str) 1036 | { 1037 | $str = preg_replace('!<\s*(style|script|link)!', '<\\1', $str); 1038 | $str = str_replace('="media/', '="./media/', $str); 1039 | return $str; 1040 | } 1041 | 1042 | // ARTICLE HTML CODE 1043 | function display_article($article) 1044 | { 1045 | global $vvb, $config; 1046 | $dateTime = new DateTime(); 1047 | $dateTime->setTimestamp($article['date']); 1048 | $formattedDate = $dateTime->format(__(DATE_ATOM)); 1049 | 1050 | echo ' 1051 | '; 1059 | } 1060 | 1061 | ?> 1062 | -------------------------------------------------------------------------------- /config.php.example: -------------------------------------------------------------------------------- 1 | SebSauvage et Bohwaz.'); 15 | 16 | // define( 'DOCS_CACHE_DURATION', 1800); 17 | // define( 'AUTOBLOGS_CACHE_DURATION', 1800); 18 | 19 | // define( 'ALLOW_FULL_UPDATE', TRUE ); 20 | // define( 'ALLOW_CHECK_UPDATE', TRUE ); 21 | 22 | /** 23 | * If you set ALLOW_NEW_AUTOBLOGS to FALSE, the following options do not matter. 24 | **/ 25 | // define( 'ALLOW_NEW_AUTOBLOGS', TRUE ); 26 | // define( 'ALLOW_NEW_AUTOBLOGS_BY_LINKS', TRUE ); 27 | // define( 'ALLOW_NEW_AUTOBLOGS_BY_SOCIAL', TRUE ); 28 | // define( 'ALLOW_NEW_AUTOBLOGS_BY_BUTTON', TRUE ); 29 | // define( 'ALLOW_NEW_AUTOBLOGS_BY_OPML_FILE', TRUE ); 30 | // define( 'ALLOW_NEW_AUTOBLOGS_BY_OPML_LINK', TRUE ); 31 | // define( 'ALLOW_NEW_AUTOBLOGS_BY_XSAF', TRUE ); 32 | 33 | /** 34 | * TwitterBridge: https://github.com/mitsukarenai/twitterbridge 35 | * twitter2feed : https://github.com/mitsukarenai/twitter2feed 36 | * rss-bridge : https://github.com/sebsauvage/rss-bridge 37 | * replace LOCAL with: 38 | - the twitterbridge request URL (example: 'http://www.some.website/twitterbridge/?u=' ) 39 | - or the rss-bridge request URL (example: 'http://www.some.website/rss-bridge/?action=display&bridge=TwitterBridge&format=AtomFormat&u=' ) 40 | - or leave LOCAL to use the included twitter2feed.php. 41 | * set to FALSE if you want to fully disable Twitter support 42 | **/ 43 | // define( 'API_TWITTER', 'LOCAL' ); 44 | 45 | /** 46 | * Import autoblogs from friend's autoblog farm - Add a link to the JSON export 47 | **/ 48 | $friends_autoblog_farm = array( 49 | 'https://raw.github.com/mitsukarenai/xsaf-bootstrap/master/3.json', 50 | // 'https://www.ecirtam.net/autoblogs/?export', 51 | // 'http://autoblog.suumitsu.eu/?export', 52 | // 'http://streisand.hoa.ro/?export', 53 | ); 54 | ?> 55 | -------------------------------------------------------------------------------- /docs/docs.txt: -------------------------------------------------------------------------------- 1 | You can manually add files in the /docs/ directory, such as PDF, docs, images, etc. 2 | You can also add subfolders in /docs/ for website mirroring. Be sure that your subfolder contains a file named index.html. 3 | 4 | Delete this file to hide the 'Autres documents' block in your autoblogs homepage. 5 | -------------------------------------------------------------------------------- /functions.php: -------------------------------------------------------------------------------- 1 | SebSauvage et Bohwaz.'); 42 | 43 | /** 44 | * Functions 45 | **/ 46 | function NoProtocolSiteURL($url) { 47 | $protocols = array("http://", "https://"); 48 | $siteurlnoproto = str_replace($protocols, "", $url); 49 | 50 | // Remove the / at the end of string 51 | if ( $siteurlnoproto[strlen($siteurlnoproto) - 1] == '/' ) 52 | $siteurlnoproto = substr($siteurlnoproto, 0, -1); 53 | 54 | // Remove index.php/html at the end of string 55 | if( strpos($url, 'index.php') || strpos($url, 'index.html') ) { 56 | $siteurlnoproto = preg_replace('#(.*)/index\.(html|php)$#', '$1', $siteurlnoproto); 57 | } 58 | 59 | return $siteurlnoproto; 60 | } 61 | 62 | 63 | function DetectRedirect($url) 64 | { 65 | if(parse_url($url, PHP_URL_HOST)==FALSE) { 66 | throw new Exception('Not a URL: '. escape ($url) ); 67 | } 68 | 69 | try { $response = get_headers($url, 1); } 70 | catch (Exception $e) { throw new Exception('RSS URL unreachable: '. escape($url) ); } 71 | if(!empty($response['Location'])) { 72 | try { $response2 = get_headers($response['Location'], 1); } 73 | catch (Exception $e) { throw new Exception('RSS URL unreachable: '. escape($url) ); } 74 | 75 | if(!empty($response2['Location'])) { 76 | throw new Exception('Too much redirection: '. escape ($url) ); 77 | } 78 | else { return $response['Location']; } 79 | } 80 | else { 81 | return $url; 82 | } 83 | } 84 | 85 | function urlHash($rssurl) { 86 | return sha1(NoProtocolSiteURL($rssurl)); 87 | } 88 | 89 | function urlToFolder($siteurl, $rssurl) { 90 | return AUTOBLOGS_FOLDER . substr(preg_replace("/[^a-z0-9]/", '', strtolower(NoProtocolSiteURL($siteurl))), 0, FOLDER_MAX_LENGTH) .'_'. urlHash($rssurl) .'/'; 91 | } 92 | 93 | function folderExists($siteurl, $rssurl) { 94 | return file_exists(urlToFolder($siteurl, $rssurl)); 95 | } 96 | 97 | function escape($str) { 98 | return htmlspecialchars($str, ENT_COMPAT, 'UTF-8', false); 99 | } 100 | 101 | function createAutoblog($type, $sitename, $siteurl, $rssurl) { 102 | if( $type == 'generic' || empty( $type )) { 103 | $var = updateType( $siteurl ); 104 | $type = $var['type']; 105 | if( !empty( $var['name']) ) { 106 | if( !stripos($siteurl, $var['name'] === false) ) 107 | $sitename = ucfirst($var['name']) . ' - ' . $sitename; 108 | } 109 | } 110 | 111 | if(folderExists($siteurl, $rssurl)) { 112 | throw new Exception('Erreur : l\'autoblog '. $sitename .' existe déjà.'); 113 | } 114 | 115 | $foldername = urlToFolder($siteurl, $rssurl); 116 | 117 | if ( mkdir($foldername, 0755, false) ) { 118 | 119 | $fp = fopen($foldername .'/index.php', 'w+'); 120 | if( !fwrite($fp, "") ) 121 | throw new Exception('Impossible d\'écrire le fichier index.php'); 122 | fclose($fp); 123 | 124 | $fp = fopen($foldername .'/vvb.ini', 'w+'); 125 | if( !fwrite($fp, '[VroumVroumBlogConfig] 126 | SITE_TYPE="'. $type .'" 127 | SITE_TITLE="'. $sitename .'" 128 | SITE_DESCRIPTION="Site original : '. $sitename .'" 129 | SITE_URL="'. $siteurl .'" 130 | FEED_URL="'. $rssurl .'" 131 | ARTICLES_PER_PAGE="'. getArticlesPerPage( $type ) .'" 132 | UPDATE_INTERVAL="'. getInterval( $type ) .'" 133 | UPDATE_TIMEOUT="'. getTimeout( $type ) .'"') ) 134 | throw new Exception('Impossible d\'écrire le fichier vvb.ini'); 135 | fclose($fp); 136 | } 137 | else 138 | throw new Exception('Impossible de créer le répertoire.'); 139 | 140 | updateXML('new_autoblog_added', 'new', $foldername, $sitename, $siteurl, $rssurl); 141 | unlink(AUTOBLOGS_CACHE_FILENAME); 142 | } 143 | 144 | function getArticlesPerPage( $type ) { 145 | switch( $type ) { 146 | case 'microblog': 147 | return 20; 148 | case 'twitter': 149 | return 20; 150 | case 'shaarli': 151 | return 20; 152 | case 'youtube': 153 | return 10; 154 | default: 155 | return 5; 156 | } 157 | } 158 | 159 | function getInterval( $type ) { 160 | switch( $type ) { 161 | case 'microblog': 162 | return 300; 163 | case 'twitter': 164 | return 300; 165 | case 'shaarli': 166 | return 1800; 167 | default: 168 | return 3600; 169 | } 170 | } 171 | 172 | function getTimeout( $type ) { 173 | switch( $type ) { 174 | default: 175 | return 30; 176 | } 177 | } 178 | 179 | function updateType($siteurl) { 180 | if( strpos($siteurl, 'twitter.com') !== FALSE ) { 181 | return array('type' => 'twitter', 'name' => 'twitter'); 182 | } 183 | elseif( strpos( $siteurl, 'shaarli' ) !== FALSE ) { 184 | return array('type' => 'shaarli', 'name' => 'shaarli'); 185 | } 186 | elseif( strpos( $siteurl, 'youtube.com' ) !== FALSE ) { 187 | return array('type' => 'youtube', 'name' => ''); 188 | } 189 | else 190 | return array('type' => 'generic', 'name' => ''); 191 | } 192 | 193 | function debug($data) 194 | { 195 | echo '
';
196 | 	var_dump($data);
197 | 	echo '
'; 198 | } 199 | 200 | function __($str) 201 | { 202 | switch ($str) 203 | { 204 | case 'Search': 205 | return 'Recherche'; 206 | case 'Update': 207 | return 'Mise à jour'; 208 | case 'Updating database... Please wait.': 209 | return 'Mise à jour de la base de données, veuillez patienter...'; 210 | case '%d results for %s': 211 | return '%d résultats pour la recherche %s'; 212 | case 'Not Found': 213 | return 'Introuvable'; 214 | case 'Article not found.': 215 | return 'Cet article n\'a pas été trouvé.'; 216 | case 'Older': 217 | return 'Plus anciens'; 218 | case 'Newer': 219 | return 'Plus récents'; 220 | case 'ATOM Feed': 221 | return 'Flux ATOM'; 222 | case 'Update complete!': 223 | return 'Mise à jour terminée !'; 224 | case 'Click here to reload this webpage.': 225 | return 'Cliquez ici pour recharger cette page.'; 226 | case 'Source:': 227 | return 'Source :'; 228 | case '_date_format': 229 | return '%A %e %B %Y à %H:%M'; 230 | case 'Media export': 231 | return 'Export fichiers media'; 232 | case 'Import running: ': 233 | return 'Import en cours : '; 234 | case ' files remaining': 235 | return ' fichiers restants'; 236 | case 'The page should refresh every second. If not, refresh manually.': 237 | return 'La page devrait se rafraîchir toutes les secondes. Si non, rafraîchissez là manuellement..'; 238 | default: 239 | return $str; 240 | } 241 | } 242 | 243 | function updateXML($status, $response_code, $autoblog_url, $autoblog_title, $autoblog_sourceurl, $autoblog_sourcefeed) 244 | { 245 | $json = json_decode(file_get_contents(RESOURCES_FOLDER.'rss.json'), true); 246 | $json[] = array( 247 | 'timestamp'=>time(), 248 | 'autoblog_url'=>$autoblog_url, 249 | 'autoblog_title'=>$autoblog_title, 250 | 'autoblog_sourceurl'=>$autoblog_sourceurl, 251 | 'autoblog_sourcefeed'=>$autoblog_sourcefeed, 252 | 'status'=>$status, 253 | 'response_code'=>$response_code 254 | ); 255 | $json = array_slice($json, -50, 50); 256 | if(file_put_contents(RESOURCES_FOLDER.'rss.json', json_encode($json), LOCK_EX) === FALSE) 257 | { return FALSE; } 258 | else { return TRUE; } 259 | } 260 | 261 | function displayXMLstatus($status, $response_code, $autoblog_url, $autoblog_title, $autoblog_sourceurl, $autoblog_sourcefeed) { 262 | switch ($status) 263 | { 264 | case 'unavailable': 265 | return 'Autoblog "'.$autoblog_title.'" : site distant inaccessible (code '.$response_code.')
Autoblog : '.$autoblog_title.'
Site : '. $autoblog_sourceurl .'
RSS : '.$autoblog_sourcefeed.''; 266 | case 'moved': 267 | return 'Autoblog "'.$autoblog_title.'" : site distant redirigé (code '.$response_code.')
Autoblog : '.$autoblog_title.'
Site : '. $autoblog_sourceurl .'
RSS : '.$autoblog_sourcefeed.''; 268 | case 'not_found': 269 | return 'Autoblog "'.$autoblog_title.'" : site distant introuvable (code '.$response_code.')
Autoblog : '.$autoblog_title.'
Site : '. $autoblog_sourceurl .'
RSS : '.$autoblog_sourcefeed.''; 270 | case 'remote_error': 271 | return 'Autoblog "'.$autoblog_title.'" : site distant a problème serveur (code '.$response_code.')
Autoblog : '.$autoblog_title.'
Site : '. $autoblog_sourceurl .'
RSS : '.$autoblog_sourcefeed.''; 272 | case 'available': 273 | return 'Autoblog "'.$autoblog_title.'" : site distant à nouveau accessible (code '.$response_code.')
Autoblog : '.$autoblog_title.'
Site : '. $autoblog_sourceurl .'
RSS : '.$autoblog_sourcefeed.''; 274 | case 'new_autoblog_added': 275 | return 'Autoblog "'.$autoblog_title.'" ajouté.
Autoblog : '.$autoblog_title.'
Site : '. $autoblog_sourceurl .'
RSS : '.$autoblog_sourcefeed.''; 276 | } 277 | } 278 | 279 | function displayXML() { 280 | header('Content-type: application/rss+xml; charset=utf-8'); 281 | echo ' 282 | '.serverUrl(true).''; 283 | echo 'Projet Autoblog'. ((strlen(HEAD_TITLE)>0) ? ' | '. HEAD_TITLE : '').'Projet Autoblog - RSS : Ajouts et changements de disponibilité.'; 284 | if(file_exists(RESOURCES_FOLDER.'rss.json')) 285 | { 286 | $json = json_decode(file_get_contents(RESOURCES_FOLDER.'rss.json'), true); 287 | rsort($json); 288 | foreach ($json as $uitem) 289 | { 290 | $item = array(); 291 | foreach($uitem AS $Key => $Value) { 292 | $item[$Key] = escape($Value); 293 | } 294 | 295 | $description = displayXMLstatus($item['status'],$item['response_code'],$item['autoblog_url'],$item['autoblog_title'],$item['autoblog_sourceurl'],$item['autoblog_sourcefeed']); 296 | $link = serverUrl(true).$item['autoblog_url']; 297 | $date = date(DATE_RSS, $item['timestamp']); 298 | print << 301 | {$item['autoblog_title']} 302 | 303 | {$link} 304 | {$item['timestamp']} 305 | Autoblog 306 | {$date} 307 | 308 | EOT; 309 | } 310 | } 311 | echo ''; 312 | } 313 | ?> 314 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | array( 41 | 'method' => 'GET', 42 | 'timeout' => 10, 43 | '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", 44 | ) 45 | ) 46 | ); 47 | return file_get_contents($url, false, $stream); 48 | } 49 | 50 | function get_title_from_feed($url) { 51 | return get_title_from_datafeed(file_get_contents_ua($url)); 52 | } 53 | 54 | function get_title_from_datafeed($data) { 55 | if($data === false) { return 'url inaccessible'; } 56 | $dom = new DOMDocument; 57 | $dom->loadXML($data) or die('xml malformé'); 58 | $title = $dom->getElementsByTagName('title'); 59 | return $title->item(0)->nodeValue; 60 | } 61 | 62 | function get_link_from_feed($url) { 63 | return get_link_from_datafeed(file_get_contents_ua($url)); 64 | } 65 | 66 | function get_link_from_datafeed($data) { 67 | if($data === false) { return 'url inaccessible'; } 68 | $xml = simplexml_load_string($data); // quick feed check 69 | 70 | // ATOM feed && RSS 1.0 /RDF && RSS 2.0 71 | if (!isset($xml->entry) && !isset($xml->item) && !isset($xml->channel->item)) 72 | { die('le flux n\'a pas une syntaxe valide');} 73 | 74 | $check = substr($data, 0, 5); 75 | if($check !== 'channel->link; 81 | if($channel['link'] === NULL) { 82 | $dom = new DOMDocument; 83 | $dom->loadXML($data) or die('xml malformé'); 84 | $link = $dom->getElementsByTagName('uri'); 85 | return $link->item(0)->nodeValue; 86 | } 87 | else { 88 | return escape($channel['link']); 89 | } 90 | } 91 | 92 | function get_size($doc) { 93 | $symbol = array('o', 'Kio', 'Mio', 'Gio', 'Tio'); 94 | $size = filesize($doc); 95 | $exp = floor(log($size) / log(1024)); 96 | $nicesize = $size / pow(1024, floor($exp)); 97 | return sprintf('%d %s', $nicesize, $symbol[$exp]); 98 | } 99 | 100 | function serverUrl($return_subfolder = false) 101 | { 102 | $https = (!empty($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS'])=='on')) || $_SERVER["SERVER_PORT"]=='443'; // HTTPS detection. 103 | $serverport = ($_SERVER["SERVER_PORT"]=='80' || ($https && $_SERVER["SERVER_PORT"]=='443') ? '' : ':'.$_SERVER["SERVER_PORT"]); 104 | if($return_subfolder === true) { 105 | $path = pathinfo( $_SERVER['PHP_SELF'] ); 106 | $finalslash = ( $path['dirname'] != '/' ) ? '/' : ''; 107 | $subfolder = $path['dirname'] . $finalslash; 108 | } else $subfolder = ''; 109 | return 'http'.($https?'s':'').'://'.$_SERVER["SERVER_NAME"].$serverport.$subfolder; 110 | } 111 | 112 | function objectCmp($a, $b) { 113 | return strcasecmp ($a->site_title, $b->site_title); 114 | } 115 | 116 | function generate_antibot() { 117 | $letters = array('zéro', 'un', 'deux', 'trois', 'quatre', 'cinq', 'six', 'sept', 'huit', 'neuf', 'dix', 'onze', 'douze', 'treize', 'quatorze', 'quinze', 'seize', 'dix-sept', 'dix-huit', 'dix-neuf', 'vingt'); 118 | return $letters[mt_rand(1, 20)]; 119 | } 120 | 121 | function check_antibot($number, $text_number) { 122 | $letters = array('zéro', 'un', 'deux', 'trois', 'quatre', 'cinq', 'six', 'sept', 'huit', 'neuf', 'dix', 'onze', 'douze', 'treize', 'quatorze', 'quinze', 'seize', 'dix-sept', 'dix-huit', 'dix-neuf', 'vingt'); 123 | return ( array_search( $text_number, $letters ) === intval($number) ) ? true : false; 124 | } 125 | 126 | function create_from_opml($opml) { 127 | global $error, $success; 128 | $cpt = 0; 129 | foreach( $opml->body->outline as $outline ) { 130 | if ( !empty( $outline['title'] ) && !empty( $outline['text'] ) && !empty( $outline['xmlUrl']) && !empty( $outline['htmlUrl'] )) { 131 | try { 132 | $sitename = escape( $outline['title'] ); 133 | $siteurl = escape($outline['htmlUrl']); 134 | 135 | $sitetype = escape($outline['text']); 136 | if ( $sitetype != 'microblog' && $sitetype != 'shaarli' && $sitetype != 'twitter' && $sitetype != 'youtube') 137 | $sitetype = 'generic'; 138 | 139 | $rssurl = DetectRedirect(escape($outline['xmlUrl'])); 140 | 141 | createAutoblog( $sitetype, $sitename, $siteurl, $rssurl ); 142 | 143 | $message = 'Autoblog "'. $sitename .'" crée avec succès. → afficher l\'autoblog.'; 144 | // Do not print iframe on big import (=> heavy and useless) 145 | if( ++$cpt < 10 ) 146 | $message .= ''; 147 | $success[] = $message; 148 | } 149 | catch (Exception $e) { 150 | $error[] = $e->getMessage(); 151 | } 152 | } 153 | } 154 | } 155 | 156 | /** 157 | * Simple version check 158 | **/ 159 | function versionCheck() { 160 | $versionfile = 'version'; 161 | $lastestUrl = 'https://raw.github.com/mitsukarenai/Projet-Autoblog/master/version'; 162 | 163 | $expire = time() - 84600 ; // 23h30 en secondes 164 | $lockfile = '.versionlock'; 165 | 166 | if (file_exists($lockfile) && filemtime($lockfile) > $expire) { 167 | if( file_get_contents($lockfile) == 'NEW' ) { 168 | // No new version installed 169 | if( filemtime( $lockfile ) > filemtime( $versionfile ) ) 170 | return true; 171 | else unlink($lockfile); 172 | } 173 | else return false; 174 | } 175 | 176 | if (file_exists($lockfile) && filemtime($lockfile) < $expire) { unlink($lockfile); } 177 | 178 | if( file_get_contents($versionfile) != file_get_contents($lastestUrl) ) { 179 | file_put_contents($lockfile, 'NEW'); 180 | return true; 181 | } 182 | file_put_contents($lockfile, '.'); 183 | return false; 184 | } 185 | $update_available = (ALLOW_CHECK_UPDATE) ? versionCheck() : false; 186 | 187 | /** 188 | * RSS Feed 189 | * 190 | **/ 191 | if( !file_exists(RESOURCES_FOLDER.'rss.json')) { 192 | file_put_contents(RESOURCES_FOLDER.'rss.json', '', LOCK_EX); 193 | } 194 | 195 | if (isset($_GET['rss'])) { 196 | displayXML(); 197 | die; 198 | } 199 | 200 | /** 201 | * SVG 202 | **/ 203 | function check( $folder ) 204 | { 205 | $randomtime=rand(86400, 259200); /* intervalle de mise à jour: de 1 à 3 jours (pour éviter que le statut de tous les autoblogs soit rafraichi en bloc et bouffe le CPU) */ 206 | $expire=time() -$randomtime ; 207 | 208 | /* SVG minimalistes */ 209 | 210 | $svg_ok = RESOURCES_FOLDER . 'icon-ok.svg'; 211 | $svg_mv = RESOURCES_FOLDER . 'icon-mv.svg'; 212 | $svg_err = RESOURCES_FOLDER . 'icon-err.svg'; 213 | 214 | $errorlog= './' . $folder . '/error.log'; 215 | 216 | $oldvalue = null; 217 | if(file_exists($errorlog)) { $oldvalue = file_get_contents($errorlog); }; 218 | if(file_exists($errorlog) && filemtime($errorlog) < $expire) { unlink($errorlog); } /* errorlog périmé ? Suppression. */ 219 | if(file_exists($errorlog)) /* errorlog existe encore ? se contenter de lire sa taille pour avoir le statut */ 220 | { 221 | if(filesize($errorlog) == "0") { return $svg_ok; } 222 | else if(filesize($errorlog) == "1") { return $svg_mv; } 223 | else { return $svg_err; } 224 | } 225 | else /* ..sinon, lancer la procédure de contrôle */ 226 | { 227 | $ini = parse_ini_file("./". $folder ."/vvb.ini") or die; 228 | $headers = get_headers($ini['FEED_URL']); 229 | 230 | if(!empty($headers)) 231 | $code=explode(" ", $headers[0]); 232 | else $code = array(); 233 | 234 | /* le flux est indisponible (typiquement: erreur DNS ou possible censure) - à vérifier */ 235 | if(empty($headers) || $headers === FALSE || (!empty($code) && ($code[1] == '500' || $code[1] == '404'))) { 236 | if( $oldvalue !== null && $oldvalue != '..' ) { 237 | updateXML('unavailable', 'nxdomain', $folder, $ini['SITE_TITLE'], $ini['SITE_URL'], $ini['FEED_URL']); 238 | } 239 | file_put_contents($errorlog, '..'); 240 | return $svg_err; 241 | } 242 | /* code retour 200: flux disponible */ 243 | if($code[1] == "200") { 244 | if( $oldvalue !== null && $oldvalue != '' ) { 245 | updateXML('available', '200', $folder, $ini['SITE_TITLE'], $ini['SITE_URL'], $ini['FEED_URL']); 246 | } 247 | file_put_contents($errorlog, ''); 248 | return $svg_ok; 249 | } 250 | /* autre code retour: un truc a changé (redirection, changement de CMS, .. bref vvb.ini doit être corrigé) */ 251 | else { 252 | if( $oldvalue !== null && $oldvalue != '.' ) { 253 | updateXML('moved', '3xx', $folder, $ini['SITE_TITLE'], $ini['SITE_URL'], $ini['FEED_URL']); 254 | } 255 | file_put_contents($errorlog, '.'); 256 | return $svg_mv; 257 | } 258 | } 259 | } 260 | 261 | /** 262 | * JSON Export 263 | **/ 264 | if (isset($_GET['export'])) { 265 | header('Content-Type: application/json'); 266 | $subdirs = glob(AUTOBLOGS_FOLDER . "*"); 267 | 268 | foreach($subdirs as $unit) { 269 | if(is_dir($unit)) { 270 | $ini = parse_ini_file($unit.'/vvb.ini'); 271 | $unit=substr($unit, 2); 272 | $config = new stdClass; 273 | 274 | foreach ($ini as $key=>$value) { 275 | $key = strtolower($key); 276 | $config->$key = $value; 277 | } 278 | unset($ini); 279 | 280 | $feed=$config->feed_url; 281 | $type=$config->site_type; 282 | $title=$config->site_title; 283 | $url=$config->site_url; 284 | $reponse[$unit] = array("SITE_TYPE"=>"$type", "SITE_TITLE"=>"$title", "SITE_URL"=>"$url", "FEED_URL"=>"$feed"); 285 | 286 | } 287 | } 288 | echo json_encode( array( "meta"=> array("xsaf-version"=>XSAF_VERSION,"xsaf-db_transfer"=>"true","xsaf-media_transfer"=>"true"), 289 | "autoblogs"=>$reponse)); 290 | die; 291 | } 292 | 293 | /** 294 | * JSON Allowed Twitter accounts export 295 | **/ 296 | if (isset($_GET['export_twitter'])) { 297 | header('Content-Type: application/json'); 298 | $subdirs = glob(AUTOBLOGS_FOLDER . "*"); 299 | $response = array(); 300 | 301 | foreach($subdirs as $unit) { 302 | if(is_dir($unit)) { 303 | $unit=substr($unit, 2); 304 | $ini = parse_ini_file($unit.'/vvb.ini'); 305 | if( $ini['SITE_TYPE'] == 'twitter' ) { 306 | preg_match('#twitter\.com/(.+)#', $ini['SITE_URL'], $username); 307 | $response[] = $username[1]; 308 | } 309 | unset($ini); 310 | } 311 | } 312 | echo json_encode( $response ); 313 | die; 314 | } 315 | 316 | /** 317 | * OPML Full Export 318 | **/ 319 | if (isset($_GET['exportopml'])) // OPML 320 | { 321 | //header('Content-Type: application/octet-stream'); 322 | header('Content-type: text/xml'); 323 | header('Content-Disposition: attachment; filename="autoblogs-'. $_SERVER['SERVER_NAME'] .'.xml"'); 324 | 325 | $opmlfile = new SimpleXMLElement(''); 326 | $opmlfile->addAttribute('version', '1.0'); 327 | $opmlhead = $opmlfile->addChild('head'); 328 | $opmlhead->addChild('title', 'Autoblog OPML export from '. $_SERVER['SERVER_NAME'] ); 329 | $opmlhead->addChild('dateCreated', date('r', time())); 330 | $opmlbody = $opmlfile->addChild('body'); 331 | 332 | $subdirs = glob(AUTOBLOGS_FOLDER . "*"); 333 | 334 | foreach($subdirs as $unit) { 335 | if(is_dir($unit)) { 336 | $unit=substr($unit, 2); 337 | $ini = parse_ini_file($unit.'/vvb.ini'); 338 | $config = new stdClass; 339 | 340 | foreach ($ini as $key=>$value) { 341 | $key = strtolower($key); 342 | $config->$key = $value; 343 | } 344 | unset($ini); 345 | 346 | $outline = $opmlbody->addChild('outline'); 347 | $outline->addAttribute('title', escape($config->site_title)); 348 | $outline->addAttribute('text', escape($config->site_type)); 349 | $outline->addAttribute('htmlUrl', escape($config->site_url)); 350 | $outline->addAttribute('xmlUrl', escape($config->feed_url)); 351 | } 352 | } 353 | echo $opmlfile->asXML(); 354 | exit; 355 | } 356 | 357 | /** 358 | * Site map 359 | **/ 360 | if (isset($_GET['sitemap'])) 361 | { 362 | header('Content-Type: application/xml'); 363 | $proto=(!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS'])=='on')?"https://":"http://"; 364 | echo ''."\n".''."\n"; 365 | echo "\n ".$proto."{$_SERVER['HTTP_HOST']}".str_replace('?sitemap', '', $_SERVER['REQUEST_URI'])."\n"; 366 | echo ' '.date('c', time())."\n"; 367 | echo " daily\n\n"; 368 | $subdirs = glob(AUTOBLOGS_FOLDER . "*"); 369 | foreach($subdirs as $unit) { 370 | if(is_dir($unit)) { 371 | $unit=substr($unit, 2); 372 | echo "\n ".$proto.$_SERVER['SERVER_NAME'].substr($_SERVER['PHP_SELF'], 0, -9)."$unit/"."\n"; 373 | echo ' '.date('c', filemtime($unit))."\n"; 374 | echo " hourly\n\n\n"; 375 | } 376 | } 377 | echo ''; 378 | die; 379 | } 380 | 381 | /** 382 | * Update ALL autblogs (except .disabled) 383 | * This action can be very slow and consume CPU if you have a lot of autoblogs 384 | **/ 385 | if( isset($_GET['updateall']) ) { 386 | $lockfile = ".updatealllock"; 387 | if( !isset( $_GET['force']) ) { 388 | $max_exec_time=time()+4; // scipt have 4 seconds to update autoblogs 389 | $expire = time() - 5 ; // 5 seconds 390 | $lockfile_contents = array(); 391 | if (file_exists($lockfile)){ 392 | $lockfile_contents = file_get_contents($lockfile); 393 | if( !isset($lockfile_contents[0]) || $lockfile_contents[0] != "a") { // détection d'une serialisation 394 | if( filemtime($lockfile) > $expire){ 395 | echo "too early"; 396 | die; 397 | }else{ 398 | // need update of all autoblogs 399 | unlink($lockfile); 400 | } 401 | } 402 | // else we need to update some autoblogs 403 | } 404 | if( file_put_contents($lockfile, date(DATE_RFC822)) ===FALSE) { 405 | echo "Merci d'ajouter des droits d'écriture sur le fichier."; 406 | die; 407 | } 408 | 409 | if(!empty($lockfile_contents)) { 410 | $subdirs = unserialize($lockfile_contents); 411 | unset($lockfile_contents); 412 | }else{ 413 | $subdirs = glob(AUTOBLOGS_FOLDER . "*"); 414 | } 415 | } 416 | elseif (ALLOW_FULL_UPDATE) { 417 | $subdirs = glob(AUTOBLOGS_FOLDER . "*"); 418 | $max_exec_time=time() * 2; // workaround to disable max exec time 419 | } 420 | else { 421 | echo "You're not allowed to force full update."; 422 | die; 423 | } 424 | $todo_subdirs = $subdirs; 425 | 426 | foreach($subdirs as $key => $unit) { 427 | if(is_dir($unit)) { 428 | if( !file_exists(ROOT_DIR . '/' . $unit . '/.disabled')) { 429 | file_get_contents(serverUrl() . substr($_SERVER['PHP_SELF'], 0, -9) . $unit . '/index.php'); 430 | unset($todo_subdirs[$key]); 431 | } 432 | } 433 | if(time() >= $max_exec_time){ 434 | break; 435 | } 436 | } 437 | if(!empty($todo_subdirs)){ 438 | // if update is not finish 439 | // save list of autoblogs who need update 440 | file_put_contents($lockfile, serialize($todo_subdirs), LOCK_EX); 441 | echo "Not finish"; 442 | }else{ 443 | echo "Done"; 444 | } 445 | exit; 446 | } 447 | 448 | $antibot = generate_antibot(); 449 | $form = '
'."\n".' 450 |
451 |
452 | 453 | 454 |
'; 455 | 456 | /** 457 | * ADD BY BOOKMARK BUTTON 458 | **/ 459 | if(!empty($_GET['via_button']) && $_GET['number'] === '17' && ALLOW_NEW_AUTOBLOGS && ALLOW_NEW_AUTOBLOGS_BY_BUTTON ) 460 | { 461 | $form = ''; 462 | 463 | if( empty($_GET['rssurl']) ) { 464 | $form .= '

URL du flux RSS incorrect.
Fermer la fenêtre.

'; 465 | } 466 | else { 467 | if(isset($_GET['add']) && $_GET['add'] === '1' && !empty($_GET['siteurl']) && !empty($_GET['sitename'])) { 468 | try { 469 | $rssurl = DetectRedirect(escape($_GET['rssurl'])); 470 | 471 | $siteurl = escape($_GET['siteurl']); 472 | $sitename = escape($_GET['sitename']); 473 | $sitetype = updateType($siteurl); // Disabled input doesn't send POST data 474 | $sitetype = $sitetype['type']; 475 | 476 | createAutoblog( $sitetype, $sitename, $siteurl, $rssurl ); 477 | 478 | if( empty($error)) { 479 | $form .= ''; 480 | $form .= '

Autoblog '. $sitename .' ajouté avec succès.
'; 481 | } 482 | else { 483 | $form .= '

    '; 484 | foreach ( $error AS $value ) 485 | $form .= '
  • '. $value .'
  • '; 486 | $form .= '
'; 487 | } 488 | } 489 | catch (Exception $e) { 490 | $form .= $e->getMessage(); 491 | } 492 | $form .= 'Fermer la fenêtre.

'; 493 | } 494 | else { 495 | try { 496 | $rssurl = DetectRedirect(escape($_GET['rssurl'])); 497 | $datafeed = file_get_contents_ua($rssurl); 498 | if( $datafeed !== false ) { 499 | $siteurl = get_link_from_datafeed($datafeed); 500 | $sitename = get_title_from_datafeed($datafeed); 501 | $sitetype = updateType($siteurl); 502 | $sitetype = $sitetype['type']; 503 | 504 | $form .= 'Merci de vérifier les informations suivantes, corrigez si nécessaire.
505 |
506 | 507 |
508 |
509 |
510 |
511 |
'; 512 | } 513 | else { 514 | $form .= '

URL du flux RSS incorrecte.
Fermer la fenêtre.

'; 515 | } 516 | } 517 | catch (Exception $e) { 518 | $form .= $e->getMessage() .'
Fermer la fenêtre.

'; 519 | } 520 | } 521 | } 522 | $form .= ''; 523 | echo $form; die; 524 | } 525 | 526 | /** 527 | * ADD BY SOCIAL / SHAARLI 528 | **/ 529 | if( !empty($_POST['socialinstance']) && ALLOW_NEW_AUTOBLOGS && ALLOW_NEW_AUTOBLOGS_BY_SOCIAL) 530 | { 531 | $socialinstance = strtolower(escape($_POST['socialinstance'])); 532 | $socialaccount = (!empty($_POST['socialaccount'])) ? strtolower(escape($_POST['socialaccount'])) : false; 533 | if( $socialaccount === false && $socialinstance !== 'shaarli') 534 | $error[] = 'Le compte social doit être renseigné.'; 535 | elseif( !empty($_POST['number']) && !empty($_POST['antibot']) && check_antibot($_POST['number'], $_POST['antibot'])) { 536 | 537 | if($socialinstance === 'twitter') { 538 | if( API_TWITTER !== FALSE ) { 539 | $sitetype = 'twitter'; 540 | $siteurl = 'http://twitter.com/'. $socialaccount; 541 | if ( API_TWITTER === 'LOCAL' ) { 542 | $rssurl = serverUrl(true).'twitter2feed.php?u='.$socialaccount; 543 | } 544 | else { 545 | $rssurl = API_TWITTER.$socialaccount; 546 | // check 547 | $twitterbridge = get_headers($rssurl, 1); 548 | if ($twitterbridge['0'] == 'HTTP/1.1 403 Forbidden') { $error[] = "La twitterbridge a refusé ce nom d'utilisateur:
\n
".htmlentities($twitterbridge['X-twitterbridge']).'
'; } 549 | } 550 | } 551 | else 552 | $error[] = 'Vous devez définir une API Twitter -> RSS dans votre fichier de configuration (see TwitterBridge).'; 553 | } 554 | elseif($socialinstance === 'statusnet' && !empty($_POST['statusneturl'])) { 555 | $sitetype = 'microblog'; 556 | $siteurl= NoProtocolSiteURL(escape($_POST['statusneturl'])); 557 | try { 558 | $rssurl = DetectRedirect("http://".$siteurl."/api/statuses/user_timeline/$socialaccount.rss"); 559 | $siteurl = DetectRedirect("http://".$siteurl."/$socialaccount"); 560 | } 561 | catch (Exception $e) { 562 | echo $error[] = $e->getMessage(); 563 | } 564 | } 565 | elseif($socialinstance === 'shaarli' && !empty($_POST['shaarliurl'])) { 566 | $sitetype = 'shaarli'; 567 | $siteurl = NoProtocolSiteURL(escape($_POST['shaarliurl'])); 568 | try { 569 | $siteurl = DetectRedirect("http://".$siteurl."/"); 570 | } 571 | catch (Exception $e) { 572 | echo $error[] = $e->getMessage(); 573 | } 574 | $rssurl = $siteurl."?do=rss"; 575 | $socialaccount = get_title_from_feed($rssurl); 576 | } 577 | elseif($socialinstance === 'youtube') { 578 | $sitetype = 'youtube'; 579 | $siteurl = 'https://www.youtube.com/user/'.$socialaccount; 580 | $rssurl = 'https://gdata.youtube.com/feeds/base/users/'.$socialaccount.'/uploads?alt=atom&orderby=published'; 581 | } 582 | if( empty($error) ) { 583 | try { 584 | $headers = get_headers($rssurl, 1); 585 | if (strpos($headers[0], '200') === FALSE) 586 | throw new Exception('Flux inaccessible (compte inexistant ?)'); 587 | 588 | createAutoblog($sitetype, ucfirst($socialinstance) .' - '. $socialaccount, $siteurl, $rssurl); 589 | $success[] = ' 590 | '.ucfirst($socialinstance) .' - '. $socialaccount.' ajouté avec succès.'; 591 | } 592 | catch (Exception $e) { 593 | echo $error[] = $e->getMessage(); 594 | } 595 | } 596 | } 597 | else 598 | $error[] = 'Antibot : chiffres incorrects.'; 599 | } 600 | 601 | /** 602 | * ADD BY GENERIC LINK 603 | **/ 604 | if( !empty($_POST['generic']) && ALLOW_NEW_AUTOBLOGS && ALLOW_NEW_AUTOBLOGS_BY_LINKS) { 605 | if(empty($_POST['rssurl'])) 606 | {$error[] = "Veuillez entrer l'adresse du flux.";} 607 | if(empty($_POST['number']) || empty($_POST['antibot']) ) 608 | {$error[] = "Vous êtes un bot ?";} 609 | elseif(! check_antibot($_POST['number'], $_POST['antibot'])) 610 | {$error[] = "Antibot : ce n'est pas le bon nombre.";} 611 | 612 | if(empty($error)) { 613 | try { 614 | $rssurl = parse_url($_POST['rssurl']); 615 | if(!isset($rssurl['query'])) $rssurl['query'] = ''; 616 | $rssurl = $rssurl['scheme'].'://'.$rssurl['host'].$rssurl['path'].'?'.html_entity_decode($rssurl['query']); 617 | $rssurl = DetectRedirect($rssurl); 618 | 619 | if(!empty($_POST['siteurl'])) { 620 | 621 | $siteurl = escape($_POST['siteurl']); 622 | $sitename = get_title_from_feed($rssurl); 623 | 624 | createAutoblog('generic', $sitename, $siteurl, $rssurl); 625 | 626 | $success[] = ' 627 | Autoblog '. $sitename .' crée avec succès.afficher l\'autoblog'; 628 | } 629 | else { 630 | // checking procedure 631 | $datafeed = file_get_contents_ua($rssurl); 632 | if( $datafeed === false ) { 633 | $error[] = 'URL "'. $rssurl .'" inaccessible.'; 634 | } 635 | $sitetype = 'generic'; 636 | $siteurl = get_link_from_datafeed($datafeed); 637 | $sitename = get_title_from_datafeed($datafeed); 638 | 639 | $form = 'Merci de vérifier les informations suivantes, corrigez si nécessaire. Tous les champs doivent être renseignés.
640 |
641 |
642 |
643 |
644 |
645 |
646 |
'; 647 | 648 | } 649 | } 650 | catch (Exception $e) { 651 | echo $error[] = $e->getMessage(); 652 | } 653 | } 654 | } 655 | 656 | /** 657 | * ADD BY OPML File 658 | **/ 659 | if( !empty($_POST['opml_file']) && ALLOW_NEW_AUTOBLOGS && ALLOW_NEW_AUTOBLOGS_BY_OPML_FILE) { 660 | if(empty($_POST['number']) || empty($_POST['antibot']) ) 661 | {$error[] = "Vous êtes un bot ?";} 662 | elseif(! check_antibot($_POST['number'], $_POST['antibot'])) 663 | {$error[] = "Antibot : ce n'est pas le bon nombre.";} 664 | 665 | if( empty( $error)) { 666 | if (is_uploaded_file($_FILES['file']['tmp_name'])) { 667 | $opml = null; 668 | if( ($opml = simplexml_load_file( $_FILES['file']['tmp_name'])) !== false ) { 669 | create_from_opml($opml); 670 | } 671 | else 672 | $error[] = "Impossible de lire le contenu du fichier OPML."; 673 | unlink($_FILES['file']['tmp_name']); 674 | } else { 675 | $error[] = "Le fichier n'a pas été envoyé."; 676 | } 677 | } 678 | } 679 | 680 | /** 681 | * ADD BY OPML Link 682 | **/ 683 | if( !empty($_POST['opml_link']) && ALLOW_NEW_AUTOBLOGS && ALLOW_NEW_AUTOBLOGS_BY_OPML_LINK) { 684 | if(empty($_POST['number']) || empty($_POST['antibot']) ) 685 | {$error[] = "Vous êtes un bot ?";} 686 | elseif(! check_antibot($_POST['number'], $_POST['antibot'])) 687 | {$error[] = "Antibot : ce n'est pas le bon nombre.";} 688 | if( empty( $_POST['opml_url'] )) 689 | {$error[] = 'Le lien est incorrect.';} 690 | 691 | if( empty( $error)) { 692 | $opml_url = escape($_POST['opml_url']); 693 | if(parse_url($opml_url, PHP_URL_HOST)==FALSE) { 694 | $error[] = "URL du fichier OPML non valide."; 695 | } else { 696 | if ( ($opml = simplexml_load_file( $opml_url )) !== false ) { 697 | create_from_opml($opml); 698 | } else { 699 | $error[] = "Impossible de lire le contenu du fichier OPML ou d'accéder à l'URL donnée."; 700 | } 701 | } 702 | 703 | } 704 | } 705 | 706 | /** 707 | * RESET CACHE 708 | **/ 709 | if( !empty($_GET['reset_cache']) ) { 710 | if( $_GET['reset_cache'] == 'docs' ) { 711 | unlink(DOCS_CACHE_FILENAME); 712 | } 713 | if( $_GET['reset_cache'] == 'autoblogs' ) { 714 | unlink(AUTOBLOGS_CACHE_FILENAME); 715 | } 716 | } 717 | 718 | ?> 719 | 720 | 721 | 722 | Projet Autoblog<?php if(strlen(HEAD_TITLE)>0) echo " | " . HEAD_TITLE; ?> 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | '; 733 | } 734 | ?> 735 | 736 | 737 |
738 |

Projet Autoblog0) echo " | " . HEAD_TITLE; ?>

739 |
740 | 741 |
742 |
743 | '."\n"; 746 | ?> 747 |

Présentation

748 |
749 | 750 |

Le Projet Autoblog a pour objectif de répliquer les articles d'un blog ou d'un site site web. Si l'article source est supprimé, et même si le site d'origine disparaît, les articles restent lisibles sur l'autoblog. L'objectif premier de ce projet est de lutter contre la censure et toute sorte de pression…

751 | 752 |

Voici une liste d'autoblogs hébergés sur (plus d'infos sur le projet).

753 | 754 |

Autres fermesRechercher

755 |
756 | 757 |
758 |
759 |

Mise à jour

760 |
761 | 762 |

Une mise à jour du Projet Autoblog est disponible !

763 | 767 |
768 | 769 | 770 |
771 |
772 |

Ajouter un autoblog

773 |
774 | 775 | Message'. (count($error) ? 's' : '') ." :

\n"; 778 | echo "
    \n"; 779 | foreach ( $error AS $value ) { 780 | echo '
  • '. $value ."
  • \n"; 781 | } 782 | foreach ( $success AS $value ) { 783 | echo '
  • '. $value ."
  • \n"; 784 | } 785 | echo "
\n"; 786 | echo " \n"; 787 | echo ' '; 788 | } 789 | 790 | $button_list = '

Ajouter un autoblog via :'."\n"; 791 | if(ALLOW_NEW_AUTOBLOGS_BY_LINKS) 792 | $button_list .= ' Flux RSS'."\n"; 793 | if(ALLOW_NEW_AUTOBLOGS_BY_SOCIAL) { 794 | $button_list .= ' Compte réseau social'."\n"; 795 | $button_list .= ' Shaarli'."\n"; 796 | } 797 | if(ALLOW_NEW_AUTOBLOGS_BY_OPML_FILE) 798 | $button_list .= ' Fichier OPML'."\n"; 799 | if(ALLOW_NEW_AUTOBLOGS_BY_OPML_LINK) 800 | $button_list .= ' Lien vers OPML'."\n"; 801 | if(ALLOW_NEW_AUTOBLOGS_BY_BUTTON) 802 | $button_list .= ' Marque page'."\n"; 803 | $button_list .= "

\n"; 804 | echo $button_list; 805 | 806 | if(ALLOW_NEW_AUTOBLOGS_BY_LINKS == TRUE) { ?> 807 |
808 |
809 |

Ajouter un site web

810 |
811 | 812 |

Si vous souhaitez que héberge un autoblog d'un site, remplissez le formulaire suivant :

813 | 814 | 815 |
816 | 818 |
819 |
820 |

Ajouter un compte social

821 |
822 | 823 |
824 |
825 | Twitter (local)
'; 829 | else 830 | echo 'Twitter (via bridge)
'; 831 | } 832 | else echo 'Twitter
'; ?> 833 | 834 |
835 | Youtube
836 |
837 | 838 | 839 |
840 |
841 |
842 |
843 |

Ajouter un Shaarli

844 |
845 | 846 |
847 | 848 |
849 |
850 | 851 | 852 |
853 |
854 | 856 |
857 |
858 |

Ajouter par fichier OPML

859 |
860 | 861 |
862 | 863 |
864 |
865 | 866 | 867 |
868 |
869 | 871 | 884 | 886 |
887 |
888 |

Marque page

889 |
890 | 891 |

892 | Pour ajouter facilement un autoblog d'un site web, glissez ce bouton dans votre barre de marque-pages → 893 | marque-page sur ce lien)'); 895 | return false;" 896 | href="javascript:(function(){var%20autoblog_url="";var%20popup=window.open("","Add%20autoblog",'height=180,width=670');popup.document.writeln('');popup.document.write('Url%20feed%20%20:%20
');var%20feed_links=new%20Array();var%20links=document.getElementsByTagName('link');if(links.length>0){for(var%20i=0;i'+links[i].title+"%20(%20"+links[i].href+"%20)
");}}}popup.document.writeln("");popup.document.writeln("");popup.document.writeln("
");popup.document.writeln("");})();">Projet Autoblog
897 |

898 | 899 |
900 | '. substr($unit, (strrpos($unit, '/')) + 1 ) .'', $size); 917 | } 918 | } 919 | if(!empty( $docs )) { 920 | echo '
921 | 922 |
923 |

Autres documents

924 |
925 | 926 |
    '."\n"; 927 | 928 | foreach( $docs as $value ) { 929 | $str = $value[0]; 930 | if ( !empty($value[1]) ) { 931 | $str = sprintf('%s (%s)', $value[0], $value[1]); 932 | } 933 | echo '
  • '. $str . "
  • \n"; 934 | } 935 | 936 | echo '
937 |
'."\n"; 938 | } 939 | } 940 | // on recuperre le contenu du buffer 941 | $contenuCache = ob_get_contents(); 942 | ob_end_clean(); // on termine la bufferisation 943 | if( !empty($contenuCache) ) { 944 | file_put_contents("$fichierCache",$contenuCache, LOCK_EX); // on écrit le contenu du buffer dans le fichier cache 945 | } 946 | echo $contenuCache; // et on sort 947 | // sinon le fichier cache existe déjà, on ne génère pas la page 948 | // et on envoie le fichier statique à la place 949 | } else { 950 | readfile($fichierCache); // affichage du contenu du fichier 951 | echo ' '."\n"; // et un petit message 952 | } 953 | ?> 954 |
955 |
956 |

Autoblogs hébergés rss

957 |
958 | 959 | 964 | 965 | 974 | '; 975 | $subdirs = glob(AUTOBLOGS_FOLDER . "*"); 976 | $autoblogs = array(); 977 | foreach($subdirs as $unit) { 978 | if(is_dir($unit)) { 979 | if( !file_exists(ROOT_DIR . '/' . $unit . '/.disabled')) { 980 | if( file_exists(ROOT_DIR . '/' . $unit . '/vvb.ini')) { 981 | $ini = parse_ini_file(ROOT_DIR . '/' . $unit . '/vvb.ini'); 982 | if($ini) { 983 | $config = new stdClass; 984 | foreach ($ini as $key=>$value) { 985 | $key = strtolower($key); 986 | $config->$key = $value; 987 | } 988 | $autoblogs[$unit] = $config; 989 | unset($ini); 990 | } 991 | } 992 | } 993 | } 994 | } 995 | 996 | uasort($autoblogs, "objectCmp"); 997 | $autoblogs_display = ''; 998 | 999 | if(!empty($autoblogs)){ 1000 | foreach ($autoblogs as $key => $autoblog) { 1001 | $opml_link='opml'; 1002 | $autoblogs_display .= '
  • 1003 |
    1004 | 1005 | 1006 | 1007 |

    '.escape($autoblog->site_title).'

    1008 |
    1009 |
    1010 |
    config ini '.$opml_link.' | '.escape($autoblog->site_type).' source : '.escape($autoblog->site_url).'
    1011 |
  • '; 1012 | } 1013 | } 1014 | echo $autoblogs_display; 1015 | 1016 | echo ' 1017 | 1018 | 1019 |

    '.count($autoblogs).' autoblogs hébergés

    '; 1020 | 1021 | // on recuperre le contenu du buffer 1022 | $contenuCache = ob_get_contents(); 1023 | ob_end_clean(); // on termine la bufferisation 1024 | if( !empty($contenuCache) ) { 1025 | file_put_contents("$fichierCache",$contenuCache, LOCK_EX); // on écrit le contenu du buffer dans le fichier cache 1026 | } 1027 | echo $contenuCache; // et on sort 1028 | // sinon le fichier cache existe déjà, on ne génère pas la page 1029 | // et on envoie le fichier statique à la place 1030 | } else { 1031 | echo ''."\n".' '; // un message de début 1032 | readfile($fichierCache); // affichage du contenu du fichier 1033 | echo "\n".' '."\n"; // et un petit message 1034 | } 1035 | ?> 1036 |
    1037 | 1038 | 1042 | 1043 | 1044 | 1058 | 1059 | 1060 | 1061 | -------------------------------------------------------------------------------- /resources/autoblog.css: -------------------------------------------------------------------------------- 1 | /** 2 | * autoblog.css 3 | * ------------ 4 | * Please do NOT edit this file. Updating your Autoblogs farm will be easier. 5 | * If you want to add your own CSS, use the file user.css 6 | * 7 | */ 8 | 9 | body {background-color:#efefef;text-align:center;color:#333;font-family:sans-serif;} 10 | a {color:black;text-decoration:none;font-weight:bold;} 11 | a:hover {color:darkred;} 12 | h1 {text-transform:uppercase;text-align:center;font-size:40pt;text-shadow: #ccc 0px 5px 5px;} 13 | h2 {text-align:center;font-size: 16pt;margin:0 0 1em 0;font-style:italic;text-shadow: #ccc 0px 5px 5px; } 14 | body > section {background-color:white;padding: 12px 10px 12px 10px;border:1px solid #aaa;max-width:70em;margin:1em auto;text-align:justify;box-shadow:0px 5px 7px #aaa;} 15 | li {list-style-type:none;} 16 | input[type="text"]{width:20em;} 17 | input[type="radio"] {width:1em;} 18 | input[type="submit"] {width:8em;} 19 | section.form {padding:0.2em;border:1px solid #fff;} 20 | section.form:hover {background-color:#FAF4DA;border:1px dotted;} 21 | section#autoblogs > ul {text-align: center;padding:0;} 22 | section#autoblogs > ul > li {width:27%;height:2em;display: inline-block;text-align:justify;margin:0; padding:20px;background-color:#eee;border: 1px solid #888;} 23 | section#autoblogs > ul > li:hover {background-color:#fff;} 24 | section#autoblogs > ul > li header, section#autoblogs > ul > li h3 {font-size: large;text-shadow: #ccc 0px 5px 5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;} 25 | section#autoblogs > ul > li h3 {display:inline;font-size:large;text-overflow:ellipsis;width:100%;} 26 | section#autoblogs > ul > li header a:hover {color:darkred; text-decoration:none;} 27 | section#autoblogs > ul > li .source {font-size:x-small;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;} 28 | section#autoblogs > ul > li .source a:hover {color:darkred; text-decoration:none;} 29 | .clear {clear:both;text-align:right;font-size:small;} 30 | #logo {float: right;} 31 | .bouton{border: 1px none;padding: 10px;border: 1px solid #777777;border-radius: 8px 8px 8px 8px;box-shadow: 0 1px 0 0 #FFFFFF inset;display: inline-block;} 32 | .success {color: green;} 33 | .error {color: red;} 34 | .button_list{display:none;} 35 | .button{box-shadow:inset 0 1px 0 0 #d9fbbe;background:0;background-color:#b8e356;border-radius:6px;border:1px solid #83c41a;display:inline-block;color:#fff;font-family:arial;font-size:14px;font-weight:700;text-decoration:none;text-shadow:1px 1px 0 #86ae47;padding:6px 24px;} 36 | .button:hover{background:0;background-color:#a5cc52;} 37 | .button:active{position:relative;top:1px;} 38 | .buttonactive{background-color:#aaa;border-radius:6px;border:1px solid #83c41a;display:inline-block;color:#fff;font-family:arial;font-size:14px;font-weight:700;text-decoration:none;text-shadow:1px 1px 0 #86ae47;padding:6px 24px;} 39 | @media screen and (max-width:1024px) { 40 | section#autoblogs > ul > li { width: 40%; } 41 | } 42 | @media screen and (max-width:640px) { 43 | h1 { font-size:20pt; } 44 | .button, .button:hover, .button:active, .buttonactive { display: block; margin: auto; text-align:center; } 45 | section#autoblogs > ul > li { width: 80%; } 46 | } 47 | @media screen and (max-width:480px) { 48 | #logo { max-width: 250px; } 49 | input[type="text"]{width:15em;} 50 | } 51 | .cache_link { 52 | float:right; 53 | } 54 | .cache_link a { 55 | font-weight:normal; 56 | color: #CCC; 57 | } 58 | -------------------------------------------------------------------------------- /resources/icon-err.svg: -------------------------------------------------------------------------------- 1 | err -------------------------------------------------------------------------------- /resources/icon-generic.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/icon-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsukarenai/Projet-Autoblog/702beb99c00a03749167b0e65cc9451d46348c46/resources/icon-logo.png -------------------------------------------------------------------------------- /resources/icon-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 36 | 37 | 41 | 44 | 45 | 49 | 52 | 53 | 57 | 60 | 61 | 65 | 68 | 69 | 73 | 76 | 77 | 81 | 84 | 85 | 89 | 92 | 93 | 97 | 100 | 101 | 105 | 108 | 109 | 113 | 116 | 117 | 121 | 124 | 125 | 129 | 132 | 133 | 134 | 136 | 140 | 144 | 145 | 149 | 153 | 154 | 158 | 162 | 163 | 167 | 171 | 172 | 176 | 179 | 180 | 184 | 187 | 188 | 192 | 195 | 196 | 200 | 204 | 205 | 206 | 208 | 212 | 216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /resources/icon-microblog.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/icon-mv.svg: -------------------------------------------------------------------------------- 1 | 2 | mv -------------------------------------------------------------------------------- /resources/icon-ok.svg: -------------------------------------------------------------------------------- 1 | 2 | ok -------------------------------------------------------------------------------- /resources/icon-shaarli.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/icon-twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/icon-youtube.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/rss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitsukarenai/Projet-Autoblog/702beb99c00a03749167b0e65cc9451d46348c46/resources/rss.png -------------------------------------------------------------------------------- /resources/user.css.example: -------------------------------------------------------------------------------- 1 | /** 2 | * user.css 3 | * ------------ 4 | * Feel free to add your custom CSS and override original CSS in this file. 5 | * Don't forget to rename user.css.example to user.css to make it works. 6 | */ 7 | 8 | body { 9 | background-color: red; 10 | } -------------------------------------------------------------------------------- /version: -------------------------------------------------------------------------------- 1 | 0.3.3 2 | -------------------------------------------------------------------------------- /xsaf3.php: -------------------------------------------------------------------------------- 1 | $expire) { 20 | echo "too early"; 21 | die; 22 | } 23 | else { 24 | if( file_exists($lockfile) ) 25 | unlink($lockfile); 26 | 27 | if( file_put_contents($lockfile, date(DATE_RFC822)) ===FALSE) { 28 | echo "Merci d'ajouter des droits d'écriture sur le dossier."; 29 | die; 30 | } 31 | } 32 | 33 | define('ROOT_DIR', __DIR__); 34 | if(file_exists("functions.php")){ 35 | include "functions.php"; 36 | }else{ 37 | echo "functions.php not found !"; 38 | die; 39 | } 40 | 41 | if(file_exists("config.php")){ 42 | include "config.php"; 43 | }else{ 44 | echo "config.php not found !"; 45 | die; 46 | } 47 | 48 | function serverUrl() { 49 | $https = (!empty($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS'])=='on')) || $_SERVER["SERVER_PORT"]=='443'; // HTTPS detection. 50 | $serverport = ($_SERVER["SERVER_PORT"]=='80' || ($https && $_SERVER["SERVER_PORT"]=='443') ? '' : ':'.$_SERVER["SERVER_PORT"]); 51 | return 'http'.($https?'s':'').'://'.$_SERVER["SERVER_NAME"].$serverport; 52 | } 53 | 54 | libxml_use_internal_errors(true); 55 | 56 | // $max_exec_time = temps max d'exécution en seconde 57 | function xsafimport($xsafremote, $max_exec_time) { 58 | if( DEBUG ) 59 | echo "\n*Traitement $xsafremote en maximum $max_exec_time secondes"; 60 | 61 | $max_exec_time+=time()-1; // -1 car l'import prend environ 1 seconde 62 | 63 | /* détection de ferme autoblog */ 64 | $json_import = file_get_contents($xsafremote); 65 | if(!empty($json_import)) { 66 | $to_update=array(); 67 | $json_import = json_decode($json_import, true); 68 | 69 | if(!isset($json_import['meta']) || !isset($json_import['meta']['xsaf-version']) || $json_import['meta']['xsaf-version'] != XSAF_VERSION){ 70 | if(DEBUG){ 71 | echo "\nxsaf-version différentes !"; 72 | } 73 | return false; 74 | } 75 | 76 | $get_remote_db = ($json_import['meta']['xsaf-db_transfer'] == "true") ? true : false; 77 | $get_remote_media = ($json_import['meta']['xsaf-media_transfer'] == "true") ? true : false; 78 | 79 | if(!empty($json_import['autoblogs'])) { 80 | foreach ($json_import['autoblogs'] as $remote_folder => $value) { 81 | if(DEBUG) debug('remote = '. $remote_folder); 82 | if(count($value)==4 && !empty($value['SITE_TYPE']) && !empty($value['SITE_TITLE']) && !empty($value['SITE_URL']) && !empty($value['FEED_URL'])) { 83 | $sitetype = escape($value['SITE_TYPE']); 84 | $sitename = escape($value['SITE_TITLE']); 85 | $siteurl = escape($value['SITE_URL']); 86 | // Do not use DetectRedirect because it's slow and it has been used when the feed was added 87 | //$rssurl = DetectRedirect(escape($value['FEED_URL'])); 88 | $rssurl = escape($value['FEED_URL']); 89 | } 90 | 91 | 92 | /* TOO SLOW 93 | $xml = simplexml_load_file($rssurl); // quick feed check 94 | // ATOM feed && RSS 1.0 /RDF && RSS 2.0 95 | $result = (!isset($xml->entry) && !isset($xml->item) && !isset($xml->channel->item)) ? false : true; */ 96 | $result = true; 97 | 98 | /* autoblog */ 99 | if( $result === true ) { 100 | $foldername = urlToFolder($siteurl, $rssurl); 101 | 102 | try { 103 | createAutoblog($sitetype, $sitename, $siteurl, $rssurl); 104 | 105 | if( DEBUG ) { 106 | echo '

    autoblog '. $sitename .' crée avec succès (DL DB : '. var_dump($get_remote_db) .' - DL media : '. var_dump($get_remote_media) .') : '. $foldername .'

    '; 107 | if( !ALLOW_REMOTE_DB_DL && !ALLOW_REMOTE_MEDIA_DL ) 108 | echo ''; 109 | } 110 | 111 | /* ============================================================================================================================================================================== */ 112 | /* récupération de la DB distante */ 113 | if($get_remote_db == true && ALLOW_REMOTE_DB_DL ) { 114 | $remote_db = str_replace("?export", $remote_folder."/articles.db", $xsafremote); 115 | copy($remote_db, './'. $foldername .'/articles.db'); 116 | } 117 | /* préparation à la récupération des médias distants */ 118 | if($get_remote_media == true && ALLOW_REMOTE_MEDIA_DL ) { 119 | $remote_media=str_replace("?export", $remote_folder."/?media", $xsafremote); 120 | if(DEBUG) 121 | debug("Récupération de la liste des médias à $remote_media
    "); 122 | $json_media_import = file_get_contents($remote_media); 123 | if(DEBUG) 124 | debug($json_media_import); 125 | $media_data = json_decode($json_media_import, true); 126 | if(DEBUG) 127 | debug($media_data); 128 | if(!empty($json_media_import) && $media_data !== null && !empty($media_data['files'])) 129 | { 130 | file_put_contents('./'. $foldername .'/import.json', $json_media_import); 131 | } 132 | } 133 | 134 | /* ============================================================================================================================================================================== */ 135 | //TODO : tester si articles.db est une DB valide 136 | //$to_update[] = serverUrl().preg_replace("/(.*)\/(.*)$/i","$1/".$foldername , $_SERVER['SCRIPT_NAME']); // url of the new autoblog 137 | } 138 | catch (Exception $e) { 139 | if( DEBUG ) 140 | echo $e->getMessage(); 141 | } 142 | } 143 | 144 | if( DEBUG ) 145 | echo '

    time : '.($max_exec_time - time()) .'

    '; 146 | if(time() >= $max_exec_time) { 147 | if( DEBUG ) 148 | echo "

    Time out !

    "; 149 | break; 150 | } 151 | } 152 | } 153 | else { 154 | if( DEBUG ) 155 | echo "Format JSON incorrect."; 156 | return false; 157 | } 158 | } 159 | return; 160 | } 161 | 162 | if( DEBUG ) echo ''; 163 | if( ALLOW_NEW_AUTOBLOGS and ALLOW_NEW_AUTOBLOGS_BY_XSAF && !empty($friends_autoblog_farm) ) { 164 | foreach( $friends_autoblog_farm AS $value ) { 165 | if( !empty($value) ) 166 | xsafimport($value, EXEC_TIME); 167 | } 168 | if(DEBUG) echo "

    XSAF import finished

    "; 169 | } 170 | elseif( DEBUG ) 171 | echo "

    XSAF désactivé. Positionnez les variables ALLOW_NEW_AUTOBLOGS et ALLOW_NEW_AUTOBLOGS_BY_XSAF à TRUE dans le fichier config.php pour l'activer.

    "; 172 | 173 | if( DEBUG ) echo ''; 174 | ?> 175 | --------------------------------------------------------------------------------