diff options
author | Nicolas Lœuillet <nicolas@coteo.com> | 2013-04-03 15:14:01 +0200 |
---|---|---|
committer | Nicolas Lœuillet <nicolas@coteo.com> | 2013-04-03 15:14:01 +0200 |
commit | 1a268ba710b2cdb4ede98af3368c43d66c4c5e53 (patch) | |
tree | 5a2f00523e7f7aefc0cd0e48254fa20aa907bb6f | |
download | wallabag-1a268ba710b2cdb4ede98af3368c43d66c4c5e53.tar.gz wallabag-1a268ba710b2cdb4ede98af3368c43d66c4c5e53.tar.zst wallabag-1a268ba710b2cdb4ede98af3368c43d66c4c5e53.zip |
first commit
-rw-r--r-- | COPYING | 14 | ||||
-rwxr-xr-x | README.md | 13 | ||||
-rw-r--r-- | cache/footer.32a3c4422ad65642b8dbb7e731f4d990.rtpl.php | 7 | ||||
-rw-r--r-- | cache/index.0b3442bc62f6c429fc13004b3c821ba0.rtpl.php | 20 | ||||
-rwxr-xr-x | css/index.html | 0 | ||||
-rw-r--r-- | css/knacss.css | 1070 | ||||
-rwxr-xr-x | css/reset.css | 35 | ||||
-rw-r--r-- | css/style.css | 35 | ||||
-rwxr-xr-x | css/typography.css | 85 | ||||
-rwxr-xr-x | inc/Encoding.php | 262 | ||||
-rwxr-xr-x | inc/JSLikeHTMLElement.php | 110 | ||||
-rwxr-xr-x | inc/Readability.php | 1103 | ||||
-rwxr-xr-x | inc/index.html | 0 | ||||
-rwxr-xr-x | inc/rain.tpl.class.php | 1043 | ||||
-rwxr-xr-x | index.php | 97 | ||||
-rwxr-xr-x | poche.sqlite | bin | 0 -> 131072 bytes | |||
-rwxr-xr-x | readityourself.php | 212 | ||||
-rwxr-xr-x | tpl/footer.html | 7 | ||||
-rwxr-xr-x | tpl/index.html | 18 |
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 | |||
3 | Abandon 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 | ||
10 | Copyright © 2010-2013 Nicolas Lœuillet <nicolas.loeuillet@gmail.com> | ||
11 | This work is free. You can redistribute it and/or modify it under the | ||
12 | terms of the Do What The Fuck You Want To Public License, Version 2, | ||
13 | as 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 © <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:memiks@memiks.fr">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 */ | ||
11 | html { | ||
12 | font-size: 62.5%; | ||
13 | } | ||
14 | body { | ||
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/ */ | ||
24 | p, | ||
25 | ul, | ||
26 | ol, | ||
27 | dl, | ||
28 | blockquote, | ||
29 | pre, | ||
30 | td, | ||
31 | th, | ||
32 | label, | ||
33 | textarea, | ||
34 | caption, | ||
35 | details, | ||
36 | figure, | ||
37 | hgroup { | ||
38 | font-size: 1em; /* equiv 14px */ | ||
39 | line-height: 1.5; | ||
40 | margin: .75em 0 0; | ||
41 | } | ||
42 | h1, .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 | } | ||
48 | h2, .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 | } | ||
54 | h3, .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 | } | ||
60 | h4, .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 | } | ||
66 | h5, .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 | } | ||
72 | h6, .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 */ | ||
97 | html, | ||
98 | body, | ||
99 | textarea, | ||
100 | figure, | ||
101 | label { | ||
102 | margin: 0; | ||
103 | padding: 0; | ||
104 | } | ||
105 | ul, | ||
106 | ol { | ||
107 | padding-left: 2em; | ||
108 | } | ||
109 | code, | ||
110 | pre, | ||
111 | samp, | ||
112 | kbd { | ||
113 | white-space: pre-wrap; | ||
114 | font-family: consolas, 'DejaVu Sans Mono', courier, monospace; | ||
115 | line-height: 1em; | ||
116 | } | ||
117 | code, kbd, mark { | ||
118 | border-radius: 2px; | ||
119 | } | ||
120 | em { | ||
121 | font-style: italic; | ||
122 | } | ||
123 | strong { | ||
124 | font-weight: bold; | ||
125 | } | ||
126 | kbd { | ||
127 | padding: 0 2px; | ||
128 | border: 1px solid #999; | ||
129 | } | ||
130 | code { | ||
131 | padding: 2px 4px; | ||
132 | background: rgba(0,0,0,.04); | ||
133 | color: #b11; | ||
134 | } | ||
135 | mark { | ||
136 | padding:2px 4px; | ||
137 | background: #ff0; | ||
138 | } | ||
139 | |||
140 | table { margin-bottom: 1.5em; } | ||
141 | |||
142 | /* avoid top margins on first content element */ | ||
143 | p:first-child, | ||
144 | ul:first-child, | ||
145 | ol:first-child, | ||
146 | dl:first-child, | ||
147 | blockquote:first-child, | ||
148 | pre:first-child, | ||
149 | h1:first-child, | ||
150 | h2:first-child, | ||
151 | h3:first-child, | ||
152 | h4:first-child, | ||
153 | h5:first-child, | ||
154 | h6:first-child { | ||
155 | margin-top: 0; | ||
156 | } | ||
157 | |||
158 | /* avoid margins on nested elements */ | ||
159 | li p, | ||
160 | li ul, | ||
161 | li ol { | ||
162 | margin-top: 0; | ||
163 | margin-bottom: 0; | ||
164 | } | ||
165 | |||
166 | /* max values */ | ||
167 | img, table, td, blockquote, code, pre, textarea, input, video { | ||
168 | max-width: 100%; | ||
169 | } | ||
170 | |||
171 | /* you shall not pass */ | ||
172 | div, 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 */ | ||
182 | img { | ||
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 | |||
190 | a img { border: 0; } | ||
191 | |||
192 | /* scripts */ | ||
193 | body > 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 | } | ||
266 | img.left { | ||
267 | margin-right: 1em; | ||
268 | } | ||
269 | |||
270 | /* right elements */ | ||
271 | .right { | ||
272 | float: right; | ||
273 | } | ||
274 | img.right { | ||
275 | margin-left: 1em; | ||
276 | } | ||
277 | |||
278 | img.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 | ||
324 | p,m = padding,margin | ||
325 | a,t,r,b,l = all,top,right,bottom,left | ||
326 | s,m,l,n = small(10px),medium(20px),large(30px),none(0) | ||
327 | source 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 | /* ----------------------------- */ | ||
400 | form, | ||
401 | fieldset { | ||
402 | border: none; | ||
403 | } | ||
404 | input, | ||
405 | button, | ||
406 | select, | ||
407 | label, | ||
408 | .btn { | ||
409 | vertical-align: middle; /* @bugfix alignment */ | ||
410 | font-family: inherit; | ||
411 | } | ||
412 | textarea { | ||
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 | |||
513 | table, | ||
514 | .table { | ||
515 | max-width : 100%; | ||
516 | table-layout: fixed; | ||
517 | border-collapse: collapse; | ||
518 | vertical-align: top; | ||
519 | } | ||
520 | table { | ||
521 | width: 100%; | ||
522 | } | ||
523 | .table { | ||
524 | display: table; | ||
525 | } | ||
526 | caption { | ||
527 | padding: 10px; | ||
528 | color: #555; | ||
529 | font-style: italic; | ||
530 | } | ||
531 | table { | ||
532 | border: 1px solid #ccc; | ||
533 | } | ||
534 | tr > * + * { | ||
535 | border-left: 1px solid #ccc; | ||
536 | } | ||
537 | th, | ||
538 | td { | ||
539 | padding: .3em .8em; | ||
540 | text-align: left; | ||
541 | border-bottom: 1px solid #ccc; | ||
542 | } | ||
543 | td { | ||
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 */ | ||
602 | label { | ||
603 | display: inline-block; | ||
604 | vertical-align: middle; | ||
605 | cursor: pointer; | ||
606 | } | ||
607 | legend { | ||
608 | border: 0; | ||
609 | white-space: normal; | ||
610 | } | ||
611 | button, | ||
612 | input, | ||
613 | select { | ||
614 | font-family: "Century Gothic", helvetica, arial, sans-serif; | ||
615 | font-size: 100%; | ||
616 | margin: 0; | ||
617 | vertical-align: middle; | ||
618 | } | ||
619 | textarea { | ||
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 | } | ||
626 | button, | ||
627 | input[type="button"], | ||
628 | input[type="reset"], | ||
629 | input[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 | } | ||
634 | input[type="checkbox"], | ||
635 | input[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 | } | ||
639 | input[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 */ | ||
645 | input[type="search"]::-webkit-search-decoration, | ||
646 | input[type="search"]::-webkit-search-cancel-button, | ||
647 | input[type="search"]::-webkit-search-results-button, | ||
648 | input[type="search"]::-webkit-search-results-decoration { | ||
649 | display: none; | ||
650 | } | ||
651 | ::-webkit-input-placeholder { color: #777; } | ||
652 | input:-moz-placeholder, | ||
653 | textarea:-moz-placeholder { color: #777; } | ||
654 | |||
655 | /* Removes inner padding and border in FF3+ */ | ||
656 | button::-moz-focus-inner, | ||
657 | input[type='button']::-moz-focus-inner, | ||
658 | input[type='reset']::-moz-focus-inner, | ||
659 | input[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 | |||
700 | ul.icon {display: block;} | ||
701 | ul.icon > li {list-style: none;} | ||
702 | ul.icon:before, | ||
703 | ul.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 | |||
853 | ol.styled {counter-reset: styled;} | ||
854 | ol.styled > li { | ||
855 | list-style-type: none; | ||
856 | counter-increment: styled; | ||
857 | margin-bottom: .3em; | ||
858 | } | ||
859 | ol.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 @@ | |||
1 | html, body, div, span, object, iframe, | ||
2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, | ||
3 | a, abbr, acronym, address, code, | ||
4 | del, dfn, em, img, q, dl, dt, dd, ol, ul, li, | ||
5 | fieldset, form, label, legend, | ||
6 | table, 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. */ | ||
19 | table { border-collapse: separate; border-spacing: 0; } | ||
20 | caption, th, td { text-align: left; font-weight: normal; } | ||
21 | table, td, th { vertical-align: middle; } | ||
22 | |||
23 | /* Remove possible quote marks (") from <q>, <blockquote>. */ | ||
24 | blockquote:before, blockquote:after, q:before, q:after { content: ""; } | ||
25 | blockquote, q { quotes: "" ""; } | ||
26 | |||
27 | /* Remove annoying border on linked images. */ | ||
28 | a img { border: none; } | ||
29 | |||
30 | |||
31 | body { | ||
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 @@ | |||
1 | body { | ||
2 | color: #222222; | ||
3 | font: 20px/1.3em Palatino,Georgia,serif; | ||
4 | background-color: #e6e6e6; | ||
5 | } | ||
6 | |||
7 | a, a:hover, a:visited { | ||
8 | color: #000; | ||
9 | } | ||
10 | header { | ||
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 | |||
33 | footer { | ||
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 | |||
2 | body { | ||
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 | |||
12 | article { | ||
13 | border: 3px solid grey; | ||
14 | max-width:700px; | ||
15 | margin: 15px auto; | ||
16 | padding: 15px; | ||
17 | } | ||
18 | |||
19 | footer { | ||
20 | border: 1px solid black; | ||
21 | padding: 15px; | ||
22 | margin: 15px auto; | ||
23 | } | ||
24 | |||
25 | p { padding:0 0 0.8125em 0; color:#111; font-weight:300;} | ||
26 | |||
27 | p + p { text-indent:1.625em;} | ||
28 | |||
29 | img { display: block; margin: 0.5em 0.8125em 0.8125em 0; padding: 0; } | ||
30 | |||
31 | p > img { display: inline-block; margin: 0; } | ||
32 | |||
33 | h1,h2{ font-weight:normal; color: #333; font-family:Georgia, serif; } | ||
34 | h3,h4,h5,h6 { font-weight: normal; color: #333; font-family:Georgia, serif; } | ||
35 | |||
36 | |||
37 | h1 { font-size: 2.125em; margin-bottom: 0.765em; line-height: 1.5em;} | ||
38 | h2 { font-size: 1.9em; margin-bottom: 0.855em; } | ||
39 | h3 { font-size: 1.7em; margin-bottom: 0.956em; } | ||
40 | h4 { font-size: 1.4em; margin-bottom: 1.161em; } | ||
41 | h5,h6 { font-size: 1.313em; margin-bottom: 1.238em; } | ||
42 | |||
43 | |||
44 | |||
45 | ul{list-style-position:outside;} | ||
46 | li ul, | ||
47 | li ol { margin:0 1.625em; } | ||
48 | ul, ol { margin: 0 0 1.625em 0; } | ||
49 | |||
50 | |||
51 | dl { margin: 0 0 1.625em 0; } | ||
52 | dl dt { font-weight: bold; } | ||
53 | dl dd { margin-left: 1.625em; } | ||
54 | |||
55 | a { color:#005AF2; text-decoration:none; } | ||
56 | a:hover { text-decoration: underline; } | ||
57 | |||
58 | |||
59 | table { margin-bottom:1.625em; border-collapse: collapse; } | ||
60 | th { font-weight:bold; } | ||
61 | tr,th,td { margin:0; padding:0 1.625em 0 1em; height:26px; } | ||
62 | tfoot { font-style: italic; } | ||
63 | caption { text-align:center; font-family:Georgia, serif; } | ||
64 | |||
65 | |||
66 | abbr, acronym { border-bottom:1px dotted #000; } | ||
67 | address { margin-top:1.625em; font-style: italic; } | ||
68 | del {color:#000;} | ||
69 | |||
70 | |||
71 | blockquote { padding:1em 1em 1.625em 1em; font-family:georgia,serif;font-style: italic; } | ||
72 | blockquote:before { content:"\201C";font-size:3em;margin-left:-.625em; font-family:georgia,serif;color:#aaa;line-height:0;}/* From Tripoli */ | ||
73 | blockquote > p {padding:0; margin:0; } | ||
74 | |||
75 | strong { font-weight: bold; } | ||
76 | em, dfn { font-style: italic; } | ||
77 | dfn { font-weight: bold; } | ||
78 | pre, code { margin: 1.625em 0; white-space: pre; } | ||
79 | pre, code, tt { font: 1em monospace; line-height: 1.5; } | ||
80 | tt { display: block; margin: 1.625em 0; } | ||
81 | hr { 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 | |||
10 | class 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 | */ | ||
35 | class 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 | ||
46 | require_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 | /* | ||
54 | if (!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']; | ||
58 | if (!preg_match('!^https?://!i', $url)) $url = 'http://'.$url; | ||
59 | $html = file_get_contents($url); | ||
60 | $r = new Readability($html, $url); | ||
61 | $r->init(); | ||
62 | echo $r->articleContent->innerHTML; | ||
63 | */ | ||
64 | |||
65 | |||
66 | class 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| ?)*){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 | |||
13 | class 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("<?","?>"), $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 | */ | ||
944 | class 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 | */ | ||
976 | class RainTpl_NotFoundException extends RainTpl_Exception{ | ||
977 | } | ||
978 | |||
979 | /** | ||
980 | * Exception thrown when syntax error occurs. | ||
981 | */ | ||
982 | class 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 | |||
26 | try | ||
27 | { | ||
28 | $db_handle = new PDO('sqlite:poche.sqlite'); | ||
29 | $db_handle->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); | ||
30 | } | ||
31 | catch (Exception $e) | ||
32 | { | ||
33 | die('error : '.$e->getMessage()); | ||
34 | } | ||
35 | |||
36 | $action = (isset ($_GET['action'])) ? htmlspecialchars($_GET['action']) : ''; | ||
37 | |||
38 | switch ($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">✓</a> <a href="#" title="toggle favorite" class="tool">☆</a> <a href="#" title="toggle mark as read" class="tool">⨯</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 | |||
3 | define("VERSION", "0.0.3"); | ||
4 | |||
5 | header('Content-type:text/html; charset=utf-8'); | ||
6 | // Set locale to French | ||
7 | setlocale(LC_ALL, 'fr_FR'); | ||
8 | |||
9 | // set timezone to Europe/Paris | ||
10 | date_default_timezone_set('Europe/Paris'); | ||
11 | |||
12 | // set charset to utf-8 important since all pages will be transform to utf-8 | ||
13 | header('Content-Type: text/html;charset=utf-8'); | ||
14 | |||
15 | // get readability library | ||
16 | require_once dirname(__FILE__).'/inc/Readability.php'; | ||
17 | |||
18 | // get Encoding library. | ||
19 | require_once dirname(__FILE__).'/inc/Encoding.php'; | ||
20 | |||
21 | // appel de la libraire RainTPL. | ||
22 | require_once dirname(__FILE__).'/inc/rain.tpl.class.php'; | ||
23 | |||
24 | // FUNCTIONS BEGIN | ||
25 | |||
26 | |||
27 | |||
28 | function url(){ | ||
29 | $protocol = ($_SERVER['HTTPS'] && $_SERVER['HTTPS'] != "off") ? "https" : "http"; | ||
30 | return $protocol . "://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; | ||
31 | } | ||
32 | |||
33 | function 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 | ||
52 | function 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 | |||
123 | function 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 | |||
154 | function 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 | |||
180 | if(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 © <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:memiks@memiks.fr">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> | ||