]> git.mxchange.org Git - friendica-addons.git/blob - retriever/retriever.php
Merge pull request #114 from mexon/retriever
[friendica-addons.git] / retriever / retriever.php
1 <?php
2 /**
3  * Name: Retrieve Feed Content
4  * Description: Follow the permalink of RSS/Atom feed items and replace the summary with the full content.
5  * Version: 0.2
6  * Author: Matthew Exon <http://mat.exon.name>
7  */
8
9 function retriever_install() {
10     register_hook('plugin_settings', 'addon/retriever/retriever.php', 'retriever_plugin_settings');
11     register_hook('plugin_settings_post', 'addon/retriever/retriever.php', 'retriever_plugin_settings_post');
12     register_hook('post_remote', 'addon/retriever/retriever.php', 'retriever_post_remote_hook');
13     register_hook('contact_photo_menu', 'addon/retriever/retriever.php', 'retriever_contact_photo_menu');
14     register_hook('cron', 'addon/retriever/retriever.php', 'retriever_cron');
15
16     $schema = file_get_contents(dirname(__file__).'/database.sql');
17     $arr = explode(';', $schema);
18     foreach ($arr as $a) {
19         $r = q($a);
20     }
21
22     $r = q("SELECT `id` FROM `pconfig` WHERE `cat` LIKE 'retriever_%%'");
23     if (count($r) || (get_config('retriever', 'dbversion') == '0.1')) {
24         $retrievers = array();
25         $r = q("SELECT SUBSTRING(`cat`, 10) AS `contact`, `k`, `v` FROM `pconfig` WHERE `cat` LIKE 'retriever%%'");
26         foreach ($r as $rr) {
27             $retrievers[$rr['contact']][$rr['k']] = $rr['v'];
28         }
29         foreach ($retrievers as $k => $v) {
30             $rr = q("SELECT `uid` FROM `contact` WHERE `id` = %d", intval($k));
31             $uid = $rr[0]['uid'];
32             $v['images'] = 'on';
33             q("INSERT INTO `retriever_rule` (`uid`, `contact-id`, `data`) VALUES (%d, %d, '%s')",
34               intval($uid), intval($k), dbesc(json_encode($v)));
35         }
36         q("DELETE FROM `pconfig` WHERE `cat` LIKE 'retriever%%'");
37     }
38     if (get_config('retriever', 'dbversion') == '0.2') {
39         q("ALTER TABLE `retriever_resource` DROP COLUMN `retriever`");
40     }
41     if (get_config('retriever', 'dbversion') == '0.3') {
42         q("ALTER TABLE `retriever_item` MODIFY COLUMN `item-uri` varchar(800) CHARACTER SET ascii NOT NULL");
43         q("ALTER TABLE `retriever_resource` MODIFY COLUMN `url` varchar(800) CHARACTER SET ascii NOT NULL");
44     }
45     if (get_config('retriever', 'dbversion') == '0.4') {
46         q("ALTER TABLE `retriever_item` ADD COLUMN `finished` tinyint(1) unsigned NOT NULL DEFAULT '0'");
47     }
48     if (get_config('retriever', 'dbversion') == '0.5') {
49         q('ALTER TABLE `retriever_resource` CHANGE `created` `created` timestamp NOT NULL DEFAULT now()');
50         q('ALTER TABLE `retriever_resource` CHANGE `completed` `completed` timestamp NULL DEFAULT NULL');
51         q('ALTER TABLE `retriever_resource` CHANGE `last-try` `last-try` timestamp NULL DEFAULT NULL');
52         q('ALTER TABLE `retriever_item` DROP KEY `all`');
53         q('ALTER TABLE `retriever_item` ADD KEY `all` (`item-uri`, `item-uid`, `contact-id`)');
54     }
55     if (get_config('retriever', 'dbversion') == '0.6') {
56         q('ALTER TABLE `retriever_item` CONVERT TO CHARACTER SET utf8 COLLATE utf8_bin');
57         q('ALTER TABLE `retriever_item` CHANGE `item-uri` `item-uri`  varchar(800) CHARACTER SET ascii COLLATE ascii_bin NOT NULL');
58         q('ALTER TABLE `retriever_resource` CONVERT TO CHARACTER SET utf8 COLLATE utf8_bin');
59         q('ALTER TABLE `retriever_resource` CHANGE `url` `url`  varchar(800) CHARACTER SET ascii COLLATE ascii_bin NOT NULL');
60         q('ALTER TABLE `retriever_rule` CONVERT TO CHARACTER SET utf8 COLLATE utf8_bin');
61     }
62     if (get_config('retriever', 'dbversion') == '0.7') {
63         $r = q("SELECT `id`, `data` FROM `retriever_rule`");
64         foreach ($r as $rr) {
65             logger('retriever_install: retriever ' . $rr['id'] . ' old config ' . $rr['data'], LOGGER_DATA);
66             $data = json_decode($rr['data'], true);
67             if ($data['pattern']) {
68                 $matches = array();
69                 if (preg_match("/\/(.*)\//", $data['pattern'], $matches)) {
70                     $data['pattern'] = $matches[1];
71                 }
72             }
73             if ($data['match']) {
74                 $include = array();
75                 foreach (explode('|', $data['match']) as $component) {
76                     $matches = array();
77                     if (preg_match("/([A-Za-z][A-Za-z0-9]*)\[@([A-Za-z][a-z0-9]*)='([^']*)'\]/", $component, $matches)) {
78                         $include[] = array(
79                             'element' => $matches[1],
80                             'attribute' => $matches[2],
81                             'value' => $matches[3]);
82                     }
83                     if (preg_match("/([A-Za-z][A-Za-z0-9]*)\[contains(concat(' ',normalize-space(@class),' '),' ([^ ']+) ')]/", $component, $matches)) {
84                         $include[] = array(
85                             'element' => $matches[1],
86                             'attribute' => $matches[2],
87                             'value' => $matches[3]);
88                     }
89                 }
90                 $data['include'] = $include;
91                 unset($data['match']);
92             }
93             if ($data['remove']) {
94                 $exclude = array();
95                 foreach (explode('|', $data['remove']) as $component) {
96                     $matches = array();
97                     if (preg_match("/([A-Za-z][A-Za-z0-9]*)\[@([A-Za-z][a-z0-9]*)='([^']*)'\]/", $component, $matches)) {
98                         $exclude[] = array(
99                             'element' => $matches[1],
100                             'attribute' => $matches[2],
101                             'value' => $matches[3]);
102                     }
103                     if (preg_match("/([A-Za-z][A-Za-z0-9]*)\[contains(concat(' ',normalize-space(@class),' '),' ([^ ']+) ')]/", $component, $matches)) {
104                         $exclude[] = array(
105                             'element' => $matches[1],
106                             'attribute' => $matches[2],
107                             'value' => $matches[3]);
108                     }
109                 }
110                 $data['exclude'] = $exclude;
111                 unset($data['remove']);
112             }
113             $r = q('UPDATE `retriever_rule` SET `data` = "%s" WHERE `id` = %d', dbesc(json_encode($data)), $rr['id']);
114             logger('retriever_install: retriever ' . $rr['id'] . ' new config ' . json_encode($data), LOGGER_DATA);
115         }
116     }
117     set_config('retriever', 'dbversion', '0.8');
118 }
119
120 function retriever_uninstall() {
121     unregister_hook('plugin_settings', 'addon/retriever/retriever.php', 'retriever_plugin_settings');
122     unregister_hook('plugin_settings_post', 'addon/retriever/retriever.php', 'retriever_plugin_settings_post');
123     unregister_hook('post_remote', 'addon/retriever/retriever.php', 'retriever_post_remote_hook');
124     unregister_hook('plugin_settings', 'addon/retriever/retriever.php', 'retriever_plugin_settings');
125     unregister_hook('plugin_settings_post', 'addon/retriever/retriever.php', 'retriever_plugin_settings_post');
126     unregister_hook('contact_photo_menu', 'addon/retriever/retriever.php', 'retriever_contact_photo_menu');
127     unregister_hook('cron', 'addon/retriever/retriever.php', 'retriever_cron');
128 }
129
130 function retriever_module() {}
131
132 function retriever_cron($a, $b) {
133     // 100 is a nice sane number.  Maybe this should be configurable.
134     retriever_retrieve_items(100);
135     retriever_tidy();
136 }
137
138 $retriever_item_count = 0;
139
140 function retriever_retrieve_items($max_items) {
141     global $retriever_item_count;
142
143     $retriever_schedule = array(array(1,'minute'),
144                                 array(10,'minute'),
145                                 array(1,'hour'),
146                                 array(1,'day'),
147                                 array(2,'day'),
148                                 array(1,'week'),
149                                 array(1,'month'));
150
151     $schedule_clauses = array();
152     for ($i = 0; $i < count($retriever_schedule); $i++) {
153         $num = $retriever_schedule[$i][0];
154         $unit = $retriever_schedule[$i][1];
155         array_push($schedule_clauses,
156                    '(`num-tries` = ' . $i . ' AND TIMESTAMPADD(' . dbesc($unit) .
157                    ', ' . intval($num) . ', `last-try`) < now())');
158     }
159
160     $retrieve_items = $max_items - $retriever_item_count;
161     do {
162         $r = q("SELECT * FROM `retriever_resource` WHERE `completed` IS NULL AND (`last-try` IS NULL OR %s) ORDER BY `last-try` ASC LIMIT %d",
163                dbesc(implode($schedule_clauses, ' OR ')),
164                intval($retrieve_items));
165         if (count($r) == 0) {
166             break;
167         }
168         foreach ($r as $rr) {
169             retrieve_resource($rr);
170             $retriever_item_count++;
171         }
172         $retrieve_items = $max_items - $retriever_item_count;
173     }
174     while ($retrieve_items > 0);
175
176     /* Look for items that are waiting even though the resource has
177      * completed.  This usually happens because we've been asked to
178      * retrospectively apply a config change.  It could also happen
179      * due to a cron job dying or something. */
180     $r = q("SELECT retriever_resource.`id` as resource, retriever_item.`id` as item FROM retriever_resource, retriever_item, retriever_rule WHERE retriever_item.`finished` = 0 AND retriever_item.`resource` = retriever_resource.`id` AND retriever_resource.`completed` IS NOT NULL AND retriever_item.`contact-id` = retriever_rule.`contact-id` AND retriever_item.`item-uid` = retriever_rule.`uid` LIMIT %d",
181            intval($retrieve_items));
182     if (!$r) {
183         $r = array();
184     }
185     foreach ($r as $rr) {
186         $resource = q("SELECT * FROM retriever_resource WHERE `id` = %d", $rr['resource']);
187         $retriever_item = retriever_get_retriever_item($rr['item']);
188         if (!$retriever_item) {
189             logger('retriever_retrieve_items: no retriever item with id ' . $rr['item']);
190             continue;
191         }
192         $item = retriever_get_item($retriever_item);
193         if (!$item) {
194             logger('retriever_retrieve_items: no item ' . $retriever_item['item-uri']);
195             continue;
196         }
197         $retriever = get_retriever($item['contact-id'], $item['uid']);
198         if (!$retriever) {
199             logger('retriever_retrieve_items: no retriever for item ' .
200                    $retriever_item['item-uri'] . ' ' . $retriever_item['uid'] . ' ' . $item['contact-id']);
201             continue;
202         }
203         retriever_apply_completed_resource_to_item($retriever, $item, $resource[0]);
204         q("UPDATE `retriever_item` SET `finished` = 1 WHERE id = %d",
205           intval($retriever_item['id']));
206         retriever_check_item_completed($item);
207     }
208 }
209
210 function retriever_tidy() {
211     q("DELETE FROM retriever_resource WHERE completed IS NOT NULL AND completed < DATE_SUB(now(), INTERVAL 1 WEEK)");
212     q("DELETE FROM retriever_resource WHERE completed IS NULL AND created < DATE_SUB(now(), INTERVAL 3 MONTH)");
213
214     $r = q("SELECT retriever_item.id FROM retriever_item LEFT OUTER JOIN retriever_resource ON (retriever_item.resource = retriever_resource.id) WHERE retriever_resource.id is null");
215     foreach ($r as $rr) {
216         q('DELETE FROM retriever_item WHERE id = %d', intval($rr['id']));
217     }
218 }
219
220 function retrieve_resource($resource) {
221     logger('retrieve_resource: ' . ($resource['num-tries'] + 1) .
222            ' attempt at resource ' . $resource['id'] . ' ' . $resource['url'], LOGGER_DEBUG);
223     q("UPDATE `retriever_resource` SET `last-try` = now(), `num-tries` = `num-tries` + 1 WHERE id = %d",
224       intval($resource['id']));
225     $data = fetch_url($resource['url'], $resource['binary'], $resource['type']);
226     $resource['type'] = get_app()->get_curl_content_type();
227     if ($data) {
228         $resource['data'] = $data;
229         q("UPDATE `retriever_resource` SET `completed` = now(), `data` = '%s', `type` = '%s' WHERE id = %d",
230           dbesc($data), dbesc($resource['type']), intval($resource['id']));
231         retriever_resource_completed($resource);
232     }
233 }
234
235 function get_retriever($contact_id, $uid, $create = false) {
236     $r = q("SELECT * FROM `retriever_rule` WHERE `contact-id` = %d AND `uid` = %d",
237            intval($contact_id), intval($uid));
238     if (count($r)) {
239         $r[0]['data'] = json_decode($r[0]['data'], true);
240         return $r[0];
241     }
242     if ($create) {
243         q("INSERT INTO `retriever_rule` (`uid`, `contact-id`) VALUES (%d, %d)",
244           intval($uid), intval($contact_id));
245         $r = q("SELECT * FROM `retriever_rule` WHERE `contact-id` = %d AND `uid` = %d",
246                intval($contact_id), intval($uid));
247         return $r[0];
248     }
249 }
250
251 function retriever_get_retriever_item($id) {
252     $retriever_items = q("SELECT * FROM `retriever_item` WHERE id = %d", intval($id));
253     if (count($retriever_items) != 1) {
254         logger('retriever_get_retriever_item: unable to find retriever_item ' . $id, LOGGER_NORMAL);
255         return;
256     }
257     return $retriever_items[0];
258 }
259
260 function retriever_get_item($retriever_item) {
261     $items = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `contact-id` = %d",
262                dbesc($retriever_item['item-uri']),
263                intval($retriever_item['item-uid']),
264                intval($retriever_item['contact-id']));
265     if (count($items) != 1) {
266         logger('retriever_get_item: unexpected number of results ' .
267                count($items) . " when searching for item $uri $uid $cid", LOGGER_NORMAL);
268         return;
269     }
270     return $items[0];
271 }
272
273 function retriever_item_completed($retriever_item_id, $resource) {
274     logger('retriever_item_completed: id ' . $retriever_item_id . ' url ' . $resource['url'], LOGGER_DEBUG);
275
276     $retriever_item = retriever_get_retriever_item($retriever_item_id);
277     if (!$retriever_item) {
278         return;
279     }
280     $retriever = get_retriever($retriever_item['contact-id'], $retriever_item['item-uid']);
281     if (!$retriever) {
282         return;
283     }
284     $item = retriever_get_item($retriever_item);
285     if (!$item) {
286         return;
287     }
288
289     retriever_apply_completed_resource_to_item($retriever, $item, $resource);
290
291     q("UPDATE `retriever_item` SET `finished` = 1 WHERE id = %d",
292       intval($retriever_item['id']));
293     retriever_check_item_completed($item);
294 }
295
296 function retriever_resource_completed($resource) {
297     logger('retriever_resource_completed: id ' . $resource['id'] . ' url ' . $resource['url'], LOGGER_DEBUG);
298     $r = q("SELECT `id` FROM `retriever_item` WHERE `resource` = %d", $resource['id']);
299     foreach ($r as $rr) {
300         retriever_item_completed($rr['id'], $resource);
301     }
302 }
303
304 function apply_retrospective($retriever, $num) {
305     $r = q("SELECT * FROM `item` WHERE `contact-id` = %d ORDER BY `received` DESC LIMIT %d",
306            intval($retriever['contact-id']), intval($num));
307     foreach ($r as $item) {
308         q('UPDATE `item` SET `visible` = 0 WHERE `id` = %d', $item['id']);
309         retriever_on_item_insert($retriever, $item);
310     }
311 }
312
313 function retriever_on_item_insert($retriever, &$item) {
314     if (!$retriever || !$retriever['id']) {
315         logger('retriever_on_item_insert: No retriever supplied', LOGGER_NORMAL);
316         return;
317     }
318     if (!$retriever["data"]['enable'] == "on") {
319         return;
320     }
321     if ($retriever["data"]['pattern']) {
322         $url = preg_replace('/' . $retriever["data"]['pattern'] . '/', $retriever["data"]['replace'], $item['plink']);
323         logger('retriever_on_item_insert: Changed ' . $item['plink'] . ' to ' . $url, LOGGER_DATA);
324     }
325     else {
326         $url = $item['plink'];
327     }
328
329     $resource = add_retriever_resource($url);
330     $retriever_item_id = add_retriever_item($item, $resource);
331 }
332
333 function add_retriever_resource($url, $binary = false) {
334     logger('add_retriever_resource: ' . $url, LOGGER_DEBUG);
335     $r = q("SELECT * FROM `retriever_resource` WHERE `url` = '%s'", dbesc($url));
336     $resource = $r[0];
337     if (count($r)) {
338         logger('add_retriever_resource: Resource ' . $url . ' already requested', LOGGER_DEBUG);
339         return $r[0];
340     }
341     else {
342         q("INSERT INTO `retriever_resource` (`binary`, `url`) " .
343           "VALUES (%d, '%s')", intval($binary ? 1 : 0), dbesc($url));
344         $r = q("SELECT * FROM `retriever_resource` WHERE `url` = '%s'", dbesc($url));
345         return $r[0];
346     }
347 }
348
349 function add_retriever_item(&$item, $resource) {
350     logger('add_retriever_item: ' . $resource['url'] . ' for ' . $item['uri'] . ' ' . $item['uid'] . ' ' . $item['contact-id'], LOGGER_DEBUG);
351
352     q("INSERT INTO `retriever_item` (`item-uri`, `item-uid`, `contact-id`, `resource`) " .
353       "VALUES ('%s', %d, %d, %d)",
354       dbesc($item['uri']), intval($item['uid']), intval($item['contact-id']), intval($resource["id"]));
355     $r = q("SELECT id FROM `retriever_item` WHERE " .
356            "`item-uri` = '%s' AND `item-uid` = %d AND `contact-id` = %d AND `resource` = %d ORDER BY id DESC",
357            dbesc($item['uri']), intval($item['uid']), intval($item['contact-id']), intval($resource['id']));
358     if (!count($r)) {
359         logger("add_retriever_item: couldn't create retriever item for " .
360                $item['uri'] . ' ' . $item['uid'] . ' ' . $item['contact-id'],
361                LOGGER_NORMAL);
362         return;
363     }
364     logger('add_retriever_item: created retriever_item ' . $r[0]['id'] . ' for item ' . $item['uri'] . ' ' . $item['uid'] . ' ' . $item['contact-id'], LOGGER_DEBUG);
365     return $r[0]['id'];
366 }
367
368 function retriever_get_encoding($resource) {
369     $matches = array();
370     if (preg_match('/charset=(.*)/', $resource['type'], $matches)) {
371         return trim(array_pop($matches));
372     }
373     return 'utf-8';
374 }
375
376 function retriever_construct_xpath($spec) {
377     if (gettype($spec) != "array") {
378         return;
379     }
380     $components = array();
381     foreach ($spec as $clause) {
382         if (!$clause['attribute']) {
383             $components[] = $clause['element'];
384             continue;
385         }
386         if ($clause['attribute'] === 'class') {
387             $components[] =
388                 $clause['element'] .
389                 "[contains(concat(' ', normalize-space(@class), ' '), ' " .
390                 $clause['value'] . " ')]";
391         }
392         else {
393             $components[] =
394                 $clause['element'] . '[@' .
395                 $clause['attribute'] . "='" .
396                 $clause['value'] . "']";
397         }
398     }
399     // It would be better to do this in smarty3 in extract.tpl
400     return implode('|', $components);
401 }
402
403 function retriever_apply_dom_filter($retriever, &$item, $resource) {
404     logger('retriever_apply_dom_filter: applying XSLT to ' . $item['id'] . ' ' . $item['plink'], LOGGER_DEBUG);
405     require_once('include/html2bbcode.php');    
406
407     if (!$retriever['data']['include']) {
408         return;
409     }
410     if (!$resource['data']) {
411         logger('retriever_apply_dom_filter: no text to work with', LOGGER_NORMAL);
412         return;
413     }
414
415     $encoding = retriever_get_encoding($resource);
416     logger('@@@ item type ' . $resource['type'] . ' encoding ' . $encoding);
417     $extracter_template = get_markup_template('extract.tpl', 'addon/retriever/');
418     $doc = new DOMDocument('1.0', 'utf-8');
419     if (strpos($resource['type'], 'html') !== false) {
420         @$doc->loadHTML($resource['data']);
421     }
422     else {
423         $doc->loadXML($resource['data']);
424     }
425     logger('@@@ actual encoding of document is ' . $doc->encoding);
426
427     $components = parse_url($item['plink']);
428     $rooturl = $components['scheme'] . "://" . $components['host'];
429     $dirurl = $rooturl . dirname($components['path']) . "/";
430
431     $params = array('$include' => retriever_construct_xpath($retriever['data']['include']),
432                     '$exclude' => retriever_construct_xpath($retriever['data']['exclude']),
433                     '$pageurl' => $item['plink'],
434                     '$dirurl' => $dirurl,
435                     '$rooturl' => $rooturl);
436     $xslt = replace_macros($extracter_template, $params);
437     $xmldoc = new DOMDocument();
438     $xmldoc->loadXML($xslt);
439     $xp = new XsltProcessor();
440     $xp->importStylesheet($xmldoc);
441     $transformed = $xp->transformToXML($doc);
442     $item['body'] = html2bbcode($transformed);
443     if (!strlen($item['body'])) {
444         logger('retriever_apply_dom_filter retriever ' . $retriever['id'] . ' item ' . $item['id'] . ': output was empty', LOGGER_NORMAL);
445         return;
446     }
447     $item['body'] .= "\n\n" . t('Retrieved') . ' ' . date("Y-m-d") . ': [url=';
448     $item['body'] .=  $item['plink'];
449     $item['body'] .= ']' . $item['plink'] . '[/url]';
450     q("UPDATE `item` SET `body` = '%s', `edited` = '%s' WHERE `id` = %d",
451       dbesc($item['body']), dbesc(datetime_convert('UTC', 'UTC')), intval($item['id']));
452 }
453
454 function retrieve_images(&$item) {
455     $matches1 = array();
456     preg_match_all("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", $item["body"], $matches1);
457     $matches2 = array();
458     preg_match_all("/\[img\](.*?)\[\/img\]/ism", $item["body"], $matches2);
459     $matches = array_merge($matches1[3], $matches2[1]);
460     logger('retrieve_images: found ' . count($matches) . ' images for item ' . $item['uri'] . ' ' . $item['uid'] . ' ' . $item['contact-id'], LOGGER_DEBUG);
461     foreach ($matches as $url) {
462         if (strpos($url, get_app()->get_baseurl()) === FALSE) {
463             $resource = add_retriever_resource($url, true);
464             if (!$resource['completed']) {
465                 add_retriever_item($item, $resource);
466             }
467             else {
468                 retriever_transform_images($item, $resource);
469             }
470         }
471     }
472 }
473
474 function retriever_check_item_completed(&$item)
475 {
476     $r = q('SELECT count(*) FROM retriever_item WHERE `item-uri` = "%s" ' .
477            'AND `item-uid` = %d AND `contact-id` = %d AND `finished` = 0',
478            dbesc($item['uri']), intval($item['uid']),
479            intval($item['contact-id']));
480     $waiting = $r[0]['count(*)'];
481     logger('retriever_check_item_completed: item ' . $item['uri'] . ' ' . $item['uid']
482            . ' '. $item['contact-id'] . ' waiting for ' . $waiting . ' resources', LOGGER_DEBUG);
483     $old_visible = $item['visible'];
484     $item['visible'] = $waiting ? 0 : 1;
485     if (($item['id'] > 0) && ($old_visible != $item['visible'])) {
486         logger('retriever_check_item_completed: changing visible flag to ' . $item['visible'] . ' and invoking notifier ("edit_post", ' . $item['id'] . ')', LOGGER_DEBUG);
487         q("UPDATE `item` SET `visible` = %d, `edited` = '%s' WHERE `id` = %d",
488           intval($item['visible']),
489           dbesc(datetime_convert('UTC', 'UTC')),
490           intval($item['id']));
491         proc_run('php', "include/notifier.php", 'edit_post', $item['id']);
492     }
493 }
494
495 function retriever_apply_completed_resource_to_item($retriever, &$item, $resource) {
496     logger('retriever_apply_completed_resource_to_item: retriever ' .
497            ($retriever ? $retriever['id'] : 'none') .
498            ' resource ' . $resource['url'] . ' plink ' . $item['plink'], LOGGER_DEBUG);
499     if (strpos($resource['type'], 'image') !== false) {
500         retriever_transform_images($item, $resource);
501     }
502     if (!$retriever) {
503         return;
504     }
505     if ((strpos($resource['type'], 'html') !== false) ||
506         (strpos($resource['type'], 'xml') !== false)) {
507         retriever_apply_dom_filter($retriever, $item, $resource);
508         if ($retriever["data"]['images'] ) {
509             retrieve_images($item);
510         }
511     }
512 }
513
514 function retriever_store_photo($item, &$resource) {
515     $hash = photo_new_resource();
516
517     if (class_exists('Imagick')) {
518         try {
519             $image = new Imagick();
520             $image->readImageBlob($resource['data']);
521             $resource['width'] = $image->getImageWidth();
522             $resource['height'] = $image->getImageHeight();
523         }
524         catch (Exception $e) {
525             logger("ImageMagick couldn't process image " . $resource['id'] . " " . $resource['url'] . ' length ' . strlen($resource['data']) . ': ' . $e->getMessage(), LOGGER_DEBUG);
526             return false;
527         }
528     }
529     if (!array_key_exists('width', $resource)) {
530         $image = @imagecreatefromstring($resource['data']);
531         if ($image === false) {
532             logger("Couldn't process image " . $resource['id'] . " " . $resource['url'], LOGGER_DEBUG);
533             return false;
534         }
535         $resource['width']  = imagesx($image);
536         $resource['height'] = imagesy($image);
537         imagedestroy($image);
538     }
539
540     $url_components = parse_url($resource['url']);
541     $filename = basename($url_components['path']);
542     if (!strlen($filename)) {
543         $filename = 'image';
544     }
545     $r = q("INSERT INTO `photo`
546                 ( `uid`, `contact-id`, `guid`, `resource-id`, `created`, `edited`, `filename`, `type`, `album`, `height`, `width`, `datasize`, `data` )
547                 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s' )",
548            intval($item['item-uid']),
549            intval($item['contact-id']),
550            dbesc(get_guid()),
551            dbesc($hash),
552            dbesc(datetime_convert()),
553            dbesc(datetime_convert()),
554            dbesc($filename),
555            dbesc($resource['type']),
556            dbesc('Retrieved Images'),
557            intval($resource['height']),
558            intval($resource['width']),
559            intval(strlen($resource['data'])),
560            dbesc($resource['data'])
561         );
562
563     return $hash;
564 }
565
566 function retriever_transform_images(&$item, $resource) {
567     require_once('include/Photo.php');  
568
569     if (!$resource["data"]) {
570         logger('retriever_transform_images: no data available for '
571                . $resource['id'] . ' ' . $resource['url'], LOGGER_NORMAL);
572         return;
573     }
574
575     $hash = retriever_store_photo($item, $resource);
576     if ($hash === false) {
577         logger('retriever_transform_images: unable to store photo '
578                . $resource['id'] . ' ' . $resource['url'], LOGGER_NORMAL);
579         return;
580     }
581
582     $new_url = get_app()->get_baseurl() . '/photo/' . $hash;
583     logger('retriever_transform_images: replacing ' . $resource['url'] . ' with ' .
584            $new_url . ' in item ' . $item['plink'], LOGGER_DEBUG);
585     $transformed = str_replace($resource["url"], $new_url, $item['body']);
586     if ($transformed === $item['body']) {
587         return;
588     }
589
590     $item['body'] = $transformed;
591     q("UPDATE `item` SET `edited` = '%s', `body` = '%s' WHERE `plink` = '%s' AND `uid` = %d AND `contact-id` = %d",
592       dbesc(datetime_convert('UTC', 'UTC')),
593       dbesc($item['body']),
594       dbesc($item['plink']),
595       intval($item['uid']),
596       intval($item['contact-id']));
597 }
598
599 function retriever_content($a) {
600     if (!local_user()) {
601         $a->page['content'] .= "<p>Please log in</p>";
602         return;
603     }
604     if ($a->argv[1] === 'help') {
605         $feeds = q("SELECT `id`, `name`, `thumb` FROM contact WHERE `uid` = %d AND `network` = 'feed'",
606                    local_user());
607         foreach ($feeds as $k=>$v) {
608             $feeds[$k]['url'] = $a->get_baseurl() . '/retriever/' . $v['id'];
609         }
610         $template = get_markup_template('/help.tpl', 'addon/retriever/');
611         $a->page['content'] .= replace_macros($template, array(
612                                                   '$config' => $a->get_baseurl() . '/settings/addon',
613                                                   '$feeds' => $feeds));
614         return;
615     }
616     if ($a->argv[1]) {
617         $retriever = get_retriever($a->argv[1], local_user(), false);
618
619         if (x($_POST["id"])) {
620             $retriever = get_retriever($a->argv[1], local_user(), true);
621             $retriever["data"] = array();
622             foreach (array('pattern', 'replace', 'enable', 'images') as $setting) {
623                 if (x($_POST['retriever_' . $setting])) {
624                     $retriever["data"][$setting] = $_POST['retriever_' . $setting];
625                 }
626             }
627             foreach ($_POST as $k=>$v) {
628                 if (preg_match("/retriever-(include|exclude)-(\d+)-(element|attribute|value)/", $k, $matches)) {
629                     $retriever['data'][$matches[1]][intval($matches[2])][$matches[3]] = $v;
630                 }
631             }
632             // You've gotta have an element, even if it's just "*"
633             foreach ($retriever['data']['include'] as $k=>$clause) {
634                 if (!$clause['element']) {
635                     unset($retriever['data']['include'][$k]);
636                 }
637             }
638             foreach ($retriever['data']['exclude'] as $k=>$clause) {
639                 if (!$clause['element']) {
640                     unset($retriever['data']['exclude'][$k]);
641                 }
642             }
643             q("UPDATE `retriever_rule` SET `data`='%s' WHERE `id` = %d",
644               dbesc(json_encode($retriever["data"])), intval($retriever["id"]));
645             $a->page['content'] .= "<p><b>Settings Updated";
646             if (x($_POST["retriever_retrospective"])) {
647                 apply_retrospective($retriever, $_POST["retriever_retrospective"]);
648                 $a->page['content'] .= " and retrospectively applied to " . $_POST["apply"] . " posts";
649             }
650             $a->page['content'] .= ".</p></b>";
651         }
652
653         $template = get_markup_template('/rule-config.tpl', 'addon/retriever/');
654         $a->page['content'] .= replace_macros($template, array(
655                                                   '$enable' => array(
656                                                       'retriever_enable',
657                                                       t('Enabled'),
658                                                       $retriever['data']['enable']),
659                                                   '$pattern' => array(
660                                                       'retriever_pattern',
661                                                       t('URL Pattern'),
662                                                       $retriever["data"]['pattern'],
663                                                       t('Regular expression matching part of the URL to replace')),
664                                                   '$replace' => array(
665                                                       'retriever_replace',
666                                                       t('URL Replace'),
667                                                       $retriever["data"]['replace'],
668                                                       t('Text to replace matching part of above regular expression')),
669                                                   '$images' => array(
670                                                       'retriever_images',
671                                                       t('Download Images'),
672                                                       $retriever['data']['images']),
673                                                   '$retrospective' => array(
674                                                       'retriever_retrospective',
675                                                       t('Retrospectively Apply'),
676                                                       '0',
677                                                       t('Reapply the rules to this number of posts')),
678                                                   '$title' => t('Retrieve Feed Content'),
679                                                   '$help' => $a->get_baseurl() . '/retriever/help',
680                                                   '$submit' => t('Submit'),
681                                                   '$id' => ($retriever["id"] ? $retriever["id"] : "create"),
682                                                   '$tag_t' => t('Tag'),
683                                                   '$attribute_t' => t('Attribute'),
684                                                   '$value_t' => t('Value'),
685                                                   '$add_t' => t('Add'),
686                                                   '$remove_t' => t('Remove'),
687                                                   '$include_t' => t('Include'),
688                                                   '$include' => $retriever['data']['include'],
689                                                   '$exclude_t' => t('Exclude'),
690                                                   '$exclude' => $retriever["data"]['exclude']));
691         return;
692     }
693 }
694
695 function retriever_contact_photo_menu($a, &$args) {
696     if (!$args) {
697         return;
698     }
699     if ($args["contact"]["network"] == "feed") {
700         $args["menu"][ 'retriever' ] = array(t('Retriever'), $a->get_baseurl() . '/retriever/' . $args["contact"]['id']);
701     }
702 }
703
704 function retriever_post_remote_hook(&$a, &$item) {
705     logger('retriever_post_remote_hook: ' . $item['uri'] . ' ' . $item['uid'] . ' ' . $item['contact-id'], LOGGER_DEBUG);
706
707     $retriever = get_retriever($item['contact-id'], $item["uid"], false);
708     if ($retriever) {
709         retriever_on_item_insert($retriever, $item);
710     }
711     else {
712         if (get_pconfig($item["uid"], 'retriever', 'all_photos')) {
713             retrieve_images($item, null);
714         }
715     }
716     retriever_check_item_completed($item);
717 }
718
719 function retriever_plugin_settings(&$a,&$s) {
720     $all_photos = get_pconfig(local_user(), 'retriever', 'all_photos');
721     $all_photos_mu = ($all_photos == 'on') ? ' checked="true"' : '';
722     $template = get_markup_template('/settings.tpl', 'addon/retriever/');
723     $s .= replace_macros($template, array(
724                              '$submit' => t('Submit'),
725                              '$title' => t('Retriever Settings'),
726                              '$help' => $a->get_baseurl() . '/retriever/help',
727                              '$all_photos' => $all_photos_mu,
728                              '$all_photos_t' => t('All Photos')));
729 }
730
731 function retriever_plugin_settings_post($a,$post) {
732     if ($_POST['all_photos']) {
733         set_pconfig(local_user(), 'retriever', 'all_photos', $_POST['all_photos']);
734     }
735     else {
736         del_pconfig(local_user(), 'retriever', 'all_photos');
737     }
738 }