aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--COPYING14
-rwxr-xr-xREADME.md13
-rw-r--r--cache/footer.32a3c4422ad65642b8dbb7e731f4d990.rtpl.php7
-rw-r--r--cache/index.0b3442bc62f6c429fc13004b3c821ba0.rtpl.php20
-rwxr-xr-xcss/index.html0
-rw-r--r--css/knacss.css1070
-rwxr-xr-xcss/reset.css35
-rw-r--r--css/style.css35
-rwxr-xr-xcss/typography.css85
-rwxr-xr-xinc/Encoding.php262
-rwxr-xr-xinc/JSLikeHTMLElement.php110
-rwxr-xr-xinc/Readability.php1103
-rwxr-xr-xinc/index.html0
-rwxr-xr-xinc/rain.tpl.class.php1043
-rwxr-xr-xindex.php97
-rwxr-xr-xpoche.sqlitebin0 -> 131072 bytes
-rwxr-xr-xreadityourself.php212
-rwxr-xr-xtpl/footer.html7
-rwxr-xr-xtpl/index.html18
19 files changed, 4131 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 00000000..ee7d6a54
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,14 @@
1 DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2 Version 2, December 2004
3
4 Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
5
6 Everyone is permitted to copy and distribute verbatim or modified
7 copies of this license document, and changing it is allowed as long
8 as the name is changed.
9
10 DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
11 TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
12
13 0. You just DO WHAT THE FUCK YOU WANT TO.
14
diff --git a/README.md b/README.md
new file mode 100755
index 00000000..b66aba18
--- /dev/null
+++ b/README.md
@@ -0,0 +1,13 @@
1# poche
2
3Abandon Pocket, Instapaper and other Readibility service : adopt poche. It is the same, but it is open source.
4
5## Usage
6
7...
8
9## License
10Copyright © 2010-2013 Nicolas Lœuillet <nicolas.loeuillet@gmail.com>
11This work is free. You can redistribute it and/or modify it under the
12terms of the Do What The Fuck You Want To Public License, Version 2,
13as published by Sam Hocevar. See the COPYING file for more details. \ No newline at end of file
diff --git a/cache/footer.32a3c4422ad65642b8dbb7e731f4d990.rtpl.php b/cache/footer.32a3c4422ad65642b8dbb7e731f4d990.rtpl.php
new file mode 100644
index 00000000..e069cabb
--- /dev/null
+++ b/cache/footer.32a3c4422ad65642b8dbb7e731f4d990.rtpl.php
@@ -0,0 +1,7 @@
1<?php if(!class_exists('raintpl')){exit;}?><footer>
2 <div>
3 Copyright &copy; <a href="http://www.memiks.fr/">memiks.fr</a> | <a href="http://shaarli.memiks.fr/">Liens</a> / <a href="http://rss.memiks.fr/">RSS</a> / <a href="http://wiki.memiks.fr/">Wiki</a> / <a href="mailto:&#109;&#101;&#109;&#105;&#107;&#115;&#064;&#109;&#101;&#109;&#105;&#107;&#115;&#046;&#102;&#114;">Contact</a><br>
4 Licence: WTF Licence<br>
5 More information HERE: <a href="http://www.memiks.fr/readityourself/">http://www.memiks.fr/readityourself/</a> Version : <span class="version"><?php echo $version;?></span>
6 </div>
7</footer>
diff --git a/cache/index.0b3442bc62f6c429fc13004b3c821ba0.rtpl.php b/cache/index.0b3442bc62f6c429fc13004b3c821ba0.rtpl.php
new file mode 100644
index 00000000..6dd72a48
--- /dev/null
+++ b/cache/index.0b3442bc62f6c429fc13004b3c821ba0.rtpl.php
@@ -0,0 +1,20 @@
1<?php if(!class_exists('raintpl')){exit;}?><html>
2 <head>
3 <link rel='stylesheet' href='./css/reset.css' type='text/css' media='all' />
4 <link rel='stylesheet' href='./css/typography.css' type='text/css' media='all' />
5
6 <title><?php echo $title;?></title>
7 </head>
8 <body>
9 <article>
10 <h1><a href="<?php echo $url;?>"><?php echo $title;?></a></h1>
11 <div id="readityourselfcontent">
12 <?php echo $content;?>
13
14 </div>
15 <span class="comeFrom">Come From : <a href="<?php echo $url;?>"><?php echo $url;?></a>
16 </article>
17 <?php $tpl = new RainTPL;$tpl_dir_temp = self::$tpl_dir;$tpl->assign( $this->var );$tpl->draw( dirname("footer") . ( substr("footer",-1,1) != "/" ? "/" : "" ) . basename("footer") );?>
18
19 </body>
20</html>
diff --git a/css/index.html b/css/index.html
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/css/index.html
diff --git a/css/knacss.css b/css/knacss.css
new file mode 100644
index 00000000..56e91f9d
--- /dev/null
+++ b/css/knacss.css
@@ -0,0 +1,1070 @@
1/*
2* www.KNACSS.com V2.6c @author: Raphael Goetter, Alsacreations
3* Licence CC-BY http://creativecommons.org/licenses/by/3.0/fr/
4*/
5
6/* ----------------------------- */
7/* ==reset */
8/* ----------------------------- */
9
10/* base font-size corresponds to 10px and is adapted to rem unit */
11html {
12 font-size: 62.5%;
13}
14body {
15 background-color: #fff;
16 color: #000;
17 font-family: "Century Gothic", helvetica, arial, sans-serif;
18 font-size: 1.4em; /* equiv 14px */
19 line-height: 1.5; /* adapt to your design */
20}
21
22/* font-sizing for content */
23/* preserve vertical-rythm, thanks to http://soqr.fr/vertical-rhythm/ */
24p,
25ul,
26ol,
27dl,
28blockquote,
29pre,
30td,
31th,
32label,
33textarea,
34caption,
35details,
36figure,
37hgroup {
38 font-size: 1em; /* equiv 14px */
39 line-height: 1.5;
40 margin: .75em 0 0;
41}
42h1, .h1-like {
43 font-size: 1.8571em; /* equiv 26px */
44 font-weight: normal;
45 line-height: 1.6154em;
46 margin: .8077em 0 0 0;
47}
48h2, .h2-like {
49 font-size: 1.7143em; /* equiv 24px */
50 font-weight: normal;
51 line-height: 1.75em;
52 margin: .875em 0 0 0;
53}
54h3, .h3-like {
55 font-size: 1.5714em; /* equiv 22px */
56 font-weight: normal;
57 line-height: 1.909em;
58 margin: .9545em 0 0 0;
59}
60h4, .h4-like {
61 font-size: 1.4286em; /* equiv 20px */
62 font-weight: normal;
63 line-height: 1.05em;
64 margin: 1.05em 0 0 0;
65}
66h5, .h5-like {
67 font-size: 1.2857em; /* equiv 18px */
68 font-weight: normal;
69 line-height: 1.1667em;
70 margin: 1.1667em 0 0 0;
71}
72h6, .h6-like {
73 font-size: 1.1429em; /* equiv 16px */
74 font-weight: normal;
75 line-height: 1.3125em;
76 margin: 1.3125em 0 0 0;
77}
78
79/* alternate font-sizing */
80.smaller {
81 font-size: .7143em; /* equiv 10px */
82}
83.small {
84 font-size: .8571em; /* equiv 12px */
85}
86.big {
87 font-size: 1.1429em; /* equiv 16px */
88}
89.bigger {
90 font-size: 1.2857em; /* equiv 18px */
91}
92.biggest {
93 font-size: 1.4286em; /* equiv 20px */
94}
95
96/* soft reset */
97html,
98body,
99textarea,
100figure,
101label {
102 margin: 0;
103 padding: 0;
104}
105ul,
106ol {
107 padding-left: 2em;
108}
109code,
110pre,
111samp,
112kbd {
113 white-space: pre-wrap;
114 font-family: consolas, 'DejaVu Sans Mono', courier, monospace;
115 line-height: 1em;
116}
117code, kbd, mark {
118 border-radius: 2px;
119}
120em {
121 font-style: italic;
122}
123strong {
124 font-weight: bold;
125}
126kbd {
127 padding: 0 2px;
128 border: 1px solid #999;
129}
130code {
131 padding: 2px 4px;
132 background: rgba(0,0,0,.04);
133 color: #b11;
134}
135mark {
136 padding:2px 4px;
137 background: #ff0;
138}
139
140table { margin-bottom: 1.5em; }
141
142/* avoid top margins on first content element */
143p:first-child,
144ul:first-child,
145ol:first-child,
146dl:first-child,
147blockquote:first-child,
148pre:first-child,
149h1:first-child,
150h2:first-child,
151h3:first-child,
152h4:first-child,
153h5:first-child,
154h6:first-child {
155 margin-top: 0;
156}
157
158/* avoid margins on nested elements */
159li p,
160li ul,
161li ol {
162 margin-top: 0;
163 margin-bottom: 0;
164}
165
166/* max values */
167img, table, td, blockquote, code, pre, textarea, input, video {
168 max-width: 100%;
169}
170
171/* you shall not pass */
172div, textarea, table, td, th, code, pre, samp {
173 word-wrap: break-word;
174 -webkit-hyphens: auto;
175 -moz-hyphens: auto;
176 -ms-hyphens: auto;
177 -o-hyphens: auto;
178 hyphens: auto;
179}
180
181/* pictures */
182img {
183 height: auto;
184 vertical-align: middle;
185}
186/* Gmap3 max-width bug fix on images */
187#map_canvas img,
188.gmnoprint img {max-width: none;}
189
190a img { border: 0; }
191
192/* scripts */
193body > script {display: none !important;}
194
195/* skip-links */
196.skip-links {
197 position: absolute;
198}
199.skip-links a {
200 position: absolute;
201 left: -7000px;
202 padding: 0.5em;
203 background: #000;
204 color:#fff;
205 text-decoration: none;
206}
207.skip-links a:focus {
208 position: static;
209}
210
211/* ----------------------------- */
212/* ==layout and modules */
213/* ----------------------------- */
214
215/* switching box model for all elements */
216* {
217 -webkit-box-sizing: border-box;
218 -moz-box-sizing: border-box;
219 box-sizing: border-box;
220}
221
222/* float layout */
223/* ----------- */
224
225/* module, contains floats (.item is the same) */
226.mod, .item {
227 overflow: hidden;
228}
229
230/* table layout */
231/* ------------------ */
232.row {
233 display: table;
234 table-layout: fixed;
235 width: 100%;
236}
237.row > *,
238.col {
239 display: table-cell;
240 vertical-align: top;
241}
242
243/* blocks that needs to be placed under floats */
244.clear,
245.line,
246.row {
247 clear: both;
248}
249
250/* blocks that must contain floats */
251.clearfix:after,
252.line:after,
253.mod:after {
254 content: "";
255 display: table;
256 clear: both;
257}
258
259/* alignments (blocks and inline) */
260/* ------------------------------ */
261
262/* left elements */
263.left {
264 float: left;
265}
266img.left {
267 margin-right: 1em;
268}
269
270/* right elements */
271.right {
272 float: right;
273}
274img.right {
275 margin-left: 1em;
276}
277
278img.left, img.right {
279 margin-bottom: 5px;
280}
281
282.center { margin-left: auto; margin-right: auto; }
283.txtleft { text-align: left; }
284.txtright { text-align: right; }
285.txtcenter { text-align: center; }
286
287/* just inline-block */
288.inbl {
289 display: inline-block;
290 vertical-align: top;
291 margin-right: -.25em;
292}
293
294/* blocks widths (percentage and pixels) */
295.w10 { width: 10%; }
296.w20 { width: 20%; }
297.w25 { width: 25%; }
298.w30 { width: 30%; }
299.w33 { width: 33.333%; }
300.w40 { width: 40%; }
301.w50 { width: 50%; }
302.w60 { width: 60%; }
303.w66 { width: 66.666%; }
304.w70 { width: 70%; }
305.w75 { width: 75%; }
306.w80 { width: 80%; }
307.w90 { width: 90%; }
308.w100 { width: 100%; }
309
310.w50p { width: 50px; }
311.w100p { width: 100px; }
312.w150p { width: 150px; }
313.w200p { width: 200px; }
314.w300p { width: 300px; }
315.w400p { width: 400px; }
316.w500p { width: 500px; }
317.w600p { width: 600px; }
318.w700p { width: 700px; }
319.w800p { width: 800px; }
320.w960p { width: 960px; }
321.mw960p { max-width: 960px; }
322
323/* spacing helpers
324p,m = padding,margin
325a,t,r,b,l = all,top,right,bottom,left
326s,m,l,n = small(10px),medium(20px),large(30px),none(0)
327source https://github.com/stubbornella/oocss/blob/master/core/spacing/space.css
328*/
329.m-reset, .ma0 { margin: 0 !important; }
330.p-reset, .pa0 { padding: 0 !important; }
331.ma1, .mas { margin: 10px !important; }
332.ma2, .mam { margin: 20px !important; }
333.ma3, .mal { margin: 30px !important; }
334.pa1, .pas { padding: 10px; }
335.pa2, .pam { padding: 20px; }
336.pa3, .pal { padding: 30px; }
337
338.mt0, .mtn { margin-top: 0 !important; }
339.mt1, .mts { margin-top: 10px !important; }
340.mt2, .mtm { margin-top: 20px !important; }
341.mt3, .mtl { margin-top: 30px !important; }
342.mr0, .mrn { margin-right: 0; }
343.mr1, .mrs { margin-right: 10px; }
344.mr2, .mrm { margin-right: 20px; }
345.mr3, .mrl { margin-right: 30px; }
346.mb0, .mbn { margin-bottom: 0 !important; }
347.mb1, .mbs { margin-bottom: 10px !important; }
348.mb2, .mbm { margin-bottom: 20px !important; }
349.mb3, .mbl { margin-bottom: 30px !important; }
350.ml0, .mln { margin-left: 0; }
351.ml1, .mls { margin-left: 10px; }
352.ml2, .mlm { margin-left: 20px; }
353.ml3, .mll { margin-left: 30px; }
354
355.pt0, .ptn { padding-top: 0; }
356.pt1, .pts { padding-top: 10px; }
357.pt2, .ptm { padding-top: 20px; }
358.pt3, .ptl { padding-top: 30px; }
359.pr0, .prn { padding-right: 0; }
360.pr1, .prs { padding-right: 10px; }
361.pr2, .prm { padding-right: 20px; }
362.pr3, .prl { padding-right: 30px; }
363.pb0, .pbn { padding-bottom: 0; }
364.pb1, .pbs { padding-bottom: 10px; }
365.pb2, .pbm { padding-bottom: 20px; }
366.pb3, .pbl { padding-bottom: 30px; }
367.pl0, .pln { padding-left: 0; }
368.pl1, .pls { padding-left: 10px; }
369.pl2, .plm { padding-left: 20px; }
370.pl3, .pll { padding-left: 30px; }
371
372/* hiding content */
373.visually-hidden {
374 position: absolute;
375 left: -7000px;
376 overflow: hidden;
377}
378[dir=rtl] .visually-hidden {
379 left: auto;
380 right: -7000px;
381}
382
383.desktop-hidden { display: none; } /* hidden on desktop */
384
385/* ----------------------------- */
386/* ==header */
387/* ----------------------------- */
388
389/* ----------------------------- */
390/* ==sidebar */
391/* ----------------------------- */
392
393/* ----------------------------- */
394/* ==footer */
395/* ----------------------------- */
396
397/* ----------------------------- */
398/* ==forms */
399/* ----------------------------- */
400form,
401fieldset {
402 border: none;
403}
404input,
405button,
406select,
407label,
408.btn {
409 vertical-align: middle; /* @bugfix alignment */
410 font-family: inherit;
411}
412textarea {
413 resize: vertical;
414 font-family: inherit;
415}
416
417/* ----------------------------- */
418/* ==main */
419/* ----------------------------- */
420
421/* ----------------------------- */
422/* ==iefix */
423/* ----------------------------- */
424
425/* haslayout for IE6/IE7 */
426.ie67 .clearfix,
427.ie67 .line,
428.ie67 .mod,
429.ie67 .row,
430.ie67 .col {
431 zoom: 1;
432}
433
434/* inline-block and table-cell for IE6/IE7 */
435/* warning: .col needs width on IE6/IE7 */
436.ie67 .btn,
437.ie67 .col,
438.ie67 .inbl {
439 display: inline;
440 zoom: 1;
441}
442.ie8 img {
443 width: auto; /* @bugfix for IE8 */
444}
445
446/* Active box-sizing for IE6/IE7 */
447/* @source https://github.com/Schepp/box-sizing-polyfill */
448/*
449.ie67 * {
450 behavior: url(/js/boxsizing.htc);
451}
452*/
453
454/* ----------------------------- */
455/* ==print */
456/* ----------------------------- */
457
458/* quick print reset */
459@media print {
460 p,
461 blockquote {
462 orphans: 2;
463 widows: 2;
464 }
465 blockquote,
466 ul,
467 ol {
468 page-break-inside: avoid;
469 }
470 h1,
471 h2,
472 h3,
473 caption {
474 page-break-after: avoid;
475 }
476}
477
478/* orientation iOS font-size fix */
479@media (orientation: landscape) and (max-device-width: 768px) {
480 html,
481 body {
482 -webkit-text-size-adjust: 100%;
483 }
484}
485/* ----------------------------- */
486/* ==grids */
487/* ----------------------------- */
488
489/* equal grids with 2% gutter */
490[class*=grid] > * {float: left; } /* direct childrens are floating */
491[class*=grid] > * + * { margin-left: 2%; } /* here's the gutter */
492.grid2 > * { width: 49%; }
493.grid3 > * { width: 32%; }
494.grid4 > * { width: 23.5%; }
495.grid5 > * { width: 18.4%; }
496.grid6 > * { width: 15%; }
497
498/* unequal grids (1-2, 2-1, 1-3 and 3-1) */
499.grid2-1 > *:first-child,
500.grid1-2 > * + * { width: 66%; }
501.grid1-2 > *:first-child,
502.grid2-1 > * + * { width: 32%; }
503.grid1-3 > *:first-child,
504.grid3-1 > * + * { width: 23.5%; }
505.grid3-1 > *:first-child,
506.grid1-3 > * + * { width: 74.5%; }
507
508
509/* ----------------------------- */
510/* ==tables */
511/* ----------------------------- */
512
513table,
514.table {
515 max-width : 100%;
516 table-layout: fixed;
517 border-collapse: collapse;
518 vertical-align: top;
519}
520table {
521 width: 100%;
522}
523.table {
524 display: table;
525}
526caption {
527 padding: 10px;
528 color: #555;
529 font-style: italic;
530}
531table {
532 border: 1px solid #ccc;
533}
534tr > * + * {
535 border-left: 1px solid #ccc;
536}
537th,
538td {
539 padding: .3em .8em;
540 text-align: left;
541 border-bottom: 1px solid #ccc;
542}
543td {
544 color: #333;
545}
546
547/* alternate tables */
548.alternate { border: 0; }
549.alternate tbody {
550 border: 1px solid #ccc;
551}
552.alternate thead tr > * + * {
553 border-left: 0;
554}
555.alternate tbody tr > * + * {
556 border-left: 1px solid #ccc;
557}
558
559/* alternate-vert tables */
560.alternate-vert {
561 border: 0;
562 border-right: 1px solid #ccc;
563}
564.alternate-vert tr > :first-child {
565 border-bottom: 0;
566}
567.alternate-vert tr > * + * {
568 border-top: 1px solid #ccc;
569}
570
571/* striped tables */
572.striped tbody tr:nth-child(odd) {
573 background: #eee;
574 background: rgba(0, 0, 0, .05);
575}
576
577/* striped-vert tables */
578.striped-vert tr > :first-child {
579 background: #eee;
580 background: rgba(0, 0, 0, .05);
581}
582/* ----------------------------- */
583/* ==forms */
584/* ----------------------------- */
585
586/* thanks to HTML5boilerplate, github.com/nathansmith/formalize and www.sitepen.com */
587
588
589/* buttons */
590.btn { display: inline-block; }
591
592.btn.alternate {}
593.btn.highlight {}
594.login {}
595.logout {}
596.primary {}
597.warning {}
598.error {}
599.success {}
600
601/* forms items */
602label {
603 display: inline-block;
604 vertical-align: middle;
605 cursor: pointer;
606}
607legend {
608 border: 0;
609 white-space: normal;
610}
611button,
612input,
613select {
614 font-family: "Century Gothic", helvetica, arial, sans-serif;
615 font-size: 100%;
616 margin: 0;
617 vertical-align: middle;
618}
619textarea {
620 overflow: auto; /* Removes default vertical scrollbar on empty textarea in IE6/7/8/9 */
621 min-height: 5em;
622 font-size: 1.75em;
623 vertical-align: top;
624 resize: vertical;
625}
626button,
627input[type="button"],
628input[type="reset"],
629input[type="submit"] {
630 cursor: pointer;
631 -webkit-appearance: button; /* clickable input types in iOS */
632 *overflow: visible; /* Corrects inner spacing displayed oddly in IE7 */
633}
634input[type="checkbox"],
635input[type="radio"] {
636 padding: 0; /* Corrects excess space around these inputs in IE8/9 */
637 *width: 13px; *height: 13px; /* Corrects excess space around these inputs in IE7 */
638}
639input[type="search"] { -webkit-appearance: textfield; }
640
641/* if select styling bugs on WebKit */
642/* select { -webkit-appearance: none; } */
643
644/* 'x' appears on right of search input when text is entered. This removes it */
645input[type="search"]::-webkit-search-decoration,
646input[type="search"]::-webkit-search-cancel-button,
647input[type="search"]::-webkit-search-results-button,
648input[type="search"]::-webkit-search-results-decoration {
649 display: none;
650}
651::-webkit-input-placeholder { color: #777; }
652input:-moz-placeholder,
653textarea:-moz-placeholder { color: #777; }
654
655/* Removes inner padding and border in FF3+ */
656button::-moz-focus-inner,
657input[type='button']::-moz-focus-inner,
658input[type='reset']::-moz-focus-inner,
659input[type='submit']::-moz-focus-inner {
660 border: 0;
661 padding: 0;
662}
663
664/* ----------------------------- */
665/* ==icons and bullets */
666/* ----------------------------- */
667
668.icon {display: inline-block;}
669
670.icon:before,
671.icon > li:before,
672.icon.after:after,
673.icon.after > li:after {
674 content: "";
675 display: inline-block;
676 vertical-align: middle;
677 position: relative; top: -.1em;
678 margin: 0 0.3em 0 0;
679 font: 1.4em/1 sans-serif;
680 color: #000;
681 text-shadow: 1px 1px 0 rgba(0,0,0,.1);
682 speak: none;
683}
684
685@media (min-device-width: 768px) {
686 .icon:before,
687 .icon > li:before,
688 .icon.after:after,
689 .icon.after > li:after {
690 font: 1em/0.6 sans-serif;
691 -webkit-transform: rotateZ(0.05deg);
692 }
693}
694
695.icon.after:after,
696.icon.after > li:after {
697 margin: 0 0 0 8px;
698}
699
700ul.icon {display: block;}
701ul.icon > li {list-style: none;}
702ul.icon:before,
703ul.icon.after:after {content:""}
704.icon.after:before,
705.icon.after > li:before {content: "" !important}
706
707.icon-rate:before,
708.icon-rate > li:before,
709.icon-rate.after:after,
710.icon-rate.after > li:after {
711 content: "\2605";
712}
713.icon-unrate:before,
714.icon-unrate > li:before,
715.icon-unrate.after:after,
716.icon-unrate.after > li:after {
717 content: "\2606";
718}
719.icon-check:before,
720.icon-check > li:before,
721.icon-check.after:after,
722.icon-check.after > li:after {
723 content: "\2713";
724}
725.icon-uncheck:before,
726.icon-uncheck > li:before,
727.icon-uncheck.after:after,
728.icon-uncheck.after > li:after {
729 content: "\2717";
730}
731.icon-cloud:before,
732.icon-cloud > li:before,
733.icon-cloud.after:after,
734.icon-cloud.after > li:after {
735 content: "\2601";
736}
737.icon-dl:before,
738.icon-dl > li:before,
739.icon-dl.after:after,
740.icon-dl.after > li:after {
741 content: "\21E3";
742 font-weight: bold;
743}
744.icon-cross:before,
745.icon-cross > li:before,
746.icon-cross.after:after,
747.icon-cross.after > li:after {
748 content: "\2716";
749 font-weight: bold;
750}
751.icon-arrow1:before,
752.icon-arrow1 > li:before,
753.icon-arrow1.after:after,
754.icon-arrow1.after > li:after {
755 content: "\2192";
756 position: relative; top: -0.15em;
757}
758.icon-arrow2:before,
759.icon-arrow2 > li:before,
760.icon-arrow2.after:after,
761.icon-arrow2.after > li:after {
762 content: "\279E";
763}
764.icon-arrow3:before,
765.icon-arrow3 > li:before,
766.icon-arrow3.after:after,
767.icon-arrow3.after > li:after {
768 content: "\279A";
769}
770.icon-bracket1:before,
771.icon-bracket1 > li:before,
772.icon-bracket1.after:after,
773.icon-bracket1.after > li:after {
774 content: "\2039";
775 font-weight: bold;
776 font-size: 1.6em;
777 position: relative; top: -0.15em;
778}
779.icon-bracket2:before,
780.icon-bracket2 > li:before,
781.icon-bracket2.after:after,
782.icon-bracket2.after > li:after {
783 content: "\203A";
784 font-weight: bold;
785 font-size: 1.6em;
786 position: relative; top: -0.15em;
787}
788.icon-up:before,
789.icon-up > li:before,
790.icon-up.after:after,
791.icon-up.after > li:after {
792 content: "\25B2";
793}
794.icon-down:before,
795.icon-down > li:before,
796.icon-down.after:after,
797.icon-down.after > li:after {
798 content: "\25BC";
799}
800.icon-bull:before,
801.icon-bull > li:before,
802.icon-bull.after:after,
803.icon-bull.after > li:after {
804 content: "\2022";
805 font-size: 1.2em;
806 top: -0.05em;
807}
808.icon-bull2:before,
809.icon-bull2 > li:before,
810.icon-bull2.after:after,
811.icon-bull2.after > li:after {
812 content: "\25E6";
813 top: -0.05em;
814}
815.icon-bull3:before,
816.icon-bull3 > li:before,
817.icon-bull3.after:after,
818.icon-bull3.after > li:after {
819 content: "\2023";
820 font-size: 1.6em;
821 top: -0.05em;
822}
823.icon-nav:before,
824.icon-nav > li:before,
825.icon-nav.after:after,
826.icon-nav.after > li:after {
827 content: "\2261";
828 font-weight: bold;
829 font-size: 1.6em;
830}
831.icon-losange:before,
832.icon-losange > li:before,
833.icon-losange.after:after,
834.icon-losange.after > li:after {
835 content: "\25C6";
836}
837.icon-asteri:before,
838.icon-asteri > li:before,
839.icon-asteri.after:after,
840.icon-asteri.after > li:after {
841 content: "\2731";
842 font-weight: bold;
843}
844.icon-mail:before,
845.icon-mail > li:before,
846.icon-mail.after:after,
847.icon-mail.after > li:after {
848 content: "\2709";
849 font-size: 1.6em;
850 top: -.05em;
851}
852
853ol.styled {counter-reset: styled;}
854ol.styled > li {
855 list-style-type: none;
856 counter-increment: styled;
857 margin-bottom: .3em;
858}
859ol.styled > li:before {
860 content: counter(styled);
861 display: inline-block;
862 width: 1em; height: 1em;
863 line-height: 1;
864 padding: 2px;
865 margin-right: .4em;
866 vertical-align: middle;
867 background: rgba(0,0,0,.5);
868 border-radius: 50%;
869 font-size: .9em;
870 text-align: center;
871 text-indent: -0.1em;
872 color: white;
873}
874/* ----------------------------- */
875/* ==desktop and retina medias */
876/* ----------------------------- */
877
878@media (min-width: 641px) {
879/* here go rules for big resources and big screens like: background-images, font-faces, etc. */
880}
881@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi), (min-resolution: 2dppx) {
882/* Style adjustments for retina devices */
883}
884
885/* ---------------------------------- */
886/* ==Responsive large / medium / tiny */
887/* ---------------------------------- */
888
889@media (min-width: 1280px) {
890
891 /* layouts for large (l) screens */
892 .large-hidden,
893 .tablet-hidden { display: none !important; }
894 .large-visible { display: block !important; }
895 .large-no-float {float: none; }
896 .large-inbl {
897 display: inline-block;
898 float: none;
899 vertical-align: top;
900 }
901 .large-row {
902 display: table;
903 table-layout: fixed;
904 width: 100% !important;
905 }
906 .large-col {
907 display: table-cell;
908 vertical-align: top;
909 }
910
911 /* widths for large (l) screens */
912 .large-w25 { width: 25% !important; }
913 .large-w33 { width: 33.3333% !important; }
914 .large-w50 { width: 50% !important; }
915 .large-w66 { width: 66.6666% !important; }
916 .large-w75 { width: 75% !important; }
917 .large-w100 {
918 display: block !important;
919 float: none !important;
920 clear: none !important;
921 width: auto !important;
922 margin-left: 0 !important;
923 margin-right: 0 !important;
924 border: 0;
925 }
926
927 /* margins for large (l) screens */
928 .large-ma0,
929 .large-man { margin: 0 !important; }
930}
931
932@media (max-width: 768px) {
933
934 /* quick tablet reset */
935 .w60,
936 .w66,
937 .w70,
938 .w75,
939 .w80,
940 .w90,
941 .w100,
942 .w600p,
943 .w700p,
944 .w800p,
945 .w960p,
946 .mw960p,
947 .medium-wauto { width: auto; }
948
949 /* layouts for medium (m) screens */
950 .medium-hidden,
951 .tablet-hidden { display: none !important; }
952 .medium-visible { display: block !important; }
953 .medium-no-float {float: none; }
954 .medium-inbl {
955 display: inline-block;
956 float: none;
957 vertical-align: top;
958 }
959 .medium-row {
960 display: table;
961 table-layout: fixed;
962 width: 100% !important;
963 }
964 .medium-col {
965 display: table-cell;
966 vertical-align: top;
967 }
968
969 /* widths for medium (m) screens */
970 .medium-w25 { width: 25% !important; }
971 .medium-w33 { width: 33.3333% !important; }
972 .medium-w50 { width: 50% !important; }
973 .medium-w66 { width: 66.6666% !important; }
974 .medium-w75 { width: 75% !important; }
975 .medium-w100 {
976 display: block !important;
977 float: none !important;
978 clear: none !important;
979 width: auto !important;
980 margin-left: 0 !important;
981 margin-right: 0 !important;
982 border: 0;
983 }
984 /* margins for medium (m) screens */
985 .medium-ma0,
986 .medium-man { margin: 0 !important; }
987
988 /* Responsive grids */
989 .grid4 > * {width: 49% !important; }
990 .grid4 > :first-child + * + * {margin-left: 0 !important;}
991 .grid6 > * {width: 32% !important; }
992 .grid6 > :first-child + * + * + * {margin-left: 0 !important;}
993}
994
995@media (max-width: 640px) {
996
997 /* quick smartphone reset */
998 .mod,
999 .item,
1000 .col,
1001 fieldset {
1002 display: block !important;
1003 float: none !important;
1004 clear: none !important;
1005 width: auto !important;
1006 margin-left: 0 !important;
1007 margin-right: 0 !important;
1008 border: 0;
1009 }
1010 .w30,
1011 .w33,
1012 .w40,
1013 .w50,
1014 .w300p,
1015 .w400p,
1016 .w500p {
1017 width: auto;
1018 }
1019 .row {
1020 display: block !important;
1021 width: 100% !important;
1022 }
1023
1024 /* layouts for tiny (t) screens */
1025 .tiny-hidden,
1026 .phone-hidden { display: none !important; }
1027 .tiny-visible { display: block !important; }
1028 .tiny-no-float {float: none;}
1029 .tiny-inbl {
1030 display: inline-block;
1031 float: none;
1032 vertical-align: top;
1033 }
1034 .tiny-row {
1035 display: table;
1036 table-layout: fixed;
1037 width: 100% !important;
1038 }
1039 .tiny-col {
1040 display: table-cell;
1041 vertical-align: top;
1042 }
1043 th,
1044 td {
1045 display: block !important;
1046 width: auto !important;
1047 text-align: left !important;
1048 }
1049 thead { display: none; }
1050
1051 /* widths for tiny (t) screens */
1052 .tiny-w25 { width: 25% !important; }
1053 .tiny-w33 { width: 33.3333% !important; }
1054 .tiny-w50 { width: 50% !important; }
1055 .tiny-w66 { width: 66.6666% !important; }
1056 .tiny-w75 { width: 75% !important; }
1057 .tiny-w100 {
1058 display: block !important;
1059 float: none !important;
1060 clear: none !important;
1061 width: auto !important;
1062 margin-left: 0 !important;
1063 margin-right: 0 !important;
1064 border: 0;
1065 }
1066 /* margins for tiny (t) screens */
1067 .tiny-ma0,
1068 .tiny-man { margin: 0 !important; }
1069}
1070
diff --git a/css/reset.css b/css/reset.css
new file mode 100755
index 00000000..530ddc3d
--- /dev/null
+++ b/css/reset.css
@@ -0,0 +1,35 @@
1html, body, div, span, object, iframe,
2h1, h2, h3, h4, h5, h6, p, blockquote, pre,
3a, abbr, acronym, address, code,
4del, dfn, em, img, q, dl, dt, dd, ol, ul, li,
5fieldset, form, label, legend,
6table, caption, tbody, tfoot, thead, tr, th, td {
7 margin: 0;
8 padding: 0;
9 border: 0;
10 font-weight: inherit;
11 font-style: inherit;
12 font-size: 100%;
13 font-family: inherit;
14 vertical-align: baseline;
15}
16
17
18/* Tables still need 'cellspacing="0"' in the markup. */
19table { border-collapse: separate; border-spacing: 0; }
20caption, th, td { text-align: left; font-weight: normal; }
21table, td, th { vertical-align: middle; }
22
23/* Remove possible quote marks (") from <q>, <blockquote>. */
24blockquote:before, blockquote:after, q:before, q:after { content: ""; }
25blockquote, q { quotes: "" ""; }
26
27/* Remove annoying border on linked images. */
28a img { border: none; }
29
30
31body {
32
33 margin: 10px;
34}
35
diff --git a/css/style.css b/css/style.css
new file mode 100644
index 00000000..41a61780
--- /dev/null
+++ b/css/style.css
@@ -0,0 +1,35 @@
1body {
2 color: #222222;
3 font: 20px/1.3em Palatino,Georgia,serif;
4 background-color: #e6e6e6;
5}
6
7a, a:hover, a:visited {
8 color: #000;
9}
10header {
11 text-align: center;
12}
13
14#main {
15 margin: 0 auto;
16}
17
18#main ul#links {
19 padding: 0;
20 list-style-type: none;
21 text-align: center;
22}
23
24#main ul#links li {
25 display: inline;
26 padding: 15px;
27}
28
29#main a.tool {
30 text-decoration: none;
31}
32
33footer {
34 text-align: right;
35} \ No newline at end of file
diff --git a/css/typography.css b/css/typography.css
new file mode 100755
index 00000000..e41db096
--- /dev/null
+++ b/css/typography.css
@@ -0,0 +1,85 @@
1
2body {
3 font:1em/1.625em "lucida grande","lucida sans unicode", sans-serif; background-color:#FFFEF0;
4 font-size-adjust:none;
5 font-style:normal;
6 font-variant:normal;
7 font-weight:normal;
8 padding: 15px;
9 margin: 15px auto;
10}
11
12article {
13 border: 3px solid grey;
14 max-width:700px;
15 margin: 15px auto;
16 padding: 15px;
17}
18
19footer {
20 border: 1px solid black;
21 padding: 15px;
22 margin: 15px auto;
23}
24
25p { padding:0 0 0.8125em 0; color:#111; font-weight:300;}
26
27p + p { text-indent:1.625em;}
28
29img { display: block; margin: 0.5em 0.8125em 0.8125em 0; padding: 0; }
30
31p > img { display: inline-block; margin: 0; }
32
33h1,h2{ font-weight:normal; color: #333; font-family:Georgia, serif; }
34h3,h4,h5,h6 { font-weight: normal; color: #333; font-family:Georgia, serif; }
35
36
37h1 { font-size: 2.125em; margin-bottom: 0.765em; line-height: 1.5em;}
38h2 { font-size: 1.9em; margin-bottom: 0.855em; }
39h3 { font-size: 1.7em; margin-bottom: 0.956em; }
40h4 { font-size: 1.4em; margin-bottom: 1.161em; }
41h5,h6 { font-size: 1.313em; margin-bottom: 1.238em; }
42
43
44
45ul{list-style-position:outside;}
46li ul,
47li ol { margin:0 1.625em; }
48ul, ol { margin: 0 0 1.625em 0; }
49
50
51dl { margin: 0 0 1.625em 0; }
52dl dt { font-weight: bold; }
53dl dd { margin-left: 1.625em; }
54
55a { color:#005AF2; text-decoration:none; }
56a:hover { text-decoration: underline; }
57
58
59table { margin-bottom:1.625em; border-collapse: collapse; }
60th { font-weight:bold; }
61tr,th,td { margin:0; padding:0 1.625em 0 1em; height:26px; }
62tfoot { font-style: italic; }
63caption { text-align:center; font-family:Georgia, serif; }
64
65
66abbr, acronym { border-bottom:1px dotted #000; }
67address { margin-top:1.625em; font-style: italic; }
68del {color:#000;}
69
70
71blockquote { padding:1em 1em 1.625em 1em; font-family:georgia,serif;font-style: italic; }
72blockquote:before { content:"\201C";font-size:3em;margin-left:-.625em; font-family:georgia,serif;color:#aaa;line-height:0;}/* From Tripoli */
73blockquote > p {padding:0; margin:0; }
74
75strong { font-weight: bold; }
76em, dfn { font-style: italic; }
77dfn { font-weight: bold; }
78pre, code { margin: 1.625em 0; white-space: pre; }
79pre, code, tt { font: 1em monospace; line-height: 1.5; }
80tt { display: block; margin: 1.625em 0; }
81hr { margin-bottom:1.625em; }
82
83.oldbook { font-family:"Warnock Pro","Goudy Old Style","Book Antiqua","Palatino",Georgia,serif; }
84.note { font-family:Georgia, "Times New Roman", Times, serif; font-style:italic; font-size:0.9em; margin:0.1em; color:#333; }
85.mono { font-family:"Courier New", Courier, monospace; }
diff --git a/inc/Encoding.php b/inc/Encoding.php
new file mode 100755
index 00000000..ac107af9
--- /dev/null
+++ b/inc/Encoding.php
@@ -0,0 +1,262 @@
1<?php
2/**
3 * @author "Sebastián Grignoli" <grignoli@framework2.com.ar>
4 * @package Encoding
5 * @version 1.1
6 * @link http://www.framework2.com.ar/dzone/forceUTF8-es/
7 * @example http://www.framework2.com.ar/dzone/forceUTF8-es/
8 */
9
10class Encoding {
11
12 protected static $win1252ToUtf8 = array(
13 128 => "\xe2\x82\xac",
14
15 130 => "\xe2\x80\x9a",
16 131 => "\xc6\x92",
17 132 => "\xe2\x80\x9e",
18 133 => "\xe2\x80\xa6",
19 134 => "\xe2\x80\xa0",
20 135 => "\xe2\x80\xa1",
21 136 => "\xcb\x86",
22 137 => "\xe2\x80\xb0",
23 138 => "\xc5\xa0",
24 139 => "\xe2\x80\xb9",
25 140 => "\xc5\x92",
26
27 142 => "\xc5\xbd",
28
29
30 145 => "\xe2\x80\x98",
31 146 => "\xe2\x80\x99",
32 147 => "\xe2\x80\x9c",
33 148 => "\xe2\x80\x9d",
34 149 => "\xe2\x80\xa2",
35 150 => "\xe2\x80\x93",
36 151 => "\xe2\x80\x94",
37 152 => "\xcb\x9c",
38 153 => "\xe2\x84\xa2",
39 154 => "\xc5\xa1",
40 155 => "\xe2\x80\xba",
41 156 => "\xc5\x93",
42
43 158 => "\xc5\xbe",
44 159 => "\xc5\xb8"
45 );
46
47 protected static $brokenUtf8ToUtf8 = array(
48 "\xc2\x80" => "\xe2\x82\xac",
49
50 "\xc2\x82" => "\xe2\x80\x9a",
51 "\xc2\x83" => "\xc6\x92",
52 "\xc2\x84" => "\xe2\x80\x9e",
53 "\xc2\x85" => "\xe2\x80\xa6",
54 "\xc2\x86" => "\xe2\x80\xa0",
55 "\xc2\x87" => "\xe2\x80\xa1",
56 "\xc2\x88" => "\xcb\x86",
57 "\xc2\x89" => "\xe2\x80\xb0",
58 "\xc2\x8a" => "\xc5\xa0",
59 "\xc2\x8b" => "\xe2\x80\xb9",
60 "\xc2\x8c" => "\xc5\x92",
61
62 "\xc2\x8e" => "\xc5\xbd",
63
64
65 "\xc2\x91" => "\xe2\x80\x98",
66 "\xc2\x92" => "\xe2\x80\x99",
67 "\xc2\x93" => "\xe2\x80\x9c",
68 "\xc2\x94" => "\xe2\x80\x9d",
69 "\xc2\x95" => "\xe2\x80\xa2",
70 "\xc2\x96" => "\xe2\x80\x93",
71 "\xc2\x97" => "\xe2\x80\x94",
72 "\xc2\x98" => "\xcb\x9c",
73 "\xc2\x99" => "\xe2\x84\xa2",
74 "\xc2\x9a" => "\xc5\xa1",
75 "\xc2\x9b" => "\xe2\x80\xba",
76 "\xc2\x9c" => "\xc5\x93",
77
78 "\xc2\x9e" => "\xc5\xbe",
79 "\xc2\x9f" => "\xc5\xb8"
80 );
81
82 protected static $utf8ToWin1252 = array(
83 "\xe2\x82\xac" => "\x80",
84
85 "\xe2\x80\x9a" => "\x82",
86 "\xc6\x92" => "\x83",
87 "\xe2\x80\x9e" => "\x84",
88 "\xe2\x80\xa6" => "\x85",
89 "\xe2\x80\xa0" => "\x86",
90 "\xe2\x80\xa1" => "\x87",
91 "\xcb\x86" => "\x88",
92 "\xe2\x80\xb0" => "\x89",
93 "\xc5\xa0" => "\x8a",
94 "\xe2\x80\xb9" => "\x8b",
95 "\xc5\x92" => "\x8c",
96
97 "\xc5\xbd" => "\x8e",
98
99
100 "\xe2\x80\x98" => "\x91",
101 "\xe2\x80\x99" => "\x92",
102 "\xe2\x80\x9c" => "\x93",
103 "\xe2\x80\x9d" => "\x94",
104 "\xe2\x80\xa2" => "\x95",
105 "\xe2\x80\x93" => "\x96",
106 "\xe2\x80\x94" => "\x97",
107 "\xcb\x9c" => "\x98",
108 "\xe2\x84\xa2" => "\x99",
109 "\xc5\xa1" => "\x9a",
110 "\xe2\x80\xba" => "\x9b",
111 "\xc5\x93" => "\x9c",
112
113 "\xc5\xbe" => "\x9e",
114 "\xc5\xb8" => "\x9f"
115 );
116
117 static function toUTF8($text){
118 /**
119 * Function Encoding::toUTF8
120 *
121 * This function leaves UTF8 characters alone, while converting almost all non-UTF8 to UTF8.
122 *
123 * It assumes that the encoding of the original string is either Windows-1252 or ISO 8859-1.
124 *
125 * It may fail to convert characters to UTF-8 if they fall into one of these scenarios:
126 *
127 * 1) when any of these characters: ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß
128 * are followed by any of these: ("group B")
129 * ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶•¸¹º»¼½¾¿
130 * For example: %ABREPRESENT%C9%BB. «REPRESENTÉ»
131 * The "«" (%AB) character will be converted, but the "É" followed by "»" (%C9%BB)
132 * is also a valid unicode character, and will be left unchanged.
133 *
134 * 2) when any of these: àáâãäåæçèéêëìíîï are followed by TWO chars from group B,
135 * 3) when any of these: ðñòó are followed by THREE chars from group B.
136 *
137 * @name toUTF8
138 * @param string $text Any string.
139 * @return string The same string, UTF8 encoded
140 *
141 */
142
143 if(is_array($text))
144 {
145 foreach($text as $k => $v)
146 {
147 $text[$k] = self::toUTF8($v);
148 }
149 return $text;
150 } elseif(is_string($text)) {
151
152 $max = strlen($text);
153 $buf = "";
154 for($i = 0; $i < $max; $i++){
155 $c1 = $text{$i};
156 if($c1>="\xc0"){ //Should be converted to UTF8, if it's not UTF8 already
157 $c2 = $i+1 >= $max? "\x00" : $text{$i+1};
158 $c3 = $i+2 >= $max? "\x00" : $text{$i+2};
159 $c4 = $i+3 >= $max? "\x00" : $text{$i+3};
160 if($c1 >= "\xc0" & $c1 <= "\xdf"){ //looks like 2 bytes UTF8
161 if($c2 >= "\x80" && $c2 <= "\xbf"){ //yeah, almost sure it's UTF8 already
162 $buf .= $c1 . $c2;
163 $i++;
164 } else { //not valid UTF8. Convert it.
165 $cc1 = (chr(ord($c1) / 64) | "\xc0");
166 $cc2 = ($c1 & "\x3f") | "\x80";
167 $buf .= $cc1 . $cc2;
168 }
169 } elseif($c1 >= "\xe0" & $c1 <= "\xef"){ //looks like 3 bytes UTF8
170 if($c2 >= "\x80" && $c2 <= "\xbf" && $c3 >= "\x80" && $c3 <= "\xbf"){ //yeah, almost sure it's UTF8 already
171 $buf .= $c1 . $c2 . $c3;
172 $i = $i + 2;
173 } else { //not valid UTF8. Convert it.
174 $cc1 = (chr(ord($c1) / 64) | "\xc0");
175 $cc2 = ($c1 & "\x3f") | "\x80";
176 $buf .= $cc1 . $cc2;
177 }
178 } elseif($c1 >= "\xf0" & $c1 <= "\xf7"){ //looks like 4 bytes UTF8
179 if($c2 >= "\x80" && $c2 <= "\xbf" && $c3 >= "\x80" && $c3 <= "\xbf" && $c4 >= "\x80" && $c4 <= "\xbf"){ //yeah, almost sure it's UTF8 already
180 $buf .= $c1 . $c2 . $c3;
181 $i = $i + 2;
182 } else { //not valid UTF8. Convert it.
183 $cc1 = (chr(ord($c1) / 64) | "\xc0");
184 $cc2 = ($c1 & "\x3f") | "\x80";
185 $buf .= $cc1 . $cc2;
186 }
187 } else { //doesn't look like UTF8, but should be converted
188 $cc1 = (chr(ord($c1) / 64) | "\xc0");
189 $cc2 = (($c1 & "\x3f") | "\x80");
190 $buf .= $cc1 . $cc2;
191 }
192 } elseif(($c1 & "\xc0") == "\x80"){ // needs conversion
193 if(isset(self::$win1252ToUtf8[ord($c1)])) { //found in Windows-1252 special cases
194 $buf .= self::$win1252ToUtf8[ord($c1)];
195 } else {
196 $cc1 = (chr(ord($c1) / 64) | "\xc0");
197 $cc2 = (($c1 & "\x3f") | "\x80");
198 $buf .= $cc1 . $cc2;
199 }
200 } else { // it doesn't need convesion
201 $buf .= $c1;
202 }
203 }
204 return $buf;
205 } else {
206 return $text;
207 }
208 }
209
210 static function toWin1252($text) {
211 if(is_array($text)) {
212 foreach($text as $k => $v) {
213 $text[$k] = self::toWin1252($v);
214 }
215 return $text;
216 } elseif(is_string($text)) {
217 return utf8_decode(str_replace(array_keys(self::$utf8ToWin1252), array_values(self::$utf8ToWin1252), self::toUTF8($text)));
218 } else {
219 return $text;
220 }
221 }
222
223 static function toISO8859($text) {
224 return self::toWin1252($text);
225 }
226
227 static function toLatin1($text) {
228 return self::toWin1252($text);
229 }
230
231 static function fixUTF8($text){
232 if(is_array($text)) {
233 foreach($text as $k => $v) {
234 $text[$k] = self::fixUTF8($v);
235 }
236 return $text;
237 }
238
239 $last = "";
240 while($last <> $text){
241 $last = $text;
242 $text = self::toUTF8(utf8_decode(str_replace(array_keys(self::$utf8ToWin1252), array_values(self::$utf8ToWin1252), $text)));
243 }
244 $text = self::toUTF8(utf8_decode(str_replace(array_keys(self::$utf8ToWin1252), array_values(self::$utf8ToWin1252), $text)));
245 return $text;
246 }
247
248 static function UTF8FixWin1252Chars($text){
249 // If you received an UTF-8 string that was converted from Windows-1252 as it was ISO8859-1
250 // (ignoring Windows-1252 chars from 80 to 9F) use this function to fix it.
251 // See: http://en.wikipedia.org/wiki/Windows-1252
252
253 return str_replace(array_keys(self::$brokenUtf8ToUtf8), array_values(self::$brokenUtf8ToUtf8), $text);
254 }
255
256 static function removeBOM($str=""){
257 if(substr($str, 0,3) == pack("CCC",0xef,0xbb,0xbf)) {
258 $str=substr($str, 3);
259 }
260 return $str;
261 }
262} \ No newline at end of file
diff --git a/inc/JSLikeHTMLElement.php b/inc/JSLikeHTMLElement.php
new file mode 100755
index 00000000..0557205f
--- /dev/null
+++ b/inc/JSLikeHTMLElement.php
@@ -0,0 +1,110 @@
1<?php
2/**
3* JavaScript-like HTML DOM Element
4*
5* This class extends PHP's DOMElement to allow
6* users to get and set the innerHTML property of
7* HTML elements in the same way it's done in
8* JavaScript.
9*
10* Example usage:
11* @code
12* require_once 'JSLikeHTMLElement.php';
13* header('Content-Type: text/plain');
14* $doc = new DOMDocument();
15* $doc->registerNodeClass('DOMElement', 'JSLikeHTMLElement');
16* $doc->loadHTML('<div><p>Para 1</p><p>Para 2</p></div>');
17* $elem = $doc->getElementsByTagName('div')->item(0);
18*
19* // print innerHTML
20* echo $elem->innerHTML; // prints '<p>Para 1</p><p>Para 2</p>'
21* echo "\n\n";
22*
23* // set innerHTML
24* $elem->innerHTML = '<a href="http://fivefilters.org">FiveFilters.org</a>';
25* echo $elem->innerHTML; // prints '<a href="http://fivefilters.org">FiveFilters.org</a>'
26* echo "\n\n";
27*
28* // print document (with our changes)
29* echo $doc->saveXML();
30* @endcode
31*
32* @author Keyvan Minoukadeh - http://www.keyvan.net - keyvan@keyvan.net
33* @see http://fivefilters.org (the project this was written for)
34*/
35class JSLikeHTMLElement extends DOMElement
36{
37 /**
38 * Used for setting innerHTML like it's done in JavaScript:
39 * @code
40 * $div->innerHTML = '<h2>Chapter 2</h2><p>The story begins...</p>';
41 * @endcode
42 */
43 public function __set($name, $value) {
44 if ($name == 'innerHTML') {
45 // first, empty the element
46 for ($x=$this->childNodes->length-1; $x>=0; $x--) {
47 $this->removeChild($this->childNodes->item($x));
48 }
49 // $value holds our new inner HTML
50 if ($value != '') {
51 $f = $this->ownerDocument->createDocumentFragment();
52 // appendXML() expects well-formed markup (XHTML)
53 $result = @$f->appendXML($value); // @ to suppress PHP warnings
54 if ($result) {
55 if ($f->hasChildNodes()) $this->appendChild($f);
56 } else {
57 // $value is probably ill-formed
58 $f = new DOMDocument();
59 $value = mb_convert_encoding($value, 'HTML-ENTITIES', 'UTF-8');
60 // Using <htmlfragment> will generate a warning, but so will bad HTML
61 // (and by this point, bad HTML is what we've got).
62 // We use it (and suppress the warning) because an HTML fragment will
63 // be wrapped around <html><body> tags which we don't really want to keep.
64 // Note: despite the warning, if loadHTML succeeds it will return true.
65 $result = @$f->loadHTML('<htmlfragment>'.$value.'</htmlfragment>');
66 if ($result) {
67 $import = $f->getElementsByTagName('htmlfragment')->item(0);
68 foreach ($import->childNodes as $child) {
69 $importedNode = $this->ownerDocument->importNode($child, true);
70 $this->appendChild($importedNode);
71 }
72 } else {
73 // oh well, we tried, we really did. :(
74 // this element is now empty
75 }
76 }
77 }
78 } else {
79 $trace = debug_backtrace();
80 trigger_error('Undefined property via __set(): '.$name.' in '.$trace[0]['file'].' on line '.$trace[0]['line'], E_USER_NOTICE);
81 }
82 }
83
84 /**
85 * Used for getting innerHTML like it's done in JavaScript:
86 * @code
87 * $string = $div->innerHTML;
88 * @endcode
89 */
90 public function __get($name)
91 {
92 if ($name == 'innerHTML') {
93 $inner = '';
94 foreach ($this->childNodes as $child) {
95 $inner .= $this->ownerDocument->saveXML($child);
96 }
97 return $inner;
98 }
99
100 $trace = debug_backtrace();
101 trigger_error('Undefined property via __get(): '.$name.' in '.$trace[0]['file'].' on line '.$trace[0]['line'], E_USER_NOTICE);
102 return null;
103 }
104
105 public function __toString()
106 {
107 return '['.$this->tagName.']';
108 }
109}
110?> \ No newline at end of file
diff --git a/inc/Readability.php b/inc/Readability.php
new file mode 100755
index 00000000..52a37d50
--- /dev/null
+++ b/inc/Readability.php
@@ -0,0 +1,1103 @@
1<?php
2/**
3* Arc90's Readability ported to PHP for FiveFilters.org
4* Based on readability.js version 1.7.1 (without multi-page support)
5* ------------------------------------------------------
6* Original URL: http://lab.arc90.com/experiments/readability/js/readability.js
7* Arc90's project URL: http://lab.arc90.com/experiments/readability/
8* JS Source: http://code.google.com/p/arc90labs-readability
9* Ported by: Keyvan Minoukadeh, http://www.keyvan.net
10* More information: http://fivefilters.org/content-only/
11* License: Apache License, Version 2.0
12* Requires: PHP5
13* Date: 2011-07-22
14*
15* Differences between the PHP port and the original
16* ------------------------------------------------------
17* Arc90's Readability is designed to run in the browser. It works on the DOM
18* tree (the parsed HTML) after the page's CSS styles have been applied and
19* Javascript code executed. This PHP port does not run inside a browser.
20* We use PHP's ability to parse HTML to build our DOM tree, but we cannot
21* rely on CSS or Javascript support. As such, the results will not always
22* match Arc90's Readability. (For example, if a web page contains CSS style
23* rules or Javascript code which hide certain HTML elements from display,
24* Arc90's Readability will dismiss those from consideration but our PHP port,
25* unable to understand CSS or Javascript, will not know any better.)
26*
27* Another significant difference is that the aim of Arc90's Readability is
28* to re-present the main content block of a given web page so users can
29* read it more easily in their browsers. Correct identification, clean up,
30* and separation of the content block is only a part of this process.
31* This PHP port is only concerned with this part, it does not include code
32* that relates to presentation in the browser - Arc90 already do
33* that extremely well, and for PDF output there's FiveFilters.org's
34* PDF Newspaper: http://fivefilters.org/pdf-newspaper/.
35*
36* Finally, this class contains methods that might be useful for developers
37* working on HTML document fragments. So without deviating too much from
38* the original code (which I don't want to do because it makes debugging
39* and updating more difficult), I've tried to make it a little more
40* developer friendly. You should be able to use the methods here on
41* existing DOMElement objects without passing an entire HTML document to
42* be parsed.
43*/
44
45// This class allows us to do JavaScript like assignements to innerHTML
46require_once(dirname(__FILE__).'/JSLikeHTMLElement.php');
47
48// Alternative usage (for testing only!)
49// uncomment the lines below and call Readability.php in your browser
50// passing it the URL of the page you'd like content from, e.g.:
51// Readability.php?url=http://medialens.org/alerts/09/090615_the_guardian_climate.php
52
53/*
54if (!isset($_GET['url']) || $_GET['url'] == '') {
55 die('Please pass a URL to the script. E.g. Readability.php?url=bla.com/story.html');
56}
57$url = $_GET['url'];
58if (!preg_match('!^https?://!i', $url)) $url = 'http://'.$url;
59$html = file_get_contents($url);
60$r = new Readability($html, $url);
61$r->init();
62echo $r->articleContent->innerHTML;
63*/
64
65
66class Readability
67{
68 /* constants */
69 const FLAG_STRIP_UNLIKELYS = 1;
70 const FLAG_WEIGHT_CLASSES = 2;
71 const FLAG_CLEAN_CONDITIONALLY = 4;
72
73 public $version = '1.7.1-without-multi-page';
74 public $convertLinksToFootnotes = false;
75 public $revertForcedParagraphElements = true;
76 public $articleTitle;
77 public $articleContent;
78 public $dom;
79 public $url = null; // optional - URL where HTML was retrieved
80 public $debug = false;
81 protected $body = null; //
82 protected $bodyCache = null; // Cache the body HTML in case we need to re-use it later
83 protected $flags = FLAG_CLEAN_CONDITIONALLY; // 1 | 2 | 4; // Start with all flags set.
84 protected $success = false; // indicates whether we were able to extract or not
85
86 /**
87 * All of the regular expressions in use within readability.
88 * Defined up here so we don't instantiate them repeatedly in loops.
89 **/
90 public $regexps = array(
91 'unlikelyCandidates' => '/combx|comment|comments|community|disqus|extra|foot|header|menu|remark|rss|shoutbox|sidebar|sponsor|ad-break|agegate|pagination|pager|popup|tweet|twitter/i',
92 'okMaybeItsACandidate' => '/and|article|body|column|main|shadow/i',
93 'positive' => '/article|body|content|entry|hentry|main|page|pagination|post|text|blog|story/i',
94 'negative' => '/combx|comment|comments|com-|contact|foot|footer|footnote|masthead|media|meta|outbrain|promo|related|scroll|shoutbox|sidebar|sponsor|shopping|tags|tool|widget/i',
95 'divToPElements' => '/<(a|blockquote|dl|div|ol|p|pre|table|ul)/i',
96 'replaceBrs' => '/(<br[^>]*>[ \n\r\t]*){2,}/i',
97 'replaceFonts' => '/<(\/?)font[^>]*>/i',
98 // 'trimRe' => '/^\s+|\s+$/g', // PHP has trim()
99 'normalize' => '/\s{2,}/',
100 'killBreaks' => '/(<br\s*\/?>(\s|&nbsp;?)*){1,}/',
101 'video' => '/http:\/\/(www\.)?(youtube|vimeo|dailymotion)\.com/i',
102 'skipFootnoteLink' => '/^\s*(\[?[a-z0-9]{1,2}\]?|^|edit|citation needed)\s*$/i'
103 );
104
105 /**
106 * Create instance of Readability
107 * @param string UTF-8 encoded string
108 * @param string (optional) URL associated with HTML (used for footnotes)
109 */
110 function __construct($html, $url=null)
111 {
112 /* Turn all double br's into p's */
113 $html = preg_replace($this->regexps['replaceBrs'], '</p><p>', $html);
114 $html = preg_replace($this->regexps['replaceFonts'], '<$1span>', $html);
115 $html = mb_convert_encoding($html, 'HTML-ENTITIES', "UTF-8");
116 $this->dom = new DOMDocument();
117 $this->dom->preserveWhiteSpace = false;
118 $this->dom->registerNodeClass('DOMElement', 'JSLikeHTMLElement');
119 if (trim($html) == '') $html = '<html></html>';
120 @$this->dom->loadHTML($html);
121 $this->url = $url;
122 }
123
124 /**
125 * Get article title element
126 * @return DOMElement
127 */
128 public function getTitle() {
129 return $this->articleTitle;
130 }
131
132 /**
133 * Get article content element
134 * @return DOMElement
135 */
136 public function getContent() {
137 return $this->articleContent;
138 }
139
140 /**
141 * Runs readability.
142 *
143 * Workflow:
144 * 1. Prep the document by removing script tags, css, etc.
145 * 2. Build readability's DOM tree.
146 * 3. Grab the article content from the current dom tree.
147 * 4. Replace the current DOM tree with the new one.
148 * 5. Read peacefully.
149 *
150 * @return boolean true if we found content, false otherwise
151 **/
152 public function init()
153 {
154 if (!isset($this->dom->documentElement)) return false;
155 $this->removeScripts($this->dom);
156 //die($this->getInnerHTML($this->dom->documentElement));
157
158 // Assume successful outcome
159 $this->success = true;
160
161 $bodyElems = $this->dom->getElementsByTagName('body');
162 if ($bodyElems->length > 0) {
163 if ($this->bodyCache == null) {
164 $this->bodyCache = $bodyElems->item(0)->innerHTML;
165 }
166 if ($this->body == null) {
167 $this->body = $bodyElems->item(0);
168 }
169 }
170
171 $this->prepDocument();
172
173 //die($this->dom->documentElement->parentNode->nodeType);
174 //$this->setInnerHTML($this->dom->documentElement, $this->getInnerHTML($this->dom->documentElement));
175 //die($this->getInnerHTML($this->dom->documentElement));
176
177 /* Build readability's DOM tree */
178 $overlay = $this->dom->createElement('div');
179 $innerDiv = $this->dom->createElement('div');
180 $articleTitle = $this->getArticleTitle();
181 $articleContent = $this->grabArticle();
182
183 if (!$articleContent) {
184 $this->success = false;
185 $articleContent = $this->dom->createElement('div');
186 $articleContent->setAttribute('id', 'readability-content');
187 $articleContent->innerHTML = '<p>Sorry, Readability was unable to parse this page for content.</p>';
188 }
189
190 $overlay->setAttribute('id', 'readOverlay');
191 $innerDiv->setAttribute('id', 'readInner');
192
193 /* Glue the structure of our document together. */
194 $innerDiv->appendChild($articleTitle);
195 $innerDiv->appendChild($articleContent);
196 $overlay->appendChild($innerDiv);
197
198 /* Clear the old HTML, insert the new content. */
199 $this->body->innerHTML = '';
200 $this->body->appendChild($overlay);
201 //document.body.insertBefore(overlay, document.body.firstChild);
202 $this->body->removeAttribute('style');
203
204 $this->postProcessContent($articleContent);
205
206 // Set title and content instance variables
207 $this->articleTitle = $articleTitle;
208 $this->articleContent = $articleContent;
209
210 return $this->success;
211 }
212
213 /**
214 * Debug
215 */
216 protected function dbg($msg) {
217 if ($this->debug) echo '* ',$msg, '<br />', "\n";
218 }
219
220 /**
221 * Run any post-process modifications to article content as necessary.
222 *
223 * @param DOMElement
224 * @return void
225 */
226 public function postProcessContent($articleContent) {
227 if ($this->convertLinksToFootnotes && !preg_match('/wikipedia\.org/', @$this->url)) {
228 $this->addFootnotes($articleContent);
229 }
230 }
231
232 /**
233 * Get the article title as an H1.
234 *
235 * @return DOMElement
236 */
237 protected function getArticleTitle() {
238 $curTitle = '';
239 $origTitle = '';
240
241 try {
242 $curTitle = $origTitle = $this->getInnerText($this->dom->getElementsByTagName('title')->item(0));
243 } catch(Exception $e) {}
244
245 if (preg_match('/ [\|\-] /', $curTitle))
246 {
247 $curTitle = preg_replace('/(.*)[\|\-] .*/i', '$1', $origTitle);
248
249 if (count(explode(' ', $curTitle)) < 3) {
250 $curTitle = preg_replace('/[^\|\-]*[\|\-](.*)/i', '$1', $origTitle);
251 }
252 }
253 else if (strpos($curTitle, ': ') !== false)
254 {
255 $curTitle = preg_replace('/.*:(.*)/i', '$1', $origTitle);
256
257 if (count(explode(' ', $curTitle)) < 3) {
258 $curTitle = preg_replace('/[^:]*[:](.*)/i','$1', $origTitle);
259 }
260 }
261 else if(strlen($curTitle) > 150 || strlen($curTitle) < 15)
262 {
263 $hOnes = $this->dom->getElementsByTagName('h1');
264 if($hOnes->length == 1)
265 {
266 $curTitle = $this->getInnerText($hOnes->item(0));
267 }
268 }
269
270 $curTitle = trim($curTitle);
271
272 if (count(explode(' ', $curTitle)) <= 4) {
273 $curTitle = $origTitle;
274 }
275
276 $articleTitle = $this->dom->createElement('h1');
277 $articleTitle->innerHTML = $curTitle;
278
279 return $articleTitle;
280 }
281
282 /**
283 * Prepare the HTML document for readability to scrape it.
284 * This includes things like stripping javascript, CSS, and handling terrible markup.
285 *
286 * @return void
287 **/
288 protected function prepDocument() {
289 /**
290 * In some cases a body element can't be found (if the HTML is totally hosed for example)
291 * so we create a new body node and append it to the document.
292 */
293 if ($this->body == null)
294 {
295 $this->body = $this->dom->createElement('body');
296 $this->dom->documentElement->appendChild($this->body);
297 }
298 $this->body->setAttribute('id', 'readabilityBody');
299
300 /* Remove all style tags in head */
301 $styleTags = $this->dom->getElementsByTagName('style');
302 for ($i = $styleTags->length-1; $i >= 0; $i--)
303 {
304 $styleTags->item($i)->parentNode->removeChild($styleTags->item($i));
305 }
306
307 /* Turn all double br's into p's */
308 /* Note, this is pretty costly as far as processing goes. Maybe optimize later. */
309 //document.body.innerHTML = document.body.innerHTML.replace(readability.regexps.replaceBrs, '</p><p>').replace(readability.regexps.replaceFonts, '<$1span>');
310 // We do this in the constructor for PHP as that's when we have raw HTML - before parsing it into a DOM tree.
311 // Manipulating innerHTML as it's done in JS is not possible in PHP.
312 }
313
314 /**
315 * For easier reading, convert this document to have footnotes at the bottom rather than inline links.
316 * @see http://www.roughtype.com/archives/2010/05/experiments_in.php
317 *
318 * @return void
319 **/
320 public function addFootnotes($articleContent) {
321 $footnotesWrapper = $this->dom->createElement('div');
322 $footnotesWrapper->setAttribute('id', 'readability-footnotes');
323 $footnotesWrapper->innerHTML = '<h3>References</h3>';
324
325 $articleFootnotes = $this->dom->createElement('ol');
326 $articleFootnotes->setAttribute('id', 'readability-footnotes-list');
327 $footnotesWrapper->appendChild($articleFootnotes);
328
329 $articleLinks = $articleContent->getElementsByTagName('a');
330
331 $linkCount = 0;
332 for ($i = 0; $i < $articleLinks->length; $i++)
333 {
334 $articleLink = $articleLinks->item($i);
335 $footnoteLink = $articleLink->cloneNode(true);
336 $refLink = $this->dom->createElement('a');
337 $footnote = $this->dom->createElement('li');
338 $linkDomain = @parse_url($footnoteLink->getAttribute('href'), PHP_URL_HOST);
339 if (!$linkDomain && isset($this->url)) $linkDomain = @parse_url($this->url, PHP_URL_HOST);
340 //linkDomain = footnoteLink.host ? footnoteLink.host : document.location.host,
341 $linkText = $this->getInnerText($articleLink);
342
343 if ((strpos($articleLink->getAttribute('class'), 'readability-DoNotFootnote') !== false) || preg_match($this->regexps['skipFootnoteLink'], $linkText)) {
344 continue;
345 }
346
347 $linkCount++;
348
349 /** Add a superscript reference after the article link */
350 $refLink->setAttribute('href', '#readabilityFootnoteLink-' . $linkCount);
351 $refLink->innerHTML = '<small><sup>[' . $linkCount . ']</sup></small>';
352 $refLink->setAttribute('class', 'readability-DoNotFootnote');
353 $refLink->setAttribute('style', 'color: inherit;');
354
355 //TODO: does this work or should we use DOMNode.isSameNode()?
356 if ($articleLink->parentNode->lastChild == $articleLink) {
357 $articleLink->parentNode->appendChild($refLink);
358 } else {
359 $articleLink->parentNode->insertBefore($refLink, $articleLink->nextSibling);
360 }
361
362 $articleLink->setAttribute('style', 'color: inherit; text-decoration: none;');
363 $articleLink->setAttribute('name', 'readabilityLink-' . $linkCount);
364
365 $footnote->innerHTML = '<small><sup><a href="#readabilityLink-' . $linkCount . '" title="Jump to Link in Article">^</a></sup></small> ';
366
367 $footnoteLink->innerHTML = ($footnoteLink->getAttribute('title') != '' ? $footnoteLink->getAttribute('title') : $linkText);
368 $footnoteLink->setAttribute('name', 'readabilityFootnoteLink-' . $linkCount);
369
370 $footnote->appendChild($footnoteLink);
371 if ($linkDomain) $footnote->innerHTML = $footnote->innerHTML . '<small> (' . $linkDomain . ')</small>';
372
373 $articleFootnotes->appendChild($footnote);
374 }
375
376 if ($linkCount > 0) {
377 $articleContent->appendChild($footnotesWrapper);
378 }
379 }
380
381 /**
382 * Reverts P elements with class 'readability-styled'
383 * to text nodes - which is what they were before.
384 *
385 * @param DOMElement
386 * @return void
387 */
388 function revertReadabilityStyledElements($articleContent) {
389 $xpath = new DOMXPath($articleContent->ownerDocument);
390 $elems = $xpath->query('.//p[@class="readability-styled"]', $articleContent);
391 //$elems = $articleContent->getElementsByTagName('p');
392 for ($i = $elems->length-1; $i >= 0; $i--) {
393 $e = $elems->item($i);
394 $e->parentNode->replaceChild($articleContent->ownerDocument->createTextNode($e->textContent), $e);
395 //if ($e->hasAttribute('class') && $e->getAttribute('class') == 'readability-styled') {
396 // $e->parentNode->replaceChild($this->dom->createTextNode($e->textContent), $e);
397 //}
398 }
399 }
400
401 /**
402 * Prepare the article node for display. Clean out any inline styles,
403 * iframes, forms, strip extraneous <p> tags, etc.
404 *
405 * @param DOMElement
406 * @return void
407 */
408 function prepArticle($articleContent) {
409 $this->cleanStyles($articleContent);
410 $this->killBreaks($articleContent);
411 if ($this->revertForcedParagraphElements) {
412 $this->revertReadabilityStyledElements($articleContent);
413 }
414
415 /* Clean out junk from the article content */
416 $this->cleanConditionally($articleContent, 'form');
417 $this->clean($articleContent, 'object');
418 $this->clean($articleContent, 'h1');
419
420 /**
421 * If there is only one h2, they are probably using it
422 * as a header and not a subheader, so remove it since we already have a header.
423 ***/
424 if ($articleContent->getElementsByTagName('h2')->length == 1) {
425 $this->clean($articleContent, 'h2');
426 }
427 $this->clean($articleContent, 'iframe');
428
429 $this->cleanHeaders($articleContent);
430
431 /* Do these last as the previous stuff may have removed junk that will affect these */
432 $this->cleanConditionally($articleContent, 'table');
433 $this->cleanConditionally($articleContent, 'ul');
434 $this->cleanConditionally($articleContent, 'div');
435
436 /* Remove extra paragraphs */
437 $articleParagraphs = $articleContent->getElementsByTagName('p');
438 for ($i = $articleParagraphs->length-1; $i >= 0; $i--)
439 {
440 $imgCount = $articleParagraphs->item($i)->getElementsByTagName('img')->length;
441 $embedCount = $articleParagraphs->item($i)->getElementsByTagName('embed')->length;
442 $objectCount = $articleParagraphs->item($i)->getElementsByTagName('object')->length;
443
444 if ($imgCount === 0 && $embedCount === 0 && $objectCount === 0 && $this->getInnerText($articleParagraphs->item($i), false) == '')
445 {
446 $articleParagraphs->item($i)->parentNode->removeChild($articleParagraphs->item($i));
447 }
448 }
449
450 try {
451 $articleContent->innerHTML = preg_replace('/<br[^>]*>\s*<p/i', '<p', $articleContent->innerHTML);
452 //articleContent.innerHTML = articleContent.innerHTML.replace(/<br[^>]*>\s*<p/gi, '<p');
453 }
454 catch (Exception $e) {
455 $this->dbg("Cleaning innerHTML of breaks failed. This is an IE strict-block-elements bug. Ignoring.: " . $e);
456 }
457 }
458
459 /**
460 * Initialize a node with the readability object. Also checks the
461 * className/id for special names to add to its score.
462 *
463 * @param Element
464 * @return void
465 **/
466 protected function initializeNode($node) {
467 $readability = $this->dom->createAttribute('readability');
468 $readability->value = 0; // this is our contentScore
469 $node->setAttributeNode($readability);
470
471 switch (strtoupper($node->tagName)) { // unsure if strtoupper is needed, but using it just in case
472 case 'DIV':
473 $readability->value += 5;
474 break;
475
476 case 'PRE':
477 case 'TD':
478 case 'BLOCKQUOTE':
479 $readability->value += 3;
480 break;
481
482 case 'ADDRESS':
483 case 'OL':
484 case 'UL':
485 case 'DL':
486 case 'DD':
487 case 'DT':
488 case 'LI':
489 case 'FORM':
490 $readability->value -= 3;
491 break;
492
493 case 'H1':
494 case 'H2':
495 case 'H3':
496 case 'H4':
497 case 'H5':
498 case 'H6':
499 case 'TH':
500 $readability->value -= 5;
501 break;
502 }
503 $readability->value += $this->getClassWeight($node);
504 }
505
506 /***
507 * grabArticle - Using a variety of metrics (content score, classname, element types), find the content that is
508 * most likely to be the stuff a user wants to read. Then return it wrapped up in a div.
509 *
510 * @return DOMElement
511 **/
512 protected function grabArticle($page=null) {
513 $stripUnlikelyCandidates = $this->flagIsActive(self::FLAG_STRIP_UNLIKELYS);
514 if (!$page) $page = $this->dom;
515 $allElements = $page->getElementsByTagName('*');
516 /**
517 * First, node prepping. Trash nodes that look cruddy (like ones with the class name "comment", etc), and turn divs
518 * into P tags where they have been used inappropriately (as in, where they contain no other block level elements.)
519 *
520 * Note: Assignment from index for performance. See http://www.peachpit.com/articles/article.aspx?p=31567&seqNum=5
521 * TODO: Shouldn't this be a reverse traversal?
522 **/
523 $node = null;
524 $nodesToScore = array();
525 for ($nodeIndex = 0; ($node = $allElements->item($nodeIndex)); $nodeIndex++) {
526 //for ($nodeIndex=$targetList->length-1; $nodeIndex >= 0; $nodeIndex--) {
527 //$node = $targetList->item($nodeIndex);
528 $tagName = strtoupper($node->tagName);
529 /* Remove unlikely candidates */
530 if ($stripUnlikelyCandidates) {
531 $unlikelyMatchString = $node->getAttribute('class') . $node->getAttribute('id');
532 if (
533 preg_match($this->regexps['unlikelyCandidates'], $unlikelyMatchString) &&
534 !preg_match($this->regexps['okMaybeItsACandidate'], $unlikelyMatchString) &&
535 $tagName != 'BODY'
536 )
537 {
538 $this->dbg('Removing unlikely candidate - ' . $unlikelyMatchString);
539 //$nodesToRemove[] = $node;
540 $node->parentNode->removeChild($node);
541 $nodeIndex--;
542 continue;
543 }
544 }
545
546 if ($tagName == 'P' || $tagName == 'TD' || $tagName == 'PRE') {
547 $nodesToScore[] = $node;
548 }
549
550 /* Turn all divs that don't have children block level elements into p's */
551 if ($tagName == 'DIV') {
552 if (!preg_match($this->regexps['divToPElements'], $node->innerHTML)) {
553 //$this->dbg('Altering div to p');
554 $newNode = $this->dom->createElement('p');
555 try {
556 $newNode->innerHTML = $node->innerHTML;
557 //$nodesToReplace[] = array('new'=>$newNode, 'old'=>$node);
558 $node->parentNode->replaceChild($newNode, $node);
559 $nodeIndex--;
560 $nodesToScore[] = $node; // or $newNode?
561 }
562 catch(Exception $e) {
563 $this->dbg('Could not alter div to p, reverting back to div.: ' . $e);
564 }
565 }
566 else
567 {
568 // EXPERIMENTAL
569 // TODO: change these p elements back to text nodes after processing
570 for ($i = 0, $il = $node->childNodes->length; $i < $il; $i++) {
571 $childNode = $node->childNodes->item($i);
572 if ($childNode->nodeType == 3) { // XML_TEXT_NODE
573 //$this->dbg('replacing text node with a p tag with the same content.');
574 $p = $this->dom->createElement('p');
575 $p->innerHTML = $childNode->nodeValue;
576 $p->setAttribute('style', 'display: inline;');
577 $p->setAttribute('class', 'readability-styled');
578 $childNode->parentNode->replaceChild($p, $childNode);
579 }
580 }
581 }
582 }
583 }
584
585 /**
586 * Loop through all paragraphs, and assign a score to them based on how content-y they look.
587 * Then add their score to their parent node.
588 *
589 * A score is determined by things like number of commas, class names, etc. Maybe eventually link density.
590 **/
591 $candidates = array();
592 for ($pt=0; $pt < count($nodesToScore); $pt++) {
593 $parentNode = $nodesToScore[$pt]->parentNode;
594 // $grandParentNode = $parentNode ? $parentNode->parentNode : null;
595 $grandParentNode = !$parentNode ? null : (($parentNode->parentNode instanceof DOMElement) ? $parentNode->parentNode : null);
596 $innerText = $this->getInnerText($nodesToScore[$pt]);
597
598 if (!$parentNode || !isset($parentNode->tagName)) {
599 continue;
600 }
601
602 /* If this paragraph is less than 25 characters, don't even count it. */
603 if(strlen($innerText) < 25) {
604 continue;
605 }
606
607 /* Initialize readability data for the parent. */
608 if (!$parentNode->hasAttribute('readability'))
609 {
610 $this->initializeNode($parentNode);
611 $candidates[] = $parentNode;
612 }
613
614 /* Initialize readability data for the grandparent. */
615 if ($grandParentNode && !$grandParentNode->hasAttribute('readability') && isset($grandParentNode->tagName))
616 {
617 $this->initializeNode($grandParentNode);
618 $candidates[] = $grandParentNode;
619 }
620
621 $contentScore = 0;
622
623 /* Add a point for the paragraph itself as a base. */
624 $contentScore++;
625
626 /* Add points for any commas within this paragraph */
627 $contentScore += count(explode(',', $innerText));
628
629 /* For every 100 characters in this paragraph, add another point. Up to 3 points. */
630 $contentScore += min(floor(strlen($innerText) / 100), 3);
631
632 /* Add the score to the parent. The grandparent gets half. */
633 $parentNode->getAttributeNode('readability')->value += $contentScore;
634
635 if ($grandParentNode) {
636 $grandParentNode->getAttributeNode('readability')->value += $contentScore/2;
637 }
638 }
639
640 /**
641 * After we've calculated scores, loop through all of the possible candidate nodes we found
642 * and find the one with the highest score.
643 **/
644 $topCandidate = null;
645 for ($c=0, $cl=count($candidates); $c < $cl; $c++)
646 {
647 /**
648 * Scale the final candidates score based on link density. Good content should have a
649 * relatively small link density (5% or less) and be mostly unaffected by this operation.
650 **/
651 $readability = $candidates[$c]->getAttributeNode('readability');
652 $readability->value = $readability->value * (1-$this->getLinkDensity($candidates[$c]));
653
654 $this->dbg('Candidate: ' . $candidates[$c]->tagName . ' (' . $candidates[$c]->getAttribute('class') . ':' . $candidates[$c]->getAttribute('id') . ') with score ' . $readability->value);
655
656 if (!$topCandidate || $readability->value > (int)$topCandidate->getAttribute('readability')) {
657 $topCandidate = $candidates[$c];
658 }
659 }
660
661 /**
662 * If we still have no top candidate, just use the body as a last resort.
663 * We also have to copy the body node so it is something we can modify.
664 **/
665 if ($topCandidate === null || strtoupper($topCandidate->tagName) == 'BODY')
666 {
667 $topCandidate = $this->dom->createElement('div');
668 if ($page instanceof DOMDocument) {
669 if (!isset($page->documentElement)) {
670 // we don't have a body either? what a mess! :)
671 } else {
672 $topCandidate->innerHTML = $page->documentElement->innerHTML;
673 $page->documentElement->innerHTML = '';
674 $page->documentElement->appendChild($topCandidate);
675 }
676 } else {
677 $topCandidate->innerHTML = $page->innerHTML;
678 $page->innerHTML = '';
679 $page->appendChild($topCandidate);
680 }
681 $this->initializeNode($topCandidate);
682 }
683
684 /**
685 * Now that we have the top candidate, look through its siblings for content that might also be related.
686 * Things like preambles, content split by ads that we removed, etc.
687 **/
688 $articleContent = $this->dom->createElement('div');
689 $articleContent->setAttribute('id', 'readability-content');
690 $siblingScoreThreshold = max(10, ((int)$topCandidate->getAttribute('readability')) * 0.2);
691 $siblingNodes = $topCandidate->parentNode->childNodes;
692 if (!isset($siblingNodes)) {
693 $siblingNodes = new stdClass;
694 $siblingNodes->length = 0;
695 }
696
697 for ($s=0, $sl=$siblingNodes->length; $s < $sl; $s++)
698 {
699 $siblingNode = $siblingNodes->item($s);
700 $append = false;
701
702 $this->dbg('Looking at sibling node: ' . $siblingNode->nodeName . (($siblingNode->nodeType === XML_ELEMENT_NODE && $siblingNode->hasAttribute('readability')) ? (' with score ' . $siblingNode->getAttribute('readability')) : ''));
703
704 //dbg('Sibling has score ' . ($siblingNode->readability ? siblingNode.readability.contentScore : 'Unknown'));
705
706 if ($siblingNode === $topCandidate)
707 // or if ($siblingNode->isSameNode($topCandidate))
708 {
709 $append = true;
710 }
711
712 $contentBonus = 0;
713 /* Give a bonus if sibling nodes and top candidates have the example same classname */
714 if ($siblingNode->nodeType === XML_ELEMENT_NODE && $siblingNode->getAttribute('class') == $topCandidate->getAttribute('class') && $topCandidate->getAttribute('class') != '') {
715 $contentBonus += ((int)$topCandidate->getAttribute('readability')) * 0.2;
716 }
717
718 if ($siblingNode->nodeType === XML_ELEMENT_NODE && $siblingNode->hasAttribute('readability') && (((int)$siblingNode->getAttribute('readability')) + $contentBonus) >= $siblingScoreThreshold)
719 {
720 $append = true;
721 }
722
723 if (strtoupper($siblingNode->nodeName) == 'P') {
724 $linkDensity = $this->getLinkDensity($siblingNode);
725 $nodeContent = $this->getInnerText($siblingNode);
726 $nodeLength = strlen($nodeContent);
727
728 if ($nodeLength > 80 && $linkDensity < 0.25)
729 {
730 $append = true;
731 }
732 else if ($nodeLength < 80 && $linkDensity === 0 && preg_match('/\.( |$)/', $nodeContent))
733 {
734 $append = true;
735 }
736 }
737
738 /* Look for a special classname */
739 if ($siblingNode->nodeType === XML_ELEMENT_NODE && $siblingNode->hasAttribute('class') && $siblingNode->getAttribute('class') != '')
740 {
741 if (preg_match($this->regexps['okMaybeItsACandidate'], $siblingNode->getAttribute('class'))) {
742 $append = true;
743 }
744 }
745
746 /* Look for a special classname */
747 if ($siblingNode->nodeType === XML_ELEMENT_NODE && $siblingNode->hasAttribute('id') && $siblingNode->getAttribute('id') != '')
748 {
749 if (preg_match($this->regexps['okMaybeItsACandidate'], $siblingNode->getAttribute('id'))) {
750 $append = true;
751 }
752 }
753
754
755 if ($append)
756 {
757 $this->dbg('Appending node: ' . $siblingNode->nodeName);
758
759 $nodeToAppend = null;
760 $sibNodeName = strtoupper($siblingNode->nodeName);
761 if ($sibNodeName != 'DIV' && $sibNodeName != 'P') {
762 /* We have a node that isn't a common block level element, like a form or td tag. Turn it into a div so it doesn't get filtered out later by accident. */
763
764 $this->dbg('Altering siblingNode of ' . $sibNodeName . ' to div.');
765 $nodeToAppend = $this->dom->createElement('div');
766 try {
767 $nodeToAppend->setAttribute('id', $siblingNode->getAttribute('id'));
768 $nodeToAppend->innerHTML = $siblingNode->innerHTML;
769 }
770 catch(Exception $e)
771 {
772 $this->dbg('Could not alter siblingNode to div, reverting back to original.');
773 $nodeToAppend = $siblingNode;
774 $s--;
775 $sl--;
776 }
777 } else {
778 $nodeToAppend = $siblingNode;
779 $s--;
780 $sl--;
781 }
782
783 /* To ensure a node does not interfere with readability styles, remove its classnames */
784 $nodeToAppend->removeAttribute('class');
785
786 /* Append sibling and subtract from our list because it removes the node when you append to another node */
787 $articleContent->appendChild($nodeToAppend);
788 }
789 }
790
791 /**
792 * So we have all of the content that we need. Now we clean it up for presentation.
793 **/
794 $this->prepArticle($articleContent);
795
796 /**
797 * Now that we've gone through the full algorithm, check to see if we got any meaningful content.
798 * If we didn't, we may need to re-run grabArticle with different flags set. This gives us a higher
799 * likelihood of finding the content, and the sieve approach gives us a higher likelihood of
800 * finding the -right- content.
801 **/
802 if (strlen($this->getInnerText($articleContent, false)) < 250)
803 {
804 // TODO: find out why element disappears sometimes, e.g. for this URL http://www.businessinsider.com/6-hedge-fund-etfs-for-average-investors-2011-7
805 // in the meantime, we check and create an empty element if it's not there.
806 if (!isset($this->body->childNodes)) $this->body = $this->dom->createElement('body');
807 $this->body->innerHTML = $this->bodyCache;
808
809 if ($this->flagIsActive(self::FLAG_STRIP_UNLIKELYS)) {
810 $this->removeFlag(self::FLAG_STRIP_UNLIKELYS);
811 return $this->grabArticle($this->body);
812 }
813 else if ($this->flagIsActive(self::FLAG_WEIGHT_CLASSES)) {
814 $this->removeFlag(self::FLAG_WEIGHT_CLASSES);
815 return $this->grabArticle($this->body);
816 }
817 else if ($this->flagIsActive(self::FLAG_CLEAN_CONDITIONALLY)) {
818 $this->removeFlag(self::FLAG_CLEAN_CONDITIONALLY);
819 return $this->grabArticle($this->body);
820 }
821 else {
822 return false;
823 }
824 }
825 return $articleContent;
826 }
827
828 /**
829 * Remove script tags from document
830 *
831 * @param DOMElement
832 * @return void
833 */
834 public function removeScripts($doc) {
835 $scripts = $doc->getElementsByTagName('script');
836 for($i = $scripts->length-1; $i >= 0; $i--)
837 {
838 $scripts->item($i)->parentNode->removeChild($scripts->item($i));
839 }
840 }
841
842 /**
843 * Get the inner text of a node.
844 * This also strips out any excess whitespace to be found.
845 *
846 * @param DOMElement $
847 * @param boolean $normalizeSpaces (default: true)
848 * @return string
849 **/
850 public function getInnerText($e, $normalizeSpaces=true) {
851 $textContent = '';
852
853 if (!isset($e->textContent) || $e->textContent == '') {
854 return '';
855 }
856
857 $textContent = trim($e->textContent);
858
859 if ($normalizeSpaces) {
860 return preg_replace($this->regexps['normalize'], ' ', $textContent);
861 } else {
862 return $textContent;
863 }
864 }
865
866 /**
867 * Get the number of times a string $s appears in the node $e.
868 *
869 * @param DOMElement $e
870 * @param string - what to count. Default is ","
871 * @return number (integer)
872 **/
873 public function getCharCount($e, $s=',') {
874 return substr_count($this->getInnerText($e), $s);
875 }
876
877 /**
878 * Remove the style attribute on every $e and under.
879 *
880 * @param DOMElement $e
881 * @return void
882 */
883 public function cleanStyles($e) {
884 if (!is_object($e)) return;
885 $elems = $e->getElementsByTagName('*');
886 foreach ($elems as $elem) {
887 $elem->removeAttribute('style');
888 }
889 }
890
891 /**
892 * Get the density of links as a percentage of the content
893 * This is the amount of text that is inside a link divided by the total text in the node.
894 *
895 * @param DOMElement $e
896 * @return number (float)
897 */
898 public function getLinkDensity($e) {
899 $links = $e->getElementsByTagName('a');
900 $textLength = strlen($this->getInnerText($e));
901 $linkLength = 0;
902 for ($i=0, $il=$links->length; $i < $il; $i++)
903 {
904 $linkLength += strlen($this->getInnerText($links->item($i)));
905 }
906 if ($textLength > 0) {
907 return $linkLength / $textLength;
908 } else {
909 return 0;
910 }
911 }
912
913 /**
914 * Get an elements class/id weight. Uses regular expressions to tell if this
915 * element looks good or bad.
916 *
917 * @param DOMElement $e
918 * @return number (Integer)
919 */
920 public function getClassWeight($e) {
921 if(!$this->flagIsActive(self::FLAG_WEIGHT_CLASSES)) {
922 return 0;
923 }
924
925 $weight = 0;
926
927 /* Look for a special classname */
928 if ($e->hasAttribute('class') && $e->getAttribute('class') != '')
929 {
930 if (preg_match($this->regexps['negative'], $e->getAttribute('class'))) {
931 $weight -= 25;
932 }
933 if (preg_match($this->regexps['positive'], $e->getAttribute('class'))) {
934 $weight += 25;
935 }
936 }
937
938 /* Look for a special ID */
939 if ($e->hasAttribute('id') && $e->getAttribute('id') != '')
940 {
941 if (preg_match($this->regexps['negative'], $e->getAttribute('id'))) {
942 $weight -= 25;
943 }
944 if (preg_match($this->regexps['positive'], $e->getAttribute('id'))) {
945 $weight += 25;
946 }
947 }
948 return $weight;
949 }
950
951 /**
952 * Remove extraneous break tags from a node.
953 *
954 * @param DOMElement $node
955 * @return void
956 */
957 public function killBreaks($node) {
958 $html = $node->innerHTML;
959 $html = preg_replace($this->regexps['killBreaks'], '<br />', $html);
960 $node->innerHTML = $html;
961 }
962
963 /**
964 * Clean a node of all elements of type "tag".
965 * (Unless it's a youtube/vimeo video. People love movies.)
966 *
967 * @param DOMElement $e
968 * @param string $tag
969 * @return void
970 */
971 public function clean($e, $tag) {
972 $targetList = $e->getElementsByTagName($tag);
973 $isEmbed = ($tag == 'object' || $tag == 'embed');
974
975 for ($y=$targetList->length-1; $y >= 0; $y--) {
976 /* Allow youtube and vimeo videos through as people usually want to see those. */
977 if ($isEmbed) {
978 $attributeValues = '';
979 for ($i=0, $il=$targetList->item($y)->attributes->length; $i < $il; $i++) {
980 $attributeValues .= $targetList->item($y)->attributes->item($i)->value . '|'; // DOMAttr? (TODO: test)
981 }
982
983 /* First, check the elements attributes to see if any of them contain youtube or vimeo */
984 if (preg_match($this->regexps['video'], $attributeValues)) {
985 continue;
986 }
987
988 /* Then check the elements inside this element for the same. */
989 if (preg_match($this->regexps['video'], $targetList->item($y)->innerHTML)) {
990 continue;
991 }
992 }
993 $targetList->item($y)->parentNode->removeChild($targetList->item($y));
994 }
995 }
996
997 /**
998 * Clean an element of all tags of type "tag" if they look fishy.
999 * "Fishy" is an algorithm based on content length, classnames,
1000 * link density, number of images & embeds, etc.
1001 *
1002 * @param DOMElement $e
1003 * @param string $tag
1004 * @return void
1005 */
1006 public function cleanConditionally($e, $tag) {
1007 if (!$this->flagIsActive(self::FLAG_CLEAN_CONDITIONALLY)) {
1008 return;
1009 }
1010
1011 $tagsList = $e->getElementsByTagName($tag);
1012 $curTagsLength = $tagsList->length;
1013
1014 /**
1015 * Gather counts for other typical elements embedded within.
1016 * Traverse backwards so we can remove nodes at the same time without effecting the traversal.
1017 *
1018 * TODO: Consider taking into account original contentScore here.
1019 */
1020 for ($i=$curTagsLength-1; $i >= 0; $i--) {
1021 $weight = $this->getClassWeight($tagsList->item($i));
1022 $contentScore = ($tagsList->item($i)->hasAttribute('readability')) ? (int)$tagsList->item($i)->getAttribute('readability') : 0;
1023
1024 $this->dbg('Cleaning Conditionally ' . $tagsList->item($i)->tagName . ' (' . $tagsList->item($i)->getAttribute('class') . ':' . $tagsList->item($i)->getAttribute('id') . ')' . (($tagsList->item($i)->hasAttribute('readability')) ? (' with score ' . $tagsList->item($i)->getAttribute('readability')) : ''));
1025
1026 if ($weight + $contentScore < 0) {
1027 $tagsList->item($i)->parentNode->removeChild($tagsList->item($i));
1028 }
1029 else if ( $this->getCharCount($tagsList->item($i), ',') < 10) {
1030 /**
1031 * If there are not very many commas, and the number of
1032 * non-paragraph elements is more than paragraphs or other ominous signs, remove the element.
1033 **/
1034 $p = $tagsList->item($i)->getElementsByTagName('p')->length;
1035 $img = $tagsList->item($i)->getElementsByTagName('img')->length;
1036 $li = $tagsList->item($i)->getElementsByTagName('li')->length-100;
1037 $input = $tagsList->item($i)->getElementsByTagName('input')->length;
1038
1039 $embedCount = 0;
1040 $embeds = $tagsList->item($i)->getElementsByTagName('embed');
1041 for ($ei=0, $il=$embeds->length; $ei < $il; $ei++) {
1042 if (preg_match($this->regexps['video'], $embeds->item($ei)->getAttribute('src'))) {
1043 $embedCount++;
1044 }
1045 }
1046
1047 $linkDensity = $this->getLinkDensity($tagsList->item($i));
1048 $contentLength = strlen($this->getInnerText($tagsList->item($i)));
1049 $toRemove = false;
1050
1051 if ( $img > $p ) {
1052 $toRemove = true;
1053 } else if ($li > $p && $tag != 'ul' && $tag != 'ol') {
1054 $toRemove = true;
1055 } else if ( $input > floor($p/3) ) {
1056 $toRemove = true;
1057 } else if ($contentLength < 25 && ($img === 0 || $img > 2) ) {
1058 $toRemove = true;
1059 } else if($weight < 25 && $linkDensity > 0.2) {
1060 $toRemove = true;
1061 } else if($weight >= 25 && $linkDensity > 0.5) {
1062 $toRemove = true;
1063 } else if(($embedCount == 1 && $contentLength < 75) || $embedCount > 1) {
1064 $toRemove = true;
1065 }
1066
1067 if ($toRemove) {
1068 $tagsList->item($i)->parentNode->removeChild($tagsList->item($i));
1069 }
1070 }
1071 }
1072 }
1073
1074 /**
1075 * Clean out spurious headers from an Element. Checks things like classnames and link density.
1076 *
1077 * @param DOMElement $e
1078 * @return void
1079 */
1080 public function cleanHeaders($e) {
1081 for ($headerIndex = 1; $headerIndex < 3; $headerIndex++) {
1082 $headers = $e->getElementsByTagName('h' . $headerIndex);
1083 for ($i=$headers->length-1; $i >=0; $i--) {
1084 if ($this->getClassWeight($headers->item($i)) < 0 || $this->getLinkDensity($headers->item($i)) > 0.33) {
1085 $headers->item($i)->parentNode->removeChild($headers->item($i));
1086 }
1087 }
1088 }
1089 }
1090
1091 public function flagIsActive($flag) {
1092 return ($this->flags & $flag) > 0;
1093 }
1094
1095 public function addFlag($flag) {
1096 $this->flags = $this->flags | $flag;
1097 }
1098
1099 public function removeFlag($flag) {
1100 $this->flags = $this->flags & ~$flag;
1101 }
1102}
1103?> \ No newline at end of file
diff --git a/inc/index.html b/inc/index.html
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/inc/index.html
diff --git a/inc/rain.tpl.class.php b/inc/rain.tpl.class.php
new file mode 100755
index 00000000..ea83b2c1
--- /dev/null
+++ b/inc/rain.tpl.class.php
@@ -0,0 +1,1043 @@
1<?php
2
3/**
4 * RainTPL
5 * -------
6 * Realized by Federico Ulfo & maintained by the Rain Team
7 * Distributed under GNU/LGPL 3 License
8 *
9 * @version 2.7.2
10 */
11
12
13class RainTPL{
14
15 // -------------------------
16 // CONFIGURATION
17 // -------------------------
18
19 /**
20 * Template directory
21 *
22 * @var string
23 */
24 static $tpl_dir = "tpl/";
25
26
27 /**
28 * Cache directory. Is the directory where RainTPL will compile the template and save the cache
29 *
30 * @var string
31 */
32 static $cache_dir = "tmp/";
33
34
35 /**
36 * Template base URL. RainTPL will add this URL to the relative paths of element selected in $path_replace_list.
37 *
38 * @var string
39 */
40 static $base_url = null;
41
42
43 /**
44 * Template extension.
45 *
46 * @var string
47 */
48 static $tpl_ext = "html";
49
50
51 /**
52 * Path replace is a cool features that replace all relative paths of images (<img src="...">), stylesheet (<link href="...">), script (<script src="...">) and link (<a href="...">)
53 * Set true to enable the path replace.
54 *
55 * @var unknown_type
56 */
57 static $path_replace = true;
58
59
60 /**
61 * You can set what the path_replace method will replace.
62 * Avaible options: a, img, link, script, input
63 *
64 * @var array
65 */
66 static $path_replace_list = array( 'a', 'img', 'link', 'script', 'input' );
67
68
69 /**
70 * You can define in the black list what string are disabled into the template tags
71 *
72 * @var unknown_type
73 */
74 static $black_list = array( '\$this', 'raintpl::', 'self::', '_SESSION', '_SERVER', '_ENV', 'eval', 'exec', 'unlink', 'rmdir' );
75
76
77 /**
78 * Check template.
79 * true: checks template update time, if changed it compile them
80 * false: loads the compiled template. Set false if server doesn't have write permission for cache_directory.
81 *
82 */
83 static $check_template_update = true;
84
85
86 /**
87 * PHP tags <? ?>
88 * True: php tags are enabled into the template
89 * False: php tags are disabled into the template and rendered as html
90 *
91 * @var bool
92 */
93 static $php_enabled = false;
94
95
96 /**
97 * Debug mode flag.
98 * True: debug mode is used, syntax errors are displayed directly in template. Execution of script is not terminated.
99 * False: exception is thrown on found error.
100 *
101 * @var bool
102 */
103 static $debug = false;
104
105 // -------------------------
106
107
108 // -------------------------
109 // RAINTPL VARIABLES
110 // -------------------------
111
112 /**
113 * Is the array where RainTPL keep the variables assigned
114 *
115 * @var array
116 */
117 public $var = array();
118
119 protected $tpl = array(), // variables to keep the template directories and info
120 $cache = false, // static cache enabled / disabled
121 $cache_id = null; // identify only one cache
122
123 protected static $config_name_sum = array(); // takes all the config to create the md5 of the file
124
125 // -------------------------
126
127
128
129 const CACHE_EXPIRE_TIME = 3600; // default cache expire time = hour
130
131
132
133 /**
134 * Assign variable
135 * eg. $t->assign('name','mickey');
136 *
137 * @param mixed $variable_name Name of template variable or associative array name/value
138 * @param mixed $value value assigned to this variable. Not set if variable_name is an associative array
139 */
140
141 function assign( $variable, $value = null ){
142 if( is_array( $variable ) )
143 $this->var += $variable;
144 else
145 $this->var[ $variable ] = $value;
146 }
147
148
149
150 /**
151 * Draw the template
152 * eg. $html = $tpl->draw( 'demo', TRUE ); // return template in string
153 * or $tpl->draw( $tpl_name ); // echo the template
154 *
155 * @param string $tpl_name template to load
156 * @param boolean $return_string true=return a string, false=echo the template
157 * @return string
158 */
159
160 function draw( $tpl_name, $return_string = false ){
161
162 try {
163 // compile the template if necessary and set the template filepath
164 $this->check_template( $tpl_name );
165 } catch (RainTpl_Exception $e) {
166 $output = $this->printDebug($e);
167 die($output);
168 }
169
170 // Cache is off and, return_string is false
171 // Rain just echo the template
172
173 if( !$this->cache && !$return_string ){
174 extract( $this->var );
175 include $this->tpl['compiled_filename'];
176 unset( $this->tpl );
177 }
178
179
180 // cache or return_string are enabled
181 // rain get the output buffer to save the output in the cache or to return it as string
182
183 else{
184
185 //----------------------
186 // get the output buffer
187 //----------------------
188 ob_start();
189 extract( $this->var );
190 include $this->tpl['compiled_filename'];
191 $raintpl_contents = ob_get_clean();
192 //----------------------
193
194
195 // save the output in the cache
196 if( $this->cache )
197 file_put_contents( $this->tpl['cache_filename'], "<?php if(!class_exists('raintpl')){exit;}?>" . $raintpl_contents );
198
199 // free memory
200 unset( $this->tpl );
201
202 // return or print the template
203 if( $return_string ) return $raintpl_contents; else echo $raintpl_contents;
204
205 }
206
207 }
208
209
210
211 /**
212 * If exists a valid cache for this template it returns the cache
213 *
214 * @param string $tpl_name Name of template (set the same of draw)
215 * @param int $expiration_time Set after how many seconds the cache expire and must be regenerated
216 * @return string it return the HTML or null if the cache must be recreated
217 */
218
219 function cache( $tpl_name, $expire_time = self::CACHE_EXPIRE_TIME, $cache_id = null ){
220
221 // set the cache_id
222 $this->cache_id = $cache_id;
223
224 if( !$this->check_template( $tpl_name ) && file_exists( $this->tpl['cache_filename'] ) && ( time() - filemtime( $this->tpl['cache_filename'] ) < $expire_time ) )
225 return substr( file_get_contents( $this->tpl['cache_filename'] ), 43 );
226 else{
227 //delete the cache of the selected template
228 if (file_exists($this->tpl['cache_filename']))
229 unlink($this->tpl['cache_filename'] );
230 $this->cache = true;
231 }
232 }
233
234
235
236 /**
237 * Configure the settings of RainTPL
238 *
239 */
240 static function configure( $setting, $value = null ){
241 if( is_array( $setting ) )
242 foreach( $setting as $key => $value )
243 self::configure( $key, $value );
244 else if( property_exists( __CLASS__, $setting ) ){
245 self::$$setting = $value;
246 self::$config_name_sum[ $setting ] = $value; // take trace of all config
247 }
248 }
249
250
251
252 // check if has to compile the template
253 // return true if the template has changed
254 protected function check_template( $tpl_name ){
255
256 if( !isset($this->tpl['checked']) ){
257
258 $tpl_basename = basename( $tpl_name ); // template basename
259 $tpl_basedir = strpos($tpl_name,"/") ? dirname($tpl_name) . '/' : null; // template basedirectory
260 $tpl_dir = self::$tpl_dir . $tpl_basedir; // template directory
261 $this->tpl['tpl_filename'] = $tpl_dir . $tpl_basename . '.' . self::$tpl_ext; // template filename
262 $temp_compiled_filename = self::$cache_dir . $tpl_basename . "." . md5( $tpl_dir . serialize(self::$config_name_sum));
263 $this->tpl['compiled_filename'] = $temp_compiled_filename . '.rtpl.php'; // cache filename
264 $this->tpl['cache_filename'] = $temp_compiled_filename . '.s_' . $this->cache_id . '.rtpl.php'; // static cache filename
265
266 // if the template doesn't exsist throw an error
267 if( self::$check_template_update && !file_exists( $this->tpl['tpl_filename'] ) ){
268 $e = new RainTpl_NotFoundException( 'Template '. $tpl_basename .' not found!' );
269 throw $e->setTemplateFile($this->tpl['tpl_filename']);
270 }
271
272 // file doesn't exsist, or the template was updated, Rain will compile the template
273 if( !file_exists( $this->tpl['compiled_filename'] ) || ( self::$check_template_update && filemtime($this->tpl['compiled_filename']) < filemtime( $this->tpl['tpl_filename'] ) ) ){
274 $this->compileFile( $tpl_basename, $tpl_basedir, $this->tpl['tpl_filename'], self::$cache_dir, $this->tpl['compiled_filename'] );
275 return true;
276 }
277 $this->tpl['checked'] = true;
278 }
279 }
280
281
282 /**
283 * execute stripslaches() on the xml block. Invoqued by preg_replace_callback function below
284 * @access protected
285 */
286 protected function xml_reSubstitution($capture) {
287 return "<?php echo '<?xml ".stripslashes($capture[1])." ?>'; ?>";
288 }
289
290 /**
291 * Compile and write the compiled template file
292 * @access protected
293 */
294 protected function compileFile( $tpl_basename, $tpl_basedir, $tpl_filename, $cache_dir, $compiled_filename ){
295
296 //read template file
297 $this->tpl['source'] = $template_code = file_get_contents( $tpl_filename );
298
299 //xml substitution
300 $template_code = preg_replace( "/<\?xml(.*?)\?>/s", "##XML\\1XML##", $template_code );
301
302 //disable php tag
303 if( !self::$php_enabled )
304 $template_code = str_replace( array("<?","?>"), array("&lt;?","?&gt;"), $template_code );
305
306 //xml re-substitution
307 $template_code = preg_replace_callback ( "/##XML(.*?)XML##/s", array($this, 'xml_reSubstitution'), $template_code );
308
309 //compile template
310 $template_compiled = "<?php if(!class_exists('raintpl')){exit;}?>" . $this->compileTemplate( $template_code, $tpl_basedir );
311
312
313 // fix the php-eating-newline-after-closing-tag-problem
314 $template_compiled = str_replace( "?>\n", "?>\n\n", $template_compiled );
315
316 // create directories
317 if( !is_dir( $cache_dir ) )
318 mkdir( $cache_dir, 0755, true );
319
320 if( !is_writable( $cache_dir ) )
321 throw new RainTpl_Exception ('Cache directory ' . $cache_dir . 'doesn\'t have write permission. Set write permission or set RAINTPL_CHECK_TEMPLATE_UPDATE to false. More details on http://www.raintpl.com/Documentation/Documentation-for-PHP-developers/Configuration/');
322
323 //write compiled file
324 file_put_contents( $compiled_filename, $template_compiled );
325 }
326
327
328
329 /**
330 * Compile template
331 * @access protected
332 */
333 protected function compileTemplate( $template_code, $tpl_basedir ){
334
335 //tag list
336 $tag_regexp = array( 'loop' => '(\{loop(?: name){0,1}="\${0,1}[^"]*"\})',
337 'loop_close' => '(\{\/loop\})',
338 'if' => '(\{if(?: condition){0,1}="[^"]*"\})',
339 'elseif' => '(\{elseif(?: condition){0,1}="[^"]*"\})',
340 'else' => '(\{else\})',
341 'if_close' => '(\{\/if\})',
342 'function' => '(\{function="[^"]*"\})',
343 'noparse' => '(\{noparse\})',
344 'noparse_close'=> '(\{\/noparse\})',
345 'ignore' => '(\{ignore\}|\{\*)',
346 'ignore_close' => '(\{\/ignore\}|\*\})',
347 'include' => '(\{include="[^"]*"(?: cache="[^"]*")?\})',
348 'template_info'=> '(\{\$template_info\})',
349 'function' => '(\{function="(\w*?)(?:.*?)"\})'
350 );
351
352 $tag_regexp = "/" . join( "|", $tag_regexp ) . "/";
353
354 //split the code with the tags regexp
355 $template_code = preg_split ( $tag_regexp, $template_code, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
356
357 //path replace (src of img, background and href of link)
358 $template_code = $this->path_replace( $template_code, $tpl_basedir );
359
360 //compile the code
361 $compiled_code = $this->compileCode( $template_code );
362
363 //return the compiled code
364 return $compiled_code;
365
366 }
367
368
369
370 /**
371 * Compile the code
372 * @access protected
373 */
374 protected function compileCode( $parsed_code ){
375
376 //variables initialization
377 $compiled_code = $open_if = $comment_is_open = $ignore_is_open = null;
378 $loop_level = 0;
379
380 //read all parsed code
381 while( $html = array_shift( $parsed_code ) ){
382
383 //close ignore tag
384 if( !$comment_is_open && ( strpos( $html, '{/ignore}' ) !== FALSE || strpos( $html, '*}' ) !== FALSE ) )
385 $ignore_is_open = false;
386
387 //code between tag ignore id deleted
388 elseif( $ignore_is_open ){
389 //ignore the code
390 }
391
392 //close no parse tag
393 elseif( strpos( $html, '{/noparse}' ) !== FALSE )
394 $comment_is_open = false;
395
396 //code between tag noparse is not compiled
397 elseif( $comment_is_open )
398 $compiled_code .= $html;
399
400 //ignore
401 elseif( strpos( $html, '{ignore}' ) !== FALSE || strpos( $html, '{*' ) !== FALSE )
402 $ignore_is_open = true;
403
404 //noparse
405 elseif( strpos( $html, '{noparse}' ) !== FALSE )
406 $comment_is_open = true;
407
408 //include tag
409 elseif( preg_match( '/\{include="([^"]*)"(?: cache="([^"]*)"){0,1}\}/', $html, $code ) ){
410
411 //variables substitution
412 $include_var = $this->var_replace( $code[ 1 ], $left_delimiter = null, $right_delimiter = null, $php_left_delimiter = '".' , $php_right_delimiter = '."', $loop_level );
413
414 // if the cache is active
415 if( isset($code[ 2 ]) ){
416
417 //dynamic include
418 $compiled_code .= '<?php $tpl = new '.get_class($this).';' .
419 'if( $cache = $tpl->cache( $template = basename("'.$include_var.'") ) )' .
420 ' echo $cache;' .
421 'else{' .
422 ' $tpl_dir_temp = self::$tpl_dir;' .
423 ' $tpl->assign( $this->var );' .
424 ( !$loop_level ? null : '$tpl->assign( "key", $key'.$loop_level.' ); $tpl->assign( "value", $value'.$loop_level.' );' ).
425 ' $tpl->draw( dirname("'.$include_var.'") . ( substr("'.$include_var.'",-1,1) != "/" ? "/" : "" ) . basename("'.$include_var.'") );'.
426 '} ?>';
427 }
428 else{
429
430 //dynamic include
431 $compiled_code .= '<?php $tpl = new '.get_class($this).';' .
432 '$tpl_dir_temp = self::$tpl_dir;' .
433 '$tpl->assign( $this->var );' .
434 ( !$loop_level ? null : '$tpl->assign( "key", $key'.$loop_level.' ); $tpl->assign( "value", $value'.$loop_level.' );' ).
435 '$tpl->draw( dirname("'.$include_var.'") . ( substr("'.$include_var.'",-1,1) != "/" ? "/" : "" ) . basename("'.$include_var.'") );'.
436 '?>';
437
438
439 }
440
441 }
442
443 //loop
444 elseif( preg_match( '/\{loop(?: name){0,1}="\${0,1}([^"]*)"\}/', $html, $code ) ){
445
446 //increase the loop counter
447 $loop_level++;
448
449 //replace the variable in the loop
450 $var = $this->var_replace( '$' . $code[ 1 ], $tag_left_delimiter=null, $tag_right_delimiter=null, $php_left_delimiter=null, $php_right_delimiter=null, $loop_level-1 );
451
452 //loop variables
453 $counter = "\$counter$loop_level"; // count iteration
454 $key = "\$key$loop_level"; // key
455 $value = "\$value$loop_level"; // value
456
457 //loop code
458 $compiled_code .= "<?php $counter=-1; if( isset($var) && is_array($var) && sizeof($var) ) foreach( $var as $key => $value ){ $counter++; ?>";
459
460 }
461
462 //close loop tag
463 elseif( strpos( $html, '{/loop}' ) !== FALSE ) {
464
465 //iterator
466 $counter = "\$counter$loop_level";
467
468 //decrease the loop counter
469 $loop_level--;
470
471 //close loop code
472 $compiled_code .= "<?php } ?>";
473
474 }
475
476 //if
477 elseif( preg_match( '/\{if(?: condition){0,1}="([^"]*)"\}/', $html, $code ) ){
478
479 //increase open if counter (for intendation)
480 $open_if++;
481
482 //tag
483 $tag = $code[ 0 ];
484
485 //condition attribute
486 $condition = $code[ 1 ];
487
488 // check if there's any function disabled by black_list
489 $this->function_check( $tag );
490
491 //variable substitution into condition (no delimiter into the condition)
492 $parsed_condition = $this->var_replace( $condition, $tag_left_delimiter = null, $tag_right_delimiter = null, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level );
493
494 //if code
495 $compiled_code .= "<?php if( $parsed_condition ){ ?>";
496
497 }
498
499 //elseif
500 elseif( preg_match( '/\{elseif(?: condition){0,1}="([^"]*)"\}/', $html, $code ) ){
501
502 //tag
503 $tag = $code[ 0 ];
504
505 //condition attribute
506 $condition = $code[ 1 ];
507
508 //variable substitution into condition (no delimiter into the condition)
509 $parsed_condition = $this->var_replace( $condition, $tag_left_delimiter = null, $tag_right_delimiter = null, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level );
510
511 //elseif code
512 $compiled_code .= "<?php }elseif( $parsed_condition ){ ?>";
513 }
514
515 //else
516 elseif( strpos( $html, '{else}' ) !== FALSE ) {
517
518 //else code
519 $compiled_code .= '<?php }else{ ?>';
520
521 }
522
523 //close if tag
524 elseif( strpos( $html, '{/if}' ) !== FALSE ) {
525
526 //decrease if counter
527 $open_if--;
528
529 // close if code
530 $compiled_code .= '<?php } ?>';
531
532 }
533
534 //function
535 elseif( preg_match( '/\{function="(\w*)(.*?)"\}/', $html, $code ) ){
536
537 //tag
538 $tag = $code[ 0 ];
539
540 //function
541 $function = $code[ 1 ];
542
543 // check if there's any function disabled by black_list
544 $this->function_check( $tag );
545
546 if( empty( $code[ 2 ] ) )
547 $parsed_function = $function . "()";
548 else
549 // parse the function
550 $parsed_function = $function . $this->var_replace( $code[ 2 ], $tag_left_delimiter = null, $tag_right_delimiter = null, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level );
551
552 //if code
553 $compiled_code .= "<?php echo $parsed_function; ?>";
554 }
555
556 // show all vars
557 elseif ( strpos( $html, '{$template_info}' ) !== FALSE ) {
558
559 //tag
560 $tag = '{$template_info}';
561
562 //if code
563 $compiled_code .= '<?php echo "<pre>"; print_r( $this->var ); echo "</pre>"; ?>';
564 }
565
566
567 //all html code
568 else{
569
570 //variables substitution (es. {$title})
571 $html = $this->var_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true );
572 //const substitution (es. {#CONST#})
573 $html = $this->const_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true );
574 //functions substitution (es. {"string"|functions})
575 $compiled_code .= $this->func_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true );
576 }
577 }
578
579 if( $open_if > 0 ) {
580 $e = new RainTpl_SyntaxException('Error! You need to close an {if} tag in ' . $this->tpl['tpl_filename'] . ' template');
581 throw $e->setTemplateFile($this->tpl['tpl_filename']);
582 }
583 return $compiled_code;
584 }
585
586
587 /**
588 * Reduce a path, eg. www/library/../filepath//file => www/filepath/file
589 * @param type $path
590 * @return type
591 */
592 protected function reduce_path( $path ){
593 $path = str_replace( "://", "@not_replace@", $path );
594 $path = str_replace( "//", "/", $path );
595 $path = str_replace( "@not_replace@", "://", $path );
596 return preg_replace('/\w+\/\.\.\//', '', $path );
597 }
598
599
600
601 /**
602 * replace the path of image src, link href and a href.
603 * url => template_dir/url
604 * url# => url
605 * http://url => http://url
606 *
607 * @param string $html
608 * @return string html sostituito
609 */
610 protected function path_replace( $html, $tpl_basedir ){
611
612 if( self::$path_replace ){
613
614 $tpl_dir = self::$base_url . self::$tpl_dir . $tpl_basedir;
615
616 // reduce the path
617 $path = $this->reduce_path($tpl_dir);
618
619 $exp = $sub = array();
620
621 if( in_array( "img", self::$path_replace_list ) ){
622 $exp = array( '/<img(.*?)src=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<img(.*?)src=(?:")([^"]+?)#(?:")/i', '/<img(.*?)src="(.*?)"/', '/<img(.*?)src=(?:\@)([^"]+?)(?:\@)/i' );
623 $sub = array( '<img$1src=@$2://$3@', '<img$1src=@$2@', '<img$1src="' . $path . '$2"', '<img$1src="$2"' );
624 }
625
626 if( in_array( "script", self::$path_replace_list ) ){
627 $exp = array_merge( $exp , array( '/<script(.*?)src=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<script(.*?)src=(?:")([^"]+?)#(?:")/i', '/<script(.*?)src="(.*?)"/', '/<script(.*?)src=(?:\@)([^"]+?)(?:\@)/i' ) );
628 $sub = array_merge( $sub , array( '<script$1src=@$2://$3@', '<script$1src=@$2@', '<script$1src="' . $path . '$2"', '<script$1src="$2"' ) );
629 }
630
631 if( in_array( "link", self::$path_replace_list ) ){
632 $exp = array_merge( $exp , array( '/<link(.*?)href=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<link(.*?)href=(?:")([^"]+?)#(?:")/i', '/<link(.*?)href="(.*?)"/', '/<link(.*?)href=(?:\@)([^"]+?)(?:\@)/i' ) );
633 $sub = array_merge( $sub , array( '<link$1href=@$2://$3@', '<link$1href=@$2@' , '<link$1href="' . $path . '$2"', '<link$1href="$2"' ) );
634 }
635
636 if( in_array( "a", self::$path_replace_list ) ){
637 $exp = array_merge( $exp , array( '/<a(.*?)href=(?:")(http\:\/\/|https\:\/\/|javascript:)([^"]+?)(?:")/i', '/<a(.*?)href="(.*?)"/', '/<a(.*?)href=(?:\@)([^"]+?)(?:\@)/i' ) );
638 $sub = array_merge( $sub , array( '<a$1href=@$2$3@', '<a$1href="' . self::$base_url . '$2"', '<a$1href="$2"' ) );
639 }
640
641 if( in_array( "input", self::$path_replace_list ) ){
642 $exp = array_merge( $exp , array( '/<input(.*?)src=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<input(.*?)src=(?:")([^"]+?)#(?:")/i', '/<input(.*?)src="(.*?)"/', '/<input(.*?)src=(?:\@)([^"]+?)(?:\@)/i' ) );
643 $sub = array_merge( $sub , array( '<input$1src=@$2://$3@', '<input$1src=@$2@', '<input$1src="' . $path . '$2"', '<input$1src="$2"' ) );
644 }
645
646 return preg_replace( $exp, $sub, $html );
647
648 }
649 else
650 return $html;
651
652 }
653
654
655
656
657
658 // replace const
659 function const_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){
660 // const
661 return preg_replace( '/\{\#(\w+)\#{0,1}\}/', $php_left_delimiter . ( $echo ? " echo " : null ) . '\\1' . $php_right_delimiter, $html );
662 }
663
664
665
666 // replace functions/modifiers on constants and strings
667 function func_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){
668
669 preg_match_all( '/' . '\{\#{0,1}(\"{0,1}.*?\"{0,1})(\|\w.*?)\#{0,1}\}' . '/', $html, $matches );
670
671 for( $i=0, $n=count($matches[0]); $i<$n; $i++ ){
672
673 //complete tag ex: {$news.title|substr:0,100}
674 $tag = $matches[ 0 ][ $i ];
675
676 //variable name ex: news.title
677 $var = $matches[ 1 ][ $i ];
678
679 //function and parameters associate to the variable ex: substr:0,100
680 $extra_var = $matches[ 2 ][ $i ];
681
682 // check if there's any function disabled by black_list
683 $this->function_check( $tag );
684
685 $extra_var = $this->var_replace( $extra_var, null, null, null, null, $loop_level );
686
687
688 // check if there's an operator = in the variable tags, if there's this is an initialization so it will not output any value
689 $is_init_variable = preg_match( "/^(\s*?)\=[^=](.*?)$/", $extra_var );
690
691 //function associate to variable
692 $function_var = ( $extra_var and $extra_var[0] == '|') ? substr( $extra_var, 1 ) : null;
693
694 //variable path split array (ex. $news.title o $news[title]) or object (ex. $news->title)
695 $temp = preg_split( "/\.|\[|\-\>/", $var );
696
697 //variable name
698 $var_name = $temp[ 0 ];
699
700 //variable path
701 $variable_path = substr( $var, strlen( $var_name ) );
702
703 //parentesis transform [ e ] in [" e in "]
704 $variable_path = str_replace( '[', '["', $variable_path );
705 $variable_path = str_replace( ']', '"]', $variable_path );
706
707 //transform .$variable in ["$variable"]
708 $variable_path = preg_replace('/\.\$(\w+)/', '["$\\1"]', $variable_path );
709
710 //transform [variable] in ["variable"]
711 $variable_path = preg_replace('/\.(\w+)/', '["\\1"]', $variable_path );
712
713 //if there's a function
714 if( $function_var ){
715
716 // check if there's a function or a static method and separate, function by parameters
717 $function_var = str_replace("::", "@double_dot@", $function_var );
718
719 // get the position of the first :
720 if( $dot_position = strpos( $function_var, ":" ) ){
721
722 // get the function and the parameters
723 $function = substr( $function_var, 0, $dot_position );
724 $params = substr( $function_var, $dot_position+1 );
725
726 }
727 else{
728
729 //get the function
730 $function = str_replace( "@double_dot@", "::", $function_var );
731 $params = null;
732
733 }
734
735 // replace back the @double_dot@ with ::
736 $function = str_replace( "@double_dot@", "::", $function );
737 $params = str_replace( "@double_dot@", "::", $params );
738
739
740 }
741 else
742 $function = $params = null;
743
744 $php_var = $var_name . $variable_path;
745
746 // compile the variable for php
747 if( isset( $function ) ){
748 if( $php_var )
749 $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $php_var, $params ) )" : "$function( $php_var )" ) . $php_right_delimiter;
750 else
751 $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $params ) )" : "$function()" ) . $php_right_delimiter;
752 }
753 else
754 $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . $php_var . $extra_var . $php_right_delimiter;
755
756 $html = str_replace( $tag, $php_var, $html );
757
758 }
759
760 return $html;
761
762 }
763
764
765
766 function var_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){
767
768 //all variables
769 if( preg_match_all( '/' . $tag_left_delimiter . '\$(\w+(?:\.\${0,1}[A-Za-z0-9_]+)*(?:(?:\[\${0,1}[A-Za-z0-9_]+\])|(?:\-\>\${0,1}[A-Za-z0-9_]+))*)(.*?)' . $tag_right_delimiter . '/', $html, $matches ) ){
770
771 for( $parsed=array(), $i=0, $n=count($matches[0]); $i<$n; $i++ )
772 $parsed[$matches[0][$i]] = array('var'=>$matches[1][$i],'extra_var'=>$matches[2][$i]);
773
774 foreach( $parsed as $tag => $array ){
775
776 //variable name ex: news.title
777 $var = $array['var'];
778
779 //function and parameters associate to the variable ex: substr:0,100
780 $extra_var = $array['extra_var'];
781
782 // check if there's any function disabled by black_list
783 $this->function_check( $tag );
784
785 $extra_var = $this->var_replace( $extra_var, null, null, null, null, $loop_level );
786
787 // check if there's an operator = in the variable tags, if there's this is an initialization so it will not output any value
788 $is_init_variable = preg_match( "/^[a-z_A-Z\.\[\](\-\>)]*=[^=]*$/", $extra_var );
789
790 //function associate to variable
791 $function_var = ( $extra_var and $extra_var[0] == '|') ? substr( $extra_var, 1 ) : null;
792
793 //variable path split array (ex. $news.title o $news[title]) or object (ex. $news->title)
794 $temp = preg_split( "/\.|\[|\-\>/", $var );
795
796 //variable name
797 $var_name = $temp[ 0 ];
798
799 //variable path
800 $variable_path = substr( $var, strlen( $var_name ) );
801
802 //parentesis transform [ e ] in [" e in "]
803 $variable_path = str_replace( '[', '["', $variable_path );
804 $variable_path = str_replace( ']', '"]', $variable_path );
805
806 //transform .$variable in ["$variable"] and .variable in ["variable"]
807 $variable_path = preg_replace('/\.(\${0,1}\w+)/', '["\\1"]', $variable_path );
808
809 // if is an assignment also assign the variable to $this->var['value']
810 if( $is_init_variable )
811 $extra_var = "=\$this->var['{$var_name}']{$variable_path}" . $extra_var;
812
813
814
815 //if there's a function
816 if( $function_var ){
817
818 // check if there's a function or a static method and separate, function by parameters
819 $function_var = str_replace("::", "@double_dot@", $function_var );
820
821
822 // get the position of the first :
823 if( $dot_position = strpos( $function_var, ":" ) ){
824
825 // get the function and the parameters
826 $function = substr( $function_var, 0, $dot_position );
827 $params = substr( $function_var, $dot_position+1 );
828
829 }
830 else{
831
832 //get the function
833 $function = str_replace( "@double_dot@", "::", $function_var );
834 $params = null;
835
836 }
837
838 // replace back the @double_dot@ with ::
839 $function = str_replace( "@double_dot@", "::", $function );
840 $params = str_replace( "@double_dot@", "::", $params );
841 }
842 else
843 $function = $params = null;
844
845 //if it is inside a loop
846 if( $loop_level ){
847 //verify the variable name
848 if( $var_name == 'key' )
849 $php_var = '$key' . $loop_level;
850 elseif( $var_name == 'value' )
851 $php_var = '$value' . $loop_level . $variable_path;
852 elseif( $var_name == 'counter' )
853 $php_var = '$counter' . $loop_level;
854 else
855 $php_var = '$' . $var_name . $variable_path;
856 }else
857 $php_var = '$' . $var_name . $variable_path;
858
859 // compile the variable for php
860 if( isset( $function ) )
861 $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $php_var, $params ) )" : "$function( $php_var )" ) . $php_right_delimiter;
862 else
863 $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . $php_var . $extra_var . $php_right_delimiter;
864
865 $html = str_replace( $tag, $php_var, $html );
866
867
868 }
869 }
870
871 return $html;
872 }
873
874
875
876 /**
877 * Check if function is in black list (sandbox)
878 *
879 * @param string $code
880 * @param string $tag
881 */
882 protected function function_check( $code ){
883
884 $preg = '#(\W|\s)' . implode( '(\W|\s)|(\W|\s)', self::$black_list ) . '(\W|\s)#';
885
886 // check if the function is in the black list (or not in white list)
887 if( count(self::$black_list) && preg_match( $preg, $code, $match ) ){
888
889 // find the line of the error
890 $line = 0;
891 $rows=explode("\n",$this->tpl['source']);
892 while( !strpos($rows[$line],$code) )
893 $line++;
894
895 // stop the execution of the script
896 $e = new RainTpl_SyntaxException('Unallowed syntax in ' . $this->tpl['tpl_filename'] . ' template');
897 throw $e->setTemplateFile($this->tpl['tpl_filename'])
898 ->setTag($code)
899 ->setTemplateLine($line);
900 }
901
902 }
903
904 /**
905 * Prints debug info about exception or passes it further if debug is disabled.
906 *
907 * @param RainTpl_Exception $e
908 * @return string
909 */
910 protected function printDebug(RainTpl_Exception $e){
911 if (!self::$debug) {
912 throw $e;
913 }
914 $output = sprintf('<h2>Exception: %s</h2><h3>%s</h3><p>template: %s</p>',
915 get_class($e),
916 $e->getMessage(),
917 $e->getTemplateFile()
918 );
919 if ($e instanceof RainTpl_SyntaxException) {
920 if (null != $e->getTemplateLine()) {
921 $output .= '<p>line: ' . $e->getTemplateLine() . '</p>';
922 }
923 if (null != $e->getTag()) {
924 $output .= '<p>in tag: ' . htmlspecialchars($e->getTag()) . '</p>';
925 }
926 if (null != $e->getTemplateLine() && null != $e->getTag()) {
927 $rows=explode("\n", htmlspecialchars($this->tpl['source']));
928 $rows[$e->getTemplateLine()] = '<font color=red>' . $rows[$e->getTemplateLine()] . '</font>';
929 $output .= '<h3>template code</h3>' . implode('<br />', $rows) . '</pre>';
930 }
931 }
932 $output .= sprintf('<h3>trace</h3><p>In %s on line %d</p><pre>%s</pre>',
933 $e->getFile(), $e->getLine(),
934 nl2br(htmlspecialchars($e->getTraceAsString()))
935 );
936 return $output;
937 }
938}
939
940
941/**
942 * Basic Rain tpl exception.
943 */
944class RainTpl_Exception extends Exception{
945 /**
946 * Path of template file with error.
947 */
948 protected $templateFile = '';
949
950 /**
951 * Returns path of template file with error.
952 *
953 * @return string
954 */
955 public function getTemplateFile()
956 {
957 return $this->templateFile;
958 }
959
960 /**
961 * Sets path of template file with error.
962 *
963 * @param string $templateFile
964 * @return RainTpl_Exception
965 */
966 public function setTemplateFile($templateFile)
967 {
968 $this->templateFile = (string) $templateFile;
969 return $this;
970 }
971}
972
973/**
974 * Exception thrown when template file does not exists.
975 */
976class RainTpl_NotFoundException extends RainTpl_Exception{
977}
978
979/**
980 * Exception thrown when syntax error occurs.
981 */
982class RainTpl_SyntaxException extends RainTpl_Exception{
983 /**
984 * Line in template file where error has occured.
985 *
986 * @var int | null
987 */
988 protected $templateLine = null;
989
990 /**
991 * Tag which caused an error.
992 *
993 * @var string | null
994 */
995 protected $tag = null;
996
997 /**
998 * Returns line in template file where error has occured
999 * or null if line is not defined.
1000 *
1001 * @return int | null
1002 */
1003 public function getTemplateLine()
1004 {
1005 return $this->templateLine;
1006 }
1007
1008 /**
1009 * Sets line in template file where error has occured.
1010 *
1011 * @param int $templateLine
1012 * @return RainTpl_SyntaxException
1013 */
1014 public function setTemplateLine($templateLine)
1015 {
1016 $this->templateLine = (int) $templateLine;
1017 return $this;
1018 }
1019
1020 /**
1021 * Returns tag which caused an error.
1022 *
1023 * @return string
1024 */
1025 public function getTag()
1026 {
1027 return $this->tag;
1028 }
1029
1030 /**
1031 * Sets tag which caused an error.
1032 *
1033 * @param string $tag
1034 * @return RainTpl_SyntaxException
1035 */
1036 public function setTag($tag)
1037 {
1038 $this->tag = (string) $tag;
1039 return $this;
1040 }
1041}
1042
1043// -- end
diff --git a/index.php b/index.php
new file mode 100755
index 00000000..8861d31e
--- /dev/null
+++ b/index.php
@@ -0,0 +1,97 @@
1<?php
2/**
3 * poche, a read it later open source system
4 *
5 * @category poche
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file
9 */
10
11/**
12 * TODO
13 * gestion des erreurs sqlite (duplicate tout ça)
14 * gérer si url vide
15 * traiter les variables passées en get
16 * récupérer le titre de la page pochée (cf readityourself.php)
17 * actions archive, fav et delete à traiter
18 * bookmarklet
19 * améliorer présentation des liens
20 * améliorer présentation d'un article
21 * aligner verticalement les icones d'action
22 * afficher liens mis en favoris et archivés
23 * tri des liens
24 */
25
26try
27{
28 $db_handle = new PDO('sqlite:poche.sqlite');
29 $db_handle->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
30}
31catch (Exception $e)
32{
33 die('error : '.$e->getMessage());
34}
35
36$action = (isset ($_GET['action'])) ? htmlspecialchars($_GET['action']) : '';
37
38switch ($action) {
39 case 'add':
40 $url = (isset ($_GET['url'])) ? htmlspecialchars($_GET['url']) : '';
41 $title = $url;
42 $query = $db_handle->prepare('INSERT INTO entries ( url, title ) VALUES (?, ?)');
43 $query->execute(array($url, $title));
44 break;
45 case 'archive':
46 break;
47 case 'fav' :
48 break;
49 case 'delete':
50 break;
51 default:
52 break;
53}
54?>
55<!DOCTYPE html>
56<!--[if lte IE 6]> <html class="no-js ie6 ie67 ie678" lang="en"> <![endif]-->
57<!--[if lte IE 7]> <html class="no-js ie7 ie67 ie678" lang="en"> <![endif]-->
58<!--[if IE 8]> <html class="no-js ie8 ie678" lang="en"> <![endif]-->
59<!--[if gt IE 8]><!--> <html class="no-js" lang="en"> <!--<![endif]-->
60<html>
61 <head>
62 <meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0">
63 <meta charset="utf-8">
64 <meta http-equiv="X-UA-Compatible" content="IE=10">
65 <title>poche : queue</title>
66 <link rel="stylesheet" href="css/knacss.css" media="all">
67 <link rel="stylesheet" href="css/style.css" media="all">
68 </head>
69 <body>
70 <header>
71 <h1>poche, a read it later open source system</h1>
72 </header>
73 <div id="main" class="w800p">
74 <ul id="links">
75 <li><a href="index.php">home</a></li>
76 <li><a href="#">favorites</a></li>
77 <li><a href="#">archive</a></li>
78 <li><a href="javascript:(function(){var%20url%20=%20location.href;var%20title%20=%20document.title%20||%20url;window.open('http://localhost/poche/index.php?action=add&url='%20+%20encodeURIComponent(url),'_self');})();">bookmarklet</a></li>
79 </ul>
80 <?php
81 $query = $db_handle->prepare("SELECT * FROM entries WHERE read=?");
82 $query->execute(array('FALSE'));
83 $entries = $query->fetchAll();
84 ?>
85 <ul id="entries">
86 <?php
87 foreach ($entries as $entry) {
88 echo '<li><a href="readityourself.php?url='.urlencode($entry['url']).'">' . $entry['title'] . '</a> <a href="#" title="toggle delete" class="tool">&#10003;</a> <a href="#" title="toggle favorite" class="tool">&#9734;</a> <a href="#" title="toggle mark as read" class="tool">&#10799;</a></li>';
89 }
90 ?>
91 </ul>
92 </div>
93 <footer class="mr2 mt3">
94 <p class="smaller">poche is a read it later open source system, based on <a href="http://www.memiks.fr/readityourself/">ReadItYourself</a>. poche is developed by <a href="http://nicolas.loeuillet.org">Nicolas Lœuillet</a> under the <a href="http://www.wtfpl.net/">Do What the Fuck You Want to Public License</a></p>
95 </footer>
96 </body>
97</html> \ No newline at end of file
diff --git a/poche.sqlite b/poche.sqlite
new file mode 100755
index 00000000..0eea873f
--- /dev/null
+++ b/poche.sqlite
Binary files differ
diff --git a/readityourself.php b/readityourself.php
new file mode 100755
index 00000000..4a4661c2
--- /dev/null
+++ b/readityourself.php
@@ -0,0 +1,212 @@
1<?php
2
3define("VERSION", "0.0.3");
4
5header('Content-type:text/html; charset=utf-8');
6// Set locale to French
7setlocale(LC_ALL, 'fr_FR');
8
9// set timezone to Europe/Paris
10date_default_timezone_set('Europe/Paris');
11
12// set charset to utf-8 important since all pages will be transform to utf-8
13header('Content-Type: text/html;charset=utf-8');
14
15// get readability library
16require_once dirname(__FILE__).'/inc/Readability.php';
17
18// get Encoding library.
19require_once dirname(__FILE__).'/inc/Encoding.php';
20
21// appel de la libraire RainTPL.
22require_once dirname(__FILE__).'/inc/rain.tpl.class.php';
23
24// FUNCTIONS BEGIN
25
26
27
28function url(){
29 $protocol = ($_SERVER['HTTPS'] && $_SERVER['HTTPS'] != "off") ? "https" : "http";
30 return $protocol . "://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
31}
32
33function generate_page($url,$title,$content) {
34 raintpl::$tpl_dir = './tpl/'; // template directory
35 raintpl::$cache_dir = "./cache/"; // cache directory
36 raintpl::$base_url = url(); // base URL of blog
37 raintpl::configure( 'path_replace', false );
38 raintpl::configure('debug', false);
39
40 $tpl = new raintpl(); //include Rain TPL
41
42 $tpl->assign( "url", $url);
43 $tpl->assign( "title", $title);
44 $tpl->assign( "content", $content);
45
46 $tpl->assign( "version", VERSION);
47
48 $tpl->draw( "index"); // draw the template
49}
50
51// function define to retrieve url content
52function get_external_file($url, $timeout) {
53 // spoofing FireFox 18.0
54 $useragent="Mozilla/5.0 (Windows NT 5.1; rv:18.0) Gecko/20100101 Firefox/18.0";
55
56 if (in_array ('curl', get_loaded_extensions())) {
57 // Fetch feed from URL
58 $curl = curl_init();
59 curl_setopt($curl, CURLOPT_URL, $url);
60 curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
61 curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
62 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
63 curl_setopt($curl, CURLOPT_HEADER, false);
64
65 // FeedBurner requires a proper USER-AGENT...
66 curl_setopt($curl, CURL_HTTP_VERSION_1_1, true);
67 curl_setopt($curl, CURLOPT_ENCODING, "gzip, deflate");
68 curl_setopt($curl, CURLOPT_USERAGENT, $useragent);
69
70 $data = curl_exec($curl);
71
72 $httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
73
74 $httpcodeOK = isset($httpcode) and ($httpcode == 200 or $httpcode == 301);
75
76 curl_close($curl);
77 } else {
78
79 // create http context and add timeout and user-agent
80 $context = stream_context_create(array('http'=>array('timeout' => $timeout, // Timeout : time until we stop waiting for the response.
81 'header'=> "User-Agent: ".$useragent, // spoot Mozilla Firefox
82 'follow_location' => true
83 )));
84
85 // only download page lesser than 4MB
86 $data = @file_get_contents($url, false, $context, -1, 4000000); // We download at most 4 MB from source.
87 // echo "<pre>http_response_header : ".print_r($http_response_header);
88
89 if(isset($http_response_header) and isset($http_response_header[0])) {
90 $httpcodeOK = isset($http_response_header) and isset($http_response_header[0]) and ((strpos($http_response_header[0], '200 OK') !== FALSE) or (strpos($http_response_header[0], '301 Moved Permanently') !== FALSE));
91 }
92 }
93
94 // if response is not empty and response is OK
95 if (isset($data) and isset($httpcodeOK) and httpcodeOK ) {
96
97 // take charset of page and get it
98 preg_match('#<meta .*charset=.*>#Usi', $data, $meta);
99
100 // if meta tag is found
101 if (!empty($meta[0])) {
102 // retrieve encoding in $enc
103 preg_match('#charset="?(.*)"#si', $meta[0], $enc);
104
105 // if charset is found set it otherwise, set it to utf-8
106 $html_charset = (!empty($enc[1])) ? strtolower($enc[1]) : 'utf-8';
107
108 } else {
109 $html_charset = 'utf-8';
110 $enc[1] = '';
111 }
112
113 // replace charset of url to charset of page
114 $data = str_replace('charset='.$enc[1], 'charset='.$html_charset, $data);
115
116 return $data;
117 }
118 else {
119 return FALSE;
120 }
121}
122
123function rel2abs($rel, $base)
124{
125 /* return if already absolute URL */
126 if (parse_url($rel, PHP_URL_SCHEME) != '') return $rel;
127
128 /* queries and anchors */
129 if ($rel[0]=='#' || $rel[0]=='?') return $base.$rel;
130
131 /* parse base URL and convert to local variables:
132 $scheme, $host, $path */
133 extract(parse_url($base));
134
135 /* remove non-directory element from path */
136 $path = preg_replace('#/[^/]*$#', '', $path);
137
138 /* destroy path if relative url points to root */
139 if ($rel[0] == '/') $path = '';
140
141 /* dirty absolute URL */
142 $abs = "$host$path/$rel";
143
144 /* replace '//' or '/./' or '/foo/../' with '/' */
145 $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#');
146 for($n=1; $n>0; $abs=preg_replace($re, '/', $abs, -1, $n)) {}
147
148 /* absolute URL is ready! */
149 return $scheme.'://'.$abs;
150}
151
152// $str=preg_replace('#(href|src)="([^:"]*)("|(?:(?:%20|\s|\+)[^"]*"))#','$1="http://wintermute.com.au/$2$3',$str);
153
154function absolutes_links($data, $base) {
155 // cherche les balises 'a' qui contiennent un href
156 $matches = array();
157 preg_match_all('#(href|src)="([^:"]*)("|(?:(?:%20|\s|\+)[^"]*"))#Si', $data, $matches, PREG_SET_ORDER);
158
159 // ne conserve que les liens ne commenant pas par un protocole protocole:// ni par une ancre #
160 foreach($matches as $i => $link) {
161 $link[1] = trim($link[1]);
162
163 if (!preg_match('#^(([a-z]+://)|(\#))#', $link[1]) ) {
164
165 $absolutePath=rel2abs($link[2],$base);
166
167 $data = str_replace($matches[$i][2], $absolutePath, $data);
168 }
169
170 }
171 return $data;
172}
173
174
175// FUNCTIONS END
176
177// EXUCUTION CODE
178
179
180if(isset($_GET['url']) && $_GET['url'] != null && trim($_GET['url']) != "") {
181 // get url link
182 if(strlen(trim($_GET['url'])) > 2048) {
183 echo "Error URL is too large !!";
184 } else {
185 $url = trim($_GET['url']);
186
187 // decode it
188 $url = html_entity_decode($url);
189
190 // if url use https protocol change it to http
191 if (!preg_match('!^https?://!i', $url)) $url = 'http://'.$url;
192
193 // convert page to utf-8
194 $html = Encoding::toUTF8(get_external_file($url,15));
195
196 if(isset($html) and strlen($html) > 0) {
197
198 // send result to readability library
199 $r = new Readability($html, $url);
200
201 if($r->init()) {
202 generate_page($url,$r->articleTitle->innerHTML,$r->articleContent->innerHTML);
203 } else {
204 // return data into an iframe
205 echo "<iframe id='readabilityframe'>".$html."</iframe>";
206 }
207 } else {
208 echo "Error unable to get link : ".$url;
209 }
210 }
211}
212?>
diff --git a/tpl/footer.html b/tpl/footer.html
new file mode 100755
index 00000000..8385b969
--- /dev/null
+++ b/tpl/footer.html
@@ -0,0 +1,7 @@
1<footer>
2 <div>
3 Copyright &copy; <a href="http://www.memiks.fr/">memiks.fr</a> | <a href="http://shaarli.memiks.fr/">Liens</a> / <a href="http://rss.memiks.fr/">RSS</a> / <a href="http://wiki.memiks.fr/">Wiki</a> / <a href="mailto:&#109;&#101;&#109;&#105;&#107;&#115;&#064;&#109;&#101;&#109;&#105;&#107;&#115;&#046;&#102;&#114;">Contact</a><br>
4 Licence: WTF Licence<br>
5 More information HERE: <a href="http://www.memiks.fr/readityourself/">http://www.memiks.fr/readityourself/</a> Version : <span class="version">{$version}</span>
6 </div>
7</footer>
diff --git a/tpl/index.html b/tpl/index.html
new file mode 100755
index 00000000..b4aba16c
--- /dev/null
+++ b/tpl/index.html
@@ -0,0 +1,18 @@
1<html>
2 <head>
3 <link rel='stylesheet' href='./css/reset.css' type='text/css' media='all' />
4 <link rel='stylesheet' href='./css/typography.css' type='text/css' media='all' />
5
6 <title>{$title}</title>
7 </head>
8 <body>
9 <article>
10 <h1><a href="{$url}">{$title}</a></h1>
11 <div id="readityourselfcontent">
12 {$content}
13 </div>
14 <span class="comeFrom">Come From : <a href="{$url}">{$url}</a>
15 </article>
16 {include="footer"}
17 </body>
18</html>