├── .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 | 
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 |
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 |
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 |
900 |
903 | '.$art['content'].'
904 | ';
905 | }
906 | }
907 | elseif (!is_null($article))
908 | {
909 | if (!$article)
910 | {
911 | echo '
912 |
913 |
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".' '."\n";
942 | if ($page > 1)
943 | echo ' ← '.__('Newer').' '."\n";
944 |
945 | $last = ceil($max / $config->articles_per_page);
946 | for ($i = 1; $i <= $last; $i++) {
947 | echo ' '.($i == $page ? ''.$i.' ' : ''.$i.' ')."\n";
948 | }
949 |
950 | if ($page < $last)
951 | echo ' '.__('Older').' → '."\n";
952 |
953 | echo ' ';
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 |
1052 |
1053 |
1054 | '.$formattedDate.'
1055 |
1056 | '.escape_content($article['content']).'
1057 |
1058 | ';
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 = '';
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 | ';
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 | ';
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 Autoblog0) echo " | " . HEAD_TITLE; ?>
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 | ';
733 | }
734 | ?>
735 |
736 |
737 |
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 fermes → Rechercher
755 |
756 |
757 |
758 |
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 |
816 |
818 |
841 |
854 |
856 |
869 |
871 |
884 |
886 |
898 |
899 |
900 | '. substr($unit, (strrpos($unit, '/')) + 1 ) .'', $size);
917 | }
918 | }
919 | if(!empty( $docs )) {
920 | echo '
921 | Regénérer le cache
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
957 |
958 |
959 |
960 | sitemap |
961 | exportJSON |
962 | exportOPML
963 |
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 |
1010 |
1011 | ';
1012 | }
1013 | }
1014 | echo $autoblogs_display;
1015 |
1016 | echo '
1017 |
1018 | Regénérer le cache
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 |
--------------------------------------------------------------------------------