--- /dev/null
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE\r
+ Version 2, December 2004\r
+\r
+ Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>\r
+\r
+ Everyone is permitted to copy and distribute verbatim or modified\r
+ copies of this license document, and changing it is allowed as long\r
+ as the name is changed.\r
+\r
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE\r
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\r
+\r
+ 0. You just DO WHAT THE FUCK YOU WANT TO.\r
+\r
--- /dev/null
+# poche
+
+Abandon Pocket, Instapaper and other Readibility service : adopt poche. It is the same, but it is open source.
+
+## Usage
+
+...
+
+## License
+Copyright © 2010-2013 Nicolas Lœuillet <nicolas.loeuillet@gmail.com>
+This work is free. You can redistribute it and/or modify it under the
+terms of the Do What The Fuck You Want To Public License, Version 2,
+as published by Sam Hocevar. See the COPYING file for more details.
\ No newline at end of file
--- /dev/null
+<?php if(!class_exists('raintpl')){exit;}?><footer>
+ <div>
+ 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>
+ Licence: WTF Licence<br>
+ More information HERE: <a href="http://www.memiks.fr/readityourself/">http://www.memiks.fr/readityourself/</a> Version : <span class="version"><?php echo $version;?></span>
+ </div>
+</footer>
--- /dev/null
+<?php if(!class_exists('raintpl')){exit;}?><html>
+ <head>
+ <link rel='stylesheet' href='./css/reset.css' type='text/css' media='all' />
+ <link rel='stylesheet' href='./css/typography.css' type='text/css' media='all' />
+
+ <title><?php echo $title;?></title>
+ </head>
+ <body>
+ <article>
+ <h1><a href="<?php echo $url;?>"><?php echo $title;?></a></h1>
+ <div id="readityourselfcontent">
+ <?php echo $content;?>
+
+ </div>
+ <span class="comeFrom">Come From : <a href="<?php echo $url;?>"><?php echo $url;?></a>
+ </article>
+ <?php $tpl = new RainTPL;$tpl_dir_temp = self::$tpl_dir;$tpl->assign( $this->var );$tpl->draw( dirname("footer") . ( substr("footer",-1,1) != "/" ? "/" : "" ) . basename("footer") );?>
+
+ </body>
+</html>
--- /dev/null
+/*
+* www.KNACSS.com V2.6c @author: Raphael Goetter, Alsacreations
+* Licence CC-BY http://creativecommons.org/licenses/by/3.0/fr/
+*/
+
+/* ----------------------------- */
+/* ==reset */
+/* ----------------------------- */
+
+/* base font-size corresponds to 10px and is adapted to rem unit */
+html {
+ font-size: 62.5%;
+}
+body {
+ background-color: #fff;
+ color: #000;
+ font-family: "Century Gothic", helvetica, arial, sans-serif;
+ font-size: 1.4em; /* equiv 14px */
+ line-height: 1.5; /* adapt to your design */
+}
+
+/* font-sizing for content */
+/* preserve vertical-rythm, thanks to http://soqr.fr/vertical-rhythm/ */
+p,
+ul,
+ol,
+dl,
+blockquote,
+pre,
+td,
+th,
+label,
+textarea,
+caption,
+details,
+figure,
+hgroup {
+ font-size: 1em; /* equiv 14px */
+ line-height: 1.5;
+ margin: .75em 0 0;
+}
+h1, .h1-like {
+ font-size: 1.8571em; /* equiv 26px */
+ font-weight: normal;
+ line-height: 1.6154em;
+ margin: .8077em 0 0 0;
+}
+h2, .h2-like {
+ font-size: 1.7143em; /* equiv 24px */
+ font-weight: normal;
+ line-height: 1.75em;
+ margin: .875em 0 0 0;
+}
+h3, .h3-like {
+ font-size: 1.5714em; /* equiv 22px */
+ font-weight: normal;
+ line-height: 1.909em;
+ margin: .9545em 0 0 0;
+}
+h4, .h4-like {
+ font-size: 1.4286em; /* equiv 20px */
+ font-weight: normal;
+ line-height: 1.05em;
+ margin: 1.05em 0 0 0;
+}
+h5, .h5-like {
+ font-size: 1.2857em; /* equiv 18px */
+ font-weight: normal;
+ line-height: 1.1667em;
+ margin: 1.1667em 0 0 0;
+}
+h6, .h6-like {
+ font-size: 1.1429em; /* equiv 16px */
+ font-weight: normal;
+ line-height: 1.3125em;
+ margin: 1.3125em 0 0 0;
+}
+
+/* alternate font-sizing */
+.smaller {
+ font-size: .7143em; /* equiv 10px */
+}
+.small {
+ font-size: .8571em; /* equiv 12px */
+}
+.big {
+ font-size: 1.1429em; /* equiv 16px */
+}
+.bigger {
+ font-size: 1.2857em; /* equiv 18px */
+}
+.biggest {
+ font-size: 1.4286em; /* equiv 20px */
+}
+
+/* soft reset */
+html,
+body,
+textarea,
+figure,
+label {
+ margin: 0;
+ padding: 0;
+}
+ul,
+ol {
+ padding-left: 2em;
+}
+code,
+pre,
+samp,
+kbd {
+ white-space: pre-wrap;
+ font-family: consolas, 'DejaVu Sans Mono', courier, monospace;
+ line-height: 1em;
+}
+code, kbd, mark {
+ border-radius: 2px;
+}
+em {
+ font-style: italic;
+}
+strong {
+ font-weight: bold;
+}
+kbd {
+ padding: 0 2px;
+ border: 1px solid #999;
+}
+code {
+ padding: 2px 4px;
+ background: rgba(0,0,0,.04);
+ color: #b11;
+}
+mark {
+ padding:2px 4px;
+ background: #ff0;
+}
+
+table { margin-bottom: 1.5em; }
+
+/* avoid top margins on first content element */
+p:first-child,
+ul:first-child,
+ol:first-child,
+dl:first-child,
+blockquote:first-child,
+pre:first-child,
+h1:first-child,
+h2:first-child,
+h3:first-child,
+h4:first-child,
+h5:first-child,
+h6:first-child {
+ margin-top: 0;
+}
+
+/* avoid margins on nested elements */
+li p,
+li ul,
+li ol {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+/* max values */
+img, table, td, blockquote, code, pre, textarea, input, video {
+ max-width: 100%;
+}
+
+/* you shall not pass */
+div, textarea, table, td, th, code, pre, samp {
+ word-wrap: break-word;
+ -webkit-hyphens: auto;
+ -moz-hyphens: auto;
+ -ms-hyphens: auto;
+ -o-hyphens: auto;
+ hyphens: auto;
+}
+
+/* pictures */
+img {
+ height: auto;
+ vertical-align: middle;
+}
+/* Gmap3 max-width bug fix on images */
+#map_canvas img,
+.gmnoprint img {max-width: none;}
+
+a img { border: 0; }
+
+/* scripts */
+body > script {display: none !important;}
+
+/* skip-links */
+.skip-links {
+ position: absolute;
+}
+.skip-links a {
+ position: absolute;
+ left: -7000px;
+ padding: 0.5em;
+ background: #000;
+ color:#fff;
+ text-decoration: none;
+}
+.skip-links a:focus {
+ position: static;
+}
+
+/* ----------------------------- */
+/* ==layout and modules */
+/* ----------------------------- */
+
+/* switching box model for all elements */
+* {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+/* float layout */
+/* ----------- */
+
+/* module, contains floats (.item is the same) */
+.mod, .item {
+ overflow: hidden;
+}
+
+/* table layout */
+/* ------------------ */
+.row {
+ display: table;
+ table-layout: fixed;
+ width: 100%;
+}
+.row > *,
+.col {
+ display: table-cell;
+ vertical-align: top;
+}
+
+/* blocks that needs to be placed under floats */
+.clear,
+.line,
+.row {
+ clear: both;
+}
+
+/* blocks that must contain floats */
+.clearfix:after,
+.line:after,
+.mod:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+/* alignments (blocks and inline) */
+/* ------------------------------ */
+
+/* left elements */
+.left {
+ float: left;
+}
+img.left {
+ margin-right: 1em;
+}
+
+/* right elements */
+.right {
+ float: right;
+}
+img.right {
+ margin-left: 1em;
+}
+
+img.left, img.right {
+ margin-bottom: 5px;
+}
+
+.center { margin-left: auto; margin-right: auto; }
+.txtleft { text-align: left; }
+.txtright { text-align: right; }
+.txtcenter { text-align: center; }
+
+/* just inline-block */
+.inbl {
+ display: inline-block;
+ vertical-align: top;
+ margin-right: -.25em;
+}
+
+/* blocks widths (percentage and pixels) */
+.w10 { width: 10%; }
+.w20 { width: 20%; }
+.w25 { width: 25%; }
+.w30 { width: 30%; }
+.w33 { width: 33.333%; }
+.w40 { width: 40%; }
+.w50 { width: 50%; }
+.w60 { width: 60%; }
+.w66 { width: 66.666%; }
+.w70 { width: 70%; }
+.w75 { width: 75%; }
+.w80 { width: 80%; }
+.w90 { width: 90%; }
+.w100 { width: 100%; }
+
+.w50p { width: 50px; }
+.w100p { width: 100px; }
+.w150p { width: 150px; }
+.w200p { width: 200px; }
+.w300p { width: 300px; }
+.w400p { width: 400px; }
+.w500p { width: 500px; }
+.w600p { width: 600px; }
+.w700p { width: 700px; }
+.w800p { width: 800px; }
+.w960p { width: 960px; }
+.mw960p { max-width: 960px; }
+
+/* spacing helpers
+p,m = padding,margin
+a,t,r,b,l = all,top,right,bottom,left
+s,m,l,n = small(10px),medium(20px),large(30px),none(0)
+source https://github.com/stubbornella/oocss/blob/master/core/spacing/space.css
+*/
+.m-reset, .ma0 { margin: 0 !important; }
+.p-reset, .pa0 { padding: 0 !important; }
+.ma1, .mas { margin: 10px !important; }
+.ma2, .mam { margin: 20px !important; }
+.ma3, .mal { margin: 30px !important; }
+.pa1, .pas { padding: 10px; }
+.pa2, .pam { padding: 20px; }
+.pa3, .pal { padding: 30px; }
+
+.mt0, .mtn { margin-top: 0 !important; }
+.mt1, .mts { margin-top: 10px !important; }
+.mt2, .mtm { margin-top: 20px !important; }
+.mt3, .mtl { margin-top: 30px !important; }
+.mr0, .mrn { margin-right: 0; }
+.mr1, .mrs { margin-right: 10px; }
+.mr2, .mrm { margin-right: 20px; }
+.mr3, .mrl { margin-right: 30px; }
+.mb0, .mbn { margin-bottom: 0 !important; }
+.mb1, .mbs { margin-bottom: 10px !important; }
+.mb2, .mbm { margin-bottom: 20px !important; }
+.mb3, .mbl { margin-bottom: 30px !important; }
+.ml0, .mln { margin-left: 0; }
+.ml1, .mls { margin-left: 10px; }
+.ml2, .mlm { margin-left: 20px; }
+.ml3, .mll { margin-left: 30px; }
+
+.pt0, .ptn { padding-top: 0; }
+.pt1, .pts { padding-top: 10px; }
+.pt2, .ptm { padding-top: 20px; }
+.pt3, .ptl { padding-top: 30px; }
+.pr0, .prn { padding-right: 0; }
+.pr1, .prs { padding-right: 10px; }
+.pr2, .prm { padding-right: 20px; }
+.pr3, .prl { padding-right: 30px; }
+.pb0, .pbn { padding-bottom: 0; }
+.pb1, .pbs { padding-bottom: 10px; }
+.pb2, .pbm { padding-bottom: 20px; }
+.pb3, .pbl { padding-bottom: 30px; }
+.pl0, .pln { padding-left: 0; }
+.pl1, .pls { padding-left: 10px; }
+.pl2, .plm { padding-left: 20px; }
+.pl3, .pll { padding-left: 30px; }
+
+/* hiding content */
+.visually-hidden {
+ position: absolute;
+ left: -7000px;
+ overflow: hidden;
+}
+[dir=rtl] .visually-hidden {
+ left: auto;
+ right: -7000px;
+}
+
+.desktop-hidden { display: none; } /* hidden on desktop */
+
+/* ----------------------------- */
+/* ==header */
+/* ----------------------------- */
+
+/* ----------------------------- */
+/* ==sidebar */
+/* ----------------------------- */
+
+/* ----------------------------- */
+/* ==footer */
+/* ----------------------------- */
+
+/* ----------------------------- */
+/* ==forms */
+/* ----------------------------- */
+form,
+fieldset {
+ border: none;
+}
+input,
+button,
+select,
+label,
+.btn {
+ vertical-align: middle; /* @bugfix alignment */
+ font-family: inherit;
+}
+textarea {
+ resize: vertical;
+ font-family: inherit;
+}
+
+/* ----------------------------- */
+/* ==main */
+/* ----------------------------- */
+
+/* ----------------------------- */
+/* ==iefix */
+/* ----------------------------- */
+
+/* haslayout for IE6/IE7 */
+.ie67 .clearfix,
+.ie67 .line,
+.ie67 .mod,
+.ie67 .row,
+.ie67 .col {
+ zoom: 1;
+}
+
+/* inline-block and table-cell for IE6/IE7 */
+/* warning: .col needs width on IE6/IE7 */
+.ie67 .btn,
+.ie67 .col,
+.ie67 .inbl {
+ display: inline;
+ zoom: 1;
+}
+.ie8 img {
+ width: auto; /* @bugfix for IE8 */
+}
+
+/* Active box-sizing for IE6/IE7 */
+/* @source https://github.com/Schepp/box-sizing-polyfill */
+/*
+.ie67 * {
+ behavior: url(/js/boxsizing.htc);
+}
+*/
+
+/* ----------------------------- */
+/* ==print */
+/* ----------------------------- */
+
+/* quick print reset */
+@media print {
+ p,
+ blockquote {
+ orphans: 2;
+ widows: 2;
+ }
+ blockquote,
+ ul,
+ ol {
+ page-break-inside: avoid;
+ }
+ h1,
+ h2,
+ h3,
+ caption {
+ page-break-after: avoid;
+ }
+}
+
+/* orientation iOS font-size fix */
+@media (orientation: landscape) and (max-device-width: 768px) {
+ html,
+ body {
+ -webkit-text-size-adjust: 100%;
+ }
+}
+/* ----------------------------- */
+/* ==grids */
+/* ----------------------------- */
+
+/* equal grids with 2% gutter */
+[class*=grid] > * {float: left; } /* direct childrens are floating */
+[class*=grid] > * + * { margin-left: 2%; } /* here's the gutter */
+.grid2 > * { width: 49%; }
+.grid3 > * { width: 32%; }
+.grid4 > * { width: 23.5%; }
+.grid5 > * { width: 18.4%; }
+.grid6 > * { width: 15%; }
+
+/* unequal grids (1-2, 2-1, 1-3 and 3-1) */
+.grid2-1 > *:first-child,
+.grid1-2 > * + * { width: 66%; }
+.grid1-2 > *:first-child,
+.grid2-1 > * + * { width: 32%; }
+.grid1-3 > *:first-child,
+.grid3-1 > * + * { width: 23.5%; }
+.grid3-1 > *:first-child,
+.grid1-3 > * + * { width: 74.5%; }
+
+
+/* ----------------------------- */
+/* ==tables */
+/* ----------------------------- */
+
+table,
+.table {
+ max-width : 100%;
+ table-layout: fixed;
+ border-collapse: collapse;
+ vertical-align: top;
+}
+table {
+ width: 100%;
+}
+.table {
+ display: table;
+}
+caption {
+ padding: 10px;
+ color: #555;
+ font-style: italic;
+}
+table {
+ border: 1px solid #ccc;
+}
+tr > * + * {
+ border-left: 1px solid #ccc;
+}
+th,
+td {
+ padding: .3em .8em;
+ text-align: left;
+ border-bottom: 1px solid #ccc;
+}
+td {
+ color: #333;
+}
+
+/* alternate tables */
+.alternate { border: 0; }
+.alternate tbody {
+ border: 1px solid #ccc;
+}
+.alternate thead tr > * + * {
+ border-left: 0;
+}
+.alternate tbody tr > * + * {
+ border-left: 1px solid #ccc;
+}
+
+/* alternate-vert tables */
+.alternate-vert {
+ border: 0;
+ border-right: 1px solid #ccc;
+}
+.alternate-vert tr > :first-child {
+ border-bottom: 0;
+}
+.alternate-vert tr > * + * {
+ border-top: 1px solid #ccc;
+}
+
+/* striped tables */
+.striped tbody tr:nth-child(odd) {
+ background: #eee;
+ background: rgba(0, 0, 0, .05);
+}
+
+/* striped-vert tables */
+.striped-vert tr > :first-child {
+ background: #eee;
+ background: rgba(0, 0, 0, .05);
+}
+/* ----------------------------- */
+/* ==forms */
+/* ----------------------------- */
+
+/* thanks to HTML5boilerplate, github.com/nathansmith/formalize and www.sitepen.com */
+
+
+/* buttons */
+.btn { display: inline-block; }
+
+.btn.alternate {}
+.btn.highlight {}
+.login {}
+.logout {}
+.primary {}
+.warning {}
+.error {}
+.success {}
+
+/* forms items */
+label {
+ display: inline-block;
+ vertical-align: middle;
+ cursor: pointer;
+}
+legend {
+ border: 0;
+ white-space: normal;
+}
+button,
+input,
+select {
+ font-family: "Century Gothic", helvetica, arial, sans-serif;
+ font-size: 100%;
+ margin: 0;
+ vertical-align: middle;
+}
+textarea {
+ overflow: auto; /* Removes default vertical scrollbar on empty textarea in IE6/7/8/9 */
+ min-height: 5em;
+ font-size: 1.75em;
+ vertical-align: top;
+ resize: vertical;
+}
+button,
+input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+ cursor: pointer;
+ -webkit-appearance: button; /* clickable input types in iOS */
+ *overflow: visible; /* Corrects inner spacing displayed oddly in IE7 */
+}
+input[type="checkbox"],
+input[type="radio"] {
+ padding: 0; /* Corrects excess space around these inputs in IE8/9 */
+ *width: 13px; *height: 13px; /* Corrects excess space around these inputs in IE7 */
+}
+input[type="search"] { -webkit-appearance: textfield; }
+
+/* if select styling bugs on WebKit */
+/* select { -webkit-appearance: none; } */
+
+/* 'x' appears on right of search input when text is entered. This removes it */
+input[type="search"]::-webkit-search-decoration,
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-results-button,
+input[type="search"]::-webkit-search-results-decoration {
+ display: none;
+}
+::-webkit-input-placeholder { color: #777; }
+input:-moz-placeholder,
+textarea:-moz-placeholder { color: #777; }
+
+/* Removes inner padding and border in FF3+ */
+button::-moz-focus-inner,
+input[type='button']::-moz-focus-inner,
+input[type='reset']::-moz-focus-inner,
+input[type='submit']::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
+/* ----------------------------- */
+/* ==icons and bullets */
+/* ----------------------------- */
+
+.icon {display: inline-block;}
+
+.icon:before,
+.icon > li:before,
+.icon.after:after,
+.icon.after > li:after {
+ content: "";
+ display: inline-block;
+ vertical-align: middle;
+ position: relative; top: -.1em;
+ margin: 0 0.3em 0 0;
+ font: 1.4em/1 sans-serif;
+ color: #000;
+ text-shadow: 1px 1px 0 rgba(0,0,0,.1);
+ speak: none;
+}
+
+@media (min-device-width: 768px) {
+ .icon:before,
+ .icon > li:before,
+ .icon.after:after,
+ .icon.after > li:after {
+ font: 1em/0.6 sans-serif;
+ -webkit-transform: rotateZ(0.05deg);
+ }
+}
+
+.icon.after:after,
+.icon.after > li:after {
+ margin: 0 0 0 8px;
+}
+
+ul.icon {display: block;}
+ul.icon > li {list-style: none;}
+ul.icon:before,
+ul.icon.after:after {content:""}
+.icon.after:before,
+.icon.after > li:before {content: "" !important}
+
+.icon-rate:before,
+.icon-rate > li:before,
+.icon-rate.after:after,
+.icon-rate.after > li:after {
+ content: "\2605";
+}
+.icon-unrate:before,
+.icon-unrate > li:before,
+.icon-unrate.after:after,
+.icon-unrate.after > li:after {
+ content: "\2606";
+}
+.icon-check:before,
+.icon-check > li:before,
+.icon-check.after:after,
+.icon-check.after > li:after {
+ content: "\2713";
+}
+.icon-uncheck:before,
+.icon-uncheck > li:before,
+.icon-uncheck.after:after,
+.icon-uncheck.after > li:after {
+ content: "\2717";
+}
+.icon-cloud:before,
+.icon-cloud > li:before,
+.icon-cloud.after:after,
+.icon-cloud.after > li:after {
+ content: "\2601";
+}
+.icon-dl:before,
+.icon-dl > li:before,
+.icon-dl.after:after,
+.icon-dl.after > li:after {
+ content: "\21E3";
+ font-weight: bold;
+}
+.icon-cross:before,
+.icon-cross > li:before,
+.icon-cross.after:after,
+.icon-cross.after > li:after {
+ content: "\2716";
+ font-weight: bold;
+}
+.icon-arrow1:before,
+.icon-arrow1 > li:before,
+.icon-arrow1.after:after,
+.icon-arrow1.after > li:after {
+ content: "\2192";
+ position: relative; top: -0.15em;
+}
+.icon-arrow2:before,
+.icon-arrow2 > li:before,
+.icon-arrow2.after:after,
+.icon-arrow2.after > li:after {
+ content: "\279E";
+}
+.icon-arrow3:before,
+.icon-arrow3 > li:before,
+.icon-arrow3.after:after,
+.icon-arrow3.after > li:after {
+ content: "\279A";
+}
+.icon-bracket1:before,
+.icon-bracket1 > li:before,
+.icon-bracket1.after:after,
+.icon-bracket1.after > li:after {
+ content: "\2039";
+ font-weight: bold;
+ font-size: 1.6em;
+ position: relative; top: -0.15em;
+}
+.icon-bracket2:before,
+.icon-bracket2 > li:before,
+.icon-bracket2.after:after,
+.icon-bracket2.after > li:after {
+ content: "\203A";
+ font-weight: bold;
+ font-size: 1.6em;
+ position: relative; top: -0.15em;
+}
+.icon-up:before,
+.icon-up > li:before,
+.icon-up.after:after,
+.icon-up.after > li:after {
+ content: "\25B2";
+}
+.icon-down:before,
+.icon-down > li:before,
+.icon-down.after:after,
+.icon-down.after > li:after {
+ content: "\25BC";
+}
+.icon-bull:before,
+.icon-bull > li:before,
+.icon-bull.after:after,
+.icon-bull.after > li:after {
+ content: "\2022";
+ font-size: 1.2em;
+ top: -0.05em;
+}
+.icon-bull2:before,
+.icon-bull2 > li:before,
+.icon-bull2.after:after,
+.icon-bull2.after > li:after {
+ content: "\25E6";
+ top: -0.05em;
+}
+.icon-bull3:before,
+.icon-bull3 > li:before,
+.icon-bull3.after:after,
+.icon-bull3.after > li:after {
+ content: "\2023";
+ font-size: 1.6em;
+ top: -0.05em;
+}
+.icon-nav:before,
+.icon-nav > li:before,
+.icon-nav.after:after,
+.icon-nav.after > li:after {
+ content: "\2261";
+ font-weight: bold;
+ font-size: 1.6em;
+}
+.icon-losange:before,
+.icon-losange > li:before,
+.icon-losange.after:after,
+.icon-losange.after > li:after {
+ content: "\25C6";
+}
+.icon-asteri:before,
+.icon-asteri > li:before,
+.icon-asteri.after:after,
+.icon-asteri.after > li:after {
+ content: "\2731";
+ font-weight: bold;
+}
+.icon-mail:before,
+.icon-mail > li:before,
+.icon-mail.after:after,
+.icon-mail.after > li:after {
+ content: "\2709";
+ font-size: 1.6em;
+ top: -.05em;
+}
+
+ol.styled {counter-reset: styled;}
+ol.styled > li {
+ list-style-type: none;
+ counter-increment: styled;
+ margin-bottom: .3em;
+}
+ol.styled > li:before {
+ content: counter(styled);
+ display: inline-block;
+ width: 1em; height: 1em;
+ line-height: 1;
+ padding: 2px;
+ margin-right: .4em;
+ vertical-align: middle;
+ background: rgba(0,0,0,.5);
+ border-radius: 50%;
+ font-size: .9em;
+ text-align: center;
+ text-indent: -0.1em;
+ color: white;
+}
+/* ----------------------------- */
+/* ==desktop and retina medias */
+/* ----------------------------- */
+
+@media (min-width: 641px) {
+/* here go rules for big resources and big screens like: background-images, font-faces, etc. */
+}
+@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi), (min-resolution: 2dppx) {
+/* Style adjustments for retina devices */
+}
+
+/* ---------------------------------- */
+/* ==Responsive large / medium / tiny */
+/* ---------------------------------- */
+
+@media (min-width: 1280px) {
+
+ /* layouts for large (l) screens */
+ .large-hidden,
+ .tablet-hidden { display: none !important; }
+ .large-visible { display: block !important; }
+ .large-no-float {float: none; }
+ .large-inbl {
+ display: inline-block;
+ float: none;
+ vertical-align: top;
+ }
+ .large-row {
+ display: table;
+ table-layout: fixed;
+ width: 100% !important;
+ }
+ .large-col {
+ display: table-cell;
+ vertical-align: top;
+ }
+
+ /* widths for large (l) screens */
+ .large-w25 { width: 25% !important; }
+ .large-w33 { width: 33.3333% !important; }
+ .large-w50 { width: 50% !important; }
+ .large-w66 { width: 66.6666% !important; }
+ .large-w75 { width: 75% !important; }
+ .large-w100 {
+ display: block !important;
+ float: none !important;
+ clear: none !important;
+ width: auto !important;
+ margin-left: 0 !important;
+ margin-right: 0 !important;
+ border: 0;
+ }
+
+ /* margins for large (l) screens */
+ .large-ma0,
+ .large-man { margin: 0 !important; }
+}
+
+@media (max-width: 768px) {
+
+ /* quick tablet reset */
+ .w60,
+ .w66,
+ .w70,
+ .w75,
+ .w80,
+ .w90,
+ .w100,
+ .w600p,
+ .w700p,
+ .w800p,
+ .w960p,
+ .mw960p,
+ .medium-wauto { width: auto; }
+
+ /* layouts for medium (m) screens */
+ .medium-hidden,
+ .tablet-hidden { display: none !important; }
+ .medium-visible { display: block !important; }
+ .medium-no-float {float: none; }
+ .medium-inbl {
+ display: inline-block;
+ float: none;
+ vertical-align: top;
+ }
+ .medium-row {
+ display: table;
+ table-layout: fixed;
+ width: 100% !important;
+ }
+ .medium-col {
+ display: table-cell;
+ vertical-align: top;
+ }
+
+ /* widths for medium (m) screens */
+ .medium-w25 { width: 25% !important; }
+ .medium-w33 { width: 33.3333% !important; }
+ .medium-w50 { width: 50% !important; }
+ .medium-w66 { width: 66.6666% !important; }
+ .medium-w75 { width: 75% !important; }
+ .medium-w100 {
+ display: block !important;
+ float: none !important;
+ clear: none !important;
+ width: auto !important;
+ margin-left: 0 !important;
+ margin-right: 0 !important;
+ border: 0;
+ }
+ /* margins for medium (m) screens */
+ .medium-ma0,
+ .medium-man { margin: 0 !important; }
+
+ /* Responsive grids */
+ .grid4 > * {width: 49% !important; }
+ .grid4 > :first-child + * + * {margin-left: 0 !important;}
+ .grid6 > * {width: 32% !important; }
+ .grid6 > :first-child + * + * + * {margin-left: 0 !important;}
+}
+
+@media (max-width: 640px) {
+
+ /* quick smartphone reset */
+ .mod,
+ .item,
+ .col,
+ fieldset {
+ display: block !important;
+ float: none !important;
+ clear: none !important;
+ width: auto !important;
+ margin-left: 0 !important;
+ margin-right: 0 !important;
+ border: 0;
+ }
+ .w30,
+ .w33,
+ .w40,
+ .w50,
+ .w300p,
+ .w400p,
+ .w500p {
+ width: auto;
+ }
+ .row {
+ display: block !important;
+ width: 100% !important;
+ }
+
+ /* layouts for tiny (t) screens */
+ .tiny-hidden,
+ .phone-hidden { display: none !important; }
+ .tiny-visible { display: block !important; }
+ .tiny-no-float {float: none;}
+ .tiny-inbl {
+ display: inline-block;
+ float: none;
+ vertical-align: top;
+ }
+ .tiny-row {
+ display: table;
+ table-layout: fixed;
+ width: 100% !important;
+ }
+ .tiny-col {
+ display: table-cell;
+ vertical-align: top;
+ }
+ th,
+ td {
+ display: block !important;
+ width: auto !important;
+ text-align: left !important;
+ }
+ thead { display: none; }
+
+ /* widths for tiny (t) screens */
+ .tiny-w25 { width: 25% !important; }
+ .tiny-w33 { width: 33.3333% !important; }
+ .tiny-w50 { width: 50% !important; }
+ .tiny-w66 { width: 66.6666% !important; }
+ .tiny-w75 { width: 75% !important; }
+ .tiny-w100 {
+ display: block !important;
+ float: none !important;
+ clear: none !important;
+ width: auto !important;
+ margin-left: 0 !important;
+ margin-right: 0 !important;
+ border: 0;
+ }
+ /* margins for tiny (t) screens */
+ .tiny-ma0,
+ .tiny-man { margin: 0 !important; }
+}
+
--- /dev/null
+html, body, div, span, object, iframe,\r
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,\r
+a, abbr, acronym, address, code,\r
+del, dfn, em, img, q, dl, dt, dd, ol, ul, li,\r
+fieldset, form, label, legend,\r
+table, caption, tbody, tfoot, thead, tr, th, td {\r
+ margin: 0;\r
+ padding: 0;\r
+ border: 0;\r
+ font-weight: inherit;\r
+ font-style: inherit;\r
+ font-size: 100%;\r
+ font-family: inherit;\r
+ vertical-align: baseline;\r
+}\r
+\r
+\r
+/* Tables still need 'cellspacing="0"' in the markup. */\r
+table { border-collapse: separate; border-spacing: 0; }\r
+caption, th, td { text-align: left; font-weight: normal; }\r
+table, td, th { vertical-align: middle; }\r
+\r
+/* Remove possible quote marks (") from <q>, <blockquote>. */\r
+blockquote:before, blockquote:after, q:before, q:after { content: ""; }\r
+blockquote, q { quotes: "" ""; }\r
+\r
+/* Remove annoying border on linked images. */\r
+a img { border: none; }\r
+\r
+\r
+body {\r
+ \r
+ margin: 10px;\r
+}\r
+\r
--- /dev/null
+body {
+ color: #222222;
+ font: 20px/1.3em Palatino,Georgia,serif;
+ background-color: #e6e6e6;
+}
+
+a, a:hover, a:visited {
+ color: #000;
+}
+header {
+ text-align: center;
+}
+
+#main {
+ margin: 0 auto;
+}
+
+#main ul#links {
+ padding: 0;
+ list-style-type: none;
+ text-align: center;
+}
+
+#main ul#links li {
+ display: inline;
+ padding: 15px;
+}
+
+#main a.tool {
+ text-decoration: none;
+}
+
+footer {
+ text-align: right;
+}
\ No newline at end of file
--- /dev/null
+\r
+body { \r
+ font:1em/1.625em "lucida grande","lucida sans unicode", sans-serif; background-color:#FFFEF0; \r
+ font-size-adjust:none;\r
+ font-style:normal;\r
+ font-variant:normal;\r
+ font-weight:normal;\r
+ padding: 15px;\r
+ margin: 15px auto;\r
+}\r
+\r
+article {\r
+ border: 3px solid grey;\r
+ max-width:700px;\r
+ margin: 15px auto;\r
+ padding: 15px;\r
+}\r
+\r
+footer {\r
+ border: 1px solid black;\r
+ padding: 15px;\r
+ margin: 15px auto;\r
+}\r
+\r
+p { padding:0 0 0.8125em 0; color:#111; font-weight:300;}\r
+\r
+p + p { text-indent:1.625em;}\r
+\r
+img { display: block; margin: 0.5em 0.8125em 0.8125em 0; padding: 0; }\r
+\r
+p > img { display: inline-block; margin: 0; }\r
+\r
+h1,h2{ font-weight:normal; color: #333; font-family:Georgia, serif; }\r
+h3,h4,h5,h6 { font-weight: normal; color: #333; font-family:Georgia, serif; }\r
+\r
+\r
+h1 { font-size: 2.125em; margin-bottom: 0.765em; line-height: 1.5em;}\r
+h2 { font-size: 1.9em; margin-bottom: 0.855em; }\r
+h3 { font-size: 1.7em; margin-bottom: 0.956em; }\r
+h4 { font-size: 1.4em; margin-bottom: 1.161em; }\r
+h5,h6 { font-size: 1.313em; margin-bottom: 1.238em; }\r
+\r
+\r
+\r
+ul{list-style-position:outside;}\r
+li ul, \r
+li ol { margin:0 1.625em; }\r
+ul, ol { margin: 0 0 1.625em 0; }\r
+\r
+\r
+dl { margin: 0 0 1.625em 0; }\r
+dl dt { font-weight: bold; }\r
+dl dd { margin-left: 1.625em; }\r
+\r
+a { color:#005AF2; text-decoration:none; }\r
+a:hover { text-decoration: underline; }\r
+\r
+\r
+table { margin-bottom:1.625em; border-collapse: collapse; }\r
+th { font-weight:bold; }\r
+tr,th,td { margin:0; padding:0 1.625em 0 1em; height:26px; }\r
+tfoot { font-style: italic; }\r
+caption { text-align:center; font-family:Georgia, serif; }\r
+\r
+\r
+abbr, acronym { border-bottom:1px dotted #000; }\r
+address { margin-top:1.625em; font-style: italic; }\r
+del {color:#000;}\r
+\r
+\r
+blockquote { padding:1em 1em 1.625em 1em; font-family:georgia,serif;font-style: italic; }\r
+blockquote:before { content:"\201C";font-size:3em;margin-left:-.625em; font-family:georgia,serif;color:#aaa;line-height:0;}/* From Tripoli */\r
+blockquote > p {padding:0; margin:0; }\r
+\r
+strong { font-weight: bold; }\r
+em, dfn { font-style: italic; }\r
+dfn { font-weight: bold; }\r
+pre, code { margin: 1.625em 0; white-space: pre; }\r
+pre, code, tt { font: 1em monospace; line-height: 1.5; } \r
+tt { display: block; margin: 1.625em 0; }\r
+hr { margin-bottom:1.625em; }\r
+\r
+.oldbook { font-family:"Warnock Pro","Goudy Old Style","Book Antiqua","Palatino",Georgia,serif; }\r
+.note { font-family:Georgia, "Times New Roman", Times, serif; font-style:italic; font-size:0.9em; margin:0.1em; color:#333; } \r
+.mono { font-family:"Courier New", Courier, monospace; }\r
--- /dev/null
+<?php \r
+/**\r
+ * @author "Sebastián Grignoli" <grignoli@framework2.com.ar>\r
+ * @package Encoding\r
+ * @version 1.1\r
+ * @link http://www.framework2.com.ar/dzone/forceUTF8-es/\r
+ * @example http://www.framework2.com.ar/dzone/forceUTF8-es/\r
+ */\r
+\r
+class Encoding {\r
+ \r
+ protected static $win1252ToUtf8 = array(\r
+ 128 => "\xe2\x82\xac",\r
+\r
+ 130 => "\xe2\x80\x9a",\r
+ 131 => "\xc6\x92",\r
+ 132 => "\xe2\x80\x9e",\r
+ 133 => "\xe2\x80\xa6",\r
+ 134 => "\xe2\x80\xa0",\r
+ 135 => "\xe2\x80\xa1",\r
+ 136 => "\xcb\x86",\r
+ 137 => "\xe2\x80\xb0",\r
+ 138 => "\xc5\xa0",\r
+ 139 => "\xe2\x80\xb9",\r
+ 140 => "\xc5\x92",\r
+\r
+ 142 => "\xc5\xbd",\r
+\r
+\r
+ 145 => "\xe2\x80\x98",\r
+ 146 => "\xe2\x80\x99",\r
+ 147 => "\xe2\x80\x9c",\r
+ 148 => "\xe2\x80\x9d",\r
+ 149 => "\xe2\x80\xa2",\r
+ 150 => "\xe2\x80\x93",\r
+ 151 => "\xe2\x80\x94",\r
+ 152 => "\xcb\x9c",\r
+ 153 => "\xe2\x84\xa2",\r
+ 154 => "\xc5\xa1",\r
+ 155 => "\xe2\x80\xba",\r
+ 156 => "\xc5\x93",\r
+\r
+ 158 => "\xc5\xbe",\r
+ 159 => "\xc5\xb8"\r
+ );\r
+ \r
+ protected static $brokenUtf8ToUtf8 = array(\r
+ "\xc2\x80" => "\xe2\x82\xac",\r
+ \r
+ "\xc2\x82" => "\xe2\x80\x9a",\r
+ "\xc2\x83" => "\xc6\x92",\r
+ "\xc2\x84" => "\xe2\x80\x9e",\r
+ "\xc2\x85" => "\xe2\x80\xa6",\r
+ "\xc2\x86" => "\xe2\x80\xa0",\r
+ "\xc2\x87" => "\xe2\x80\xa1",\r
+ "\xc2\x88" => "\xcb\x86",\r
+ "\xc2\x89" => "\xe2\x80\xb0",\r
+ "\xc2\x8a" => "\xc5\xa0",\r
+ "\xc2\x8b" => "\xe2\x80\xb9",\r
+ "\xc2\x8c" => "\xc5\x92",\r
+ \r
+ "\xc2\x8e" => "\xc5\xbd",\r
+ \r
+ \r
+ "\xc2\x91" => "\xe2\x80\x98",\r
+ "\xc2\x92" => "\xe2\x80\x99",\r
+ "\xc2\x93" => "\xe2\x80\x9c",\r
+ "\xc2\x94" => "\xe2\x80\x9d",\r
+ "\xc2\x95" => "\xe2\x80\xa2",\r
+ "\xc2\x96" => "\xe2\x80\x93",\r
+ "\xc2\x97" => "\xe2\x80\x94",\r
+ "\xc2\x98" => "\xcb\x9c",\r
+ "\xc2\x99" => "\xe2\x84\xa2",\r
+ "\xc2\x9a" => "\xc5\xa1",\r
+ "\xc2\x9b" => "\xe2\x80\xba",\r
+ "\xc2\x9c" => "\xc5\x93",\r
+ \r
+ "\xc2\x9e" => "\xc5\xbe",\r
+ "\xc2\x9f" => "\xc5\xb8"\r
+ );\r
+ \r
+ protected static $utf8ToWin1252 = array(\r
+ "\xe2\x82\xac" => "\x80",\r
+ \r
+ "\xe2\x80\x9a" => "\x82",\r
+ "\xc6\x92" => "\x83",\r
+ "\xe2\x80\x9e" => "\x84",\r
+ "\xe2\x80\xa6" => "\x85",\r
+ "\xe2\x80\xa0" => "\x86",\r
+ "\xe2\x80\xa1" => "\x87",\r
+ "\xcb\x86" => "\x88",\r
+ "\xe2\x80\xb0" => "\x89",\r
+ "\xc5\xa0" => "\x8a",\r
+ "\xe2\x80\xb9" => "\x8b",\r
+ "\xc5\x92" => "\x8c",\r
+ \r
+ "\xc5\xbd" => "\x8e",\r
+ \r
+ \r
+ "\xe2\x80\x98" => "\x91",\r
+ "\xe2\x80\x99" => "\x92",\r
+ "\xe2\x80\x9c" => "\x93",\r
+ "\xe2\x80\x9d" => "\x94",\r
+ "\xe2\x80\xa2" => "\x95",\r
+ "\xe2\x80\x93" => "\x96",\r
+ "\xe2\x80\x94" => "\x97",\r
+ "\xcb\x9c" => "\x98",\r
+ "\xe2\x84\xa2" => "\x99",\r
+ "\xc5\xa1" => "\x9a",\r
+ "\xe2\x80\xba" => "\x9b",\r
+ "\xc5\x93" => "\x9c",\r
+ \r
+ "\xc5\xbe" => "\x9e",\r
+ "\xc5\xb8" => "\x9f"\r
+ );\r
+\r
+ static function toUTF8($text){\r
+ /**\r
+ * Function Encoding::toUTF8\r
+ *\r
+ * This function leaves UTF8 characters alone, while converting almost all non-UTF8 to UTF8.\r
+ * \r
+ * It assumes that the encoding of the original string is either Windows-1252 or ISO 8859-1.\r
+ *\r
+ * It may fail to convert characters to UTF-8 if they fall into one of these scenarios:\r
+ *\r
+ * 1) when any of these characters: ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß\r
+ * are followed by any of these: ("group B")\r
+ * ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶•¸¹º»¼½¾¿\r
+ * For example: %ABREPRESENT%C9%BB. «REPRESENTÉ»\r
+ * The "«" (%AB) character will be converted, but the "É" followed by "»" (%C9%BB) \r
+ * is also a valid unicode character, and will be left unchanged.\r
+ *\r
+ * 2) when any of these: àáâãäåæçèéêëìíîï are followed by TWO chars from group B,\r
+ * 3) when any of these: ðñòó are followed by THREE chars from group B.\r
+ *\r
+ * @name toUTF8\r
+ * @param string $text Any string.\r
+ * @return string The same string, UTF8 encoded\r
+ *\r
+ */\r
+\r
+ if(is_array($text))\r
+ {\r
+ foreach($text as $k => $v)\r
+ {\r
+ $text[$k] = self::toUTF8($v);\r
+ }\r
+ return $text;\r
+ } elseif(is_string($text)) {\r
+ \r
+ $max = strlen($text);\r
+ $buf = "";\r
+ for($i = 0; $i < $max; $i++){\r
+ $c1 = $text{$i};\r
+ if($c1>="\xc0"){ //Should be converted to UTF8, if it's not UTF8 already\r
+ $c2 = $i+1 >= $max? "\x00" : $text{$i+1};\r
+ $c3 = $i+2 >= $max? "\x00" : $text{$i+2};\r
+ $c4 = $i+3 >= $max? "\x00" : $text{$i+3};\r
+ if($c1 >= "\xc0" & $c1 <= "\xdf"){ //looks like 2 bytes UTF8\r
+ if($c2 >= "\x80" && $c2 <= "\xbf"){ //yeah, almost sure it's UTF8 already\r
+ $buf .= $c1 . $c2;\r
+ $i++;\r
+ } else { //not valid UTF8. Convert it.\r
+ $cc1 = (chr(ord($c1) / 64) | "\xc0");\r
+ $cc2 = ($c1 & "\x3f") | "\x80";\r
+ $buf .= $cc1 . $cc2;\r
+ }\r
+ } elseif($c1 >= "\xe0" & $c1 <= "\xef"){ //looks like 3 bytes UTF8\r
+ if($c2 >= "\x80" && $c2 <= "\xbf" && $c3 >= "\x80" && $c3 <= "\xbf"){ //yeah, almost sure it's UTF8 already\r
+ $buf .= $c1 . $c2 . $c3;\r
+ $i = $i + 2;\r
+ } else { //not valid UTF8. Convert it.\r
+ $cc1 = (chr(ord($c1) / 64) | "\xc0");\r
+ $cc2 = ($c1 & "\x3f") | "\x80";\r
+ $buf .= $cc1 . $cc2;\r
+ }\r
+ } elseif($c1 >= "\xf0" & $c1 <= "\xf7"){ //looks like 4 bytes UTF8\r
+ if($c2 >= "\x80" && $c2 <= "\xbf" && $c3 >= "\x80" && $c3 <= "\xbf" && $c4 >= "\x80" && $c4 <= "\xbf"){ //yeah, almost sure it's UTF8 already\r
+ $buf .= $c1 . $c2 . $c3;\r
+ $i = $i + 2;\r
+ } else { //not valid UTF8. Convert it.\r
+ $cc1 = (chr(ord($c1) / 64) | "\xc0");\r
+ $cc2 = ($c1 & "\x3f") | "\x80";\r
+ $buf .= $cc1 . $cc2;\r
+ }\r
+ } else { //doesn't look like UTF8, but should be converted\r
+ $cc1 = (chr(ord($c1) / 64) | "\xc0");\r
+ $cc2 = (($c1 & "\x3f") | "\x80");\r
+ $buf .= $cc1 . $cc2;\r
+ }\r
+ } elseif(($c1 & "\xc0") == "\x80"){ // needs conversion\r
+ if(isset(self::$win1252ToUtf8[ord($c1)])) { //found in Windows-1252 special cases\r
+ $buf .= self::$win1252ToUtf8[ord($c1)];\r
+ } else {\r
+ $cc1 = (chr(ord($c1) / 64) | "\xc0");\r
+ $cc2 = (($c1 & "\x3f") | "\x80");\r
+ $buf .= $cc1 . $cc2;\r
+ }\r
+ } else { // it doesn't need convesion\r
+ $buf .= $c1;\r
+ }\r
+ }\r
+ return $buf;\r
+ } else {\r
+ return $text;\r
+ }\r
+ }\r
+\r
+ static function toWin1252($text) {\r
+ if(is_array($text)) {\r
+ foreach($text as $k => $v) {\r
+ $text[$k] = self::toWin1252($v);\r
+ }\r
+ return $text;\r
+ } elseif(is_string($text)) {\r
+ return utf8_decode(str_replace(array_keys(self::$utf8ToWin1252), array_values(self::$utf8ToWin1252), self::toUTF8($text)));\r
+ } else {\r
+ return $text;\r
+ }\r
+ }\r
+\r
+ static function toISO8859($text) {\r
+ return self::toWin1252($text);\r
+ }\r
+\r
+ static function toLatin1($text) {\r
+ return self::toWin1252($text);\r
+ }\r
+\r
+ static function fixUTF8($text){\r
+ if(is_array($text)) {\r
+ foreach($text as $k => $v) {\r
+ $text[$k] = self::fixUTF8($v);\r
+ }\r
+ return $text;\r
+ }\r
+\r
+ $last = "";\r
+ while($last <> $text){\r
+ $last = $text;\r
+ $text = self::toUTF8(utf8_decode(str_replace(array_keys(self::$utf8ToWin1252), array_values(self::$utf8ToWin1252), $text)));\r
+ }\r
+ $text = self::toUTF8(utf8_decode(str_replace(array_keys(self::$utf8ToWin1252), array_values(self::$utf8ToWin1252), $text)));\r
+ return $text;\r
+ }\r
+ \r
+ static function UTF8FixWin1252Chars($text){\r
+ // If you received an UTF-8 string that was converted from Windows-1252 as it was ISO8859-1 \r
+ // (ignoring Windows-1252 chars from 80 to 9F) use this function to fix it.\r
+ // See: http://en.wikipedia.org/wiki/Windows-1252\r
+ \r
+ return str_replace(array_keys(self::$brokenUtf8ToUtf8), array_values(self::$brokenUtf8ToUtf8), $text);\r
+ }\r
+ \r
+ static function removeBOM($str=""){\r
+ if(substr($str, 0,3) == pack("CCC",0xef,0xbb,0xbf)) {\r
+ $str=substr($str, 3);\r
+ }\r
+ return $str;\r
+ }\r
+}
\ No newline at end of file
--- /dev/null
+<?php
+/**
+* JavaScript-like HTML DOM Element
+*
+* This class extends PHP's DOMElement to allow
+* users to get and set the innerHTML property of
+* HTML elements in the same way it's done in
+* JavaScript.
+*
+* Example usage:
+* @code
+* require_once 'JSLikeHTMLElement.php';
+* header('Content-Type: text/plain');
+* $doc = new DOMDocument();
+* $doc->registerNodeClass('DOMElement', 'JSLikeHTMLElement');
+* $doc->loadHTML('<div><p>Para 1</p><p>Para 2</p></div>');
+* $elem = $doc->getElementsByTagName('div')->item(0);
+*
+* // print innerHTML
+* echo $elem->innerHTML; // prints '<p>Para 1</p><p>Para 2</p>'
+* echo "\n\n";
+*
+* // set innerHTML
+* $elem->innerHTML = '<a href="http://fivefilters.org">FiveFilters.org</a>';
+* echo $elem->innerHTML; // prints '<a href="http://fivefilters.org">FiveFilters.org</a>'
+* echo "\n\n";
+*
+* // print document (with our changes)
+* echo $doc->saveXML();
+* @endcode
+*
+* @author Keyvan Minoukadeh - http://www.keyvan.net - keyvan@keyvan.net
+* @see http://fivefilters.org (the project this was written for)
+*/
+class JSLikeHTMLElement extends DOMElement
+{
+ /**
+ * Used for setting innerHTML like it's done in JavaScript:
+ * @code
+ * $div->innerHTML = '<h2>Chapter 2</h2><p>The story begins...</p>';
+ * @endcode
+ */
+ public function __set($name, $value) {
+ if ($name == 'innerHTML') {
+ // first, empty the element
+ for ($x=$this->childNodes->length-1; $x>=0; $x--) {
+ $this->removeChild($this->childNodes->item($x));
+ }
+ // $value holds our new inner HTML
+ if ($value != '') {
+ $f = $this->ownerDocument->createDocumentFragment();
+ // appendXML() expects well-formed markup (XHTML)
+ $result = @$f->appendXML($value); // @ to suppress PHP warnings
+ if ($result) {
+ if ($f->hasChildNodes()) $this->appendChild($f);
+ } else {
+ // $value is probably ill-formed
+ $f = new DOMDocument();
+ $value = mb_convert_encoding($value, 'HTML-ENTITIES', 'UTF-8');
+ // Using <htmlfragment> will generate a warning, but so will bad HTML
+ // (and by this point, bad HTML is what we've got).
+ // We use it (and suppress the warning) because an HTML fragment will
+ // be wrapped around <html><body> tags which we don't really want to keep.
+ // Note: despite the warning, if loadHTML succeeds it will return true.
+ $result = @$f->loadHTML('<htmlfragment>'.$value.'</htmlfragment>');
+ if ($result) {
+ $import = $f->getElementsByTagName('htmlfragment')->item(0);
+ foreach ($import->childNodes as $child) {
+ $importedNode = $this->ownerDocument->importNode($child, true);
+ $this->appendChild($importedNode);
+ }
+ } else {
+ // oh well, we tried, we really did. :(
+ // this element is now empty
+ }
+ }
+ }
+ } else {
+ $trace = debug_backtrace();
+ trigger_error('Undefined property via __set(): '.$name.' in '.$trace[0]['file'].' on line '.$trace[0]['line'], E_USER_NOTICE);
+ }
+ }
+
+ /**
+ * Used for getting innerHTML like it's done in JavaScript:
+ * @code
+ * $string = $div->innerHTML;
+ * @endcode
+ */
+ public function __get($name)
+ {
+ if ($name == 'innerHTML') {
+ $inner = '';
+ foreach ($this->childNodes as $child) {
+ $inner .= $this->ownerDocument->saveXML($child);
+ }
+ return $inner;
+ }
+
+ $trace = debug_backtrace();
+ trigger_error('Undefined property via __get(): '.$name.' in '.$trace[0]['file'].' on line '.$trace[0]['line'], E_USER_NOTICE);
+ return null;
+ }
+
+ public function __toString()
+ {
+ return '['.$this->tagName.']';
+ }
+}
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/**
+* Arc90's Readability ported to PHP for FiveFilters.org
+* Based on readability.js version 1.7.1 (without multi-page support)
+* ------------------------------------------------------
+* Original URL: http://lab.arc90.com/experiments/readability/js/readability.js
+* Arc90's project URL: http://lab.arc90.com/experiments/readability/
+* JS Source: http://code.google.com/p/arc90labs-readability
+* Ported by: Keyvan Minoukadeh, http://www.keyvan.net
+* More information: http://fivefilters.org/content-only/
+* License: Apache License, Version 2.0
+* Requires: PHP5
+* Date: 2011-07-22
+*
+* Differences between the PHP port and the original
+* ------------------------------------------------------
+* Arc90's Readability is designed to run in the browser. It works on the DOM
+* tree (the parsed HTML) after the page's CSS styles have been applied and
+* Javascript code executed. This PHP port does not run inside a browser.
+* We use PHP's ability to parse HTML to build our DOM tree, but we cannot
+* rely on CSS or Javascript support. As such, the results will not always
+* match Arc90's Readability. (For example, if a web page contains CSS style
+* rules or Javascript code which hide certain HTML elements from display,
+* Arc90's Readability will dismiss those from consideration but our PHP port,
+* unable to understand CSS or Javascript, will not know any better.)
+*
+* Another significant difference is that the aim of Arc90's Readability is
+* to re-present the main content block of a given web page so users can
+* read it more easily in their browsers. Correct identification, clean up,
+* and separation of the content block is only a part of this process.
+* This PHP port is only concerned with this part, it does not include code
+* that relates to presentation in the browser - Arc90 already do
+* that extremely well, and for PDF output there's FiveFilters.org's
+* PDF Newspaper: http://fivefilters.org/pdf-newspaper/.
+*
+* Finally, this class contains methods that might be useful for developers
+* working on HTML document fragments. So without deviating too much from
+* the original code (which I don't want to do because it makes debugging
+* and updating more difficult), I've tried to make it a little more
+* developer friendly. You should be able to use the methods here on
+* existing DOMElement objects without passing an entire HTML document to
+* be parsed.
+*/
+
+// This class allows us to do JavaScript like assignements to innerHTML
+require_once(dirname(__FILE__).'/JSLikeHTMLElement.php');
+
+// Alternative usage (for testing only!)
+// uncomment the lines below and call Readability.php in your browser
+// passing it the URL of the page you'd like content from, e.g.:
+// Readability.php?url=http://medialens.org/alerts/09/090615_the_guardian_climate.php
+
+/*
+if (!isset($_GET['url']) || $_GET['url'] == '') {
+ die('Please pass a URL to the script. E.g. Readability.php?url=bla.com/story.html');
+}
+$url = $_GET['url'];
+if (!preg_match('!^https?://!i', $url)) $url = 'http://'.$url;
+$html = file_get_contents($url);
+$r = new Readability($html, $url);
+$r->init();
+echo $r->articleContent->innerHTML;
+*/
+
+
+class Readability
+{
+ /* constants */
+ const FLAG_STRIP_UNLIKELYS = 1;
+ const FLAG_WEIGHT_CLASSES = 2;
+ const FLAG_CLEAN_CONDITIONALLY = 4;
+
+ public $version = '1.7.1-without-multi-page';
+ public $convertLinksToFootnotes = false;
+ public $revertForcedParagraphElements = true;
+ public $articleTitle;
+ public $articleContent;
+ public $dom;
+ public $url = null; // optional - URL where HTML was retrieved
+ public $debug = false;
+ protected $body = null; //
+ protected $bodyCache = null; // Cache the body HTML in case we need to re-use it later
+ protected $flags = FLAG_CLEAN_CONDITIONALLY; // 1 | 2 | 4; // Start with all flags set.
+ protected $success = false; // indicates whether we were able to extract or not
+
+ /**
+ * All of the regular expressions in use within readability.
+ * Defined up here so we don't instantiate them repeatedly in loops.
+ **/
+ public $regexps = array(
+ 'unlikelyCandidates' => '/combx|comment|comments|community|disqus|extra|foot|header|menu|remark|rss|shoutbox|sidebar|sponsor|ad-break|agegate|pagination|pager|popup|tweet|twitter/i',
+ 'okMaybeItsACandidate' => '/and|article|body|column|main|shadow/i',
+ 'positive' => '/article|body|content|entry|hentry|main|page|pagination|post|text|blog|story/i',
+ 'negative' => '/combx|comment|comments|com-|contact|foot|footer|footnote|masthead|media|meta|outbrain|promo|related|scroll|shoutbox|sidebar|sponsor|shopping|tags|tool|widget/i',
+ 'divToPElements' => '/<(a|blockquote|dl|div|ol|p|pre|table|ul)/i',
+ 'replaceBrs' => '/(<br[^>]*>[ \n\r\t]*){2,}/i',
+ 'replaceFonts' => '/<(\/?)font[^>]*>/i',
+ // 'trimRe' => '/^\s+|\s+$/g', // PHP has trim()
+ 'normalize' => '/\s{2,}/',
+ 'killBreaks' => '/(<br\s*\/?>(\s| ?)*){1,}/',
+ 'video' => '/http:\/\/(www\.)?(youtube|vimeo|dailymotion)\.com/i',
+ 'skipFootnoteLink' => '/^\s*(\[?[a-z0-9]{1,2}\]?|^|edit|citation needed)\s*$/i'
+ );
+
+ /**
+ * Create instance of Readability
+ * @param string UTF-8 encoded string
+ * @param string (optional) URL associated with HTML (used for footnotes)
+ */
+ function __construct($html, $url=null)
+ {
+ /* Turn all double br's into p's */
+ $html = preg_replace($this->regexps['replaceBrs'], '</p><p>', $html);
+ $html = preg_replace($this->regexps['replaceFonts'], '<$1span>', $html);
+ $html = mb_convert_encoding($html, 'HTML-ENTITIES', "UTF-8");
+ $this->dom = new DOMDocument();
+ $this->dom->preserveWhiteSpace = false;
+ $this->dom->registerNodeClass('DOMElement', 'JSLikeHTMLElement');
+ if (trim($html) == '') $html = '<html></html>';
+ @$this->dom->loadHTML($html);
+ $this->url = $url;
+ }
+
+ /**
+ * Get article title element
+ * @return DOMElement
+ */
+ public function getTitle() {
+ return $this->articleTitle;
+ }
+
+ /**
+ * Get article content element
+ * @return DOMElement
+ */
+ public function getContent() {
+ return $this->articleContent;
+ }
+
+ /**
+ * Runs readability.
+ *
+ * Workflow:
+ * 1. Prep the document by removing script tags, css, etc.
+ * 2. Build readability's DOM tree.
+ * 3. Grab the article content from the current dom tree.
+ * 4. Replace the current DOM tree with the new one.
+ * 5. Read peacefully.
+ *
+ * @return boolean true if we found content, false otherwise
+ **/
+ public function init()
+ {
+ if (!isset($this->dom->documentElement)) return false;
+ $this->removeScripts($this->dom);
+ //die($this->getInnerHTML($this->dom->documentElement));
+
+ // Assume successful outcome
+ $this->success = true;
+
+ $bodyElems = $this->dom->getElementsByTagName('body');
+ if ($bodyElems->length > 0) {
+ if ($this->bodyCache == null) {
+ $this->bodyCache = $bodyElems->item(0)->innerHTML;
+ }
+ if ($this->body == null) {
+ $this->body = $bodyElems->item(0);
+ }
+ }
+
+ $this->prepDocument();
+
+ //die($this->dom->documentElement->parentNode->nodeType);
+ //$this->setInnerHTML($this->dom->documentElement, $this->getInnerHTML($this->dom->documentElement));
+ //die($this->getInnerHTML($this->dom->documentElement));
+
+ /* Build readability's DOM tree */
+ $overlay = $this->dom->createElement('div');
+ $innerDiv = $this->dom->createElement('div');
+ $articleTitle = $this->getArticleTitle();
+ $articleContent = $this->grabArticle();
+
+ if (!$articleContent) {
+ $this->success = false;
+ $articleContent = $this->dom->createElement('div');
+ $articleContent->setAttribute('id', 'readability-content');
+ $articleContent->innerHTML = '<p>Sorry, Readability was unable to parse this page for content.</p>';
+ }
+
+ $overlay->setAttribute('id', 'readOverlay');
+ $innerDiv->setAttribute('id', 'readInner');
+
+ /* Glue the structure of our document together. */
+ $innerDiv->appendChild($articleTitle);
+ $innerDiv->appendChild($articleContent);
+ $overlay->appendChild($innerDiv);
+
+ /* Clear the old HTML, insert the new content. */
+ $this->body->innerHTML = '';
+ $this->body->appendChild($overlay);
+ //document.body.insertBefore(overlay, document.body.firstChild);
+ $this->body->removeAttribute('style');
+
+ $this->postProcessContent($articleContent);
+
+ // Set title and content instance variables
+ $this->articleTitle = $articleTitle;
+ $this->articleContent = $articleContent;
+
+ return $this->success;
+ }
+
+ /**
+ * Debug
+ */
+ protected function dbg($msg) {
+ if ($this->debug) echo '* ',$msg, '<br />', "\n";
+ }
+
+ /**
+ * Run any post-process modifications to article content as necessary.
+ *
+ * @param DOMElement
+ * @return void
+ */
+ public function postProcessContent($articleContent) {
+ if ($this->convertLinksToFootnotes && !preg_match('/wikipedia\.org/', @$this->url)) {
+ $this->addFootnotes($articleContent);
+ }
+ }
+
+ /**
+ * Get the article title as an H1.
+ *
+ * @return DOMElement
+ */
+ protected function getArticleTitle() {
+ $curTitle = '';
+ $origTitle = '';
+
+ try {
+ $curTitle = $origTitle = $this->getInnerText($this->dom->getElementsByTagName('title')->item(0));
+ } catch(Exception $e) {}
+
+ if (preg_match('/ [\|\-] /', $curTitle))
+ {
+ $curTitle = preg_replace('/(.*)[\|\-] .*/i', '$1', $origTitle);
+
+ if (count(explode(' ', $curTitle)) < 3) {
+ $curTitle = preg_replace('/[^\|\-]*[\|\-](.*)/i', '$1', $origTitle);
+ }
+ }
+ else if (strpos($curTitle, ': ') !== false)
+ {
+ $curTitle = preg_replace('/.*:(.*)/i', '$1', $origTitle);
+
+ if (count(explode(' ', $curTitle)) < 3) {
+ $curTitle = preg_replace('/[^:]*[:](.*)/i','$1', $origTitle);
+ }
+ }
+ else if(strlen($curTitle) > 150 || strlen($curTitle) < 15)
+ {
+ $hOnes = $this->dom->getElementsByTagName('h1');
+ if($hOnes->length == 1)
+ {
+ $curTitle = $this->getInnerText($hOnes->item(0));
+ }
+ }
+
+ $curTitle = trim($curTitle);
+
+ if (count(explode(' ', $curTitle)) <= 4) {
+ $curTitle = $origTitle;
+ }
+
+ $articleTitle = $this->dom->createElement('h1');
+ $articleTitle->innerHTML = $curTitle;
+
+ return $articleTitle;
+ }
+
+ /**
+ * Prepare the HTML document for readability to scrape it.
+ * This includes things like stripping javascript, CSS, and handling terrible markup.
+ *
+ * @return void
+ **/
+ protected function prepDocument() {
+ /**
+ * In some cases a body element can't be found (if the HTML is totally hosed for example)
+ * so we create a new body node and append it to the document.
+ */
+ if ($this->body == null)
+ {
+ $this->body = $this->dom->createElement('body');
+ $this->dom->documentElement->appendChild($this->body);
+ }
+ $this->body->setAttribute('id', 'readabilityBody');
+
+ /* Remove all style tags in head */
+ $styleTags = $this->dom->getElementsByTagName('style');
+ for ($i = $styleTags->length-1; $i >= 0; $i--)
+ {
+ $styleTags->item($i)->parentNode->removeChild($styleTags->item($i));
+ }
+
+ /* Turn all double br's into p's */
+ /* Note, this is pretty costly as far as processing goes. Maybe optimize later. */
+ //document.body.innerHTML = document.body.innerHTML.replace(readability.regexps.replaceBrs, '</p><p>').replace(readability.regexps.replaceFonts, '<$1span>');
+ // We do this in the constructor for PHP as that's when we have raw HTML - before parsing it into a DOM tree.
+ // Manipulating innerHTML as it's done in JS is not possible in PHP.
+ }
+
+ /**
+ * For easier reading, convert this document to have footnotes at the bottom rather than inline links.
+ * @see http://www.roughtype.com/archives/2010/05/experiments_in.php
+ *
+ * @return void
+ **/
+ public function addFootnotes($articleContent) {
+ $footnotesWrapper = $this->dom->createElement('div');
+ $footnotesWrapper->setAttribute('id', 'readability-footnotes');
+ $footnotesWrapper->innerHTML = '<h3>References</h3>';
+
+ $articleFootnotes = $this->dom->createElement('ol');
+ $articleFootnotes->setAttribute('id', 'readability-footnotes-list');
+ $footnotesWrapper->appendChild($articleFootnotes);
+
+ $articleLinks = $articleContent->getElementsByTagName('a');
+
+ $linkCount = 0;
+ for ($i = 0; $i < $articleLinks->length; $i++)
+ {
+ $articleLink = $articleLinks->item($i);
+ $footnoteLink = $articleLink->cloneNode(true);
+ $refLink = $this->dom->createElement('a');
+ $footnote = $this->dom->createElement('li');
+ $linkDomain = @parse_url($footnoteLink->getAttribute('href'), PHP_URL_HOST);
+ if (!$linkDomain && isset($this->url)) $linkDomain = @parse_url($this->url, PHP_URL_HOST);
+ //linkDomain = footnoteLink.host ? footnoteLink.host : document.location.host,
+ $linkText = $this->getInnerText($articleLink);
+
+ if ((strpos($articleLink->getAttribute('class'), 'readability-DoNotFootnote') !== false) || preg_match($this->regexps['skipFootnoteLink'], $linkText)) {
+ continue;
+ }
+
+ $linkCount++;
+
+ /** Add a superscript reference after the article link */
+ $refLink->setAttribute('href', '#readabilityFootnoteLink-' . $linkCount);
+ $refLink->innerHTML = '<small><sup>[' . $linkCount . ']</sup></small>';
+ $refLink->setAttribute('class', 'readability-DoNotFootnote');
+ $refLink->setAttribute('style', 'color: inherit;');
+
+ //TODO: does this work or should we use DOMNode.isSameNode()?
+ if ($articleLink->parentNode->lastChild == $articleLink) {
+ $articleLink->parentNode->appendChild($refLink);
+ } else {
+ $articleLink->parentNode->insertBefore($refLink, $articleLink->nextSibling);
+ }
+
+ $articleLink->setAttribute('style', 'color: inherit; text-decoration: none;');
+ $articleLink->setAttribute('name', 'readabilityLink-' . $linkCount);
+
+ $footnote->innerHTML = '<small><sup><a href="#readabilityLink-' . $linkCount . '" title="Jump to Link in Article">^</a></sup></small> ';
+
+ $footnoteLink->innerHTML = ($footnoteLink->getAttribute('title') != '' ? $footnoteLink->getAttribute('title') : $linkText);
+ $footnoteLink->setAttribute('name', 'readabilityFootnoteLink-' . $linkCount);
+
+ $footnote->appendChild($footnoteLink);
+ if ($linkDomain) $footnote->innerHTML = $footnote->innerHTML . '<small> (' . $linkDomain . ')</small>';
+
+ $articleFootnotes->appendChild($footnote);
+ }
+
+ if ($linkCount > 0) {
+ $articleContent->appendChild($footnotesWrapper);
+ }
+ }
+
+ /**
+ * Reverts P elements with class 'readability-styled'
+ * to text nodes - which is what they were before.
+ *
+ * @param DOMElement
+ * @return void
+ */
+ function revertReadabilityStyledElements($articleContent) {
+ $xpath = new DOMXPath($articleContent->ownerDocument);
+ $elems = $xpath->query('.//p[@class="readability-styled"]', $articleContent);
+ //$elems = $articleContent->getElementsByTagName('p');
+ for ($i = $elems->length-1; $i >= 0; $i--) {
+ $e = $elems->item($i);
+ $e->parentNode->replaceChild($articleContent->ownerDocument->createTextNode($e->textContent), $e);
+ //if ($e->hasAttribute('class') && $e->getAttribute('class') == 'readability-styled') {
+ // $e->parentNode->replaceChild($this->dom->createTextNode($e->textContent), $e);
+ //}
+ }
+ }
+
+ /**
+ * Prepare the article node for display. Clean out any inline styles,
+ * iframes, forms, strip extraneous <p> tags, etc.
+ *
+ * @param DOMElement
+ * @return void
+ */
+ function prepArticle($articleContent) {
+ $this->cleanStyles($articleContent);
+ $this->killBreaks($articleContent);
+ if ($this->revertForcedParagraphElements) {
+ $this->revertReadabilityStyledElements($articleContent);
+ }
+
+ /* Clean out junk from the article content */
+ $this->cleanConditionally($articleContent, 'form');
+ $this->clean($articleContent, 'object');
+ $this->clean($articleContent, 'h1');
+
+ /**
+ * If there is only one h2, they are probably using it
+ * as a header and not a subheader, so remove it since we already have a header.
+ ***/
+ if ($articleContent->getElementsByTagName('h2')->length == 1) {
+ $this->clean($articleContent, 'h2');
+ }
+ $this->clean($articleContent, 'iframe');
+
+ $this->cleanHeaders($articleContent);
+
+ /* Do these last as the previous stuff may have removed junk that will affect these */
+ $this->cleanConditionally($articleContent, 'table');
+ $this->cleanConditionally($articleContent, 'ul');
+ $this->cleanConditionally($articleContent, 'div');
+
+ /* Remove extra paragraphs */
+ $articleParagraphs = $articleContent->getElementsByTagName('p');
+ for ($i = $articleParagraphs->length-1; $i >= 0; $i--)
+ {
+ $imgCount = $articleParagraphs->item($i)->getElementsByTagName('img')->length;
+ $embedCount = $articleParagraphs->item($i)->getElementsByTagName('embed')->length;
+ $objectCount = $articleParagraphs->item($i)->getElementsByTagName('object')->length;
+
+ if ($imgCount === 0 && $embedCount === 0 && $objectCount === 0 && $this->getInnerText($articleParagraphs->item($i), false) == '')
+ {
+ $articleParagraphs->item($i)->parentNode->removeChild($articleParagraphs->item($i));
+ }
+ }
+
+ try {
+ $articleContent->innerHTML = preg_replace('/<br[^>]*>\s*<p/i', '<p', $articleContent->innerHTML);
+ //articleContent.innerHTML = articleContent.innerHTML.replace(/<br[^>]*>\s*<p/gi, '<p');
+ }
+ catch (Exception $e) {
+ $this->dbg("Cleaning innerHTML of breaks failed. This is an IE strict-block-elements bug. Ignoring.: " . $e);
+ }
+ }
+
+ /**
+ * Initialize a node with the readability object. Also checks the
+ * className/id for special names to add to its score.
+ *
+ * @param Element
+ * @return void
+ **/
+ protected function initializeNode($node) {
+ $readability = $this->dom->createAttribute('readability');
+ $readability->value = 0; // this is our contentScore
+ $node->setAttributeNode($readability);
+
+ switch (strtoupper($node->tagName)) { // unsure if strtoupper is needed, but using it just in case
+ case 'DIV':
+ $readability->value += 5;
+ break;
+
+ case 'PRE':
+ case 'TD':
+ case 'BLOCKQUOTE':
+ $readability->value += 3;
+ break;
+
+ case 'ADDRESS':
+ case 'OL':
+ case 'UL':
+ case 'DL':
+ case 'DD':
+ case 'DT':
+ case 'LI':
+ case 'FORM':
+ $readability->value -= 3;
+ break;
+
+ case 'H1':
+ case 'H2':
+ case 'H3':
+ case 'H4':
+ case 'H5':
+ case 'H6':
+ case 'TH':
+ $readability->value -= 5;
+ break;
+ }
+ $readability->value += $this->getClassWeight($node);
+ }
+
+ /***
+ * grabArticle - Using a variety of metrics (content score, classname, element types), find the content that is
+ * most likely to be the stuff a user wants to read. Then return it wrapped up in a div.
+ *
+ * @return DOMElement
+ **/
+ protected function grabArticle($page=null) {
+ $stripUnlikelyCandidates = $this->flagIsActive(self::FLAG_STRIP_UNLIKELYS);
+ if (!$page) $page = $this->dom;
+ $allElements = $page->getElementsByTagName('*');
+ /**
+ * First, node prepping. Trash nodes that look cruddy (like ones with the class name "comment", etc), and turn divs
+ * into P tags where they have been used inappropriately (as in, where they contain no other block level elements.)
+ *
+ * Note: Assignment from index for performance. See http://www.peachpit.com/articles/article.aspx?p=31567&seqNum=5
+ * TODO: Shouldn't this be a reverse traversal?
+ **/
+ $node = null;
+ $nodesToScore = array();
+ for ($nodeIndex = 0; ($node = $allElements->item($nodeIndex)); $nodeIndex++) {
+ //for ($nodeIndex=$targetList->length-1; $nodeIndex >= 0; $nodeIndex--) {
+ //$node = $targetList->item($nodeIndex);
+ $tagName = strtoupper($node->tagName);
+ /* Remove unlikely candidates */
+ if ($stripUnlikelyCandidates) {
+ $unlikelyMatchString = $node->getAttribute('class') . $node->getAttribute('id');
+ if (
+ preg_match($this->regexps['unlikelyCandidates'], $unlikelyMatchString) &&
+ !preg_match($this->regexps['okMaybeItsACandidate'], $unlikelyMatchString) &&
+ $tagName != 'BODY'
+ )
+ {
+ $this->dbg('Removing unlikely candidate - ' . $unlikelyMatchString);
+ //$nodesToRemove[] = $node;
+ $node->parentNode->removeChild($node);
+ $nodeIndex--;
+ continue;
+ }
+ }
+
+ if ($tagName == 'P' || $tagName == 'TD' || $tagName == 'PRE') {
+ $nodesToScore[] = $node;
+ }
+
+ /* Turn all divs that don't have children block level elements into p's */
+ if ($tagName == 'DIV') {
+ if (!preg_match($this->regexps['divToPElements'], $node->innerHTML)) {
+ //$this->dbg('Altering div to p');
+ $newNode = $this->dom->createElement('p');
+ try {
+ $newNode->innerHTML = $node->innerHTML;
+ //$nodesToReplace[] = array('new'=>$newNode, 'old'=>$node);
+ $node->parentNode->replaceChild($newNode, $node);
+ $nodeIndex--;
+ $nodesToScore[] = $node; // or $newNode?
+ }
+ catch(Exception $e) {
+ $this->dbg('Could not alter div to p, reverting back to div.: ' . $e);
+ }
+ }
+ else
+ {
+ // EXPERIMENTAL
+ // TODO: change these p elements back to text nodes after processing
+ for ($i = 0, $il = $node->childNodes->length; $i < $il; $i++) {
+ $childNode = $node->childNodes->item($i);
+ if ($childNode->nodeType == 3) { // XML_TEXT_NODE
+ //$this->dbg('replacing text node with a p tag with the same content.');
+ $p = $this->dom->createElement('p');
+ $p->innerHTML = $childNode->nodeValue;
+ $p->setAttribute('style', 'display: inline;');
+ $p->setAttribute('class', 'readability-styled');
+ $childNode->parentNode->replaceChild($p, $childNode);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Loop through all paragraphs, and assign a score to them based on how content-y they look.
+ * Then add their score to their parent node.
+ *
+ * A score is determined by things like number of commas, class names, etc. Maybe eventually link density.
+ **/
+ $candidates = array();
+ for ($pt=0; $pt < count($nodesToScore); $pt++) {
+ $parentNode = $nodesToScore[$pt]->parentNode;
+ // $grandParentNode = $parentNode ? $parentNode->parentNode : null;
+ $grandParentNode = !$parentNode ? null : (($parentNode->parentNode instanceof DOMElement) ? $parentNode->parentNode : null);
+ $innerText = $this->getInnerText($nodesToScore[$pt]);
+
+ if (!$parentNode || !isset($parentNode->tagName)) {
+ continue;
+ }
+
+ /* If this paragraph is less than 25 characters, don't even count it. */
+ if(strlen($innerText) < 25) {
+ continue;
+ }
+
+ /* Initialize readability data for the parent. */
+ if (!$parentNode->hasAttribute('readability'))
+ {
+ $this->initializeNode($parentNode);
+ $candidates[] = $parentNode;
+ }
+
+ /* Initialize readability data for the grandparent. */
+ if ($grandParentNode && !$grandParentNode->hasAttribute('readability') && isset($grandParentNode->tagName))
+ {
+ $this->initializeNode($grandParentNode);
+ $candidates[] = $grandParentNode;
+ }
+
+ $contentScore = 0;
+
+ /* Add a point for the paragraph itself as a base. */
+ $contentScore++;
+
+ /* Add points for any commas within this paragraph */
+ $contentScore += count(explode(',', $innerText));
+
+ /* For every 100 characters in this paragraph, add another point. Up to 3 points. */
+ $contentScore += min(floor(strlen($innerText) / 100), 3);
+
+ /* Add the score to the parent. The grandparent gets half. */
+ $parentNode->getAttributeNode('readability')->value += $contentScore;
+
+ if ($grandParentNode) {
+ $grandParentNode->getAttributeNode('readability')->value += $contentScore/2;
+ }
+ }
+
+ /**
+ * After we've calculated scores, loop through all of the possible candidate nodes we found
+ * and find the one with the highest score.
+ **/
+ $topCandidate = null;
+ for ($c=0, $cl=count($candidates); $c < $cl; $c++)
+ {
+ /**
+ * Scale the final candidates score based on link density. Good content should have a
+ * relatively small link density (5% or less) and be mostly unaffected by this operation.
+ **/
+ $readability = $candidates[$c]->getAttributeNode('readability');
+ $readability->value = $readability->value * (1-$this->getLinkDensity($candidates[$c]));
+
+ $this->dbg('Candidate: ' . $candidates[$c]->tagName . ' (' . $candidates[$c]->getAttribute('class') . ':' . $candidates[$c]->getAttribute('id') . ') with score ' . $readability->value);
+
+ if (!$topCandidate || $readability->value > (int)$topCandidate->getAttribute('readability')) {
+ $topCandidate = $candidates[$c];
+ }
+ }
+
+ /**
+ * If we still have no top candidate, just use the body as a last resort.
+ * We also have to copy the body node so it is something we can modify.
+ **/
+ if ($topCandidate === null || strtoupper($topCandidate->tagName) == 'BODY')
+ {
+ $topCandidate = $this->dom->createElement('div');
+ if ($page instanceof DOMDocument) {
+ if (!isset($page->documentElement)) {
+ // we don't have a body either? what a mess! :)
+ } else {
+ $topCandidate->innerHTML = $page->documentElement->innerHTML;
+ $page->documentElement->innerHTML = '';
+ $page->documentElement->appendChild($topCandidate);
+ }
+ } else {
+ $topCandidate->innerHTML = $page->innerHTML;
+ $page->innerHTML = '';
+ $page->appendChild($topCandidate);
+ }
+ $this->initializeNode($topCandidate);
+ }
+
+ /**
+ * Now that we have the top candidate, look through its siblings for content that might also be related.
+ * Things like preambles, content split by ads that we removed, etc.
+ **/
+ $articleContent = $this->dom->createElement('div');
+ $articleContent->setAttribute('id', 'readability-content');
+ $siblingScoreThreshold = max(10, ((int)$topCandidate->getAttribute('readability')) * 0.2);
+ $siblingNodes = $topCandidate->parentNode->childNodes;
+ if (!isset($siblingNodes)) {
+ $siblingNodes = new stdClass;
+ $siblingNodes->length = 0;
+ }
+
+ for ($s=0, $sl=$siblingNodes->length; $s < $sl; $s++)
+ {
+ $siblingNode = $siblingNodes->item($s);
+ $append = false;
+
+ $this->dbg('Looking at sibling node: ' . $siblingNode->nodeName . (($siblingNode->nodeType === XML_ELEMENT_NODE && $siblingNode->hasAttribute('readability')) ? (' with score ' . $siblingNode->getAttribute('readability')) : ''));
+
+ //dbg('Sibling has score ' . ($siblingNode->readability ? siblingNode.readability.contentScore : 'Unknown'));
+
+ if ($siblingNode === $topCandidate)
+ // or if ($siblingNode->isSameNode($topCandidate))
+ {
+ $append = true;
+ }
+
+ $contentBonus = 0;
+ /* Give a bonus if sibling nodes and top candidates have the example same classname */
+ if ($siblingNode->nodeType === XML_ELEMENT_NODE && $siblingNode->getAttribute('class') == $topCandidate->getAttribute('class') && $topCandidate->getAttribute('class') != '') {
+ $contentBonus += ((int)$topCandidate->getAttribute('readability')) * 0.2;
+ }
+
+ if ($siblingNode->nodeType === XML_ELEMENT_NODE && $siblingNode->hasAttribute('readability') && (((int)$siblingNode->getAttribute('readability')) + $contentBonus) >= $siblingScoreThreshold)
+ {
+ $append = true;
+ }
+
+ if (strtoupper($siblingNode->nodeName) == 'P') {
+ $linkDensity = $this->getLinkDensity($siblingNode);
+ $nodeContent = $this->getInnerText($siblingNode);
+ $nodeLength = strlen($nodeContent);
+
+ if ($nodeLength > 80 && $linkDensity < 0.25)
+ {
+ $append = true;
+ }
+ else if ($nodeLength < 80 && $linkDensity === 0 && preg_match('/\.( |$)/', $nodeContent))
+ {
+ $append = true;
+ }
+ }
+
+ /* Look for a special classname */
+ if ($siblingNode->nodeType === XML_ELEMENT_NODE && $siblingNode->hasAttribute('class') && $siblingNode->getAttribute('class') != '')
+ {
+ if (preg_match($this->regexps['okMaybeItsACandidate'], $siblingNode->getAttribute('class'))) {
+ $append = true;
+ }
+ }
+
+ /* Look for a special classname */
+ if ($siblingNode->nodeType === XML_ELEMENT_NODE && $siblingNode->hasAttribute('id') && $siblingNode->getAttribute('id') != '')
+ {
+ if (preg_match($this->regexps['okMaybeItsACandidate'], $siblingNode->getAttribute('id'))) {
+ $append = true;
+ }
+ }
+
+
+ if ($append)
+ {
+ $this->dbg('Appending node: ' . $siblingNode->nodeName);
+
+ $nodeToAppend = null;
+ $sibNodeName = strtoupper($siblingNode->nodeName);
+ if ($sibNodeName != 'DIV' && $sibNodeName != 'P') {
+ /* 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. */
+
+ $this->dbg('Altering siblingNode of ' . $sibNodeName . ' to div.');
+ $nodeToAppend = $this->dom->createElement('div');
+ try {
+ $nodeToAppend->setAttribute('id', $siblingNode->getAttribute('id'));
+ $nodeToAppend->innerHTML = $siblingNode->innerHTML;
+ }
+ catch(Exception $e)
+ {
+ $this->dbg('Could not alter siblingNode to div, reverting back to original.');
+ $nodeToAppend = $siblingNode;
+ $s--;
+ $sl--;
+ }
+ } else {
+ $nodeToAppend = $siblingNode;
+ $s--;
+ $sl--;
+ }
+
+ /* To ensure a node does not interfere with readability styles, remove its classnames */
+ $nodeToAppend->removeAttribute('class');
+
+ /* Append sibling and subtract from our list because it removes the node when you append to another node */
+ $articleContent->appendChild($nodeToAppend);
+ }
+ }
+
+ /**
+ * So we have all of the content that we need. Now we clean it up for presentation.
+ **/
+ $this->prepArticle($articleContent);
+
+ /**
+ * Now that we've gone through the full algorithm, check to see if we got any meaningful content.
+ * If we didn't, we may need to re-run grabArticle with different flags set. This gives us a higher
+ * likelihood of finding the content, and the sieve approach gives us a higher likelihood of
+ * finding the -right- content.
+ **/
+ if (strlen($this->getInnerText($articleContent, false)) < 250)
+ {
+ // 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
+ // in the meantime, we check and create an empty element if it's not there.
+ if (!isset($this->body->childNodes)) $this->body = $this->dom->createElement('body');
+ $this->body->innerHTML = $this->bodyCache;
+
+ if ($this->flagIsActive(self::FLAG_STRIP_UNLIKELYS)) {
+ $this->removeFlag(self::FLAG_STRIP_UNLIKELYS);
+ return $this->grabArticle($this->body);
+ }
+ else if ($this->flagIsActive(self::FLAG_WEIGHT_CLASSES)) {
+ $this->removeFlag(self::FLAG_WEIGHT_CLASSES);
+ return $this->grabArticle($this->body);
+ }
+ else if ($this->flagIsActive(self::FLAG_CLEAN_CONDITIONALLY)) {
+ $this->removeFlag(self::FLAG_CLEAN_CONDITIONALLY);
+ return $this->grabArticle($this->body);
+ }
+ else {
+ return false;
+ }
+ }
+ return $articleContent;
+ }
+
+ /**
+ * Remove script tags from document
+ *
+ * @param DOMElement
+ * @return void
+ */
+ public function removeScripts($doc) {
+ $scripts = $doc->getElementsByTagName('script');
+ for($i = $scripts->length-1; $i >= 0; $i--)
+ {
+ $scripts->item($i)->parentNode->removeChild($scripts->item($i));
+ }
+ }
+
+ /**
+ * Get the inner text of a node.
+ * This also strips out any excess whitespace to be found.
+ *
+ * @param DOMElement $
+ * @param boolean $normalizeSpaces (default: true)
+ * @return string
+ **/
+ public function getInnerText($e, $normalizeSpaces=true) {
+ $textContent = '';
+
+ if (!isset($e->textContent) || $e->textContent == '') {
+ return '';
+ }
+
+ $textContent = trim($e->textContent);
+
+ if ($normalizeSpaces) {
+ return preg_replace($this->regexps['normalize'], ' ', $textContent);
+ } else {
+ return $textContent;
+ }
+ }
+
+ /**
+ * Get the number of times a string $s appears in the node $e.
+ *
+ * @param DOMElement $e
+ * @param string - what to count. Default is ","
+ * @return number (integer)
+ **/
+ public function getCharCount($e, $s=',') {
+ return substr_count($this->getInnerText($e), $s);
+ }
+
+ /**
+ * Remove the style attribute on every $e and under.
+ *
+ * @param DOMElement $e
+ * @return void
+ */
+ public function cleanStyles($e) {
+ if (!is_object($e)) return;
+ $elems = $e->getElementsByTagName('*');
+ foreach ($elems as $elem) {
+ $elem->removeAttribute('style');
+ }
+ }
+
+ /**
+ * Get the density of links as a percentage of the content
+ * This is the amount of text that is inside a link divided by the total text in the node.
+ *
+ * @param DOMElement $e
+ * @return number (float)
+ */
+ public function getLinkDensity($e) {
+ $links = $e->getElementsByTagName('a');
+ $textLength = strlen($this->getInnerText($e));
+ $linkLength = 0;
+ for ($i=0, $il=$links->length; $i < $il; $i++)
+ {
+ $linkLength += strlen($this->getInnerText($links->item($i)));
+ }
+ if ($textLength > 0) {
+ return $linkLength / $textLength;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Get an elements class/id weight. Uses regular expressions to tell if this
+ * element looks good or bad.
+ *
+ * @param DOMElement $e
+ * @return number (Integer)
+ */
+ public function getClassWeight($e) {
+ if(!$this->flagIsActive(self::FLAG_WEIGHT_CLASSES)) {
+ return 0;
+ }
+
+ $weight = 0;
+
+ /* Look for a special classname */
+ if ($e->hasAttribute('class') && $e->getAttribute('class') != '')
+ {
+ if (preg_match($this->regexps['negative'], $e->getAttribute('class'))) {
+ $weight -= 25;
+ }
+ if (preg_match($this->regexps['positive'], $e->getAttribute('class'))) {
+ $weight += 25;
+ }
+ }
+
+ /* Look for a special ID */
+ if ($e->hasAttribute('id') && $e->getAttribute('id') != '')
+ {
+ if (preg_match($this->regexps['negative'], $e->getAttribute('id'))) {
+ $weight -= 25;
+ }
+ if (preg_match($this->regexps['positive'], $e->getAttribute('id'))) {
+ $weight += 25;
+ }
+ }
+ return $weight;
+ }
+
+ /**
+ * Remove extraneous break tags from a node.
+ *
+ * @param DOMElement $node
+ * @return void
+ */
+ public function killBreaks($node) {
+ $html = $node->innerHTML;
+ $html = preg_replace($this->regexps['killBreaks'], '<br />', $html);
+ $node->innerHTML = $html;
+ }
+
+ /**
+ * Clean a node of all elements of type "tag".
+ * (Unless it's a youtube/vimeo video. People love movies.)
+ *
+ * @param DOMElement $e
+ * @param string $tag
+ * @return void
+ */
+ public function clean($e, $tag) {
+ $targetList = $e->getElementsByTagName($tag);
+ $isEmbed = ($tag == 'object' || $tag == 'embed');
+
+ for ($y=$targetList->length-1; $y >= 0; $y--) {
+ /* Allow youtube and vimeo videos through as people usually want to see those. */
+ if ($isEmbed) {
+ $attributeValues = '';
+ for ($i=0, $il=$targetList->item($y)->attributes->length; $i < $il; $i++) {
+ $attributeValues .= $targetList->item($y)->attributes->item($i)->value . '|'; // DOMAttr? (TODO: test)
+ }
+
+ /* First, check the elements attributes to see if any of them contain youtube or vimeo */
+ if (preg_match($this->regexps['video'], $attributeValues)) {
+ continue;
+ }
+
+ /* Then check the elements inside this element for the same. */
+ if (preg_match($this->regexps['video'], $targetList->item($y)->innerHTML)) {
+ continue;
+ }
+ }
+ $targetList->item($y)->parentNode->removeChild($targetList->item($y));
+ }
+ }
+
+ /**
+ * Clean an element of all tags of type "tag" if they look fishy.
+ * "Fishy" is an algorithm based on content length, classnames,
+ * link density, number of images & embeds, etc.
+ *
+ * @param DOMElement $e
+ * @param string $tag
+ * @return void
+ */
+ public function cleanConditionally($e, $tag) {
+ if (!$this->flagIsActive(self::FLAG_CLEAN_CONDITIONALLY)) {
+ return;
+ }
+
+ $tagsList = $e->getElementsByTagName($tag);
+ $curTagsLength = $tagsList->length;
+
+ /**
+ * Gather counts for other typical elements embedded within.
+ * Traverse backwards so we can remove nodes at the same time without effecting the traversal.
+ *
+ * TODO: Consider taking into account original contentScore here.
+ */
+ for ($i=$curTagsLength-1; $i >= 0; $i--) {
+ $weight = $this->getClassWeight($tagsList->item($i));
+ $contentScore = ($tagsList->item($i)->hasAttribute('readability')) ? (int)$tagsList->item($i)->getAttribute('readability') : 0;
+
+ $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')) : ''));
+
+ if ($weight + $contentScore < 0) {
+ $tagsList->item($i)->parentNode->removeChild($tagsList->item($i));
+ }
+ else if ( $this->getCharCount($tagsList->item($i), ',') < 10) {
+ /**
+ * If there are not very many commas, and the number of
+ * non-paragraph elements is more than paragraphs or other ominous signs, remove the element.
+ **/
+ $p = $tagsList->item($i)->getElementsByTagName('p')->length;
+ $img = $tagsList->item($i)->getElementsByTagName('img')->length;
+ $li = $tagsList->item($i)->getElementsByTagName('li')->length-100;
+ $input = $tagsList->item($i)->getElementsByTagName('input')->length;
+
+ $embedCount = 0;
+ $embeds = $tagsList->item($i)->getElementsByTagName('embed');
+ for ($ei=0, $il=$embeds->length; $ei < $il; $ei++) {
+ if (preg_match($this->regexps['video'], $embeds->item($ei)->getAttribute('src'))) {
+ $embedCount++;
+ }
+ }
+
+ $linkDensity = $this->getLinkDensity($tagsList->item($i));
+ $contentLength = strlen($this->getInnerText($tagsList->item($i)));
+ $toRemove = false;
+
+ if ( $img > $p ) {
+ $toRemove = true;
+ } else if ($li > $p && $tag != 'ul' && $tag != 'ol') {
+ $toRemove = true;
+ } else if ( $input > floor($p/3) ) {
+ $toRemove = true;
+ } else if ($contentLength < 25 && ($img === 0 || $img > 2) ) {
+ $toRemove = true;
+ } else if($weight < 25 && $linkDensity > 0.2) {
+ $toRemove = true;
+ } else if($weight >= 25 && $linkDensity > 0.5) {
+ $toRemove = true;
+ } else if(($embedCount == 1 && $contentLength < 75) || $embedCount > 1) {
+ $toRemove = true;
+ }
+
+ if ($toRemove) {
+ $tagsList->item($i)->parentNode->removeChild($tagsList->item($i));
+ }
+ }
+ }
+ }
+
+ /**
+ * Clean out spurious headers from an Element. Checks things like classnames and link density.
+ *
+ * @param DOMElement $e
+ * @return void
+ */
+ public function cleanHeaders($e) {
+ for ($headerIndex = 1; $headerIndex < 3; $headerIndex++) {
+ $headers = $e->getElementsByTagName('h' . $headerIndex);
+ for ($i=$headers->length-1; $i >=0; $i--) {
+ if ($this->getClassWeight($headers->item($i)) < 0 || $this->getLinkDensity($headers->item($i)) > 0.33) {
+ $headers->item($i)->parentNode->removeChild($headers->item($i));
+ }
+ }
+ }
+ }
+
+ public function flagIsActive($flag) {
+ return ($this->flags & $flag) > 0;
+ }
+
+ public function addFlag($flag) {
+ $this->flags = $this->flags | $flag;
+ }
+
+ public function removeFlag($flag) {
+ $this->flags = $this->flags & ~$flag;
+ }
+}
+?>
\ No newline at end of file
--- /dev/null
+<?php
+
+/**
+ * RainTPL
+ * -------
+ * Realized by Federico Ulfo & maintained by the Rain Team
+ * Distributed under GNU/LGPL 3 License
+ *
+ * @version 2.7.2
+ */
+
+
+class RainTPL{
+
+ // -------------------------
+ // CONFIGURATION
+ // -------------------------
+
+ /**
+ * Template directory
+ *
+ * @var string
+ */
+ static $tpl_dir = "tpl/";
+
+
+ /**
+ * Cache directory. Is the directory where RainTPL will compile the template and save the cache
+ *
+ * @var string
+ */
+ static $cache_dir = "tmp/";
+
+
+ /**
+ * Template base URL. RainTPL will add this URL to the relative paths of element selected in $path_replace_list.
+ *
+ * @var string
+ */
+ static $base_url = null;
+
+
+ /**
+ * Template extension.
+ *
+ * @var string
+ */
+ static $tpl_ext = "html";
+
+
+ /**
+ * 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="...">)
+ * Set true to enable the path replace.
+ *
+ * @var unknown_type
+ */
+ static $path_replace = true;
+
+
+ /**
+ * You can set what the path_replace method will replace.
+ * Avaible options: a, img, link, script, input
+ *
+ * @var array
+ */
+ static $path_replace_list = array( 'a', 'img', 'link', 'script', 'input' );
+
+
+ /**
+ * You can define in the black list what string are disabled into the template tags
+ *
+ * @var unknown_type
+ */
+ static $black_list = array( '\$this', 'raintpl::', 'self::', '_SESSION', '_SERVER', '_ENV', 'eval', 'exec', 'unlink', 'rmdir' );
+
+
+ /**
+ * Check template.
+ * true: checks template update time, if changed it compile them
+ * false: loads the compiled template. Set false if server doesn't have write permission for cache_directory.
+ *
+ */
+ static $check_template_update = true;
+
+
+ /**
+ * PHP tags <? ?>
+ * True: php tags are enabled into the template
+ * False: php tags are disabled into the template and rendered as html
+ *
+ * @var bool
+ */
+ static $php_enabled = false;
+
+
+ /**
+ * Debug mode flag.
+ * True: debug mode is used, syntax errors are displayed directly in template. Execution of script is not terminated.
+ * False: exception is thrown on found error.
+ *
+ * @var bool
+ */
+ static $debug = false;
+
+ // -------------------------
+
+
+ // -------------------------
+ // RAINTPL VARIABLES
+ // -------------------------
+
+ /**
+ * Is the array where RainTPL keep the variables assigned
+ *
+ * @var array
+ */
+ public $var = array();
+
+ protected $tpl = array(), // variables to keep the template directories and info
+ $cache = false, // static cache enabled / disabled
+ $cache_id = null; // identify only one cache
+
+ protected static $config_name_sum = array(); // takes all the config to create the md5 of the file
+
+ // -------------------------
+
+
+
+ const CACHE_EXPIRE_TIME = 3600; // default cache expire time = hour
+
+
+
+ /**
+ * Assign variable
+ * eg. $t->assign('name','mickey');
+ *
+ * @param mixed $variable_name Name of template variable or associative array name/value
+ * @param mixed $value value assigned to this variable. Not set if variable_name is an associative array
+ */
+
+ function assign( $variable, $value = null ){
+ if( is_array( $variable ) )
+ $this->var += $variable;
+ else
+ $this->var[ $variable ] = $value;
+ }
+
+
+
+ /**
+ * Draw the template
+ * eg. $html = $tpl->draw( 'demo', TRUE ); // return template in string
+ * or $tpl->draw( $tpl_name ); // echo the template
+ *
+ * @param string $tpl_name template to load
+ * @param boolean $return_string true=return a string, false=echo the template
+ * @return string
+ */
+
+ function draw( $tpl_name, $return_string = false ){
+
+ try {
+ // compile the template if necessary and set the template filepath
+ $this->check_template( $tpl_name );
+ } catch (RainTpl_Exception $e) {
+ $output = $this->printDebug($e);
+ die($output);
+ }
+
+ // Cache is off and, return_string is false
+ // Rain just echo the template
+
+ if( !$this->cache && !$return_string ){
+ extract( $this->var );
+ include $this->tpl['compiled_filename'];
+ unset( $this->tpl );
+ }
+
+
+ // cache or return_string are enabled
+ // rain get the output buffer to save the output in the cache or to return it as string
+
+ else{
+
+ //----------------------
+ // get the output buffer
+ //----------------------
+ ob_start();
+ extract( $this->var );
+ include $this->tpl['compiled_filename'];
+ $raintpl_contents = ob_get_clean();
+ //----------------------
+
+
+ // save the output in the cache
+ if( $this->cache )
+ file_put_contents( $this->tpl['cache_filename'], "<?php if(!class_exists('raintpl')){exit;}?>" . $raintpl_contents );
+
+ // free memory
+ unset( $this->tpl );
+
+ // return or print the template
+ if( $return_string ) return $raintpl_contents; else echo $raintpl_contents;
+
+ }
+
+ }
+
+
+
+ /**
+ * If exists a valid cache for this template it returns the cache
+ *
+ * @param string $tpl_name Name of template (set the same of draw)
+ * @param int $expiration_time Set after how many seconds the cache expire and must be regenerated
+ * @return string it return the HTML or null if the cache must be recreated
+ */
+
+ function cache( $tpl_name, $expire_time = self::CACHE_EXPIRE_TIME, $cache_id = null ){
+
+ // set the cache_id
+ $this->cache_id = $cache_id;
+
+ if( !$this->check_template( $tpl_name ) && file_exists( $this->tpl['cache_filename'] ) && ( time() - filemtime( $this->tpl['cache_filename'] ) < $expire_time ) )
+ return substr( file_get_contents( $this->tpl['cache_filename'] ), 43 );
+ else{
+ //delete the cache of the selected template
+ if (file_exists($this->tpl['cache_filename']))
+ unlink($this->tpl['cache_filename'] );
+ $this->cache = true;
+ }
+ }
+
+
+
+ /**
+ * Configure the settings of RainTPL
+ *
+ */
+ static function configure( $setting, $value = null ){
+ if( is_array( $setting ) )
+ foreach( $setting as $key => $value )
+ self::configure( $key, $value );
+ else if( property_exists( __CLASS__, $setting ) ){
+ self::$$setting = $value;
+ self::$config_name_sum[ $setting ] = $value; // take trace of all config
+ }
+ }
+
+
+
+ // check if has to compile the template
+ // return true if the template has changed
+ protected function check_template( $tpl_name ){
+
+ if( !isset($this->tpl['checked']) ){
+
+ $tpl_basename = basename( $tpl_name ); // template basename
+ $tpl_basedir = strpos($tpl_name,"/") ? dirname($tpl_name) . '/' : null; // template basedirectory
+ $tpl_dir = self::$tpl_dir . $tpl_basedir; // template directory
+ $this->tpl['tpl_filename'] = $tpl_dir . $tpl_basename . '.' . self::$tpl_ext; // template filename
+ $temp_compiled_filename = self::$cache_dir . $tpl_basename . "." . md5( $tpl_dir . serialize(self::$config_name_sum));
+ $this->tpl['compiled_filename'] = $temp_compiled_filename . '.rtpl.php'; // cache filename
+ $this->tpl['cache_filename'] = $temp_compiled_filename . '.s_' . $this->cache_id . '.rtpl.php'; // static cache filename
+
+ // if the template doesn't exsist throw an error
+ if( self::$check_template_update && !file_exists( $this->tpl['tpl_filename'] ) ){
+ $e = new RainTpl_NotFoundException( 'Template '. $tpl_basename .' not found!' );
+ throw $e->setTemplateFile($this->tpl['tpl_filename']);
+ }
+
+ // file doesn't exsist, or the template was updated, Rain will compile the template
+ if( !file_exists( $this->tpl['compiled_filename'] ) || ( self::$check_template_update && filemtime($this->tpl['compiled_filename']) < filemtime( $this->tpl['tpl_filename'] ) ) ){
+ $this->compileFile( $tpl_basename, $tpl_basedir, $this->tpl['tpl_filename'], self::$cache_dir, $this->tpl['compiled_filename'] );
+ return true;
+ }
+ $this->tpl['checked'] = true;
+ }
+ }
+
+
+ /**
+ * execute stripslaches() on the xml block. Invoqued by preg_replace_callback function below
+ * @access protected
+ */
+ protected function xml_reSubstitution($capture) {
+ return "<?php echo '<?xml ".stripslashes($capture[1])." ?>'; ?>";
+ }
+
+ /**
+ * Compile and write the compiled template file
+ * @access protected
+ */
+ protected function compileFile( $tpl_basename, $tpl_basedir, $tpl_filename, $cache_dir, $compiled_filename ){
+
+ //read template file
+ $this->tpl['source'] = $template_code = file_get_contents( $tpl_filename );
+
+ //xml substitution
+ $template_code = preg_replace( "/<\?xml(.*?)\?>/s", "##XML\\1XML##", $template_code );
+
+ //disable php tag
+ if( !self::$php_enabled )
+ $template_code = str_replace( array("<?","?>"), array("<?","?>"), $template_code );
+
+ //xml re-substitution
+ $template_code = preg_replace_callback ( "/##XML(.*?)XML##/s", array($this, 'xml_reSubstitution'), $template_code );
+
+ //compile template
+ $template_compiled = "<?php if(!class_exists('raintpl')){exit;}?>" . $this->compileTemplate( $template_code, $tpl_basedir );
+
+
+ // fix the php-eating-newline-after-closing-tag-problem
+ $template_compiled = str_replace( "?>\n", "?>\n\n", $template_compiled );
+
+ // create directories
+ if( !is_dir( $cache_dir ) )
+ mkdir( $cache_dir, 0755, true );
+
+ if( !is_writable( $cache_dir ) )
+ 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/');
+
+ //write compiled file
+ file_put_contents( $compiled_filename, $template_compiled );
+ }
+
+
+
+ /**
+ * Compile template
+ * @access protected
+ */
+ protected function compileTemplate( $template_code, $tpl_basedir ){
+
+ //tag list
+ $tag_regexp = array( 'loop' => '(\{loop(?: name){0,1}="\${0,1}[^"]*"\})',
+ 'loop_close' => '(\{\/loop\})',
+ 'if' => '(\{if(?: condition){0,1}="[^"]*"\})',
+ 'elseif' => '(\{elseif(?: condition){0,1}="[^"]*"\})',
+ 'else' => '(\{else\})',
+ 'if_close' => '(\{\/if\})',
+ 'function' => '(\{function="[^"]*"\})',
+ 'noparse' => '(\{noparse\})',
+ 'noparse_close'=> '(\{\/noparse\})',
+ 'ignore' => '(\{ignore\}|\{\*)',
+ 'ignore_close' => '(\{\/ignore\}|\*\})',
+ 'include' => '(\{include="[^"]*"(?: cache="[^"]*")?\})',
+ 'template_info'=> '(\{\$template_info\})',
+ 'function' => '(\{function="(\w*?)(?:.*?)"\})'
+ );
+
+ $tag_regexp = "/" . join( "|", $tag_regexp ) . "/";
+
+ //split the code with the tags regexp
+ $template_code = preg_split ( $tag_regexp, $template_code, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
+
+ //path replace (src of img, background and href of link)
+ $template_code = $this->path_replace( $template_code, $tpl_basedir );
+
+ //compile the code
+ $compiled_code = $this->compileCode( $template_code );
+
+ //return the compiled code
+ return $compiled_code;
+
+ }
+
+
+
+ /**
+ * Compile the code
+ * @access protected
+ */
+ protected function compileCode( $parsed_code ){
+
+ //variables initialization
+ $compiled_code = $open_if = $comment_is_open = $ignore_is_open = null;
+ $loop_level = 0;
+
+ //read all parsed code
+ while( $html = array_shift( $parsed_code ) ){
+
+ //close ignore tag
+ if( !$comment_is_open && ( strpos( $html, '{/ignore}' ) !== FALSE || strpos( $html, '*}' ) !== FALSE ) )
+ $ignore_is_open = false;
+
+ //code between tag ignore id deleted
+ elseif( $ignore_is_open ){
+ //ignore the code
+ }
+
+ //close no parse tag
+ elseif( strpos( $html, '{/noparse}' ) !== FALSE )
+ $comment_is_open = false;
+
+ //code between tag noparse is not compiled
+ elseif( $comment_is_open )
+ $compiled_code .= $html;
+
+ //ignore
+ elseif( strpos( $html, '{ignore}' ) !== FALSE || strpos( $html, '{*' ) !== FALSE )
+ $ignore_is_open = true;
+
+ //noparse
+ elseif( strpos( $html, '{noparse}' ) !== FALSE )
+ $comment_is_open = true;
+
+ //include tag
+ elseif( preg_match( '/\{include="([^"]*)"(?: cache="([^"]*)"){0,1}\}/', $html, $code ) ){
+
+ //variables substitution
+ $include_var = $this->var_replace( $code[ 1 ], $left_delimiter = null, $right_delimiter = null, $php_left_delimiter = '".' , $php_right_delimiter = '."', $loop_level );
+
+ // if the cache is active
+ if( isset($code[ 2 ]) ){
+
+ //dynamic include
+ $compiled_code .= '<?php $tpl = new '.get_class($this).';' .
+ 'if( $cache = $tpl->cache( $template = basename("'.$include_var.'") ) )' .
+ ' echo $cache;' .
+ 'else{' .
+ ' $tpl_dir_temp = self::$tpl_dir;' .
+ ' $tpl->assign( $this->var );' .
+ ( !$loop_level ? null : '$tpl->assign( "key", $key'.$loop_level.' ); $tpl->assign( "value", $value'.$loop_level.' );' ).
+ ' $tpl->draw( dirname("'.$include_var.'") . ( substr("'.$include_var.'",-1,1) != "/" ? "/" : "" ) . basename("'.$include_var.'") );'.
+ '} ?>';
+ }
+ else{
+
+ //dynamic include
+ $compiled_code .= '<?php $tpl = new '.get_class($this).';' .
+ '$tpl_dir_temp = self::$tpl_dir;' .
+ '$tpl->assign( $this->var );' .
+ ( !$loop_level ? null : '$tpl->assign( "key", $key'.$loop_level.' ); $tpl->assign( "value", $value'.$loop_level.' );' ).
+ '$tpl->draw( dirname("'.$include_var.'") . ( substr("'.$include_var.'",-1,1) != "/" ? "/" : "" ) . basename("'.$include_var.'") );'.
+ '?>';
+
+
+ }
+
+ }
+
+ //loop
+ elseif( preg_match( '/\{loop(?: name){0,1}="\${0,1}([^"]*)"\}/', $html, $code ) ){
+
+ //increase the loop counter
+ $loop_level++;
+
+ //replace the variable in the loop
+ $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 );
+
+ //loop variables
+ $counter = "\$counter$loop_level"; // count iteration
+ $key = "\$key$loop_level"; // key
+ $value = "\$value$loop_level"; // value
+
+ //loop code
+ $compiled_code .= "<?php $counter=-1; if( isset($var) && is_array($var) && sizeof($var) ) foreach( $var as $key => $value ){ $counter++; ?>";
+
+ }
+
+ //close loop tag
+ elseif( strpos( $html, '{/loop}' ) !== FALSE ) {
+
+ //iterator
+ $counter = "\$counter$loop_level";
+
+ //decrease the loop counter
+ $loop_level--;
+
+ //close loop code
+ $compiled_code .= "<?php } ?>";
+
+ }
+
+ //if
+ elseif( preg_match( '/\{if(?: condition){0,1}="([^"]*)"\}/', $html, $code ) ){
+
+ //increase open if counter (for intendation)
+ $open_if++;
+
+ //tag
+ $tag = $code[ 0 ];
+
+ //condition attribute
+ $condition = $code[ 1 ];
+
+ // check if there's any function disabled by black_list
+ $this->function_check( $tag );
+
+ //variable substitution into condition (no delimiter into the condition)
+ $parsed_condition = $this->var_replace( $condition, $tag_left_delimiter = null, $tag_right_delimiter = null, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level );
+
+ //if code
+ $compiled_code .= "<?php if( $parsed_condition ){ ?>";
+
+ }
+
+ //elseif
+ elseif( preg_match( '/\{elseif(?: condition){0,1}="([^"]*)"\}/', $html, $code ) ){
+
+ //tag
+ $tag = $code[ 0 ];
+
+ //condition attribute
+ $condition = $code[ 1 ];
+
+ //variable substitution into condition (no delimiter into the condition)
+ $parsed_condition = $this->var_replace( $condition, $tag_left_delimiter = null, $tag_right_delimiter = null, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level );
+
+ //elseif code
+ $compiled_code .= "<?php }elseif( $parsed_condition ){ ?>";
+ }
+
+ //else
+ elseif( strpos( $html, '{else}' ) !== FALSE ) {
+
+ //else code
+ $compiled_code .= '<?php }else{ ?>';
+
+ }
+
+ //close if tag
+ elseif( strpos( $html, '{/if}' ) !== FALSE ) {
+
+ //decrease if counter
+ $open_if--;
+
+ // close if code
+ $compiled_code .= '<?php } ?>';
+
+ }
+
+ //function
+ elseif( preg_match( '/\{function="(\w*)(.*?)"\}/', $html, $code ) ){
+
+ //tag
+ $tag = $code[ 0 ];
+
+ //function
+ $function = $code[ 1 ];
+
+ // check if there's any function disabled by black_list
+ $this->function_check( $tag );
+
+ if( empty( $code[ 2 ] ) )
+ $parsed_function = $function . "()";
+ else
+ // parse the function
+ $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 );
+
+ //if code
+ $compiled_code .= "<?php echo $parsed_function; ?>";
+ }
+
+ // show all vars
+ elseif ( strpos( $html, '{$template_info}' ) !== FALSE ) {
+
+ //tag
+ $tag = '{$template_info}';
+
+ //if code
+ $compiled_code .= '<?php echo "<pre>"; print_r( $this->var ); echo "</pre>"; ?>';
+ }
+
+
+ //all html code
+ else{
+
+ //variables substitution (es. {$title})
+ $html = $this->var_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true );
+ //const substitution (es. {#CONST#})
+ $html = $this->const_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true );
+ //functions substitution (es. {"string"|functions})
+ $compiled_code .= $this->func_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true );
+ }
+ }
+
+ if( $open_if > 0 ) {
+ $e = new RainTpl_SyntaxException('Error! You need to close an {if} tag in ' . $this->tpl['tpl_filename'] . ' template');
+ throw $e->setTemplateFile($this->tpl['tpl_filename']);
+ }
+ return $compiled_code;
+ }
+
+
+ /**
+ * Reduce a path, eg. www/library/../filepath//file => www/filepath/file
+ * @param type $path
+ * @return type
+ */
+ protected function reduce_path( $path ){
+ $path = str_replace( "://", "@not_replace@", $path );
+ $path = str_replace( "//", "/", $path );
+ $path = str_replace( "@not_replace@", "://", $path );
+ return preg_replace('/\w+\/\.\.\//', '', $path );
+ }
+
+
+
+ /**
+ * replace the path of image src, link href and a href.
+ * url => template_dir/url
+ * url# => url
+ * http://url => http://url
+ *
+ * @param string $html
+ * @return string html sostituito
+ */
+ protected function path_replace( $html, $tpl_basedir ){
+
+ if( self::$path_replace ){
+
+ $tpl_dir = self::$base_url . self::$tpl_dir . $tpl_basedir;
+
+ // reduce the path
+ $path = $this->reduce_path($tpl_dir);
+
+ $exp = $sub = array();
+
+ if( in_array( "img", self::$path_replace_list ) ){
+ $exp = array( '/<img(.*?)src=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<img(.*?)src=(?:")([^"]+?)#(?:")/i', '/<img(.*?)src="(.*?)"/', '/<img(.*?)src=(?:\@)([^"]+?)(?:\@)/i' );
+ $sub = array( '<img$1src=@$2://$3@', '<img$1src=@$2@', '<img$1src="' . $path . '$2"', '<img$1src="$2"' );
+ }
+
+ if( in_array( "script", self::$path_replace_list ) ){
+ $exp = array_merge( $exp , array( '/<script(.*?)src=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<script(.*?)src=(?:")([^"]+?)#(?:")/i', '/<script(.*?)src="(.*?)"/', '/<script(.*?)src=(?:\@)([^"]+?)(?:\@)/i' ) );
+ $sub = array_merge( $sub , array( '<script$1src=@$2://$3@', '<script$1src=@$2@', '<script$1src="' . $path . '$2"', '<script$1src="$2"' ) );
+ }
+
+ if( in_array( "link", self::$path_replace_list ) ){
+ $exp = array_merge( $exp , array( '/<link(.*?)href=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<link(.*?)href=(?:")([^"]+?)#(?:")/i', '/<link(.*?)href="(.*?)"/', '/<link(.*?)href=(?:\@)([^"]+?)(?:\@)/i' ) );
+ $sub = array_merge( $sub , array( '<link$1href=@$2://$3@', '<link$1href=@$2@' , '<link$1href="' . $path . '$2"', '<link$1href="$2"' ) );
+ }
+
+ if( in_array( "a", self::$path_replace_list ) ){
+ $exp = array_merge( $exp , array( '/<a(.*?)href=(?:")(http\:\/\/|https\:\/\/|javascript:)([^"]+?)(?:")/i', '/<a(.*?)href="(.*?)"/', '/<a(.*?)href=(?:\@)([^"]+?)(?:\@)/i' ) );
+ $sub = array_merge( $sub , array( '<a$1href=@$2$3@', '<a$1href="' . self::$base_url . '$2"', '<a$1href="$2"' ) );
+ }
+
+ if( in_array( "input", self::$path_replace_list ) ){
+ $exp = array_merge( $exp , array( '/<input(.*?)src=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<input(.*?)src=(?:")([^"]+?)#(?:")/i', '/<input(.*?)src="(.*?)"/', '/<input(.*?)src=(?:\@)([^"]+?)(?:\@)/i' ) );
+ $sub = array_merge( $sub , array( '<input$1src=@$2://$3@', '<input$1src=@$2@', '<input$1src="' . $path . '$2"', '<input$1src="$2"' ) );
+ }
+
+ return preg_replace( $exp, $sub, $html );
+
+ }
+ else
+ return $html;
+
+ }
+
+
+
+
+
+ // replace const
+ function const_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){
+ // const
+ return preg_replace( '/\{\#(\w+)\#{0,1}\}/', $php_left_delimiter . ( $echo ? " echo " : null ) . '\\1' . $php_right_delimiter, $html );
+ }
+
+
+
+ // replace functions/modifiers on constants and strings
+ function func_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){
+
+ preg_match_all( '/' . '\{\#{0,1}(\"{0,1}.*?\"{0,1})(\|\w.*?)\#{0,1}\}' . '/', $html, $matches );
+
+ for( $i=0, $n=count($matches[0]); $i<$n; $i++ ){
+
+ //complete tag ex: {$news.title|substr:0,100}
+ $tag = $matches[ 0 ][ $i ];
+
+ //variable name ex: news.title
+ $var = $matches[ 1 ][ $i ];
+
+ //function and parameters associate to the variable ex: substr:0,100
+ $extra_var = $matches[ 2 ][ $i ];
+
+ // check if there's any function disabled by black_list
+ $this->function_check( $tag );
+
+ $extra_var = $this->var_replace( $extra_var, null, null, null, null, $loop_level );
+
+
+ // check if there's an operator = in the variable tags, if there's this is an initialization so it will not output any value
+ $is_init_variable = preg_match( "/^(\s*?)\=[^=](.*?)$/", $extra_var );
+
+ //function associate to variable
+ $function_var = ( $extra_var and $extra_var[0] == '|') ? substr( $extra_var, 1 ) : null;
+
+ //variable path split array (ex. $news.title o $news[title]) or object (ex. $news->title)
+ $temp = preg_split( "/\.|\[|\-\>/", $var );
+
+ //variable name
+ $var_name = $temp[ 0 ];
+
+ //variable path
+ $variable_path = substr( $var, strlen( $var_name ) );
+
+ //parentesis transform [ e ] in [" e in "]
+ $variable_path = str_replace( '[', '["', $variable_path );
+ $variable_path = str_replace( ']', '"]', $variable_path );
+
+ //transform .$variable in ["$variable"]
+ $variable_path = preg_replace('/\.\$(\w+)/', '["$\\1"]', $variable_path );
+
+ //transform [variable] in ["variable"]
+ $variable_path = preg_replace('/\.(\w+)/', '["\\1"]', $variable_path );
+
+ //if there's a function
+ if( $function_var ){
+
+ // check if there's a function or a static method and separate, function by parameters
+ $function_var = str_replace("::", "@double_dot@", $function_var );
+
+ // get the position of the first :
+ if( $dot_position = strpos( $function_var, ":" ) ){
+
+ // get the function and the parameters
+ $function = substr( $function_var, 0, $dot_position );
+ $params = substr( $function_var, $dot_position+1 );
+
+ }
+ else{
+
+ //get the function
+ $function = str_replace( "@double_dot@", "::", $function_var );
+ $params = null;
+
+ }
+
+ // replace back the @double_dot@ with ::
+ $function = str_replace( "@double_dot@", "::", $function );
+ $params = str_replace( "@double_dot@", "::", $params );
+
+
+ }
+ else
+ $function = $params = null;
+
+ $php_var = $var_name . $variable_path;
+
+ // compile the variable for php
+ if( isset( $function ) ){
+ if( $php_var )
+ $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $php_var, $params ) )" : "$function( $php_var )" ) . $php_right_delimiter;
+ else
+ $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $params ) )" : "$function()" ) . $php_right_delimiter;
+ }
+ else
+ $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . $php_var . $extra_var . $php_right_delimiter;
+
+ $html = str_replace( $tag, $php_var, $html );
+
+ }
+
+ return $html;
+
+ }
+
+
+
+ function var_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){
+
+ //all variables
+ 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 ) ){
+
+ for( $parsed=array(), $i=0, $n=count($matches[0]); $i<$n; $i++ )
+ $parsed[$matches[0][$i]] = array('var'=>$matches[1][$i],'extra_var'=>$matches[2][$i]);
+
+ foreach( $parsed as $tag => $array ){
+
+ //variable name ex: news.title
+ $var = $array['var'];
+
+ //function and parameters associate to the variable ex: substr:0,100
+ $extra_var = $array['extra_var'];
+
+ // check if there's any function disabled by black_list
+ $this->function_check( $tag );
+
+ $extra_var = $this->var_replace( $extra_var, null, null, null, null, $loop_level );
+
+ // check if there's an operator = in the variable tags, if there's this is an initialization so it will not output any value
+ $is_init_variable = preg_match( "/^[a-z_A-Z\.\[\](\-\>)]*=[^=]*$/", $extra_var );
+
+ //function associate to variable
+ $function_var = ( $extra_var and $extra_var[0] == '|') ? substr( $extra_var, 1 ) : null;
+
+ //variable path split array (ex. $news.title o $news[title]) or object (ex. $news->title)
+ $temp = preg_split( "/\.|\[|\-\>/", $var );
+
+ //variable name
+ $var_name = $temp[ 0 ];
+
+ //variable path
+ $variable_path = substr( $var, strlen( $var_name ) );
+
+ //parentesis transform [ e ] in [" e in "]
+ $variable_path = str_replace( '[', '["', $variable_path );
+ $variable_path = str_replace( ']', '"]', $variable_path );
+
+ //transform .$variable in ["$variable"] and .variable in ["variable"]
+ $variable_path = preg_replace('/\.(\${0,1}\w+)/', '["\\1"]', $variable_path );
+
+ // if is an assignment also assign the variable to $this->var['value']
+ if( $is_init_variable )
+ $extra_var = "=\$this->var['{$var_name}']{$variable_path}" . $extra_var;
+
+
+
+ //if there's a function
+ if( $function_var ){
+
+ // check if there's a function or a static method and separate, function by parameters
+ $function_var = str_replace("::", "@double_dot@", $function_var );
+
+
+ // get the position of the first :
+ if( $dot_position = strpos( $function_var, ":" ) ){
+
+ // get the function and the parameters
+ $function = substr( $function_var, 0, $dot_position );
+ $params = substr( $function_var, $dot_position+1 );
+
+ }
+ else{
+
+ //get the function
+ $function = str_replace( "@double_dot@", "::", $function_var );
+ $params = null;
+
+ }
+
+ // replace back the @double_dot@ with ::
+ $function = str_replace( "@double_dot@", "::", $function );
+ $params = str_replace( "@double_dot@", "::", $params );
+ }
+ else
+ $function = $params = null;
+
+ //if it is inside a loop
+ if( $loop_level ){
+ //verify the variable name
+ if( $var_name == 'key' )
+ $php_var = '$key' . $loop_level;
+ elseif( $var_name == 'value' )
+ $php_var = '$value' . $loop_level . $variable_path;
+ elseif( $var_name == 'counter' )
+ $php_var = '$counter' . $loop_level;
+ else
+ $php_var = '$' . $var_name . $variable_path;
+ }else
+ $php_var = '$' . $var_name . $variable_path;
+
+ // compile the variable for php
+ if( isset( $function ) )
+ $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $php_var, $params ) )" : "$function( $php_var )" ) . $php_right_delimiter;
+ else
+ $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . $php_var . $extra_var . $php_right_delimiter;
+
+ $html = str_replace( $tag, $php_var, $html );
+
+
+ }
+ }
+
+ return $html;
+ }
+
+
+
+ /**
+ * Check if function is in black list (sandbox)
+ *
+ * @param string $code
+ * @param string $tag
+ */
+ protected function function_check( $code ){
+
+ $preg = '#(\W|\s)' . implode( '(\W|\s)|(\W|\s)', self::$black_list ) . '(\W|\s)#';
+
+ // check if the function is in the black list (or not in white list)
+ if( count(self::$black_list) && preg_match( $preg, $code, $match ) ){
+
+ // find the line of the error
+ $line = 0;
+ $rows=explode("\n",$this->tpl['source']);
+ while( !strpos($rows[$line],$code) )
+ $line++;
+
+ // stop the execution of the script
+ $e = new RainTpl_SyntaxException('Unallowed syntax in ' . $this->tpl['tpl_filename'] . ' template');
+ throw $e->setTemplateFile($this->tpl['tpl_filename'])
+ ->setTag($code)
+ ->setTemplateLine($line);
+ }
+
+ }
+
+ /**
+ * Prints debug info about exception or passes it further if debug is disabled.
+ *
+ * @param RainTpl_Exception $e
+ * @return string
+ */
+ protected function printDebug(RainTpl_Exception $e){
+ if (!self::$debug) {
+ throw $e;
+ }
+ $output = sprintf('<h2>Exception: %s</h2><h3>%s</h3><p>template: %s</p>',
+ get_class($e),
+ $e->getMessage(),
+ $e->getTemplateFile()
+ );
+ if ($e instanceof RainTpl_SyntaxException) {
+ if (null != $e->getTemplateLine()) {
+ $output .= '<p>line: ' . $e->getTemplateLine() . '</p>';
+ }
+ if (null != $e->getTag()) {
+ $output .= '<p>in tag: ' . htmlspecialchars($e->getTag()) . '</p>';
+ }
+ if (null != $e->getTemplateLine() && null != $e->getTag()) {
+ $rows=explode("\n", htmlspecialchars($this->tpl['source']));
+ $rows[$e->getTemplateLine()] = '<font color=red>' . $rows[$e->getTemplateLine()] . '</font>';
+ $output .= '<h3>template code</h3>' . implode('<br />', $rows) . '</pre>';
+ }
+ }
+ $output .= sprintf('<h3>trace</h3><p>In %s on line %d</p><pre>%s</pre>',
+ $e->getFile(), $e->getLine(),
+ nl2br(htmlspecialchars($e->getTraceAsString()))
+ );
+ return $output;
+ }
+}
+
+
+/**
+ * Basic Rain tpl exception.
+ */
+class RainTpl_Exception extends Exception{
+ /**
+ * Path of template file with error.
+ */
+ protected $templateFile = '';
+
+ /**
+ * Returns path of template file with error.
+ *
+ * @return string
+ */
+ public function getTemplateFile()
+ {
+ return $this->templateFile;
+ }
+
+ /**
+ * Sets path of template file with error.
+ *
+ * @param string $templateFile
+ * @return RainTpl_Exception
+ */
+ public function setTemplateFile($templateFile)
+ {
+ $this->templateFile = (string) $templateFile;
+ return $this;
+ }
+}
+
+/**
+ * Exception thrown when template file does not exists.
+ */
+class RainTpl_NotFoundException extends RainTpl_Exception{
+}
+
+/**
+ * Exception thrown when syntax error occurs.
+ */
+class RainTpl_SyntaxException extends RainTpl_Exception{
+ /**
+ * Line in template file where error has occured.
+ *
+ * @var int | null
+ */
+ protected $templateLine = null;
+
+ /**
+ * Tag which caused an error.
+ *
+ * @var string | null
+ */
+ protected $tag = null;
+
+ /**
+ * Returns line in template file where error has occured
+ * or null if line is not defined.
+ *
+ * @return int | null
+ */
+ public function getTemplateLine()
+ {
+ return $this->templateLine;
+ }
+
+ /**
+ * Sets line in template file where error has occured.
+ *
+ * @param int $templateLine
+ * @return RainTpl_SyntaxException
+ */
+ public function setTemplateLine($templateLine)
+ {
+ $this->templateLine = (int) $templateLine;
+ return $this;
+ }
+
+ /**
+ * Returns tag which caused an error.
+ *
+ * @return string
+ */
+ public function getTag()
+ {
+ return $this->tag;
+ }
+
+ /**
+ * Sets tag which caused an error.
+ *
+ * @param string $tag
+ * @return RainTpl_SyntaxException
+ */
+ public function setTag($tag)
+ {
+ $this->tag = (string) $tag;
+ return $this;
+ }
+}
+
+// -- end
--- /dev/null
+<?php
+/**
+ * poche, a read it later open source system
+ *
+ * @category poche
+ * @author Nicolas Lœuillet <nicolas@loeuillet.org>
+ * @copyright 2013
+ * @license http://www.wtfpl.net/ see COPYING file
+ */
+
+/**
+ * TODO
+ * gestion des erreurs sqlite (duplicate tout ça)
+ * gérer si url vide
+ * traiter les variables passées en get
+ * récupérer le titre de la page pochée (cf readityourself.php)
+ * actions archive, fav et delete à traiter
+ * bookmarklet
+ * améliorer présentation des liens
+ * améliorer présentation d'un article
+ * aligner verticalement les icones d'action
+ * afficher liens mis en favoris et archivés
+ * tri des liens
+ */
+
+try
+{
+ $db_handle = new PDO('sqlite:poche.sqlite');
+ $db_handle->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+}
+catch (Exception $e)
+{
+ die('error : '.$e->getMessage());
+}
+
+$action = (isset ($_GET['action'])) ? htmlspecialchars($_GET['action']) : '';
+
+switch ($action) {
+ case 'add':
+ $url = (isset ($_GET['url'])) ? htmlspecialchars($_GET['url']) : '';
+ $title = $url;
+ $query = $db_handle->prepare('INSERT INTO entries ( url, title ) VALUES (?, ?)');
+ $query->execute(array($url, $title));
+ break;
+ case 'archive':
+ break;
+ case 'fav' :
+ break;
+ case 'delete':
+ break;
+ default:
+ break;
+}
+?>
+<!DOCTYPE html>
+<!--[if lte IE 6]> <html class="no-js ie6 ie67 ie678" lang="en"> <![endif]-->
+<!--[if lte IE 7]> <html class="no-js ie7 ie67 ie678" lang="en"> <![endif]-->
+<!--[if IE 8]> <html class="no-js ie8 ie678" lang="en"> <![endif]-->
+<!--[if gt IE 8]><!--> <html class="no-js" lang="en"> <!--<![endif]-->
+<html>
+ <head>
+ <meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0">
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=10">
+ <title>poche : queue</title>
+ <link rel="stylesheet" href="css/knacss.css" media="all">
+ <link rel="stylesheet" href="css/style.css" media="all">
+ </head>
+ <body>
+ <header>
+ <h1>poche, a read it later open source system</h1>
+ </header>
+ <div id="main" class="w800p">
+ <ul id="links">
+ <li><a href="index.php">home</a></li>
+ <li><a href="#">favorites</a></li>
+ <li><a href="#">archive</a></li>
+ <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>
+ </ul>
+ <?php
+ $query = $db_handle->prepare("SELECT * FROM entries WHERE read=?");
+ $query->execute(array('FALSE'));
+ $entries = $query->fetchAll();
+ ?>
+ <ul id="entries">
+ <?php
+ foreach ($entries as $entry) {
+ 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>';
+ }
+ ?>
+ </ul>
+ </div>
+ <footer class="mr2 mt3">
+ <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>
+ </footer>
+ </body>
+</html>
\ No newline at end of file
--- /dev/null
+<?php
+
+define("VERSION", "0.0.3");
+
+header('Content-type:text/html; charset=utf-8');
+// Set locale to French
+setlocale(LC_ALL, 'fr_FR');
+
+// set timezone to Europe/Paris
+date_default_timezone_set('Europe/Paris');
+
+// set charset to utf-8 important since all pages will be transform to utf-8
+header('Content-Type: text/html;charset=utf-8');
+
+// get readability library
+require_once dirname(__FILE__).'/inc/Readability.php';
+
+// get Encoding library.
+require_once dirname(__FILE__).'/inc/Encoding.php';
+
+// appel de la libraire RainTPL.
+require_once dirname(__FILE__).'/inc/rain.tpl.class.php';
+
+// FUNCTIONS BEGIN
+
+
+
+function url(){
+ $protocol = ($_SERVER['HTTPS'] && $_SERVER['HTTPS'] != "off") ? "https" : "http";
+ return $protocol . "://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
+}
+
+function generate_page($url,$title,$content) {
+ raintpl::$tpl_dir = './tpl/'; // template directory
+ raintpl::$cache_dir = "./cache/"; // cache directory
+ raintpl::$base_url = url(); // base URL of blog
+ raintpl::configure( 'path_replace', false );
+ raintpl::configure('debug', false);
+
+ $tpl = new raintpl(); //include Rain TPL
+
+ $tpl->assign( "url", $url);
+ $tpl->assign( "title", $title);
+ $tpl->assign( "content", $content);
+
+ $tpl->assign( "version", VERSION);
+
+ $tpl->draw( "index"); // draw the template
+}
+
+// function define to retrieve url content
+function get_external_file($url, $timeout) {
+ // spoofing FireFox 18.0
+ $useragent="Mozilla/5.0 (Windows NT 5.1; rv:18.0) Gecko/20100101 Firefox/18.0";
+
+ if (in_array ('curl', get_loaded_extensions())) {
+ // Fetch feed from URL
+ $curl = curl_init();
+ curl_setopt($curl, CURLOPT_URL, $url);
+ curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
+ curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($curl, CURLOPT_HEADER, false);
+
+ // FeedBurner requires a proper USER-AGENT...
+ curl_setopt($curl, CURL_HTTP_VERSION_1_1, true);
+ curl_setopt($curl, CURLOPT_ENCODING, "gzip, deflate");
+ curl_setopt($curl, CURLOPT_USERAGENT, $useragent);
+
+ $data = curl_exec($curl);
+
+ $httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+
+ $httpcodeOK = isset($httpcode) and ($httpcode == 200 or $httpcode == 301);
+
+ curl_close($curl);
+ } else {
+
+ // create http context and add timeout and user-agent
+ $context = stream_context_create(array('http'=>array('timeout' => $timeout, // Timeout : time until we stop waiting for the response.
+ 'header'=> "User-Agent: ".$useragent, // spoot Mozilla Firefox
+ 'follow_location' => true
+ )));
+
+ // only download page lesser than 4MB
+ $data = @file_get_contents($url, false, $context, -1, 4000000); // We download at most 4 MB from source.
+ // echo "<pre>http_response_header : ".print_r($http_response_header);
+
+ if(isset($http_response_header) and isset($http_response_header[0])) {
+ $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));
+ }
+ }
+
+ // if response is not empty and response is OK
+ if (isset($data) and isset($httpcodeOK) and httpcodeOK ) {
+
+ // take charset of page and get it
+ preg_match('#<meta .*charset=.*>#Usi', $data, $meta);
+
+ // if meta tag is found
+ if (!empty($meta[0])) {
+ // retrieve encoding in $enc
+ preg_match('#charset="?(.*)"#si', $meta[0], $enc);
+
+ // if charset is found set it otherwise, set it to utf-8
+ $html_charset = (!empty($enc[1])) ? strtolower($enc[1]) : 'utf-8';
+
+ } else {
+ $html_charset = 'utf-8';
+ $enc[1] = '';
+ }
+
+ // replace charset of url to charset of page
+ $data = str_replace('charset='.$enc[1], 'charset='.$html_charset, $data);
+
+ return $data;
+ }
+ else {
+ return FALSE;
+ }
+}
+
+function rel2abs($rel, $base)
+{
+ /* return if already absolute URL */
+ if (parse_url($rel, PHP_URL_SCHEME) != '') return $rel;
+
+ /* queries and anchors */
+ if ($rel[0]=='#' || $rel[0]=='?') return $base.$rel;
+
+ /* parse base URL and convert to local variables:
+ $scheme, $host, $path */
+ extract(parse_url($base));
+
+ /* remove non-directory element from path */
+ $path = preg_replace('#/[^/]*$#', '', $path);
+
+ /* destroy path if relative url points to root */
+ if ($rel[0] == '/') $path = '';
+
+ /* dirty absolute URL */
+ $abs = "$host$path/$rel";
+
+ /* replace '//' or '/./' or '/foo/../' with '/' */
+ $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#');
+ for($n=1; $n>0; $abs=preg_replace($re, '/', $abs, -1, $n)) {}
+
+ /* absolute URL is ready! */
+ return $scheme.'://'.$abs;
+}
+
+// $str=preg_replace('#(href|src)="([^:"]*)("|(?:(?:%20|\s|\+)[^"]*"))#','$1="http://wintermute.com.au/$2$3',$str);
+
+function absolutes_links($data, $base) {
+ // cherche les balises 'a' qui contiennent un href
+ $matches = array();
+ preg_match_all('#(href|src)="([^:"]*)("|(?:(?:%20|\s|\+)[^"]*"))#Si', $data, $matches, PREG_SET_ORDER);
+
+ // ne conserve que les liens ne commençant pas par un protocole « protocole:// » ni par une ancre « # »
+ foreach($matches as $i => $link) {
+ $link[1] = trim($link[1]);
+
+ if (!preg_match('#^(([a-z]+://)|(\#))#', $link[1]) ) {
+
+ $absolutePath=rel2abs($link[2],$base);
+
+ $data = str_replace($matches[$i][2], $absolutePath, $data);
+ }
+
+ }
+ return $data;
+}
+
+
+// FUNCTIONS END
+
+// EXUCUTION CODE
+
+
+if(isset($_GET['url']) && $_GET['url'] != null && trim($_GET['url']) != "") {
+ // get url link
+ if(strlen(trim($_GET['url'])) > 2048) {
+ echo "Error URL is too large !!";
+ } else {
+ $url = trim($_GET['url']);
+
+ // decode it
+ $url = html_entity_decode($url);
+
+ // if url use https protocol change it to http
+ if (!preg_match('!^https?://!i', $url)) $url = 'http://'.$url;
+
+ // convert page to utf-8
+ $html = Encoding::toUTF8(get_external_file($url,15));
+
+ if(isset($html) and strlen($html) > 0) {
+
+ // send result to readability library
+ $r = new Readability($html, $url);
+
+ if($r->init()) {
+ generate_page($url,$r->articleTitle->innerHTML,$r->articleContent->innerHTML);
+ } else {
+ // return data into an iframe
+ echo "<iframe id='readabilityframe'>".$html."</iframe>";
+ }
+ } else {
+ echo "Error unable to get link : ".$url;
+ }
+ }
+}
+?>
--- /dev/null
+<footer>
+ <div>
+ 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>
+ Licence: WTF Licence<br>
+ More information HERE: <a href="http://www.memiks.fr/readityourself/">http://www.memiks.fr/readityourself/</a> Version : <span class="version">{$version}</span>
+ </div>
+</footer>
--- /dev/null
+<html>
+ <head>
+ <link rel='stylesheet' href='./css/reset.css' type='text/css' media='all' />
+ <link rel='stylesheet' href='./css/typography.css' type='text/css' media='all' />
+
+ <title>{$title}</title>
+ </head>
+ <body>
+ <article>
+ <h1><a href="{$url}">{$title}</a></h1>
+ <div id="readityourselfcontent">
+ {$content}
+ </div>
+ <span class="comeFrom">Come From : <a href="{$url}">{$url}</a>
+ </article>
+ {include="footer"}
+ </body>
+</html>