aboutsummaryrefslogtreecommitdiffhomepage
path: root/inc
diff options
context:
space:
mode:
authorNicolas Lœuillet <nicolas@loeuillet.org>2014-04-03 14:42:03 +0200
committerNicolas Lœuillet <nicolas@loeuillet.org>2014-04-03 14:42:03 +0200
commit0d67b00d5d3b7ce1b76b639dcc65c415a5f13439 (patch)
tree7c4d113bb1c3d90ef53e08138a2850576395780f /inc
parent99679d06884120c57f43b44e55e03595f1f87bed (diff)
parent7d2f1aa2793595aa6cdc58a58260184234cfa809 (diff)
downloadwallabag-0d67b00d5d3b7ce1b76b639dcc65c415a5f13439.tar.gz
wallabag-0d67b00d5d3b7ce1b76b639dcc65c415a5f13439.tar.zst
wallabag-0d67b00d5d3b7ce1b76b639dcc65c415a5f13439.zip
Merge pull request #595 from wallabag/dev1.6.11.6.0
wallabag 1.6.0
Diffstat (limited to 'inc')
-rw-r--r--inc/3rdparty/Session.class.php40
-rw-r--r--inc/3rdparty/class.messages.php3
-rw-r--r--inc/3rdparty/libraries/feedwriter/FeedItem.php1
-rwxr-xr-x[-rw-r--r--]inc/3rdparty/libraries/feedwriter/FeedWriter.php168
-rwxr-xr-xinc/3rdparty/makefulltextfeed.php353
-rwxr-xr-xinc/3rdparty/makefulltextfeedHelpers.php355
-rwxr-xr-xinc/poche/Database.class.php117
-rwxr-xr-xinc/poche/Poche.class.php663
-rwxr-xr-x[-rw-r--r--]inc/poche/Tools.class.php81
-rwxr-xr-xinc/poche/config.inc.php.new14
-rw-r--r--inc/poche/global.inc.php2
11 files changed, 915 insertions, 882 deletions
diff --git a/inc/3rdparty/Session.class.php b/inc/3rdparty/Session.class.php
index b30a31f3..59dfbe67 100644
--- a/inc/3rdparty/Session.class.php
+++ b/inc/3rdparty/Session.class.php
@@ -31,9 +31,9 @@ class Session
31 public static $sessionName = ''; 31 public static $sessionName = '';
32 // If the user does not access any page within this time, 32 // If the user does not access any page within this time,
33 // his/her session is considered expired (3600 sec. = 1 hour) 33 // his/her session is considered expired (3600 sec. = 1 hour)
34 public static $inactivityTimeout = 86400; 34 public static $inactivityTimeout = 3600;
35 // Extra timeout for long sessions (if enabled) (82800 sec. = 23 hours) 35 // Extra timeout for long sessions (if enabled) (82800 sec. = 23 hours)
36 public static $longSessionTimeout = 31536000; 36 public static $longSessionTimeout = 7776000; // 7776000 = 90 days
37 // If you get disconnected often or if your IP address changes often. 37 // If you get disconnected often or if your IP address changes often.
38 // Let you disable session cookie hijacking protection 38 // Let you disable session cookie hijacking protection
39 public static $disableSessionProtection = false; 39 public static $disableSessionProtection = false;
@@ -48,8 +48,13 @@ class Session
48 /** 48 /**
49 * Initialize session 49 * Initialize session
50 */ 50 */
51 public static function init() 51 public static function init($longlastingsession = false)
52 { 52 {
53 //check if session name is correct
54 if ( (session_id() && !empty(self::$sessionName) && session_name()!=self::$sessionName) || $longlastingsession ) {
55 session_destroy();
56 }
57
53 // Force cookie path (but do not change lifetime) 58 // Force cookie path (but do not change lifetime)
54 $cookie = session_get_cookie_params(); 59 $cookie = session_get_cookie_params();
55 // Default cookie expiration and path. 60 // Default cookie expiration and path.
@@ -61,12 +66,22 @@ class Session
61 if (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on") { 66 if (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on") {
62 $ssl = true; 67 $ssl = true;
63 } 68 }
64 session_set_cookie_params($cookie['lifetime'], $cookiedir, $_SERVER['HTTP_HOST'], $ssl); 69
70 if ( $longlastingsession ) {
71 session_set_cookie_params(self::$longSessionTimeout, $cookiedir, null, $ssl, true);
72 }
73 else {
74 session_set_cookie_params(0, $cookiedir, null, $ssl, true);
75 }
76 //set server side valid session timeout
77 //WARNING! this may not work in shared session environment. See http://www.php.net/manual/en/session.configuration.php#ini.session.gc-maxlifetime about min value: it can be set in any application
78 ini_set('session.gc_maxlifetime', self::$longSessionTimeout);
79
65 // Use cookies to store session. 80 // Use cookies to store session.
66 ini_set('session.use_cookies', 1); 81 ini_set('session.use_cookies', 1);
67 // Force cookies for session (phpsessionID forbidden in URL) 82 // Force cookies for session (phpsessionID forbidden in URL)
68 ini_set('session.use_only_cookies', 1); 83 ini_set('session.use_only_cookies', 1);
69 if (!session_id()) { 84 if ( !session_id() ) {
70 // Prevent php to use sessionID in URL if cookies are disabled. 85 // Prevent php to use sessionID in URL if cookies are disabled.
71 ini_set('session.use_trans_sid', false); 86 ini_set('session.use_trans_sid', false);
72 if (!empty(self::$sessionName)) { 87 if (!empty(self::$sessionName)) {
@@ -115,6 +130,9 @@ class Session
115 if (self::banCanLogin()) { 130 if (self::banCanLogin()) {
116 if ($login === $loginTest && $password === $passwordTest) { 131 if ($login === $loginTest && $password === $passwordTest) {
117 self::banLoginOk(); 132 self::banLoginOk();
133
134 self::init($longlastingsession);
135
118 // Generate unique random number to sign forms (HMAC) 136 // Generate unique random number to sign forms (HMAC)
119 $_SESSION['uid'] = sha1(uniqid('', true).'_'.mt_rand()); 137 $_SESSION['uid'] = sha1(uniqid('', true).'_'.mt_rand());
120 $_SESSION['ip'] = self::_allIPs(); 138 $_SESSION['ip'] = self::_allIPs();
@@ -135,6 +153,7 @@ class Session
135 self::banLoginFailed(); 153 self::banLoginFailed();
136 } 154 }
137 155
156 self::init();
138 return false; 157 return false;
139 } 158 }
140 159
@@ -143,7 +162,14 @@ class Session
143 */ 162 */
144 public static function logout() 163 public static function logout()
145 { 164 {
146 unset($_SESSION['uid'],$_SESSION['ip'],$_SESSION['expires_on'],$_SESSION['tokens'], $_SESSION['login'], $_SESSION['pass'], $_SESSION['longlastingsession'], $_SESSION['poche_user']); 165 // unset($_SESSION['uid'],$_SESSION['ip'],$_SESSION['expires_on'],$_SESSION['tokens'], $_SESSION['login'], $_SESSION['pass'], $_SESSION['longlastingsession'], $_SESSION['poche_user']);
166
167 // Destruction du cookie (le code peut paraître complexe mais c'est pour être certain de reprendre les mêmes paramètres)
168 $args = array_merge(array(session_name(), ''), array_values(session_get_cookie_params()));
169 $args[2] = time() - 3600;
170 call_user_func_array('setcookie', $args);
171 // Suppression physique de la session
172 session_destroy();
147 } 173 }
148 174
149 /** 175 /**
@@ -157,7 +183,7 @@ class Session
157 || (self::$disableSessionProtection === false 183 || (self::$disableSessionProtection === false
158 && $_SESSION['ip'] !== self::_allIPs()) 184 && $_SESSION['ip'] !== self::_allIPs())
159 || time() >= $_SESSION['expires_on']) { 185 || time() >= $_SESSION['expires_on']) {
160 self::logout(); 186 //self::logout();
161 187
162 return false; 188 return false;
163 } 189 }
diff --git a/inc/3rdparty/class.messages.php b/inc/3rdparty/class.messages.php
index e60bd3a1..27c28f43 100644
--- a/inc/3rdparty/class.messages.php
+++ b/inc/3rdparty/class.messages.php
@@ -59,6 +59,7 @@ class Messages {
59 $this->msgId = md5(uniqid()); 59 $this->msgId = md5(uniqid());
60 60
61 // Create the session array if it doesnt already exist 61 // Create the session array if it doesnt already exist
62 settype($_SESSION, 'array');
62 if( !array_key_exists('flash_messages', $_SESSION) ) $_SESSION['flash_messages'] = array(); 63 if( !array_key_exists('flash_messages', $_SESSION) ) $_SESSION['flash_messages'] = array();
63 64
64 } 65 }
@@ -228,4 +229,4 @@ class Messages {
228 229
229 230
230} // end class 231} // end class
231?> \ No newline at end of file 232?>
diff --git a/inc/3rdparty/libraries/feedwriter/FeedItem.php b/inc/3rdparty/libraries/feedwriter/FeedItem.php
index 9373deeb..0eae5e08 100644
--- a/inc/3rdparty/libraries/feedwriter/FeedItem.php
+++ b/inc/3rdparty/libraries/feedwriter/FeedItem.php
@@ -156,6 +156,7 @@
156 if($this->version == RSS2 || $this->version == RSS1) 156 if($this->version == RSS2 || $this->version == RSS1)
157 { 157 {
158 $this->setElement('link', $link); 158 $this->setElement('link', $link);
159 $this->setElement('guid', $link);
159 } 160 }
160 else 161 else
161 { 162 {
diff --git a/inc/3rdparty/libraries/feedwriter/FeedWriter.php b/inc/3rdparty/libraries/feedwriter/FeedWriter.php
index adb2526c..5d16e765 100644..100755
--- a/inc/3rdparty/libraries/feedwriter/FeedWriter.php
+++ b/inc/3rdparty/libraries/feedwriter/FeedWriter.php
@@ -9,9 +9,9 @@ define('JSONP', 3, true);
9 * Genarate RSS2 or JSON (original: RSS 1.0, RSS2.0 and ATOM Feed) 9 * Genarate RSS2 or JSON (original: RSS 1.0, RSS2.0 and ATOM Feed)
10 * 10 *
11 * Modified for FiveFilters.org's Full-Text RSS project 11 * Modified for FiveFilters.org's Full-Text RSS project
12 * to allow for inclusion of hubs, JSON output. 12 * to allow for inclusion of hubs, JSON output.
13 * Stripped RSS1 and ATOM support. 13 * Stripped RSS1 and ATOM support.
14 * 14 *
15 * @package UnivarselFeedWriter 15 * @package UnivarselFeedWriter
16 * @author Anis uddin Ahmad <anisniit@gmail.com> 16 * @author Anis uddin Ahmad <anisniit@gmail.com>
17 * @link http://www.ajaxray.com/projects/rss 17 * @link http://www.ajaxray.com/projects/rss
@@ -26,32 +26,32 @@ define('JSONP', 3, true);
26 private $CDATAEncoding = array(); // The tag names which have to encoded as CDATA 26 private $CDATAEncoding = array(); // The tag names which have to encoded as CDATA
27 private $xsl = null; // stylesheet to render RSS (used by Chrome) 27 private $xsl = null; // stylesheet to render RSS (used by Chrome)
28 private $json = null; // JSON object 28 private $json = null; // JSON object
29 29
30 private $version = null; 30 private $version = null;
31 31
32 /** 32 /**
33 * Constructor 33 * Constructor
34 * 34 *
35 * @param constant the version constant (RSS2 or JSON). 35 * @param constant the version constant (RSS2 or JSON).
36 */ 36 */
37 function __construct($version = RSS2) 37 function __construct($version = RSS2)
38 { 38 {
39 $this->version = $version; 39 $this->version = $version;
40 40
41 // Setting default value for assential channel elements 41 // Setting default value for assential channel elements
42 $this->channels['title'] = $version . ' Feed'; 42 $this->channels['title'] = $version . ' Feed';
43 $this->channels['link'] = 'http://www.ajaxray.com/blog'; 43 $this->channels['link'] = 'http://www.ajaxray.com/blog';
44 44
45 //Tag names to encode in CDATA 45 //Tag names to encode in CDATA
46 $this->CDATAEncoding = array('description', 'content:encoded', 'content', 'subtitle', 'summary'); 46 $this->CDATAEncoding = array('description', 'content:encoded', 'content', 'subtitle', 'summary');
47 } 47 }
48 48
49 public function setFormat($format) { 49 public function setFormat($format) {
50 $this->version = $format; 50 $this->version = $format;
51 } 51 }
52 52
53 // Start # public functions --------------------------------------------- 53 // Start # public functions ---------------------------------------------
54 54
55 /** 55 /**
56 * Set a channel element 56 * Set a channel element
57 * @access public 57 * @access public
@@ -63,11 +63,11 @@ define('JSONP', 3, true);
63 { 63 {
64 $this->channels[$elementName] = $content ; 64 $this->channels[$elementName] = $content ;
65 } 65 }
66 66
67 /** 67 /**
68 * Set multiple channel elements from an array. Array elements 68 * Set multiple channel elements from an array. Array elements
69 * should be 'channelName' => 'channelContent' format. 69 * should be 'channelName' => 'channelContent' format.
70 * 70 *
71 * @access public 71 * @access public
72 * @param array array of channels 72 * @param array array of channels
73 * @return void 73 * @return void
@@ -75,30 +75,30 @@ define('JSONP', 3, true);
75 public function setChannelElementsFromArray($elementArray) 75 public function setChannelElementsFromArray($elementArray)
76 { 76 {
77 if(! is_array($elementArray)) return; 77 if(! is_array($elementArray)) return;
78 foreach ($elementArray as $elementName => $content) 78 foreach ($elementArray as $elementName => $content)
79 { 79 {
80 $this->setChannelElement($elementName, $content); 80 $this->setChannelElement($elementName, $content);
81 } 81 }
82 } 82 }
83 83
84 /** 84 /**
85 * Genarate the actual RSS/JSON file 85 * Genarate the actual RSS/JSON file
86 * 86 *
87 * @access public 87 * @access public
88 * @return void 88 * @return void
89 */ 89 */
90 public function genarateFeed() 90 public function genarateFeed()
91 { 91 {
92 if ($this->version == RSS2) { 92 if ($this->version == RSS2) {
93 header('Content-type: text/xml; charset=UTF-8'); 93// header('Content-type: text/xml; charset=UTF-8');
94 // this line prevents Chrome 20 from prompting download 94 // this line prevents Chrome 20 from prompting download
95 // used by Google: https://news.google.com/news/feeds?ned=us&topic=b&output=rss 95 // used by Google: https://news.google.com/news/feeds?ned=us&topic=b&output=rss
96 header('X-content-type-options: nosniff'); 96// header('X-content-type-options: nosniff');
97 } elseif ($this->version == JSON) { 97 } elseif ($this->version == JSON) {
98 header('Content-type: application/json; charset=UTF-8'); 98// header('Content-type: application/json; charset=UTF-8');
99 $this->json = new stdClass(); 99 $this->json = new stdClass();
100 } elseif ($this->version == JSONP) { 100 } elseif ($this->version == JSONP) {
101 header('Content-type: application/javascript; charset=UTF-8'); 101// header('Content-type: application/javascript; charset=UTF-8');
102 $this->json = new stdClass(); 102 $this->json = new stdClass();
103 } 103 }
104 $this->printHead(); 104 $this->printHead();
@@ -109,10 +109,10 @@ define('JSONP', 3, true);
109 echo json_encode($this->json); 109 echo json_encode($this->json);
110 } 110 }
111 } 111 }
112 112
113 /** 113 /**
114 * Create a new FeedItem. 114 * Create a new FeedItem.
115 * 115 *
116 * @access public 116 * @access public
117 * @return object instance of FeedItem class 117 * @return object instance of FeedItem class
118 */ 118 */
@@ -121,24 +121,24 @@ define('JSONP', 3, true);
121 $Item = new FeedItem($this->version); 121 $Item = new FeedItem($this->version);
122 return $Item; 122 return $Item;
123 } 123 }
124 124
125 /** 125 /**
126 * Add a FeedItem to the main class 126 * Add a FeedItem to the main class
127 * 127 *
128 * @access public 128 * @access public
129 * @param object instance of FeedItem class 129 * @param object instance of FeedItem class
130 * @return void 130 * @return void
131 */ 131 */
132 public function addItem($feedItem) 132 public function addItem($feedItem)
133 { 133 {
134 $this->items[] = $feedItem; 134 $this->items[] = $feedItem;
135 } 135 }
136 136
137 // Wrapper functions ------------------------------------------------------------------- 137 // Wrapper functions -------------------------------------------------------------------
138 138
139 /** 139 /**
140 * Set the 'title' channel element 140 * Set the 'title' channel element
141 * 141 *
142 * @access public 142 * @access public
143 * @param srting value of 'title' channel tag 143 * @param srting value of 'title' channel tag
144 * @return void 144 * @return void
@@ -147,59 +147,59 @@ define('JSONP', 3, true);
147 { 147 {
148 $this->setChannelElement('title', $title); 148 $this->setChannelElement('title', $title);
149 } 149 }
150 150
151 /** 151 /**
152 * Add a hub to the channel element 152 * Add a hub to the channel element
153 * 153 *
154 * @access public 154 * @access public
155 * @param string URL 155 * @param string URL
156 * @return void 156 * @return void
157 */ 157 */
158 public function addHub($hub) 158 public function addHub($hub)
159 { 159 {
160 $this->hubs[] = $hub; 160 $this->hubs[] = $hub;
161 } 161 }
162 162
163 /** 163 /**
164 * Set XSL URL 164 * Set XSL URL
165 * 165 *
166 * @access public 166 * @access public
167 * @param string URL 167 * @param string URL
168 * @return void 168 * @return void
169 */ 169 */
170 public function setXsl($xsl) 170 public function setXsl($xsl)
171 { 171 {
172 $this->xsl = $xsl; 172 $this->xsl = $xsl;
173 } 173 }
174 174
175 /** 175 /**
176 * Set self URL 176 * Set self URL
177 * 177 *
178 * @access public 178 * @access public
179 * @param string URL 179 * @param string URL
180 * @return void 180 * @return void
181 */ 181 */
182 public function setSelf($self) 182 public function setSelf($self)
183 { 183 {
184 $this->self = $self; 184 $this->self = $self;
185 } 185 }
186 186
187 /** 187 /**
188 * Set the 'description' channel element 188 * Set the 'description' channel element
189 * 189 *
190 * @access public 190 * @access public
191 * @param srting value of 'description' channel tag 191 * @param srting value of 'description' channel tag
192 * @return void 192 * @return void
193 */ 193 */
194 public function setDescription($desciption) 194 public function setDescription($desciption)
195 { 195 {
196 $tag = ($this->version == ATOM)? 'subtitle' : 'description'; 196 $tag = ($this->version == ATOM)? 'subtitle' : 'description';
197 $this->setChannelElement($tag, $desciption); 197 $this->setChannelElement($tag, $desciption);
198 } 198 }
199 199
200 /** 200 /**
201 * Set the 'link' channel element 201 * Set the 'link' channel element
202 * 202 *
203 * @access public 203 * @access public
204 * @param srting value of 'link' channel tag 204 * @param srting value of 'link' channel tag
205 * @return void 205 * @return void
@@ -208,10 +208,10 @@ define('JSONP', 3, true);
208 { 208 {
209 $this->setChannelElement('link', $link); 209 $this->setChannelElement('link', $link);
210 } 210 }
211 211
212 /** 212 /**
213 * Set the 'image' channel element 213 * Set the 'image' channel element
214 * 214 *
215 * @access public 215 * @access public
216 * @param srting title of image 216 * @param srting title of image
217 * @param srting link url of the imahe 217 * @param srting link url of the imahe
@@ -222,14 +222,14 @@ define('JSONP', 3, true);
222 { 222 {
223 $this->setChannelElement('image', array('title'=>$title, 'link'=>$link, 'url'=>$url)); 223 $this->setChannelElement('image', array('title'=>$title, 'link'=>$link, 'url'=>$url));
224 } 224 }
225 225
226 // End # public functions ---------------------------------------------- 226 // End # public functions ----------------------------------------------
227 227
228 // Start # private functions ---------------------------------------------- 228 // Start # private functions ----------------------------------------------
229 229
230 /** 230 /**
231 * Prints the xml and rss namespace 231 * Prints the xml and rss namespace
232 * 232 *
233 * @access private 233 * @access private
234 * @return void 234 * @return void
235 */ 235 */
@@ -247,10 +247,10 @@ define('JSONP', 3, true);
247 $this->json->rss = array('@attributes' => array('version' => '2.0')); 247 $this->json->rss = array('@attributes' => array('version' => '2.0'));
248 } 248 }
249 } 249 }
250 250
251 /** 251 /**
252 * Closes the open tags at the end of file 252 * Closes the open tags at the end of file
253 * 253 *
254 * @access private 254 * @access private
255 * @return void 255 * @return void
256 */ 256 */
@@ -258,14 +258,14 @@ define('JSONP', 3, true);
258 { 258 {
259 if ($this->version == RSS2) 259 if ($this->version == RSS2)
260 { 260 {
261 echo '</channel>',PHP_EOL,'</rss>'; 261 echo '</channel>',PHP_EOL,'</rss>';
262 } 262 }
263 // do nothing for JSON 263 // do nothing for JSON
264 } 264 }
265 265
266 /** 266 /**
267 * Creates a single node as xml format 267 * Creates a single node as xml format
268 * 268 *
269 * @access private 269 * @access private
270 * @param string name of the tag 270 * @param string name of the tag
271 * @param mixed tag value as string or array of nested tags in 'tagName' => 'tagValue' format 271 * @param mixed tag value as string or array of nested tags in 'tagName' => 'tagValue' format
@@ -273,22 +273,22 @@ define('JSONP', 3, true);
273 * @return string formatted xml tag 273 * @return string formatted xml tag
274 */ 274 */
275 private function makeNode($tagName, $tagContent, $attributes = null) 275 private function makeNode($tagName, $tagContent, $attributes = null)
276 { 276 {
277 if ($this->version == RSS2) 277 if ($this->version == RSS2)
278 { 278 {
279 $nodeText = ''; 279 $nodeText = '';
280 $attrText = ''; 280 $attrText = '';
281 if (is_array($attributes)) 281 if (is_array($attributes))
282 { 282 {
283 foreach ($attributes as $key => $value) 283 foreach ($attributes as $key => $value)
284 { 284 {
285 $attrText .= " $key=\"$value\" "; 285 $attrText .= " $key=\"$value\" ";
286 } 286 }
287 } 287 }
288 $nodeText .= "<{$tagName}{$attrText}>"; 288 $nodeText .= "<{$tagName}{$attrText}>";
289 if (is_array($tagContent)) 289 if (is_array($tagContent))
290 { 290 {
291 foreach ($tagContent as $key => $value) 291 foreach ($tagContent as $key => $value)
292 { 292 {
293 $nodeText .= $this->makeNode($key, $value); 293 $nodeText .= $this->makeNode($key, $value);
294 } 294 }
@@ -297,7 +297,7 @@ define('JSONP', 3, true);
297 { 297 {
298 //$nodeText .= (in_array($tagName, $this->CDATAEncoding))? $tagContent : htmlentities($tagContent); 298 //$nodeText .= (in_array($tagName, $this->CDATAEncoding))? $tagContent : htmlentities($tagContent);
299 $nodeText .= htmlspecialchars($tagContent); 299 $nodeText .= htmlspecialchars($tagContent);
300 } 300 }
301 //$nodeText .= (in_array($tagName, $this->CDATAEncoding))? "]]></$tagName>" : "</$tagName>"; 301 //$nodeText .= (in_array($tagName, $this->CDATAEncoding))? "]]></$tagName>" : "</$tagName>";
302 $nodeText .= "</$tagName>"; 302 $nodeText .= "</$tagName>";
303 return $nodeText . PHP_EOL; 303 return $nodeText . PHP_EOL;
@@ -321,7 +321,7 @@ define('JSONP', 3, true);
321 } 321 }
322 return ''; // should not get here 322 return ''; // should not get here
323 } 323 }
324 324
325 private function json_keys(array $array) { 325 private function json_keys(array $array) {
326 $new = array(); 326 $new = array();
327 foreach ($array as $key => $val) { 327 foreach ($array as $key => $val) {
@@ -334,7 +334,7 @@ define('JSONP', 3, true);
334 } 334 }
335 return $new; 335 return $new;
336 } 336 }
337 337
338 /** 338 /**
339 * @desc Print channels 339 * @desc Print channels
340 * @access private 340 * @access private
@@ -344,7 +344,7 @@ define('JSONP', 3, true);
344 { 344 {
345 //Start channel tag 345 //Start channel tag
346 if ($this->version == RSS2) { 346 if ($this->version == RSS2) {
347 echo '<channel>' . PHP_EOL; 347 echo '<channel>' . PHP_EOL;
348 // add hubs 348 // add hubs
349 foreach ($this->hubs as $hub) { 349 foreach ($this->hubs as $hub) {
350 //echo $this->makeNode('link', '', array('rel'=>'hub', 'href'=>$hub, 'xmlns'=>'http://www.w3.org/2005/Atom')); 350 //echo $this->makeNode('link', '', array('rel'=>'hub', 'href'=>$hub, 'xmlns'=>'http://www.w3.org/2005/Atom'));
@@ -356,7 +356,7 @@ define('JSONP', 3, true);
356 echo '<link rel="self" href="'.htmlspecialchars($this->self).'" xmlns="http://www.w3.org/2005/Atom" />' . PHP_EOL; 356 echo '<link rel="self" href="'.htmlspecialchars($this->self).'" xmlns="http://www.w3.org/2005/Atom" />' . PHP_EOL;
357 } 357 }
358 //Print Items of channel 358 //Print Items of channel
359 foreach ($this->channels as $key => $value) 359 foreach ($this->channels as $key => $value)
360 { 360 {
361 echo $this->makeNode($key, $value); 361 echo $this->makeNode($key, $value);
362 } 362 }
@@ -364,26 +364,26 @@ define('JSONP', 3, true);
364 $this->json->rss['channel'] = (object)$this->json_keys($this->channels); 364 $this->json->rss['channel'] = (object)$this->json_keys($this->channels);
365 } 365 }
366 } 366 }
367 367
368 /** 368 /**
369 * Prints formatted feed items 369 * Prints formatted feed items
370 * 370 *
371 * @access private 371 * @access private
372 * @return void 372 * @return void
373 */ 373 */
374 private function printItems() 374 private function printItems()
375 { 375 {
376 foreach ($this->items as $item) { 376 foreach ($this->items as $item) {
377 $itemElements = $item->getElements(); 377 $itemElements = $item->getElements();
378 378
379 echo $this->startItem(); 379 echo $this->startItem();
380 380
381 if ($this->version == JSON || $this->version == JSONP) { 381 if ($this->version == JSON || $this->version == JSONP) {
382 $json_item = array(); 382 $json_item = array();
383 } 383 }
384 384
385 foreach ($itemElements as $thisElement) { 385 foreach ($itemElements as $thisElement) {
386 foreach ($thisElement as $instance) { 386 foreach ($thisElement as $instance) {
387 if ($this->version == RSS2) { 387 if ($this->version == RSS2) {
388 echo $this->makeNode($instance['name'], $instance['content'], $instance['attributes']); 388 echo $this->makeNode($instance['name'], $instance['content'], $instance['attributes']);
389 } elseif ($this->version == JSON || $this->version == JSONP) { 389 } elseif ($this->version == JSON || $this->version == JSONP) {
@@ -406,10 +406,10 @@ define('JSONP', 3, true);
406 } 406 }
407 } 407 }
408 } 408 }
409 409
410 /** 410 /**
411 * Make the starting tag of channels 411 * Make the starting tag of channels
412 * 412 *
413 * @access private 413 * @access private
414 * @return void 414 * @return void
415 */ 415 */
@@ -417,14 +417,14 @@ define('JSONP', 3, true);
417 { 417 {
418 if ($this->version == RSS2) 418 if ($this->version == RSS2)
419 { 419 {
420 echo '<item>' . PHP_EOL; 420 echo '<item>' . PHP_EOL;
421 } 421 }
422 // nothing for JSON 422 // nothing for JSON
423 } 423 }
424 424
425 /** 425 /**
426 * Closes feed item tag 426 * Closes feed item tag
427 * 427 *
428 * @access private 428 * @access private
429 * @return void 429 * @return void
430 */ 430 */
@@ -432,10 +432,10 @@ define('JSONP', 3, true);
432 { 432 {
433 if ($this->version == RSS2) 433 if ($this->version == RSS2)
434 { 434 {
435 echo '</item>' . PHP_EOL; 435 echo '</item>' . PHP_EOL;
436 } 436 }
437 // nothing for JSON 437 // nothing for JSON
438 } 438 }
439 439
440 // End # private functions ---------------------------------------------- 440 // End # private functions ----------------------------------------------
441 } \ No newline at end of file 441 } \ No newline at end of file
diff --git a/inc/3rdparty/makefulltextfeed.php b/inc/3rdparty/makefulltextfeed.php
index 2852c4c2..135964f1 100755
--- a/inc/3rdparty/makefulltextfeed.php
+++ b/inc/3rdparty/makefulltextfeed.php
@@ -55,42 +55,8 @@ if (get_magic_quotes_gpc()) {
55 55
56// set include path 56// set include path
57set_include_path(realpath(dirname(__FILE__).'/libraries').PATH_SEPARATOR.get_include_path()); 57set_include_path(realpath(dirname(__FILE__).'/libraries').PATH_SEPARATOR.get_include_path());
58// Autoloading of classes allows us to include files only when they're 58
59// needed. If we've got a cached copy, for example, only Zend_Cache is loaded. 59require_once dirname(__FILE__).'/makefulltextfeedHelpers.php';
60function autoload($class_name) {
61 static $dir = null;
62 if ($dir === null) $dir = dirname(__FILE__).'/libraries/';
63 static $mapping = array(
64 // Include FeedCreator for RSS/Atom creation
65 'FeedWriter' => 'feedwriter/FeedWriter.php',
66 'FeedItem' => 'feedwriter/FeedItem.php',
67 // Include ContentExtractor and Readability for identifying and extracting content from URLs
68 'ContentExtractor' => 'content-extractor/ContentExtractor.php',
69 'SiteConfig' => 'content-extractor/SiteConfig.php',
70 'Readability' => 'readability/Readability.php',
71 // Include Humble HTTP Agent to allow parallel requests and response caching
72 'HumbleHttpAgent' => 'humble-http-agent/HumbleHttpAgent.php',
73 'SimplePie_HumbleHttpAgent' => 'humble-http-agent/SimplePie_HumbleHttpAgent.php',
74 'CookieJar' => 'humble-http-agent/CookieJar.php',
75 // Include Zend Cache to improve performance (cache results)
76 'Zend_Cache' => 'Zend/Cache.php',
77 // Language detect
78 'Text_LanguageDetect' => 'language-detect/LanguageDetect.php',
79 // HTML5 Lib
80 'HTML5_Parser' => 'html5/Parser.php',
81 // htmLawed - used if XSS filter is enabled (xss_filter)
82 'htmLawed' => 'htmLawed/htmLawed.php'
83 );
84 if (isset($mapping[$class_name])) {
85 debug("** Loading class $class_name ({$mapping[$class_name]})");
86 require $dir.$mapping[$class_name];
87 return true;
88 } else {
89 return false;
90 }
91}
92spl_autoload_register('autoload');
93require dirname(__FILE__).'/libraries/simplepie/autoloader.php';
94 60
95//////////////////////////////// 61////////////////////////////////
96// Load config file 62// Load config file
@@ -415,6 +381,7 @@ if (!$debug_mode) {
415////////////////////////////////// 381//////////////////////////////////
416// Set up HTTP agent 382// Set up HTTP agent
417////////////////////////////////// 383//////////////////////////////////
384global $http;
418$http = new HumbleHttpAgent(); 385$http = new HumbleHttpAgent();
419$http->debug = $debug_mode; 386$http->debug = $debug_mode;
420$http->userAgentMap = $options->user_agents; 387$http->userAgentMap = $options->user_agents;
@@ -478,29 +445,6 @@ if ($html_only || !$result) {
478 $isDummyFeed = true; 445 $isDummyFeed = true;
479 unset($feed, $result); 446 unset($feed, $result);
480 // create single item dummy feed object 447 // create single item dummy feed object
481 class DummySingleItemFeed {
482 public $item;
483 function __construct($url) { $this->item = new DummySingleItem($url); }
484 public function get_title() { return ''; }
485 public function get_description() { return 'Content extracted from '.$this->item->url; }
486 public function get_link() { return $this->item->url; }
487 public function get_language() { return false; }
488 public function get_image_url() { return false; }
489 public function get_items($start=0, $max=1) { return array(0=>$this->item); }
490 }
491 class DummySingleItem {
492 public $url;
493 function __construct($url) { $this->url = $url; }
494 public function get_permalink() { return $this->url; }
495 public function get_title() { return null; }
496 public function get_date($format='') { return false; }
497 public function get_author($key=0) { return null; }
498 public function get_authors() { return null; }
499 public function get_description() { return ''; }
500 public function get_enclosure($key=0, $prefer=null) { return null; }
501 public function get_enclosures() { return null; }
502 public function get_categories() { return null; }
503 }
504 $feed = new DummySingleItemFeed($url); 448 $feed = new DummySingleItemFeed($url);
505} 449}
506 450
@@ -903,294 +847,3 @@ if (!$debug_mode) {
903 if ($callback) echo ');'; 847 if ($callback) echo ');';
904} 848}
905 849
906///////////////////////////////
907// HELPER FUNCTIONS
908///////////////////////////////
909
910function url_allowed($url) {
911 global $options;
912 if (!empty($options->allowed_urls)) {
913 $allowed = false;
914 foreach ($options->allowed_urls as $allowurl) {
915 if (stristr($url, $allowurl) !== false) {
916 $allowed = true;
917 break;
918 }
919 }
920 if (!$allowed) return false;
921 } else {
922 foreach ($options->blocked_urls as $blockurl) {
923 if (stristr($url, $blockurl) !== false) {
924 return false;
925 }
926 }
927 }
928 return true;
929}
930
931//////////////////////////////////////////////
932// Convert $html to UTF8
933// (uses HTTP headers and HTML to find encoding)
934// adapted from http://stackoverflow.com/questions/910793/php-detect-encoding-and-make-everything-utf-8
935//////////////////////////////////////////////
936function convert_to_utf8($html, $header=null)
937{
938 $encoding = null;
939 if ($html || $header) {
940 if (is_array($header)) $header = implode("\n", $header);
941 if (!$header || !preg_match_all('/^Content-Type:\s+([^;]+)(?:;\s*charset=["\']?([^;"\'\n]*))?/im', $header, $match, PREG_SET_ORDER)) {
942 // error parsing the response
943 debug('Could not find Content-Type header in HTTP response');
944 } else {
945 $match = end($match); // get last matched element (in case of redirects)
946 if (isset($match[2])) $encoding = trim($match[2], "\"' \r\n\0\x0B\t");
947 }
948 // TODO: check to see if encoding is supported (can we convert it?)
949 // If it's not, result will be empty string.
950 // For now we'll check for invalid encoding types returned by some sites, e.g. 'none'
951 // Problem URL: http://facta.co.jp/blog/archives/20111026001026.html
952 if (!$encoding || $encoding == 'none') {
953 // search for encoding in HTML - only look at the first 50000 characters
954 // Why 50000? See, for example, http://www.lemonde.fr/festival-de-cannes/article/2012/05/23/deux-cretes-en-goguette-sur-la-croisette_1705732_766360.html
955 // TODO: improve this so it looks at smaller chunks first
956 $html_head = substr($html, 0, 50000);
957 if (preg_match('/^<\?xml\s+version=(?:"[^"]*"|\'[^\']*\')\s+encoding=("[^"]*"|\'[^\']*\')/s', $html_head, $match)) {
958 $encoding = trim($match[1], '"\'');
959 } elseif (preg_match('/<meta\s+http-equiv=["\']?Content-Type["\']? content=["\'][^;]+;\s*charset=["\']?([^;"\'>]+)/i', $html_head, $match)) {
960 $encoding = trim($match[1]);
961 } elseif (preg_match_all('/<meta\s+([^>]+)>/i', $html_head, $match)) {
962 foreach ($match[1] as $_test) {
963 if (preg_match('/charset=["\']?([^"\']+)/i', $_test, $_m)) {
964 $encoding = trim($_m[1]);
965 break;
966 }
967 }
968 }
969 }
970 if (isset($encoding)) $encoding = trim($encoding);
971 // trim is important here!
972 if (!$encoding || (strtolower($encoding) == 'iso-8859-1')) {
973 // replace MS Word smart qutoes
974 $trans = array();
975 $trans[chr(130)] = '&sbquo;'; // Single Low-9 Quotation Mark
976 $trans[chr(131)] = '&fnof;'; // Latin Small Letter F With Hook
977 $trans[chr(132)] = '&bdquo;'; // Double Low-9 Quotation Mark
978 $trans[chr(133)] = '&hellip;'; // Horizontal Ellipsis
979 $trans[chr(134)] = '&dagger;'; // Dagger
980 $trans[chr(135)] = '&Dagger;'; // Double Dagger
981 $trans[chr(136)] = '&circ;'; // Modifier Letter Circumflex Accent
982 $trans[chr(137)] = '&permil;'; // Per Mille Sign
983 $trans[chr(138)] = '&Scaron;'; // Latin Capital Letter S With Caron
984 $trans[chr(139)] = '&lsaquo;'; // Single Left-Pointing Angle Quotation Mark
985 $trans[chr(140)] = '&OElig;'; // Latin Capital Ligature OE
986 $trans[chr(145)] = '&lsquo;'; // Left Single Quotation Mark
987 $trans[chr(146)] = '&rsquo;'; // Right Single Quotation Mark
988 $trans[chr(147)] = '&ldquo;'; // Left Double Quotation Mark
989 $trans[chr(148)] = '&rdquo;'; // Right Double Quotation Mark
990 $trans[chr(149)] = '&bull;'; // Bullet
991 $trans[chr(150)] = '&ndash;'; // En Dash
992 $trans[chr(151)] = '&mdash;'; // Em Dash
993 $trans[chr(152)] = '&tilde;'; // Small Tilde
994 $trans[chr(153)] = '&trade;'; // Trade Mark Sign
995 $trans[chr(154)] = '&scaron;'; // Latin Small Letter S With Caron
996 $trans[chr(155)] = '&rsaquo;'; // Single Right-Pointing Angle Quotation Mark
997 $trans[chr(156)] = '&oelig;'; // Latin Small Ligature OE
998 $trans[chr(159)] = '&Yuml;'; // Latin Capital Letter Y With Diaeresis
999 $html = strtr($html, $trans);
1000 }
1001 if (!$encoding) {
1002 debug('No character encoding found, so treating as UTF-8');
1003 $encoding = 'utf-8';
1004 } else {
1005 debug('Character encoding: '.$encoding);
1006 if (strtolower($encoding) != 'utf-8') {
1007 debug('Converting to UTF-8');
1008 $html = SimplePie_Misc::change_encoding($html, $encoding, 'utf-8');
1009 /*
1010 if (function_exists('iconv')) {
1011 // iconv appears to handle certain character encodings better than mb_convert_encoding
1012 $html = iconv($encoding, 'utf-8', $html);
1013 } else {
1014 $html = mb_convert_encoding($html, 'utf-8', $encoding);
1015 }
1016 */
1017 }
1018 }
1019 }
1020 return $html;
1021}
1022
1023function makeAbsolute($base, $elem) {
1024 $base = new SimplePie_IRI($base);
1025 // remove '//' in URL path (used to prevent URLs from resolving properly)
1026 // TODO: check if this is still the case
1027 if (isset($base->path)) $base->path = preg_replace('!//+!', '/', $base->path);
1028 foreach(array('a'=>'href', 'img'=>'src') as $tag => $attr) {
1029 $elems = $elem->getElementsByTagName($tag);
1030 for ($i = $elems->length-1; $i >= 0; $i--) {
1031 $e = $elems->item($i);
1032 //$e->parentNode->replaceChild($articleContent->ownerDocument->createTextNode($e->textContent), $e);
1033 makeAbsoluteAttr($base, $e, $attr);
1034 }
1035 if (strtolower($elem->tagName) == $tag) makeAbsoluteAttr($base, $elem, $attr);
1036 }
1037}
1038function makeAbsoluteAttr($base, $e, $attr) {
1039 if ($e->hasAttribute($attr)) {
1040 // Trim leading and trailing white space. I don't really like this but
1041 // unfortunately it does appear on some sites. e.g. <img src=" /path/to/image.jpg" />
1042 $url = trim(str_replace('%20', ' ', $e->getAttribute($attr)));
1043 $url = str_replace(' ', '%20', $url);
1044 if (!preg_match('!https?://!i', $url)) {
1045 if ($absolute = SimplePie_IRI::absolutize($base, $url)) {
1046 $e->setAttribute($attr, $absolute);
1047 }
1048 }
1049 }
1050}
1051function makeAbsoluteStr($base, $url) {
1052 $base = new SimplePie_IRI($base);
1053 // remove '//' in URL path (causes URLs not to resolve properly)
1054 if (isset($base->path)) $base->path = preg_replace('!//+!', '/', $base->path);
1055 if (preg_match('!^https?://!i', $url)) {
1056 // already absolute
1057 return $url;
1058 } else {
1059 if ($absolute = SimplePie_IRI::absolutize($base, $url)) {
1060 return $absolute;
1061 }
1062 return false;
1063 }
1064}
1065// returns single page response, or false if not found
1066function getSinglePage($item, $html, $url) {
1067 global $http, $extractor;
1068 debug('Looking for site config files to see if single page link exists');
1069 $site_config = $extractor->buildSiteConfig($url, $html);
1070 $splink = null;
1071 if (!empty($site_config->single_page_link)) {
1072 $splink = $site_config->single_page_link;
1073 } elseif (!empty($site_config->single_page_link_in_feed)) {
1074 // single page link xpath is targeted at feed
1075 $splink = $site_config->single_page_link_in_feed;
1076 // so let's replace HTML with feed item description
1077 $html = $item->get_description();
1078 }
1079 if (isset($splink)) {
1080 // Build DOM tree from HTML
1081 $readability = new Readability($html, $url);
1082 $xpath = new DOMXPath($readability->dom);
1083 // Loop through single_page_link xpath expressions
1084 $single_page_url = null;
1085 foreach ($splink as $pattern) {
1086 $elems = @$xpath->evaluate($pattern, $readability->dom);
1087 if (is_string($elems)) {
1088 $single_page_url = trim($elems);
1089 break;
1090 } elseif ($elems instanceof DOMNodeList && $elems->length > 0) {
1091 foreach ($elems as $item) {
1092 if ($item instanceof DOMElement && $item->hasAttribute('href')) {
1093 $single_page_url = $item->getAttribute('href');
1094 break 2;
1095 } elseif ($item instanceof DOMAttr && $item->value) {
1096 $single_page_url = $item->value;
1097 break 2;
1098 }
1099 }
1100 }
1101 }
1102 // If we've got URL, resolve against $url
1103 if (isset($single_page_url) && ($single_page_url = makeAbsoluteStr($url, $single_page_url))) {
1104 // check it's not what we have already!
1105 if ($single_page_url != $url) {
1106 // it's not, so let's try to fetch it...
1107 $_prev_ref = $http->referer;
1108 $http->referer = $single_page_url;
1109 if (($response = $http->get($single_page_url, true)) && $response['status_code'] < 300) {
1110 $http->referer = $_prev_ref;
1111 return $response;
1112 }
1113 $http->referer = $_prev_ref;
1114 }
1115 }
1116 }
1117 return false;
1118}
1119
1120// based on content-type http header, decide what to do
1121// param: HTTP headers string
1122// return: array with keys: 'mime', 'type', 'subtype', 'action', 'name'
1123// e.g. array('mime'=>'image/jpeg', 'type'=>'image', 'subtype'=>'jpeg', 'action'=>'link', 'name'=>'Image')
1124function get_mime_action_info($headers) {
1125 global $options;
1126 // check if action defined for returned Content-Type
1127 $info = array();
1128 if (preg_match('!^Content-Type:\s*(([-\w]+)/([-\w\+]+))!im', $headers, $match)) {
1129 // look for full mime type (e.g. image/jpeg) or just type (e.g. image)
1130 // match[1] = full mime type, e.g. image/jpeg
1131 // match[2] = first part, e.g. image
1132 // match[3] = last part, e.g. jpeg
1133 $info['mime'] = strtolower(trim($match[1]));
1134 $info['type'] = strtolower(trim($match[2]));
1135 $info['subtype'] = strtolower(trim($match[3]));
1136 foreach (array($info['mime'], $info['type']) as $_mime) {
1137 if (isset($options->content_type_exc[$_mime])) {
1138 $info['action'] = $options->content_type_exc[$_mime]['action'];
1139 $info['name'] = $options->content_type_exc[$_mime]['name'];
1140 break;
1141 }
1142 }
1143 }
1144 return $info;
1145}
1146
1147function remove_url_cruft($url) {
1148 // remove google analytics for the time being
1149 // regex adapted from http://navitronic.co.uk/2010/12/removing-google-analytics-cruft-from-urls/
1150 // https://gist.github.com/758177
1151 return preg_replace('/(\?|\&)utm_[a-z]+=[^\&]+/', '', $url);
1152}
1153
1154function make_substitutions($string) {
1155 if ($string == '') return $string;
1156 global $item, $effective_url;
1157 $string = str_replace('{url}', htmlspecialchars($item->get_permalink()), $string);
1158 $string = str_replace('{effective-url}', htmlspecialchars($effective_url), $string);
1159 return $string;
1160}
1161
1162function get_cache() {
1163 global $options, $valid_key;
1164 static $cache = null;
1165 if ($cache === null) {
1166 $frontendOptions = array(
1167 'lifetime' => 10*60, // cache lifetime of 10 minutes
1168 'automatic_serialization' => false,
1169 'write_control' => false,
1170 'automatic_cleaning_factor' => $options->cache_cleanup,
1171 'ignore_user_abort' => false
1172 );
1173 $backendOptions = array(
1174 'cache_dir' => ($valid_key) ? $options->cache_dir.'/rss-with-key/' : $options->cache_dir.'/rss/', // directory where to put the cache files
1175 'file_locking' => false,
1176 'read_control' => true,
1177 'read_control_type' => 'strlen',
1178 'hashed_directory_level' => $options->cache_directory_level,
1179 'hashed_directory_perm' => 0777,
1180 'cache_file_perm' => 0664,
1181 'file_name_prefix' => 'ff'
1182 );
1183 // getting a Zend_Cache_Core object
1184 $cache = Zend_Cache::factory('Core', 'File', $frontendOptions, $backendOptions);
1185 }
1186 return $cache;
1187}
1188
1189function debug($msg) {
1190 global $debug_mode;
1191 if ($debug_mode) {
1192 echo '* ',$msg,"\n";
1193 ob_flush();
1194 flush();
1195 }
1196} \ No newline at end of file
diff --git a/inc/3rdparty/makefulltextfeedHelpers.php b/inc/3rdparty/makefulltextfeedHelpers.php
new file mode 100755
index 00000000..1c11b8f6
--- /dev/null
+++ b/inc/3rdparty/makefulltextfeedHelpers.php
@@ -0,0 +1,355 @@
1<?php
2
3// Autoloading of classes allows us to include files only when they're
4// needed. If we've got a cached copy, for example, only Zend_Cache is loaded.
5function autoload($class_name) {
6 static $dir = null;
7 if ($dir === null) $dir = dirname(__FILE__).'/libraries/';
8 static $mapping = array(
9 // Include FeedCreator for RSS/Atom creation
10 'FeedWriter' => 'feedwriter/FeedWriter.php',
11 'FeedItem' => 'feedwriter/FeedItem.php',
12 // Include ContentExtractor and Readability for identifying and extracting content from URLs
13 'ContentExtractor' => 'content-extractor/ContentExtractor.php',
14 'SiteConfig' => 'content-extractor/SiteConfig.php',
15 'Readability' => 'readability/Readability.php',
16 // Include Humble HTTP Agent to allow parallel requests and response caching
17 'HumbleHttpAgent' => 'humble-http-agent/HumbleHttpAgent.php',
18 'SimplePie_HumbleHttpAgent' => 'humble-http-agent/SimplePie_HumbleHttpAgent.php',
19 'CookieJar' => 'humble-http-agent/CookieJar.php',
20 // Include Zend Cache to improve performance (cache results)
21 'Zend_Cache' => 'Zend/Cache.php',
22 // Language detect
23 'Text_LanguageDetect' => 'language-detect/LanguageDetect.php',
24 // HTML5 Lib
25 'HTML5_Parser' => 'html5/Parser.php',
26 // htmLawed - used if XSS filter is enabled (xss_filter)
27 'htmLawed' => 'htmLawed/htmLawed.php'
28 );
29 if (isset($mapping[$class_name])) {
30 debug("** Loading class $class_name ({$mapping[$class_name]})");
31 require $dir.$mapping[$class_name];
32 return true;
33 } else {
34 return false;
35 }
36}
37spl_autoload_register('autoload');
38require dirname(__FILE__).'/libraries/simplepie/autoloader.php';
39
40
41class DummySingleItemFeed {
42 public $item;
43 function __construct($url) { $this->item = new DummySingleItem($url); }
44 public function get_title() { return ''; }
45 public function get_description() { return 'Content extracted from '.$this->item->url; }
46 public function get_link() { return $this->item->url; }
47 public function get_language() { return false; }
48 public function get_image_url() { return false; }
49 public function get_items($start=0, $max=1) { return array(0=>$this->item); }
50}
51class DummySingleItem {
52 public $url;
53 function __construct($url) { $this->url = $url; }
54 public function get_permalink() { return $this->url; }
55 public function get_title() { return null; }
56 public function get_date($format='') { return false; }
57 public function get_author($key=0) { return null; }
58 public function get_authors() { return null; }
59 public function get_description() { return ''; }
60 public function get_enclosure($key=0, $prefer=null) { return null; }
61 public function get_enclosures() { return null; }
62 public function get_categories() { return null; }
63}
64
65///////////////////////////////
66// HELPER FUNCTIONS
67///////////////////////////////
68
69function url_allowed($url) {
70 global $options;
71 if (!empty($options->allowed_urls)) {
72 $allowed = false;
73 foreach ($options->allowed_urls as $allowurl) {
74 if (stristr($url, $allowurl) !== false) {
75 $allowed = true;
76 break;
77 }
78 }
79 if (!$allowed) return false;
80 } else {
81 foreach ($options->blocked_urls as $blockurl) {
82 if (stristr($url, $blockurl) !== false) {
83 return false;
84 }
85 }
86 }
87 return true;
88}
89
90//////////////////////////////////////////////
91// Convert $html to UTF8
92// (uses HTTP headers and HTML to find encoding)
93// adapted from http://stackoverflow.com/questions/910793/php-detect-encoding-and-make-everything-utf-8
94//////////////////////////////////////////////
95function convert_to_utf8($html, $header=null)
96{
97 $encoding = null;
98 if ($html || $header) {
99 if (is_array($header)) $header = implode("\n", $header);
100 if (!$header || !preg_match_all('/^Content-Type:\s+([^;]+)(?:;\s*charset=["\']?([^;"\'\n]*))?/im', $header, $match, PREG_SET_ORDER)) {
101 // error parsing the response
102 debug('Could not find Content-Type header in HTTP response');
103 } else {
104 $match = end($match); // get last matched element (in case of redirects)
105 if (isset($match[2])) $encoding = trim($match[2], "\"' \r\n\0\x0B\t");
106 }
107 // TODO: check to see if encoding is supported (can we convert it?)
108 // If it's not, result will be empty string.
109 // For now we'll check for invalid encoding types returned by some sites, e.g. 'none'
110 // Problem URL: http://facta.co.jp/blog/archives/20111026001026.html
111 if (!$encoding || $encoding == 'none') {
112 // search for encoding in HTML - only look at the first 50000 characters
113 // Why 50000? See, for example, http://www.lemonde.fr/festival-de-cannes/article/2012/05/23/deux-cretes-en-goguette-sur-la-croisette_1705732_766360.html
114 // TODO: improve this so it looks at smaller chunks first
115 $html_head = substr($html, 0, 50000);
116 if (preg_match('/^<\?xml\s+version=(?:"[^"]*"|\'[^\']*\')\s+encoding=("[^"]*"|\'[^\']*\')/s', $html_head, $match)) {
117 $encoding = trim($match[1], '"\'');
118 } elseif (preg_match('/<meta\s+http-equiv=["\']?Content-Type["\']? content=["\'][^;]+;\s*charset=["\']?([^;"\'>]+)/i', $html_head, $match)) {
119 $encoding = trim($match[1]);
120 } elseif (preg_match_all('/<meta\s+([^>]+)>/i', $html_head, $match)) {
121 foreach ($match[1] as $_test) {
122 if (preg_match('/charset=["\']?([^"\']+)/i', $_test, $_m)) {
123 $encoding = trim($_m[1]);
124 break;
125 }
126 }
127 }
128 }
129 if (isset($encoding)) $encoding = trim($encoding);
130 // trim is important here!
131 if (!$encoding || (strtolower($encoding) == 'iso-8859-1')) {
132 // replace MS Word smart qutoes
133 $trans = array();
134 $trans[chr(130)] = '&sbquo;'; // Single Low-9 Quotation Mark
135 $trans[chr(131)] = '&fnof;'; // Latin Small Letter F With Hook
136 $trans[chr(132)] = '&bdquo;'; // Double Low-9 Quotation Mark
137 $trans[chr(133)] = '&hellip;'; // Horizontal Ellipsis
138 $trans[chr(134)] = '&dagger;'; // Dagger
139 $trans[chr(135)] = '&Dagger;'; // Double Dagger
140 $trans[chr(136)] = '&circ;'; // Modifier Letter Circumflex Accent
141 $trans[chr(137)] = '&permil;'; // Per Mille Sign
142 $trans[chr(138)] = '&Scaron;'; // Latin Capital Letter S With Caron
143 $trans[chr(139)] = '&lsaquo;'; // Single Left-Pointing Angle Quotation Mark
144 $trans[chr(140)] = '&OElig;'; // Latin Capital Ligature OE
145 $trans[chr(145)] = '&lsquo;'; // Left Single Quotation Mark
146 $trans[chr(146)] = '&rsquo;'; // Right Single Quotation Mark
147 $trans[chr(147)] = '&ldquo;'; // Left Double Quotation Mark
148 $trans[chr(148)] = '&rdquo;'; // Right Double Quotation Mark
149 $trans[chr(149)] = '&bull;'; // Bullet
150 $trans[chr(150)] = '&ndash;'; // En Dash
151 $trans[chr(151)] = '&mdash;'; // Em Dash
152 $trans[chr(152)] = '&tilde;'; // Small Tilde
153 $trans[chr(153)] = '&trade;'; // Trade Mark Sign
154 $trans[chr(154)] = '&scaron;'; // Latin Small Letter S With Caron
155 $trans[chr(155)] = '&rsaquo;'; // Single Right-Pointing Angle Quotation Mark
156 $trans[chr(156)] = '&oelig;'; // Latin Small Ligature OE
157 $trans[chr(159)] = '&Yuml;'; // Latin Capital Letter Y With Diaeresis
158 $html = strtr($html, $trans);
159 }
160 if (!$encoding) {
161 debug('No character encoding found, so treating as UTF-8');
162 $encoding = 'utf-8';
163 } else {
164 debug('Character encoding: '.$encoding);
165 if (strtolower($encoding) != 'utf-8') {
166 debug('Converting to UTF-8');
167 $html = SimplePie_Misc::change_encoding($html, $encoding, 'utf-8');
168 /*
169 if (function_exists('iconv')) {
170 // iconv appears to handle certain character encodings better than mb_convert_encoding
171 $html = iconv($encoding, 'utf-8', $html);
172 } else {
173 $html = mb_convert_encoding($html, 'utf-8', $encoding);
174 }
175 */
176 }
177 }
178 }
179 return $html;
180}
181
182function makeAbsolute($base, $elem) {
183 $base = new SimplePie_IRI($base);
184 // remove '//' in URL path (used to prevent URLs from resolving properly)
185 // TODO: check if this is still the case
186 if (isset($base->path)) $base->path = preg_replace('!//+!', '/', $base->path);
187 foreach(array('a'=>'href', 'img'=>'src') as $tag => $attr) {
188 $elems = $elem->getElementsByTagName($tag);
189 for ($i = $elems->length-1; $i >= 0; $i--) {
190 $e = $elems->item($i);
191 //$e->parentNode->replaceChild($articleContent->ownerDocument->createTextNode($e->textContent), $e);
192 makeAbsoluteAttr($base, $e, $attr);
193 }
194 if (strtolower($elem->tagName) == $tag) makeAbsoluteAttr($base, $elem, $attr);
195 }
196}
197function makeAbsoluteAttr($base, $e, $attr) {
198 if ($e->hasAttribute($attr)) {
199 // Trim leading and trailing white space. I don't really like this but
200 // unfortunately it does appear on some sites. e.g. <img src=" /path/to/image.jpg" />
201 $url = trim(str_replace('%20', ' ', $e->getAttribute($attr)));
202 $url = str_replace(' ', '%20', $url);
203 if (!preg_match('!https?://!i', $url)) {
204 if ($absolute = SimplePie_IRI::absolutize($base, $url)) {
205 $e->setAttribute($attr, $absolute);
206 }
207 }
208 }
209}
210function makeAbsoluteStr($base, $url) {
211 $base = new SimplePie_IRI($base);
212 // remove '//' in URL path (causes URLs not to resolve properly)
213 if (isset($base->path)) $base->path = preg_replace('!//+!', '/', $base->path);
214 if (preg_match('!^https?://!i', $url)) {
215 // already absolute
216 return $url;
217 } else {
218 if ($absolute = SimplePie_IRI::absolutize($base, $url)) {
219 return $absolute;
220 }
221 return false;
222 }
223}
224// returns single page response, or false if not found
225function getSinglePage($item, $html, $url) {
226 global $http, $extractor;
227 debug('Looking for site config files to see if single page link exists');
228 $site_config = $extractor->buildSiteConfig($url, $html);
229 $splink = null;
230 if (!empty($site_config->single_page_link)) {
231 $splink = $site_config->single_page_link;
232 } elseif (!empty($site_config->single_page_link_in_feed)) {
233 // single page link xpath is targeted at feed
234 $splink = $site_config->single_page_link_in_feed;
235 // so let's replace HTML with feed item description
236 $html = $item->get_description();
237 }
238 if (isset($splink)) {
239 // Build DOM tree from HTML
240 $readability = new Readability($html, $url);
241 $xpath = new DOMXPath($readability->dom);
242 // Loop through single_page_link xpath expressions
243 $single_page_url = null;
244 foreach ($splink as $pattern) {
245 $elems = @$xpath->evaluate($pattern, $readability->dom);
246 if (is_string($elems)) {
247 $single_page_url = trim($elems);
248 break;
249 } elseif ($elems instanceof DOMNodeList && $elems->length > 0) {
250 foreach ($elems as $item) {
251 if ($item instanceof DOMElement && $item->hasAttribute('href')) {
252 $single_page_url = $item->getAttribute('href');
253 break 2;
254 } elseif ($item instanceof DOMAttr && $item->value) {
255 $single_page_url = $item->value;
256 break 2;
257 }
258 }
259 }
260 }
261 // If we've got URL, resolve against $url
262 if (isset($single_page_url) && ($single_page_url = makeAbsoluteStr($url, $single_page_url))) {
263 // check it's not what we have already!
264 if ($single_page_url != $url) {
265 // it's not, so let's try to fetch it...
266 $_prev_ref = $http->referer;
267 $http->referer = $single_page_url;
268 if (($response = $http->get($single_page_url, true)) && $response['status_code'] < 300) {
269 $http->referer = $_prev_ref;
270 return $response;
271 }
272 $http->referer = $_prev_ref;
273 }
274 }
275 }
276 return false;
277}
278
279// based on content-type http header, decide what to do
280// param: HTTP headers string
281// return: array with keys: 'mime', 'type', 'subtype', 'action', 'name'
282// e.g. array('mime'=>'image/jpeg', 'type'=>'image', 'subtype'=>'jpeg', 'action'=>'link', 'name'=>'Image')
283function get_mime_action_info($headers) {
284 global $options;
285 // check if action defined for returned Content-Type
286 $info = array();
287 if (preg_match('!^Content-Type:\s*(([-\w]+)/([-\w\+]+))!im', $headers, $match)) {
288 // look for full mime type (e.g. image/jpeg) or just type (e.g. image)
289 // match[1] = full mime type, e.g. image/jpeg
290 // match[2] = first part, e.g. image
291 // match[3] = last part, e.g. jpeg
292 $info['mime'] = strtolower(trim($match[1]));
293 $info['type'] = strtolower(trim($match[2]));
294 $info['subtype'] = strtolower(trim($match[3]));
295 foreach (array($info['mime'], $info['type']) as $_mime) {
296 if (isset($options->content_type_exc[$_mime])) {
297 $info['action'] = $options->content_type_exc[$_mime]['action'];
298 $info['name'] = $options->content_type_exc[$_mime]['name'];
299 break;
300 }
301 }
302 }
303 return $info;
304}
305
306function remove_url_cruft($url) {
307 // remove google analytics for the time being
308 // regex adapted from http://navitronic.co.uk/2010/12/removing-google-analytics-cruft-from-urls/
309 // https://gist.github.com/758177
310 return preg_replace('/(\?|\&)utm_[a-z]+=[^\&]+/', '', $url);
311}
312
313function make_substitutions($string) {
314 if ($string == '') return $string;
315 global $item, $effective_url;
316 $string = str_replace('{url}', htmlspecialchars($item->get_permalink()), $string);
317 $string = str_replace('{effective-url}', htmlspecialchars($effective_url), $string);
318 return $string;
319}
320
321function get_cache() {
322 global $options, $valid_key;
323 static $cache = null;
324 if ($cache === null) {
325 $frontendOptions = array(
326 'lifetime' => 10*60, // cache lifetime of 10 minutes
327 'automatic_serialization' => false,
328 'write_control' => false,
329 'automatic_cleaning_factor' => $options->cache_cleanup,
330 'ignore_user_abort' => false
331 );
332 $backendOptions = array(
333 'cache_dir' => ($valid_key) ? $options->cache_dir.'/rss-with-key/' : $options->cache_dir.'/rss/', // directory where to put the cache files
334 'file_locking' => false,
335 'read_control' => true,
336 'read_control_type' => 'strlen',
337 'hashed_directory_level' => $options->cache_directory_level,
338 'hashed_directory_perm' => 0777,
339 'cache_file_perm' => 0664,
340 'file_name_prefix' => 'ff'
341 );
342 // getting a Zend_Cache_Core object
343 $cache = Zend_Cache::factory('Core', 'File', $frontendOptions, $backendOptions);
344 }
345 return $cache;
346}
347
348function debug($msg) {
349 global $debug_mode;
350 if ($debug_mode) {
351 echo '* ',$msg,"\n";
352 ob_flush();
353 flush();
354 }
355}
diff --git a/inc/poche/Database.class.php b/inc/poche/Database.class.php
index c998fe14..6244df88 100755
--- a/inc/poche/Database.class.php
+++ b/inc/poche/Database.class.php
@@ -18,7 +18,7 @@ class Database {
18 'default' => 'ORDER BY entries.id' 18 'default' => 'ORDER BY entries.id'
19 ); 19 );
20 20
21 function __construct() 21 function __construct()
22 { 22 {
23 switch (STORAGE) { 23 switch (STORAGE) {
24 case 'sqlite': 24 case 'sqlite':
@@ -27,11 +27,11 @@ class Database {
27 break; 27 break;
28 case 'mysql': 28 case 'mysql':
29 $db_path = 'mysql:host=' . STORAGE_SERVER . ';dbname=' . STORAGE_DB; 29 $db_path = 'mysql:host=' . STORAGE_SERVER . ';dbname=' . STORAGE_DB;
30 $this->handle = new PDO($db_path, STORAGE_USER, STORAGE_PASSWORD); 30 $this->handle = new PDO($db_path, STORAGE_USER, STORAGE_PASSWORD);
31 break; 31 break;
32 case 'postgres': 32 case 'postgres':
33 $db_path = 'pgsql:host=' . STORAGE_SERVER . ';dbname=' . STORAGE_DB; 33 $db_path = 'pgsql:host=' . STORAGE_SERVER . ';dbname=' . STORAGE_DB;
34 $this->handle = new PDO($db_path, STORAGE_USER, STORAGE_PASSWORD); 34 $this->handle = new PDO($db_path, STORAGE_USER, STORAGE_PASSWORD);
35 break; 35 break;
36 } 36 }
37 37
@@ -51,7 +51,7 @@ class Database {
51 } 51 }
52 $hasAdmin = count($query->fetchAll()); 52 $hasAdmin = count($query->fetchAll());
53 53
54 if ($hasAdmin == 0) 54 if ($hasAdmin == 0)
55 return false; 55 return false;
56 56
57 return true; 57 return true;
@@ -140,7 +140,7 @@ class Database {
140 $sql = 'INSERT INTO users_config ( user_id, name, value ) VALUES (?, ?, ?)'; 140 $sql = 'INSERT INTO users_config ( user_id, name, value ) VALUES (?, ?, ?)';
141 $params = array($id_user, 'language', LANG); 141 $params = array($id_user, 'language', LANG);
142 $query = $this->executeQuery($sql, $params); 142 $query = $this->executeQuery($sql, $params);
143 143
144 $sql = 'INSERT INTO users_config ( user_id, name, value ) VALUES (?, ?, ?)'; 144 $sql = 'INSERT INTO users_config ( user_id, name, value ) VALUES (?, ?, ?)';
145 $params = array($id_user, 'theme', DEFAULT_THEME); 145 $params = array($id_user, 'theme', DEFAULT_THEME);
146 $query = $this->executeQuery($sql, $params); 146 $query = $this->executeQuery($sql, $params);
@@ -153,7 +153,7 @@ class Database {
153 $query = $this->executeQuery($sql, array($id)); 153 $query = $this->executeQuery($sql, array($id));
154 $result = $query->fetchAll(); 154 $result = $query->fetchAll();
155 $user_config = array(); 155 $user_config = array();
156 156
157 foreach ($result as $key => $value) { 157 foreach ($result as $key => $value) {
158 $user_config[$value['name']] = $value['value']; 158 $user_config[$value['name']] = $value['value'];
159 } 159 }
@@ -201,10 +201,10 @@ class Database {
201 $params_update = array($password, $userId); 201 $params_update = array($password, $userId);
202 $query = $this->executeQuery($sql_update, $params_update); 202 $query = $this->executeQuery($sql_update, $params_update);
203 } 203 }
204 204
205 public function updateUserConfig($userId, $key, $value) { 205 public function updateUserConfig($userId, $key, $value) {
206 $config = $this->getConfigUser($userId); 206 $config = $this->getConfigUser($userId);
207 207
208 if (! isset($config[$key])) { 208 if (! isset($config[$key])) {
209 $sql = "INSERT INTO users_config (value, user_id, name) VALUES (?, ?, ?)"; 209 $sql = "INSERT INTO users_config (value, user_id, name) VALUES (?, ?, ?)";
210 } 210 }
@@ -230,6 +230,36 @@ class Database {
230 } 230 }
231 } 231 }
232 232
233 public function updateContentAndTitle($id, $title, $body, $user_id) {
234 $sql_action = 'UPDATE entries SET content = ?, title = ? WHERE id=? AND user_id=?';
235 $params_action = array($body, $title, $id, $user_id);
236 $query = $this->executeQuery($sql_action, $params_action);
237
238 return $query;
239 }
240
241 public function retrieveUnfetchedEntries($user_id, $limit) {
242
243 $sql_limit = "LIMIT 0,".$limit;
244 if (STORAGE == 'postgres') {
245 $sql_limit = "LIMIT ".$limit." OFFSET 0";
246 }
247
248 $sql = "SELECT * FROM entries WHERE (content = '' OR content IS NULL) AND user_id=? ORDER BY id " . $sql_limit;
249 $query = $this->executeQuery($sql, array($user_id));
250 $entries = $query->fetchAll();
251
252 return $entries;
253 }
254
255 public function retrieveUnfetchedEntriesCount($user_id) {
256 $sql = "SELECT count(*) FROM entries WHERE (content = '' OR content IS NULL) AND user_id=?";
257 $query = $this->executeQuery($sql, array($user_id));
258 list($count) = $query->fetch();
259
260 return $count;
261 }
262
233 public function retrieveAll($user_id) { 263 public function retrieveAll($user_id) {
234 $sql = "SELECT * FROM entries WHERE user_id=? ORDER BY id"; 264 $sql = "SELECT * FROM entries WHERE user_id=? ORDER BY id";
235 $query = $this->executeQuery($sql, array($user_id)); 265 $query = $this->executeQuery($sql, array($user_id));
@@ -294,24 +324,24 @@ class Database {
294 return $entries; 324 return $entries;
295 } 325 }
296 326
297 public function getEntriesByViewCount($view, $user_id, $tag_id = 0) { 327 public function getEntriesByViewCount($view, $user_id, $tag_id = 0) {
298 switch ($view) { 328 switch ($view) {
299 case 'archive': 329 case 'archive':
300 $sql = "SELECT count(*) FROM entries WHERE user_id=? AND is_read=? "; 330 $sql = "SELECT count(*) FROM entries WHERE user_id=? AND is_read=? ";
301 $params = array($user_id, 1); 331 $params = array($user_id, 1);
302 break; 332 break;
303 case 'fav' : 333 case 'fav' :
304 $sql = "SELECT count(*) FROM entries WHERE user_id=? AND is_fav=? "; 334 $sql = "SELECT count(*) FROM entries WHERE user_id=? AND is_fav=? ";
305 $params = array($user_id, 1); 335 $params = array($user_id, 1);
306 break; 336 break;
307 case 'tag' : 337 case 'tag' :
308 $sql = "SELECT count(*) FROM entries 338 $sql = "SELECT count(*) FROM entries
309 LEFT JOIN tags_entries ON tags_entries.entry_id=entries.id 339 LEFT JOIN tags_entries ON tags_entries.entry_id=entries.id
310 WHERE entries.user_id=? AND tags_entries.tag_id = ? "; 340 WHERE entries.user_id=? AND tags_entries.tag_id = ? ";
311 $params = array($user_id, $tag_id); 341 $params = array($user_id, $tag_id);
312 break; 342 break;
313 default: 343 default:
314 $sql = "SELECT count(*) FROM entries WHERE user_id=? AND is_read=? "; 344 $sql = "SELECT count(*) FROM entries WHERE user_id=? AND is_read=? ";
315 $params = array($user_id, 0); 345 $params = array($user_id, 0);
316 break; 346 break;
317 } 347 }
@@ -319,7 +349,7 @@ class Database {
319 $query = $this->executeQuery($sql, $params); 349 $query = $this->executeQuery($sql, $params);
320 list($count) = $query->fetch(); 350 list($count) = $query->fetch();
321 351
322 return $count; 352 return $count;
323 } 353 }
324 354
325 public function updateContent($id, $content, $user_id) { 355 public function updateContent($id, $content, $user_id) {
@@ -329,11 +359,24 @@ class Database {
329 return $query; 359 return $query;
330 } 360 }
331 361
332 public function add($url, $title, $content, $user_id) { 362 /**
333 $sql_action = 'INSERT INTO entries ( url, title, content, user_id ) VALUES (?, ?, ?, ?)'; 363 *
334 $params_action = array($url, $title, $content, $user_id); 364 * @param string $url
335 $query = $this->executeQuery($sql_action, $params_action); 365 * @param string $title
336 return $query; 366 * @param string $content
367 * @param integer $user_id
368 * @return integer $id of inserted record
369 */
370 public function add($url, $title, $content, $user_id, $isFavorite=0, $isRead=0) {
371 $sql_action = 'INSERT INTO entries ( url, title, content, user_id, is_fav, is_read ) VALUES (?, ?, ?, ?, ?, ?)';
372 $params_action = array($url, $title, $content, $user_id, $isFavorite, $isRead);
373 if ( !$this->executeQuery($sql_action, $params_action) ) {
374 $id = null;
375 }
376 else {
377 $id = intval($this->getLastId( (STORAGE == 'postgres') ? 'users_id_seq' : '' ));
378 }
379 return $id;
337 } 380 }
338 381
339 public function deleteById($id, $user_id) { 382 public function deleteById($id, $user_id) {
@@ -364,13 +407,25 @@ class Database {
364 public function getLastId($column = '') { 407 public function getLastId($column = '') {
365 return $this->getHandle()->lastInsertId($column); 408 return $this->getHandle()->lastInsertId($column);
366 } 409 }
410
411 public function search($term, $user_id, $limit = '') {
412 $search = '%'.$term.'%';
413 $sql_action = "SELECT * FROM entries WHERE user_id=? AND (content LIKE ? OR title LIKE ? OR url LIKE ?) "; //searches in content, title and URL
414 $sql_action .= $this->getEntriesOrder().' ' . $limit;
415 $params_action = array($user_id, $search, $search, $search);
416 $query = $this->executeQuery($sql_action, $params_action);
417 return $query->fetchAll();
418 }
367 419
368 public function retrieveAllTags($user_id) { 420 public function retrieveAllTags($user_id, $term = null) {
369 $sql = "SELECT DISTINCT tags.* FROM tags 421 $sql = "SELECT DISTINCT tags.*, count(entries.id) AS entriescount FROM tags
370 LEFT JOIN tags_entries ON tags_entries.tag_id=tags.id 422 LEFT JOIN tags_entries ON tags_entries.tag_id=tags.id
371 LEFT JOIN entries ON tags_entries.entry_id=entries.id 423 LEFT JOIN entries ON tags_entries.entry_id=entries.id
372 WHERE entries.user_id=?"; 424 WHERE entries.user_id=?
373 $query = $this->executeQuery($sql, array($user_id)); 425 ". (($term) ? "AND lower(tags.value) LIKE ?" : '') ."
426 GROUP BY tags.id, tags.value
427 ORDER BY tags.value";
428 $query = $this->executeQuery($sql, (($term)? array($user_id, strtolower('%'.$term.'%')) : array($user_id) ));
374 $tags = $query->fetchAll(); 429 $tags = $query->fetchAll();
375 430
376 return $tags; 431 return $tags;
@@ -390,7 +445,7 @@ class Database {
390 } 445 }
391 446
392 public function retrieveEntriesByTag($tag_id, $user_id) { 447 public function retrieveEntriesByTag($tag_id, $user_id) {
393 $sql = 448 $sql =
394 "SELECT entries.* FROM entries 449 "SELECT entries.* FROM entries
395 LEFT JOIN tags_entries ON tags_entries.entry_id=entries.id 450 LEFT JOIN tags_entries ON tags_entries.entry_id=entries.id
396 WHERE tags_entries.tag_id = ? AND entries.user_id=?"; 451 WHERE tags_entries.tag_id = ? AND entries.user_id=?";
@@ -401,7 +456,7 @@ class Database {
401 } 456 }
402 457
403 public function retrieveTagsByEntry($entry_id) { 458 public function retrieveTagsByEntry($entry_id) {
404 $sql = 459 $sql =
405 "SELECT tags.* FROM tags 460 "SELECT tags.* FROM tags
406 LEFT JOIN tags_entries ON tags_entries.tag_id=tags.id 461 LEFT JOIN tags_entries ON tags_entries.tag_id=tags.id
407 WHERE tags_entries.entry_id = ?"; 462 WHERE tags_entries.entry_id = ?";
diff --git a/inc/poche/Poche.class.php b/inc/poche/Poche.class.php
index 34f2ff5a..a662f695 100755
--- a/inc/poche/Poche.class.php
+++ b/inc/poche/Poche.class.php
@@ -18,7 +18,7 @@ class Poche
18 public $tpl; 18 public $tpl;
19 public $messages; 19 public $messages;
20 public $pagination; 20 public $pagination;
21 21
22 private $currentTheme = ''; 22 private $currentTheme = '';
23 private $currentLanguage = ''; 23 private $currentLanguage = '';
24 private $notInstalledMessage = array(); 24 private $notInstalledMessage = array();
@@ -32,20 +32,21 @@ class Poche
32 'fr_FR.utf8' => 'Français', 32 'fr_FR.utf8' => 'Français',
33 'it_IT.utf8' => 'Italiano', 33 'it_IT.utf8' => 'Italiano',
34 'pl_PL.utf8' => 'Polski', 34 'pl_PL.utf8' => 'Polski',
35 'pt_BR.utf8' => 'Português (Brasil)',
35 'ru_RU.utf8' => 'Pусский', 36 'ru_RU.utf8' => 'Pусский',
36 'sl_SI.utf8' => 'Slovenščina', 37 'sl_SI.utf8' => 'Slovenščina',
37 'uk_UA.utf8' => 'Українськй', 38 'uk_UA.utf8' => 'Українськ',
38 ); 39 );
39 public function __construct() 40 public function __construct()
40 { 41 {
41 if ($this->configFileIsAvailable()) { 42 if ($this->configFileIsAvailable()) {
42 $this->init(); 43 $this->init();
43 } 44 }
44 45
45 if ($this->themeIsInstalled()) { 46 if ($this->themeIsInstalled()) {
46 $this->initTpl(); 47 $this->initTpl();
47 } 48 }
48 49
49 if ($this->systemIsInstalled()) { 50 if ($this->systemIsInstalled()) {
50 $this->store = new Database(); 51 $this->store = new Database();
51 $this->messages = new Messages(); 52 $this->messages = new Messages();
@@ -56,12 +57,10 @@ class Poche
56 $this->store->checkTags(); 57 $this->store->checkTags();
57 } 58 }
58 } 59 }
59 60
60 private function init() 61 private function init()
61 { 62 {
62 Tools::initPhp(); 63 Tools::initPhp();
63 Session::$sessionName = 'poche';
64 Session::init();
65 64
66 if (isset($_SESSION['poche_user']) && $_SESSION['poche_user'] != array()) { 65 if (isset($_SESSION['poche_user']) && $_SESSION['poche_user'] != array()) {
67 $this->user = $_SESSION['poche_user']; 66 $this->user = $_SESSION['poche_user'];
@@ -75,28 +74,28 @@ class Poche
75 $language = $this->user->getConfigValue('language'); 74 $language = $this->user->getConfigValue('language');
76 putenv('LC_ALL=' . $language); 75 putenv('LC_ALL=' . $language);
77 setlocale(LC_ALL, $language); 76 setlocale(LC_ALL, $language);
78 bindtextdomain($language, LOCALE); 77 bindtextdomain($language, LOCALE);
79 textdomain($language); 78 textdomain($language);
80 79
81 # Pagination 80 # Pagination
82 $this->pagination = new Paginator($this->user->getConfigValue('pager'), 'p'); 81 $this->pagination = new Paginator($this->user->getConfigValue('pager'), 'p');
83 82
84 # Set up theme 83 # Set up theme
85 $themeDirectory = $this->user->getConfigValue('theme'); 84 $themeDirectory = $this->user->getConfigValue('theme');
86 85
87 if ($themeDirectory === false) { 86 if ($themeDirectory === false) {
88 $themeDirectory = DEFAULT_THEME; 87 $themeDirectory = DEFAULT_THEME;
89 } 88 }
90 89
91 $this->currentTheme = $themeDirectory; 90 $this->currentTheme = $themeDirectory;
92 91
93 # Set up language 92 # Set up language
94 $languageDirectory = $this->user->getConfigValue('language'); 93 $languageDirectory = $this->user->getConfigValue('language');
95 94
96 if ($languageDirectory === false) { 95 if ($languageDirectory === false) {
97 $languageDirectory = DEFAULT_THEME; 96 $languageDirectory = DEFAULT_THEME;
98 } 97 }
99 98
100 $this->currentLanguage = $languageDirectory; 99 $this->currentLanguage = $languageDirectory;
101 } 100 }
102 101
@@ -109,7 +108,7 @@ class Poche
109 108
110 return true; 109 return true;
111 } 110 }
112 111
113 public function themeIsInstalled() { 112 public function themeIsInstalled() {
114 $passTheme = TRUE; 113 $passTheme = TRUE;
115 # Twig is an absolute requirement for Poche to function. Abort immediately if the Composer installer hasn't been run yet 114 # Twig is an absolute requirement for Poche to function. Abort immediately if the Composer installer hasn't been run yet
@@ -124,27 +123,27 @@ class Poche
124 self::$canRenderTemplates = false; 123 self::$canRenderTemplates = false;
125 124
126 $passTheme = FALSE; 125 $passTheme = FALSE;
127 } 126 }
128 127
129 # Check if the selected theme and its requirements are present 128 # Check if the selected theme and its requirements are present
130 $theme = $this->getTheme(); 129 $theme = $this->getTheme();
131 130
132 if ($theme != '' && ! is_dir(THEME . '/' . $theme)) { 131 if ($theme != '' && ! is_dir(THEME . '/' . $theme)) {
133 $this->notInstalledMessage[] = 'The currently selected theme (' . $theme . ') does not seem to be properly installed (Missing directory: ' . THEME . '/' . $theme . ')'; 132 $this->notInstalledMessage[] = 'The currently selected theme (' . $theme . ') does not seem to be properly installed (Missing directory: ' . THEME . '/' . $theme . ')';
134 133
135 self::$canRenderTemplates = false; 134 self::$canRenderTemplates = false;
136 135
137 $passTheme = FALSE; 136 $passTheme = FALSE;
138 } 137 }
139 138
140 $themeInfo = $this->getThemeInfo($theme); 139 $themeInfo = $this->getThemeInfo($theme);
141 if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) { 140 if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) {
142 foreach ($themeInfo['requirements'] as $requiredTheme) { 141 foreach ($themeInfo['requirements'] as $requiredTheme) {
143 if (! is_dir(THEME . '/' . $requiredTheme)) { 142 if (! is_dir(THEME . '/' . $requiredTheme)) {
144 $this->notInstalledMessage[] = 'The required "' . $requiredTheme . '" theme is missing for the current theme (' . $theme . ')'; 143 $this->notInstalledMessage[] = 'The required "' . $requiredTheme . '" theme is missing for the current theme (' . $theme . ')';
145 144
146 self::$canRenderTemplates = false; 145 self::$canRenderTemplates = false;
147 146
148 $passTheme = FALSE; 147 $passTheme = FALSE;
149 } 148 }
150 } 149 }
@@ -154,21 +153,21 @@ class Poche
154 return FALSE; 153 return FALSE;
155 } 154 }
156 155
157 156
158 return true; 157 return true;
159 } 158 }
160 159
161 /** 160 /**
162 * all checks before installation. 161 * all checks before installation.
163 * @todo move HTML to template 162 * @todo move HTML to template
164 * @return boolean 163 * @return boolean
165 */ 164 */
166 public function systemIsInstalled() 165 public function systemIsInstalled()
167 { 166 {
168 $msg = TRUE; 167 $msg = TRUE;
169 168
170 $configSalt = defined('SALT') ? constant('SALT') : ''; 169 $configSalt = defined('SALT') ? constant('SALT') : '';
171 170
172 if (empty($configSalt)) { 171 if (empty($configSalt)) {
173 $this->notInstalledMessage[] = 'You have not yet filled in the SALT value in the config.inc.php file.'; 172 $this->notInstalledMessage[] = 'You have not yet filled in the SALT value in the config.inc.php file.';
174 $msg = FALSE; 173 $msg = FALSE;
@@ -194,7 +193,7 @@ class Poche
194 193
195 return true; 194 return true;
196 } 195 }
197 196
198 public function getNotInstalledMessage() { 197 public function getNotInstalledMessage() {
199 return $this->notInstalledMessage; 198 return $this->notInstalledMessage;
200 } 199 }
@@ -203,7 +202,7 @@ class Poche
203 { 202 {
204 $loaderChain = new Twig_Loader_Chain(); 203 $loaderChain = new Twig_Loader_Chain();
205 $theme = $this->getTheme(); 204 $theme = $this->getTheme();
206 205
207 # add the current theme as first to the loader chain so Twig will look there first for overridden template files 206 # add the current theme as first to the loader chain so Twig will look there first for overridden template files
208 try { 207 try {
209 $loaderChain->addLoader(new Twig_Loader_Filesystem(THEME . '/' . $theme)); 208 $loaderChain->addLoader(new Twig_Loader_Filesystem(THEME . '/' . $theme));
@@ -211,7 +210,7 @@ class Poche
211 # @todo isInstalled() should catch this, inject Twig later 210 # @todo isInstalled() should catch this, inject Twig later
212 die('The currently selected theme (' . $theme . ') does not seem to be properly installed (' . THEME . '/' . $theme .' is missing)'); 211 die('The currently selected theme (' . $theme . ') does not seem to be properly installed (' . THEME . '/' . $theme .' is missing)');
213 } 212 }
214 213
215 # add all required themes to the loader chain 214 # add all required themes to the loader chain
216 $themeInfo = $this->getThemeInfo($theme); 215 $themeInfo = $this->getThemeInfo($theme);
217 if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) { 216 if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) {
@@ -224,16 +223,16 @@ class Poche
224 } 223 }
225 } 224 }
226 } 225 }
227 226
228 if (DEBUG_POCHE) { 227 if (DEBUG_POCHE) {
229 $twigParams = array(); 228 $twigParams = array();
230 } else { 229 } else {
231 $twigParams = array('cache' => CACHE); 230 $twigParams = array('cache' => CACHE);
232 } 231 }
233 232
234 $this->tpl = new Twig_Environment($loaderChain, $twigParams); 233 $this->tpl = new Twig_Environment($loaderChain, $twigParams);
235 $this->tpl->addExtension(new Twig_Extensions_Extension_I18n()); 234 $this->tpl->addExtension(new Twig_Extensions_Extension_I18n());
236 235
237 # filter to display domain name of an url 236 # filter to display domain name of an url
238 $filter = new Twig_SimpleFilter('getDomain', 'Tools::getDomain'); 237 $filter = new Twig_SimpleFilter('getDomain', 'Tools::getDomain');
239 $this->tpl->addFilter($filter); 238 $this->tpl->addFilter($filter);
@@ -252,7 +251,7 @@ class Poche
252 'poche_url' => Tools::getPocheUrl() 251 'poche_url' => Tools::getPocheUrl()
253 )); 252 ));
254 if (isset($_GET['install'])) { 253 if (isset($_GET['install'])) {
255 if (($_POST['password'] == $_POST['password_repeat']) 254 if (($_POST['password'] == $_POST['password_repeat'])
256 && $_POST['password'] != "" && $_POST['login'] != "") { 255 && $_POST['password'] != "" && $_POST['login'] != "") {
257 # let's rock, install poche baby ! 256 # let's rock, install poche baby !
258 if ($this->store->install($_POST['login'], Tools::encodeString($_POST['password'] . $_POST['login']))) 257 if ($this->store->install($_POST['login'], Tools::encodeString($_POST['password'] . $_POST['login'])))
@@ -269,7 +268,7 @@ class Poche
269 } 268 }
270 exit(); 269 exit();
271 } 270 }
272 271
273 public function getTheme() { 272 public function getTheme() {
274 return $this->currentTheme; 273 return $this->currentTheme;
275 } 274 }
@@ -294,7 +293,7 @@ class Poche
294 if (is_file($themeIniFile) && is_readable($themeIniFile)) { 293 if (is_file($themeIniFile) && is_readable($themeIniFile)) {
295 $themeInfo = parse_ini_file($themeIniFile); 294 $themeInfo = parse_ini_file($themeIniFile);
296 } 295 }
297 296
298 if ($themeInfo === false) { 297 if ($themeInfo === false) {
299 $themeInfo = array(); 298 $themeInfo = array();
300 } 299 }
@@ -305,7 +304,7 @@ class Poche
305 304
306 return $themeInfo; 305 return $themeInfo;
307 } 306 }
308 307
309 public function getInstalledThemes() { 308 public function getInstalledThemes() {
310 $handle = opendir(THEME); 309 $handle = opendir(THEME);
311 $themes = array(); 310 $themes = array();
@@ -332,28 +331,28 @@ class Poche
332 public function getInstalledLanguages() { 331 public function getInstalledLanguages() {
333 $handle = opendir(LOCALE); 332 $handle = opendir(LOCALE);
334 $languages = array(); 333 $languages = array();
335 334
336 while (($language = readdir($handle)) !== false) { 335 while (($language = readdir($handle)) !== false) {
337 # Languages are stored in a directory, so all directory names are languages 336 # Languages are stored in a directory, so all directory names are languages
338 # @todo move language installation data to database 337 # @todo move language installation data to database
339 if (! is_dir(LOCALE . '/' . $language) || in_array($language, array('..', '.'))) { 338 if (! is_dir(LOCALE . '/' . $language) || in_array($language, array('..', '.', 'tools'))) {
340 continue; 339 continue;
341 } 340 }
342 341
343 $current = false; 342 $current = false;
344 343
345 if ($language === $this->getLanguage()) { 344 if ($language === $this->getLanguage()) {
346 $current = true; 345 $current = true;
347 } 346 }
348 347
349 $languages[] = array('name' => $this->language_names[$language], 'value' => $language, 'current' => $current); 348 $languages[] = array('name' => (isset($this->language_names[$language]) ? $this->language_names[$language] : $language), 'value' => $language, 'current' => $current);
350 } 349 }
351 350
352 return $languages; 351 return $languages;
353 } 352 }
354 353
355 public function getDefaultConfig() 354 public function getDefaultConfig()
356 { 355 {
357 return array( 356 return array(
358 'pager' => PAGINATION, 357 'pager' => PAGINATION,
359 'language' => LANG, 358 'language' => LANG,
@@ -361,60 +360,6 @@ class Poche
361 ); 360 );
362 } 361 }
363 362
364 protected function getPageContent(Url $url)
365 {
366 // Saving and clearing context
367 $REAL = array();
368 foreach( $GLOBALS as $key => $value ) {
369 if( $key != "GLOBALS" && $key != "_SESSION" ) {
370 $GLOBALS[$key] = array();
371 $REAL[$key] = $value;
372 }
373 }
374 // Saving and clearing session
375 $REAL_SESSION = array();
376 foreach( $_SESSION as $key => $value ) {
377 $REAL_SESSION[$key] = $value;
378 unset($_SESSION[$key]);
379 }
380
381 // Running code in different context
382 $scope = function() {
383 extract( func_get_arg(1) );
384 $_GET = $_REQUEST = array(
385 "url" => $url->getUrl(),
386 "max" => 5,
387 "links" => "preserve",
388 "exc" => "",
389 "format" => "json",
390 "submit" => "Create Feed"
391 );
392 ob_start();
393 require func_get_arg(0);
394 $json = ob_get_flush();
395 return $json;
396 };
397 $json = $scope( "inc/3rdparty/makefulltextfeed.php", array("url" => $url) );
398
399 // Clearing and restoring context
400 foreach( $GLOBALS as $key => $value ) {
401 if( $key != "GLOBALS" && $key != "_SESSION" ) {
402 unset($GLOBALS[$key]);
403 }
404 }
405 foreach( $REAL as $key => $value ) {
406 $GLOBALS[$key] = $value;
407 }
408 // Clearing and restoring session
409 foreach( $_SESSION as $key => $value ) {
410 unset($_SESSION[$key]);
411 }
412 foreach( $REAL_SESSION as $key => $value ) {
413 $_SESSION[$key] = $value;
414 }
415 return json_decode($json, true);
416 }
417
418 /** 363 /**
419 * Call action (mark as fav, archive, delete, etc.) 364 * Call action (mark as fav, archive, delete, etc.)
420 */ 365 */
@@ -423,28 +368,24 @@ class Poche
423 switch ($action) 368 switch ($action)
424 { 369 {
425 case 'add': 370 case 'add':
426 $content = $this->getPageContent($url); 371 $content = Tools::getPageContent($url);
427 $title = ($content['rss']['channel']['item']['title'] != '') ? $content['rss']['channel']['item']['title'] : _('Untitled'); 372 $title = ($content['rss']['channel']['item']['title'] != '') ? $content['rss']['channel']['item']['title'] : _('Untitled');
428 $body = $content['rss']['channel']['item']['description']; 373 $body = $content['rss']['channel']['item']['description'];
429 374
430 // clean content from prevent xss attack 375 // clean content from prevent xss attack
431 $config = HTMLPurifier_Config::createDefault(); 376 $config = HTMLPurifier_Config::createDefault();
377 $config->set('Cache.SerializerPath', CACHE);
432 $purifier = new HTMLPurifier($config); 378 $purifier = new HTMLPurifier($config);
433 $title = $purifier->purify($title); 379 $title = $purifier->purify($title);
434 $body = $purifier->purify($body); 380 $body = $purifier->purify($body);
435 381
436 //search for possible duplicate if not in import mode 382 //search for possible duplicate
437 if (!$import) { 383 $duplicate = NULL;
438 $duplicate = $this->store->retrieveOneByURL($url->getUrl(), $this->user->getId()); 384 $duplicate = $this->store->retrieveOneByURL($url->getUrl(), $this->user->getId());
439 }
440 385
441 if ($this->store->add($url->getUrl(), $title, $body, $this->user->getId())) { 386 $last_id = $this->store->add($url->getUrl(), $title, $body, $this->user->getId());
387 if ( $last_id ) {
442 Tools::logm('add link ' . $url->getUrl()); 388 Tools::logm('add link ' . $url->getUrl());
443 $sequence = '';
444 if (STORAGE == 'postgres') {
445 $sequence = 'entries_id_seq';
446 }
447 $last_id = $this->store->getLastId($sequence);
448 if (DOWNLOAD_PICTURES) { 389 if (DOWNLOAD_PICTURES) {
449 $content = filtre_picture($body, $url->getUrl(), $last_id); 390 $content = filtre_picture($body, $url->getUrl(), $last_id);
450 Tools::logm('updating content article'); 391 Tools::logm('updating content article');
@@ -464,23 +405,17 @@ class Poche
464 } 405 }
465 } 406 }
466 407
467 if (!$import) { 408 $this->messages->add('s', _('the link has been added successfully'));
468 $this->messages->add('s', _('the link has been added successfully'));
469 }
470 } 409 }
471 else { 410 else {
472 if (!$import) { 411 $this->messages->add('e', _('error during insertion : the link wasn\'t added'));
473 $this->messages->add('e', _('error during insertion : the link wasn\'t added')); 412 Tools::logm('error during insertion : the link wasn\'t added ' . $url->getUrl());
474 Tools::logm('error during insertion : the link wasn\'t added ' . $url->getUrl());
475 }
476 } 413 }
477 414
478 if (!$import) { 415 if ($autoclose == TRUE) {
479 if ($autoclose == TRUE) { 416 Tools::redirect('?view=home');
480 Tools::redirect('?view=home'); 417 } else {
481 } else { 418 Tools::redirect('?view=home&closewin=true');
482 Tools::redirect('?view=home&closewin=true');
483 }
484 } 419 }
485 break; 420 break;
486 case 'delete': 421 case 'delete':
@@ -501,62 +436,56 @@ class Poche
501 case 'toggle_fav' : 436 case 'toggle_fav' :
502 $this->store->favoriteById($id, $this->user->getId()); 437 $this->store->favoriteById($id, $this->user->getId());
503 Tools::logm('mark as favorite link #' . $id); 438 Tools::logm('mark as favorite link #' . $id);
504 if (!$import) { 439 Tools::redirect();
505 Tools::redirect();
506 }
507 break; 440 break;
508 case 'toggle_archive' : 441 case 'toggle_archive' :
509 $this->store->archiveById($id, $this->user->getId()); 442 $this->store->archiveById($id, $this->user->getId());
510 Tools::logm('archive link #' . $id); 443 Tools::logm('archive link #' . $id);
511 if (!$import) { 444 Tools::redirect();
512 Tools::redirect();
513 }
514 break; 445 break;
515 case 'archive_all' : 446 case 'archive_all' :
516 $this->store->archiveAll($this->user->getId()); 447 $this->store->archiveAll($this->user->getId());
517 Tools::logm('archive all links'); 448 Tools::logm('archive all links');
518 if (!$import) { 449 Tools::redirect();
519 Tools::redirect();
520 }
521 break; 450 break;
522 case 'add_tag' : 451 case 'add_tag' :
523 if($import){ 452 $tags = explode(',', $_POST['value']);
524 $entry_id = $id; 453 $entry_id = $_POST['entry_id'];
525 $tags = explode(',', $tags);
526 }
527 else{
528 $tags = explode(',', $_POST['value']);
529 $entry_id = $_POST['entry_id'];
530 }
531 $entry = $this->store->retrieveOneById($entry_id, $this->user->getId()); 454 $entry = $this->store->retrieveOneById($entry_id, $this->user->getId());
532 if (!$entry) { 455 if (!$entry) {
533 $this->messages->add('e', _('Article not found!')); 456 $this->messages->add('e', _('Article not found!'));
534 Tools::logm('error : article not found'); 457 Tools::logm('error : article not found');
535 Tools::redirect(); 458 Tools::redirect();
536 } 459 }
460 //get all already set tags to preven duplicates
461 $already_set_tags = array();
462 $entry_tags = $this->store->retrieveTagsByEntry($entry_id);
463 foreach ($entry_tags as $tag) {
464 $already_set_tags[] = $tag['value'];
465 }
537 foreach($tags as $key => $tag_value) { 466 foreach($tags as $key => $tag_value) {
538 $value = trim($tag_value); 467 $value = trim($tag_value);
539 $tag = $this->store->retrieveTagByValue($value); 468 if ($value && !in_array($value, $already_set_tags)) {
540 469 $tag = $this->store->retrieveTagByValue($value);
541 if (is_null($tag)) { 470
542 # we create the tag 471 if (is_null($tag)) {
543 $tag = $this->store->createTag($value); 472 # we create the tag
544 $sequence = ''; 473 $tag = $this->store->createTag($value);
545 if (STORAGE == 'postgres') { 474 $sequence = '';
546 $sequence = 'tags_id_seq'; 475 if (STORAGE == 'postgres') {
547 } 476 $sequence = 'tags_id_seq';
548 $tag_id = $this->store->getLastId($sequence); 477 }
549 } 478 $tag_id = $this->store->getLastId($sequence);
550 else { 479 }
551 $tag_id = $tag['id']; 480 else {
481 $tag_id = $tag['id'];
482 }
483
484 # we assign the tag to the article
485 $this->store->setTagToEntry($tag_id, $entry_id);
552 } 486 }
553
554 # we assign the tag to the article
555 $this->store->setTagToEntry($tag_id, $entry_id);
556 }
557 if(!$import) {
558 Tools::redirect();
559 } 487 }
488 Tools::redirect();
560 break; 489 break;
561 case 'remove_tag' : 490 case 'remove_tag' :
562 $tag_id = $_GET['tag_id']; 491 $tag_id = $_GET['tag_id'];
@@ -581,8 +510,12 @@ class Poche
581 switch ($view) 510 switch ($view)
582 { 511 {
583 case 'config': 512 case 'config':
584 $dev = trim($this->getPocheVersion('dev')); 513 $dev_infos = $this->getPocheVersion('dev');
585 $prod = trim($this->getPocheVersion('prod')); 514 $dev = trim($dev_infos[0]);
515 $check_time_dev = date('d-M-Y H:i', $dev_infos[1]);
516 $prod_infos = $this->getPocheVersion('prod');
517 $prod = trim($prod_infos[0]);
518 $check_time_prod = date('d-M-Y H:i', $prod_infos[1]);
586 $compare_dev = version_compare(POCHE, $dev); 519 $compare_dev = version_compare(POCHE, $dev);
587 $compare_prod = version_compare(POCHE, $prod); 520 $compare_prod = version_compare(POCHE, $prod);
588 $themes = $this->getInstalledThemes(); 521 $themes = $this->getInstalledThemes();
@@ -594,6 +527,8 @@ class Poche
594 'languages' => $languages, 527 'languages' => $languages,
595 'dev' => $dev, 528 'dev' => $dev,
596 'prod' => $prod, 529 'prod' => $prod,
530 'check_time_dev' => $check_time_dev,
531 'check_time_prod' => $check_time_prod,
597 'compare_dev' => $compare_dev, 532 'compare_dev' => $compare_dev,
598 'compare_prod' => $compare_prod, 533 'compare_prod' => $compare_prod,
599 'token' => $token, 534 'token' => $token,
@@ -619,13 +554,36 @@ class Poche
619 break; 554 break;
620 case 'tags': 555 case 'tags':
621 $token = $this->user->getConfigValue('token'); 556 $token = $this->user->getConfigValue('token');
622 $tags = $this->store->retrieveAllTags($this->user->getId()); 557 //if term is set - search tags for this term
558 $term = Tools::checkVar('term');
559 $tags = $this->store->retrieveAllTags($this->user->getId(), $term);
560 if (Tools::isAjaxRequest()) {
561 $result = array();
562 foreach ($tags as $tag) {
563 $result[] = $tag['value'];
564 }
565 echo json_encode($result);
566 exit;
567 }
623 $tpl_vars = array( 568 $tpl_vars = array(
624 'token' => $token, 569 'token' => $token,
625 'user_id' => $this->user->getId(), 570 'user_id' => $this->user->getId(),
626 'tags' => $tags, 571 'tags' => $tags,
627 ); 572 );
628 break; 573 break;
574 case 'search':
575 if (isset($_GET['search'])) {
576 $search = filter_var($_GET['search'], FILTER_SANITIZE_STRING);
577 $tpl_vars['entries'] = $this->store->search($search, $this->user->getId());
578 $count = count($tpl_vars['entries']);
579 $this->pagination->set_total($count);
580 $page_links = str_replace(array('previous', 'next'), array(_('previous'), _('next')),
581 $this->pagination->page_links('?view=' . $view . '?search=' . $search . '&sort=' . $_SESSION['sort'] . '&' ));
582 $tpl_vars['page_links'] = $page_links;
583 $tpl_vars['nb_results'] = $count;
584 $tpl_vars['search_term'] = $search;
585 }
586 break;
629 case 'view': 587 case 'view':
630 $entry = $this->store->retrieveOneById($id, $this->user->getId()); 588 $entry = $this->store->retrieveOneById($id, $this->user->getId());
631 if ($entry != NULL) { 589 if ($entry != NULL) {
@@ -660,8 +618,9 @@ class Poche
660 'entries' => '', 618 'entries' => '',
661 'page_links' => '', 619 'page_links' => '',
662 'nb_results' => '', 620 'nb_results' => '',
621 'listmode' => (isset($_COOKIE['listmode']) ? true : false),
663 ); 622 );
664 623
665 //if id is given - we retrive entries by tag: id is tag id 624 //if id is given - we retrive entries by tag: id is tag id
666 if ($id) { 625 if ($id) {
667 $tpl_vars['tag'] = $this->store->retrieveTag($id, $this->user->getId()); 626 $tpl_vars['tag'] = $this->store->retrieveTag($id, $this->user->getId());
@@ -686,8 +645,8 @@ class Poche
686 } 645 }
687 646
688 /** 647 /**
689 * update the password of the current user. 648 * update the password of the current user.
690 * if MODE_DEMO is TRUE, the password can't be updated. 649 * if MODE_DEMO is TRUE, the password can't be updated.
691 * @todo add the return value 650 * @todo add the return value
692 * @todo set the new password in function header like this updatePassword($newPassword) 651 * @todo set the new password in function header like this updatePassword($newPassword)
693 * @return boolean 652 * @return boolean
@@ -715,42 +674,44 @@ class Poche
715 } 674 }
716 } 675 }
717 } 676 }
718 677
719 public function updateTheme() 678 public function updateTheme()
720 { 679 {
721 # no data 680 # no data
722 if (empty($_POST['theme'])) { 681 if (empty($_POST['theme'])) {
723 } 682 }
724 683
725 # we are not going to change it to the current theme... 684 # we are not going to change it to the current theme...
726 if ($_POST['theme'] == $this->getTheme()) { 685 if ($_POST['theme'] == $this->getTheme()) {
727 $this->messages->add('w', _('still using the "' . $this->getTheme() . '" theme!')); 686 $this->messages->add('w', _('still using the "' . $this->getTheme() . '" theme!'));
728 Tools::redirect('?view=config'); 687 Tools::redirect('?view=config');
729 } 688 }
730 689
731 $themes = $this->getInstalledThemes(); 690 $themes = $this->getInstalledThemes();
732 $actualTheme = false; 691 $actualTheme = false;
733 692
734 foreach (array_keys($themes) as $theme) { 693 foreach (array_keys($themes) as $theme) {
735 if ($theme == $_POST['theme']) { 694 if ($theme == $_POST['theme']) {
736 $actualTheme = true; 695 $actualTheme = true;
737 break; 696 break;
738 } 697 }
739 } 698 }
740 699
741 if (! $actualTheme) { 700 if (! $actualTheme) {
742 $this->messages->add('e', _('that theme does not seem to be installed')); 701 $this->messages->add('e', _('that theme does not seem to be installed'));
743 Tools::redirect('?view=config'); 702 Tools::redirect('?view=config');
744 } 703 }
745 704
746 $this->store->updateUserConfig($this->user->getId(), 'theme', $_POST['theme']); 705 $this->store->updateUserConfig($this->user->getId(), 'theme', $_POST['theme']);
747 $this->messages->add('s', _('you have changed your theme preferences')); 706 $this->messages->add('s', _('you have changed your theme preferences'));
748 707
749 $currentConfig = $_SESSION['poche_user']->config; 708 $currentConfig = $_SESSION['poche_user']->config;
750 $currentConfig['theme'] = $_POST['theme']; 709 $currentConfig['theme'] = $_POST['theme'];
751 710
752 $_SESSION['poche_user']->setConfig($currentConfig); 711 $_SESSION['poche_user']->setConfig($currentConfig);
753 712
713 $this->emptyCache();
714
754 Tools::redirect('?view=config'); 715 Tools::redirect('?view=config');
755 } 716 }
756 717
@@ -759,39 +720,40 @@ class Poche
759 # no data 720 # no data
760 if (empty($_POST['language'])) { 721 if (empty($_POST['language'])) {
761 } 722 }
762 723
763 # we are not going to change it to the current language... 724 # we are not going to change it to the current language...
764 if ($_POST['language'] == $this->getLanguage()) { 725 if ($_POST['language'] == $this->getLanguage()) {
765 $this->messages->add('w', _('still using the "' . $this->getLanguage() . '" language!')); 726 $this->messages->add('w', _('still using the "' . $this->getLanguage() . '" language!'));
766 Tools::redirect('?view=config'); 727 Tools::redirect('?view=config');
767 } 728 }
768 729
769 $languages = $this->getInstalledLanguages(); 730 $languages = $this->getInstalledLanguages();
770 $actualLanguage = false; 731 $actualLanguage = false;
771 732
772 foreach ($languages as $language) { 733 foreach ($languages as $language) {
773 if ($language['value'] == $_POST['language']) { 734 if ($language['value'] == $_POST['language']) {
774 $actualLanguage = true; 735 $actualLanguage = true;
775 break; 736 break;
776 } 737 }
777 } 738 }
778 739
779 if (! $actualLanguage) { 740 if (! $actualLanguage) {
780 $this->messages->add('e', _('that language does not seem to be installed')); 741 $this->messages->add('e', _('that language does not seem to be installed'));
781 Tools::redirect('?view=config'); 742 Tools::redirect('?view=config');
782 } 743 }
783 744
784 $this->store->updateUserConfig($this->user->getId(), 'language', $_POST['language']); 745 $this->store->updateUserConfig($this->user->getId(), 'language', $_POST['language']);
785 $this->messages->add('s', _('you have changed your language preferences')); 746 $this->messages->add('s', _('you have changed your language preferences'));
786 747
787 $currentConfig = $_SESSION['poche_user']->config; 748 $currentConfig = $_SESSION['poche_user']->config;
788 $currentConfig['language'] = $_POST['language']; 749 $currentConfig['language'] = $_POST['language'];
789 750
790 $_SESSION['poche_user']->setConfig($currentConfig); 751 $_SESSION['poche_user']->setConfig($currentConfig);
791 752
753 $this->emptyCache();
754
792 Tools::redirect('?view=config'); 755 Tools::redirect('?view=config');
793 } 756 }
794
795 /** 757 /**
796 * get credentials from differents sources 758 * get credentials from differents sources
797 * it redirects the user to the $referer link 759 * it redirects the user to the $referer link
@@ -846,7 +808,7 @@ class Poche
846 /** 808 /**
847 * log out the poche user. It cleans the session. 809 * log out the poche user. It cleans the session.
848 * @todo add the return value 810 * @todo add the return value
849 * @return boolean 811 * @return boolean
850 */ 812 */
851 public function logout() 813 public function logout()
852 { 814 {
@@ -857,225 +819,123 @@ class Poche
857 } 819 }
858 820
859 /** 821 /**
860 * import from Instapaper. poche needs a ./instapaper-export.html file 822 * import datas into your poche
861 * @todo add the return value
862 * @param string $targetFile the file used for importing
863 * @return boolean 823 * @return boolean
864 */ 824 */
865 private function importFromInstapaper($targetFile) 825 public function import() {
866 { 826
867 # TODO gestion des articles favs 827 if (!defined('IMPORT_LIMIT')) {
868 $html = new simple_html_dom(); 828 define('IMPORT_LIMIT', 5);
869 $html->load_file($targetFile); 829 }
870 Tools::logm('starting import from instapaper'); 830 if (!defined('IMPORT_DELAY')) {
871 831 define('IMPORT_DELAY', 5);
872 $read = 0; 832 }
873 $errors = array(); 833
874 foreach($html->find('ol') as $ul) 834 if ( isset($_FILES['file']) ) {
875 { 835 // assume, that file is in json format
876 foreach($ul->find('li') as $li) 836 $str_data = file_get_contents($_FILES['file']['tmp_name']);
877 { 837 $data = json_decode($str_data, true);
878 $a = $li->find('a'); 838
879 $url = new Url(base64_encode($a[0]->href)); 839 if ( $data === null ) {
880 $this->action('add', $url, 0, TRUE); 840 //not json - assume html
881 if ($read == '1') { 841 $html = new simple_html_dom();
882 $sequence = ''; 842 $html->load_file($_FILES['file']['tmp_name']);
883 if (STORAGE == 'postgres') { 843 $data = array();
884 $sequence = 'entries_id_seq'; 844 $read = 0;
885 } 845 foreach (array('ol','ul') as $list) {
886 $last_id = $this->store->getLastId($sequence); 846 foreach ($html->find($list) as $ul) {
887 $this->action('toggle_archive', $url, $last_id, TRUE); 847 foreach ($ul->find('li') as $li) {
888 } 848 $tmpEntry = array();
849 $a = $li->find('a');
850 $tmpEntry['url'] = $a[0]->href;
851 $tmpEntry['tags'] = $a[0]->tags;
852 $tmpEntry['is_read'] = $read;
853 if ($tmpEntry['url']) {
854 $data[] = $tmpEntry;
855 }
856 }
857 # the second <ol/ul> is for read links
858 $read = ((sizeof($data) && $read)?0:1);
889 } 859 }
890 860 }
891 # the second <ol> is for read links
892 $read = 1;
893 } 861 }
894 $this->messages->add('s', _('import from instapaper completed'));
895 Tools::logm('import from instapaper completed');
896 Tools::redirect();
897 }
898 862
899 /** 863 //for readability structure
900 * import from Pocket. poche needs a ./ril_export.html file 864 foreach ($data as $record) {
901 * @todo add the return value 865 if (is_array($record)) {
902 * @param string $targetFile the file used for importing 866 $data[] = $record;
903 * @return boolean 867 foreach ($record as $record2) {
904 */ 868 if (is_array($record2)) {
905 private function importFromPocket($targetFile) 869 $data[] = $record2;
906 { 870 }
907 # TODO gestion des articles favs 871 }
908 $html = new simple_html_dom(); 872 }
909 $html->load_file($targetFile); 873 }
910 Tools::logm('starting import from pocket'); 874
911 875 $i = 0; //counter for articles inserted
912 $read = 0; 876 foreach ($data as $record) {
913 $errors = array(); 877 $url = trim( isset($record['article__url']) ? $record['article__url'] : (isset($record['url']) ? $record['url'] : '') );
914 foreach($html->find('ul') as $ul) 878 if ( $url ) {
915 { 879 $title = (isset($record['title']) ? $record['title'] : _('Untitled - Import - ').'</a> <a href="./?import">'._('click to finish import').'</a><a>');
916 foreach($ul->find('li') as $li) 880 $body = (isset($record['content']) ? $record['content'] : '');
917 { 881 $isRead = (isset($record['is_read']) ? intval($record['is_read']) : (isset($record['archive'])?intval($record['archive']):0));
918 $a = $li->find('a'); 882 $isFavorite = (isset($record['is_fav']) ? intval($record['is_fav']) : (isset($record['favorite'])?intval($record['favorite']):0) );
919 $url = new Url(base64_encode($a[0]->href)); 883 //insert new record
920 $this->action('add', $url, 0, TRUE); 884 $id = $this->store->add($url, $title, $body, $this->user->getId(), $isFavorite, $isRead);
921 $sequence = ''; 885 if ( $id ) {
922 if (STORAGE == 'postgres') { 886 //increment no of records inserted
923 $sequence = 'entries_id_seq'; 887 $i++;
924 } 888 if ( isset($record['tags']) && trim($record['tags']) ) {
925 $last_id = $this->store->getLastId($sequence); 889 //@TODO: set tags
926 if ($read == '1') { 890
927 $this->action('toggle_archive', $url, $last_id, TRUE); 891 }
928 }
929 $tags = $a[0]->tags;
930 if(!empty($tags)) {
931 $this->action('add_tag',$url,$last_id,true,false,$tags);
932 }
933 } 892 }
934 893 }
935 # the second <ul> is for read links
936 $read = 1;
937 } 894 }
938 $this->messages->add('s', _('import from pocket completed'));
939 Tools::logm('import from pocket completed');
940 Tools::redirect();
941 }
942 895
943 /** 896 if ( $i > 0 ) {
944 * import from Readability. poche needs a ./readability file 897 $this->messages->add('s', _('Articles inserted: ').$i._('. Please note, that some may be marked as "read".'));
945 * @todo add the return value
946 * @param string $targetFile the file used for importing
947 * @return boolean
948 */
949 private function importFromReadability($targetFile)
950 {
951 # TODO gestion des articles lus / favs
952 $str_data = file_get_contents($targetFile);
953 $data = json_decode($str_data,true);
954 Tools::logm('starting import from Readability');
955 $count = 0;
956 foreach ($data as $key => $value) {
957 $url = NULL;
958 $favorite = FALSE;
959 $archive = FALSE;
960 foreach ($value as $item) {
961 foreach ($item as $attr => $value) {
962 if ($attr == 'article__url') {
963 $url = new Url(base64_encode($value));
964 }
965 $sequence = '';
966 if (STORAGE == 'postgres') {
967 $sequence = 'entries_id_seq';
968 }
969 if ($value == 'true') {
970 if ($attr == 'favorite') {
971 $favorite = TRUE;
972 }
973 if ($attr == 'archive') {
974 $archive = TRUE;
975 }
976 }
977 }
978
979 # we can add the url
980 if (!is_null($url) && $url->isCorrect()) {
981 $this->action('add', $url, 0, TRUE);
982 $count++;
983 if ($favorite) {
984 $last_id = $this->store->getLastId($sequence);
985 $this->action('toggle_fav', $url, $last_id, TRUE);
986 }
987 if ($archive) {
988 $last_id = $this->store->getLastId($sequence);
989 $this->action('toggle_archive', $url, $last_id, TRUE);
990 }
991 }
992 }
993 } 898 }
994 $this->messages->add('s', _('import from Readability completed. ' . $count . ' new links.')); 899 }
995 Tools::logm('import from Readability completed'); 900 //file parsing finished here
901
902 //now download article contents if any
903
904 //check if we need to download any content
905 $recordsDownloadRequired = $this->store->retrieveUnfetchedEntriesCount($this->user->getId());
906 if ( $recordsDownloadRequired == 0 ) {
907 //nothing to download
908 $this->messages->add('s', _('Import finished.'));
996 Tools::redirect(); 909 Tools::redirect();
997 } 910 }
911 else {
912 //if just inserted - don't download anything, download will start in next reload
913 if ( !isset($_FILES['file']) ) {
914 //download next batch
915 $items = $this->store->retrieveUnfetchedEntries($this->user->getId(), IMPORT_LIMIT);
998 916
999 /** 917 $config = HTMLPurifier_Config::createDefault();
1000 * import from Poche exported file 918 $config->set('Cache.SerializerPath', CACHE);
1001 * @param string $targetFile the file used for importing 919 $purifier = new HTMLPurifier($config);
1002 * @return boolean
1003 */
1004 private function importFromPoche($targetFile)
1005 {
1006 $str_data = file_get_contents($targetFile);
1007 $data = json_decode($str_data,true);
1008 Tools::logm('starting import from Poche');
1009 920
921 foreach ($items as $item) {
922 $url = new Url(base64_encode($item['url']));
923 $content = Tools::getPageContent($url);
1010 924
1011 $sequence = ''; 925 $title = (($content['rss']['channel']['item']['title'] != '') ? $content['rss']['channel']['item']['title'] : _('Untitled'));
1012 if (STORAGE == 'postgres') { 926 $body = (($content['rss']['channel']['item']['description'] != '') ? $content['rss']['channel']['item']['description'] : _('Undefined'));
1013 $sequence = 'entries_id_seq';
1014 }
1015 927
1016 $count = 0; 928 //clean content to prevent xss attack
1017 foreach ($data as $value) { 929 $title = $purifier->purify($title);
1018 930 $body = $purifier->purify($body);
1019 $url = new Url(base64_encode($value['url'])); 931
1020 $favorite = ($value['is_fav'] == -1); 932 $this->store->updateContentAndTitle($item['id'], $title, $body, $this->user->getId());
1021 $archive = ($value['is_read'] == -1); 933 }
1022
1023 # we can add the url
1024 if (!is_null($url) && $url->isCorrect()) {
1025
1026 $this->action('add', $url, 0, TRUE);
1027
1028 $count++;
1029 if ($favorite) {
1030 $last_id = $this->store->getLastId($sequence);
1031 $this->action('toggle_fav', $url, $last_id, TRUE);
1032 }
1033 if ($archive) {
1034 $last_id = $this->store->getLastId($sequence);
1035 $this->action('toggle_archive', $url, $last_id, TRUE);
1036 }
1037 }
1038
1039 }
1040 $this->messages->add('s', _('import from Poche completed. ' . $count . ' new links.'));
1041 Tools::logm('import from Poche completed');
1042 Tools::redirect();
1043 }
1044 934
1045 /**
1046 * import datas into your poche
1047 * @param string $from name of the service to import : pocket, instapaper or readability
1048 * @todo add the return value
1049 * @return boolean
1050 */
1051 public function import($from)
1052 {
1053 $providers = array(
1054 'pocket' => 'importFromPocket',
1055 'readability' => 'importFromReadability',
1056 'instapaper' => 'importFromInstapaper',
1057 'poche' => 'importFromPoche',
1058 );
1059
1060 if (! isset($providers[$from])) {
1061 $this->messages->add('e', _('Unknown import provider.'));
1062 Tools::redirect();
1063 }
1064
1065 $targetDefinition = 'IMPORT_' . strtoupper($from) . '_FILE';
1066 $targetFile = constant($targetDefinition);
1067
1068 if (! defined($targetDefinition)) {
1069 $this->messages->add('e', _('Incomplete inc/poche/define.inc.php file, please define "' . $targetDefinition . '".'));
1070 Tools::redirect();
1071 }
1072
1073 if (! file_exists($targetFile)) {
1074 $this->messages->add('e', _('Could not find required "' . $targetFile . '" import file.'));
1075 Tools::redirect();
1076 } 935 }
1077 936 }
1078 $this->$providers[$from]($targetFile); 937
938 return array('includeImport'=>true, 'import'=>array('recordsDownloadRequired'=>$recordsDownloadRequired, 'recordsUnderDownload'=> IMPORT_LIMIT, 'delay'=> IMPORT_DELAY * 1000) );
1079 } 939 }
1080 940
1081 /** 941 /**
@@ -1084,6 +944,9 @@ class Poche
1084 */ 944 */
1085 public function export() 945 public function export()
1086 { 946 {
947 $filename = "wallabag-export-".$this->user->getId()."-".date("Y-m-d").".json";
948 header('Content-Disposition: attachment; filename='.$filename);
949
1087 $entries = $this->store->retrieveAll($this->user->getId()); 950 $entries = $this->store->retrieveAll($this->user->getId());
1088 echo $this->tpl->render('export.twig', array( 951 echo $this->tpl->render('export.twig', array(
1089 'export' => Tools::renderJson($entries), 952 'export' => Tools::renderJson($entries),
@@ -1099,21 +962,29 @@ class Poche
1099 private function getPocheVersion($which = 'prod') 962 private function getPocheVersion($which = 'prod')
1100 { 963 {
1101 $cache_file = CACHE . '/' . $which; 964 $cache_file = CACHE . '/' . $which;
965 $check_time = time();
1102 966
1103 # checks if the cached version file exists 967 # checks if the cached version file exists
1104 if (file_exists($cache_file) && (filemtime($cache_file) > (time() - 86400 ))) { 968 if (file_exists($cache_file) && (filemtime($cache_file) > (time() - 86400 ))) {
1105 $version = file_get_contents($cache_file); 969 $version = file_get_contents($cache_file);
970 $check_time = filemtime($cache_file);
1106 } else { 971 } else {
1107 $version = file_get_contents('http://static.wallabag.org/versions/' . $which); 972 $version = file_get_contents('http://static.wallabag.org/versions/' . $which);
1108 file_put_contents($cache_file, $version, LOCK_EX); 973 file_put_contents($cache_file, $version, LOCK_EX);
1109 } 974 }
1110 return $version; 975 return array($version, $check_time);
1111 } 976 }
1112 977
1113 public function generateToken() 978 public function generateToken()
1114 { 979 {
1115 if (ini_get('open_basedir') === '') { 980 if (ini_get('open_basedir') === '') {
1116 $token = substr(base64_encode(file_get_contents('/dev/urandom', false, null, 0, 20)), 0, 15); 981 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
982 echo 'This is a server using Windows!';
983 // alternative to /dev/urandom for Windows
984 $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
985 } else {
986 $token = substr(base64_encode(file_get_contents('/dev/urandom', false, null, 0, 20)), 0, 15);
987 }
1117 } 988 }
1118 else { 989 else {
1119 $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20); 990 $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
@@ -1124,6 +995,7 @@ class Poche
1124 $currentConfig = $_SESSION['poche_user']->config; 995 $currentConfig = $_SESSION['poche_user']->config;
1125 $currentConfig['token'] = $token; 996 $currentConfig['token'] = $token;
1126 $_SESSION['poche_user']->setConfig($currentConfig); 997 $_SESSION['poche_user']->setConfig($currentConfig);
998 Tools::redirect();
1127 } 999 }
1128 1000
1129 public function generateFeeds($token, $user_id, $tag_id, $type = 'home') 1001 public function generateFeeds($token, $user_id, $tag_id, $type = 'home')
@@ -1131,6 +1003,10 @@ class Poche
1131 $allowed_types = array('home', 'fav', 'archive', 'tag'); 1003 $allowed_types = array('home', 'fav', 'archive', 'tag');
1132 $config = $this->store->getConfigUser($user_id); 1004 $config = $this->store->getConfigUser($user_id);
1133 1005
1006 if ($config == null) {
1007 die(_('User with this id (' . $user_id . ') does not exist.'));
1008 }
1009
1134 if (!in_array($type, $allowed_types) || 1010 if (!in_array($type, $allowed_types) ||
1135 $token != $config['token']) { 1011 $token != $config['token']) {
1136 die(_('Uh, there is a problem while generating feeds.')); 1012 die(_('Uh, there is a problem while generating feeds.'));
@@ -1140,8 +1016,9 @@ class Poche
1140 $feed = new FeedWriter(RSS2); 1016 $feed = new FeedWriter(RSS2);
1141 $feed->setTitle('wallabag — ' . $type . ' feed'); 1017 $feed->setTitle('wallabag — ' . $type . ' feed');
1142 $feed->setLink(Tools::getPocheUrl()); 1018 $feed->setLink(Tools::getPocheUrl());
1143 $feed->setChannelElement('updated', date(DATE_RSS , time())); 1019 $feed->setChannelElement('pubDate', date(DATE_RSS , time()));
1144 $feed->setChannelElement('author', 'wallabag'); 1020 $feed->setChannelElement('generator', 'wallabag');
1021 $feed->setDescription('wallabag ' . $type . ' elements');
1145 1022
1146 if ($type == 'tag') { 1023 if ($type == 'tag') {
1147 $entries = $this->store->retrieveEntriesByTag($tag_id, $user_id); 1024 $entries = $this->store->retrieveEntriesByTag($tag_id, $user_id);
@@ -1154,7 +1031,7 @@ class Poche
1154 foreach ($entries as $entry) { 1031 foreach ($entries as $entry) {
1155 $newItem = $feed->createNewItem(); 1032 $newItem = $feed->createNewItem();
1156 $newItem->setTitle($entry['title']); 1033 $newItem->setTitle($entry['title']);
1157 $newItem->setLink(Tools::getPocheUrl() . '?view=view&amp;id=' . $entry['id']); 1034 $newItem->setLink($entry['url']);
1158 $newItem->setDate(time()); 1035 $newItem->setDate(time());
1159 $newItem->setDescription($entry['content']); 1036 $newItem->setDescription($entry['content']);
1160 $feed->addItem($newItem); 1037 $feed->addItem($newItem);
diff --git a/inc/poche/Tools.class.php b/inc/poche/Tools.class.php
index 4ed28ed1..a130e94b 100644..100755
--- a/inc/poche/Tools.class.php
+++ b/inc/poche/Tools.class.php
@@ -7,7 +7,7 @@
7 * @copyright 2013 7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file 8 * @license http://www.wtfpl.net/ see COPYING file
9 */ 9 */
10 10
11class Tools 11class Tools
12{ 12{
13 public static function initPhp() 13 public static function initPhp()
@@ -42,7 +42,7 @@ class Tools
42 && (strtolower($_SERVER['HTTPS']) == 'on')) 42 && (strtolower($_SERVER['HTTPS']) == 'on'))
43 || (isset($_SERVER["SERVER_PORT"]) 43 || (isset($_SERVER["SERVER_PORT"])
44 && $_SERVER["SERVER_PORT"] == '443') // HTTPS detection. 44 && $_SERVER["SERVER_PORT"] == '443') // HTTPS detection.
45 || (isset($_SERVER["SERVER_PORT"]) //Custom HTTPS port detection 45 || (isset($_SERVER["SERVER_PORT"]) //Custom HTTPS port detection
46 && $_SERVER["SERVER_PORT"] == SSL_PORT) 46 && $_SERVER["SERVER_PORT"] == SSL_PORT)
47 || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) 47 || (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])
48 && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'); 48 && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https');
@@ -148,7 +148,7 @@ class Tools
148 ); 148 );
149 149
150 # only download page lesser than 4MB 150 # only download page lesser than 4MB
151 $data = @file_get_contents($url, false, $context, -1, 4000000); 151 $data = @file_get_contents($url, false, $context, -1, 4000000);
152 152
153 if (isset($http_response_header) and isset($http_response_header[0])) { 153 if (isset($http_response_header) and isset($http_response_header[0])) {
154 $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)); 154 $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));
@@ -193,14 +193,14 @@ class Tools
193 193
194 public static function logm($message) 194 public static function logm($message)
195 { 195 {
196 if (DEBUG_POCHE) { 196 if (DEBUG_POCHE && php_sapi_name() != 'cli') {
197 $t = strval(date('Y/m/d_H:i:s')) . ' - ' . $_SERVER["REMOTE_ADDR"] . ' - ' . strval($message) . "\n"; 197 $t = strval(date('Y/m/d_H:i:s')) . ' - ' . $_SERVER["REMOTE_ADDR"] . ' - ' . strval($message) . "\n";
198 file_put_contents(CACHE . '/log.txt', $t, FILE_APPEND); 198 file_put_contents(CACHE . '/log.txt', $t, FILE_APPEND);
199 error_log('DEBUG POCHE : ' . $message); 199 error_log('DEBUG POCHE : ' . $message);
200 } 200 }
201 } 201 }
202 202
203 public static function encodeString($string) 203 public static function encodeString($string)
204 { 204 {
205 return sha1($string . SALT); 205 return sha1($string . SALT);
206 } 206 }
@@ -241,7 +241,6 @@ class Tools
241 } 241 }
242 } 242 }
243 243
244
245 public static function download_db() { 244 public static function download_db() {
246 header('Content-Disposition: attachment; filename="poche.sqlite.gz"'); 245 header('Content-Disposition: attachment; filename="poche.sqlite.gz"');
247 self::status(200); 246 self::status(200);
@@ -252,4 +251,74 @@ class Tools
252 251
253 exit; 252 exit;
254 } 253 }
254
255 public static function getPageContent(Url $url)
256 {
257 // Saving and clearing context
258 $REAL = array();
259 foreach( $GLOBALS as $key => $value ) {
260 if( $key != 'GLOBALS' && $key != '_SESSION' && $key != 'HTTP_SESSION_VARS' ) {
261 $GLOBALS[$key] = array();
262 $REAL[$key] = $value;
263 }
264 }
265 // Saving and clearing session
266 if ( isset($_SESSION) ) {
267 $REAL_SESSION = array();
268 foreach( $_SESSION as $key => $value ) {
269 $REAL_SESSION[$key] = $value;
270 unset($_SESSION[$key]);
271 }
272 }
273
274 // Running code in different context
275 $scope = function() {
276 extract( func_get_arg(1) );
277 $_GET = $_REQUEST = array(
278 "url" => $url->getUrl(),
279 "max" => 5,
280 "links" => "preserve",
281 "exc" => "",
282 "format" => "json",
283 "submit" => "Create Feed"
284 );
285 ob_start();
286 require func_get_arg(0);
287 $json = ob_get_contents();
288 ob_end_clean();
289 return $json;
290 };
291 $json = $scope( "inc/3rdparty/makefulltextfeed.php", array("url" => $url) );
292
293 // Clearing and restoring context
294 foreach( $GLOBALS as $key => $value ) {
295 if( $key != "GLOBALS" && $key != "_SESSION" ) {
296 unset($GLOBALS[$key]);
297 }
298 }
299 foreach( $REAL as $key => $value ) {
300 $GLOBALS[$key] = $value;
301 }
302 // Clearing and restoring session
303 if ( isset($REAL_SESSION) ) {
304 foreach( $_SESSION as $key => $value ) {
305 unset($_SESSION[$key]);
306 }
307 foreach( $REAL_SESSION as $key => $value ) {
308 $_SESSION[$key] = $value;
309 }
310 }
311
312 return json_decode($json, true);
313 }
314
315 /**
316 * Returns whether we handle an AJAX (XMLHttpRequest) request.
317 * @return boolean whether we handle an AJAX (XMLHttpRequest) request.
318 */
319 public static function isAjaxRequest()
320 {
321 return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH']==='XMLHttpRequest';
322 }
323
255} 324}
diff --git a/inc/poche/config.inc.php.new b/inc/poche/config.inc.php.new
index 8d52497b..83b3c4c0 100755
--- a/inc/poche/config.inc.php.new
+++ b/inc/poche/config.inc.php.new
@@ -52,12 +52,8 @@ define ('CACHE', ROOT . '/cache');
52 52
53define ('PAGINATION', '10'); 53define ('PAGINATION', '10');
54 54
55define ('POCKET_FILE', '/ril_export.html'); 55//limit for download of articles during import
56define ('READABILITY_FILE', '/readability'); 56define ('IMPORT_LIMIT', 5);
57define ('INSTAPAPER_FILE', '/instapaper-export.html'); 57//delay between downloads (in sec)
58define ('POCHE_FILE', '/poche-export'); 58define ('IMPORT_DELAY', 5);
59 59
60define ('IMPORT_POCKET_FILE', ROOT . POCKET_FILE);
61define ('IMPORT_READABILITY_FILE', ROOT . READABILITY_FILE);
62define ('IMPORT_INSTAPAPER_FILE', ROOT . INSTAPAPER_FILE);
63define ('IMPORT_POCHE_FILE', ROOT . POCHE_FILE); \ No newline at end of file
diff --git a/inc/poche/global.inc.php b/inc/poche/global.inc.php
index d22b0588..15091387 100644
--- a/inc/poche/global.inc.php
+++ b/inc/poche/global.inc.php
@@ -38,7 +38,7 @@ if (! file_exists(ROOT . '/vendor/autoload.php')) {
38 require_once ROOT . '/vendor/autoload.php'; 38 require_once ROOT . '/vendor/autoload.php';
39} 39}
40 40
41# system configuration; database credentials et cetera 41# system configuration; database credentials et caetera
42if (! file_exists(INCLUDES . '/poche/config.inc.php')) { 42if (! file_exists(INCLUDES . '/poche/config.inc.php')) {
43 Poche::$configFileAvailable = false; 43 Poche::$configFileAvailable = false;
44} else { 44} else {