3 require_once('include/bbcode.php');
4 require_once('include/oembed.php');
5 require_once('include/salmon.php');
6 require_once('include/crypto.php');
7 require_once('include/Photo.php');
8 require_once('include/tags.php');
9 require_once('include/files.php');
10 require_once('include/text.php');
11 require_once('include/email.php');
12 require_once('include/threads.php');
13 require_once('include/socgraph.php');
14 require_once('include/plaintext.php');
15 require_once('include/ostatus.php');
16 require_once('include/feed.php');
17 require_once('include/Contact.php');
18 require_once('mod/share.php');
19 require_once('include/enotify.php');
20 require_once('include/dfrn.php');
21 require_once('include/group.php');
23 require_once('library/defuse/php-encryption-1.2.1/Crypto.php');
25 function construct_verb($item) {
33 * The purpose of this function is to apply system message length limits to
34 * imported messages without including any embedded photos in the length
36 if(! function_exists('limit_body_size')) {
37 function limit_body_size($body) {
39 // logger('limit_body_size: start', LOGGER_DEBUG);
41 $maxlen = get_max_import_size();
43 // If the length of the body, including the embedded images, is smaller
44 // than the maximum, then don't waste time looking for the images
45 if($maxlen && (strlen($body) > $maxlen)) {
47 logger('limit_body_size: the total body length exceeds the limit', LOGGER_DEBUG);
54 $img_start = strpos($orig_body, '[img');
55 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
56 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
57 while(($img_st_close !== false) && ($img_end !== false)) {
59 $img_st_close++; // make it point to AFTER the closing bracket
60 $img_end += $img_start;
61 $img_end += strlen('[/img]');
63 if(! strcmp(substr($orig_body, $img_start + $img_st_close, 5), 'data:')) {
64 // This is an embedded image
66 if( ($textlen + $img_start) > $maxlen ) {
67 if($textlen < $maxlen) {
68 logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG);
69 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
74 $new_body = $new_body . substr($orig_body, 0, $img_start);
75 $textlen += $img_start;
78 $new_body = $new_body . substr($orig_body, $img_start, $img_end - $img_start);
82 if( ($textlen + $img_end) > $maxlen ) {
83 if($textlen < $maxlen) {
84 logger('limit_body_size: the limit happens before the end of a non-embedded image', LOGGER_DEBUG);
85 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
90 $new_body = $new_body . substr($orig_body, 0, $img_end);
94 $orig_body = substr($orig_body, $img_end);
96 if($orig_body === false) // in case the body ends on a closing image tag
99 $img_start = strpos($orig_body, '[img');
100 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
101 $img_end = ($img_start !== false ? strpos(substr($orig_body, $img_start), '[/img]') : false);
104 if( ($textlen + strlen($orig_body)) > $maxlen) {
105 if($textlen < $maxlen) {
106 logger('limit_body_size: the limit happens after the end of the last image', LOGGER_DEBUG);
107 $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
112 logger('limit_body_size: the text size with embedded images extracted did not violate the limit', LOGGER_DEBUG);
113 $new_body = $new_body . $orig_body;
114 $textlen += strlen($orig_body);
123 function title_is_body($title, $body) {
125 $title = strip_tags($title);
126 $title = trim($title);
127 $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
128 $title = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $title);
130 $body = strip_tags($body);
132 $body = html_entity_decode($body, ENT_QUOTES, 'UTF-8');
133 $body = str_replace(array("\n", "\r", "\t", " "), array("","","",""), $body);
135 if (strlen($title) < strlen($body))
136 $body = substr($body, 0, strlen($title));
138 if (($title != $body) and (substr($title, -3) == "...")) {
139 $pos = strrpos($title, "...");
141 $title = substr($title, 0, $pos);
142 $body = substr($body, 0, $pos);
146 return($title == $body);
149 function add_page_info_data($data) {
150 call_hooks('page_info_data', $data);
152 // It maybe is a rich content, but if it does have everything that a link has,
153 // then treat it that way
154 if (($data["type"] == "rich") AND is_string($data["title"]) AND
155 is_string($data["text"]) AND (sizeof($data["images"]) > 0))
156 $data["type"] = "link";
158 if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url))
161 if ($no_photos AND ($data["type"] == "photo"))
164 if (sizeof($data["images"]) > 0)
165 $preview = $data["images"][0];
169 // Escape some bad characters
170 $data["url"] = str_replace(array("[", "]"), array("[", "]"), htmlentities($data["url"], ENT_QUOTES, 'UTF-8', false));
171 $data["title"] = str_replace(array("[", "]"), array("[", "]"), htmlentities($data["title"], ENT_QUOTES, 'UTF-8', false));
173 $text = "[attachment type='".$data["type"]."'";
175 if ($data["url"] != "")
176 $text .= " url='".$data["url"]."'";
177 if ($data["title"] != "")
178 $text .= " title='".$data["title"]."'";
179 if (sizeof($data["images"]) > 0) {
180 $preview = str_replace(array("[", "]"), array("[", "]"), htmlentities($data["images"][0]["src"], ENT_QUOTES, 'UTF-8', false));
181 // if the preview picture is larger than 500 pixels then show it in a larger mode
182 // But only, if the picture isn't higher than large (To prevent huge posts)
183 if (($data["images"][0]["width"] >= 500) AND ($data["images"][0]["width"] >= $data["images"][0]["height"]))
184 $text .= " image='".$preview."'";
186 $text .= " preview='".$preview."'";
188 $text .= "]".$data["text"]."[/attachment]";
191 if (isset($data["keywords"]) AND count($data["keywords"])) {
194 foreach ($data["keywords"] AS $keyword) {
195 /// @todo make a positive list of allowed characters
196 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'", "’", "`", "(", ")", "„", "“"),
197 array("","", "", "", "", "", "", "", "", "", "", ""), $keyword);
198 $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] ";
202 return "\n".$text.$hashtags;
205 function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
206 require_once("mod/parse_url.php");
208 $data = parseurl_getsiteinfo_cached($url, true);
211 $data["images"][0]["src"] = $photo;
213 logger('fetch page info for '.$url.' '.print_r($data, true), LOGGER_DEBUG);
215 if (!$keywords AND isset($data["keywords"]))
216 unset($data["keywords"]);
218 if (($keyword_blacklist != "") AND isset($data["keywords"])) {
219 $list = explode(",", $keyword_blacklist);
220 foreach ($list AS $keyword) {
221 $keyword = trim($keyword);
222 $index = array_search($keyword, $data["keywords"]);
223 if ($index !== false)
224 unset($data["keywords"][$index]);
231 function add_page_keywords($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
232 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
235 if (isset($data["keywords"]) AND count($data["keywords"])) {
237 foreach ($data["keywords"] AS $keyword) {
238 $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"),
239 array("","", "", "", "", ""), $keyword);
244 $tags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url]";
251 function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") {
252 $data = query_page_info($url, $no_photos, $photo, $keywords, $keyword_blacklist);
254 $text = add_page_info_data($data);
259 function add_page_info_to_body($body, $texturl = false, $no_photos = false) {
261 logger('add_page_info_to_body: fetch page info for body '.$body, LOGGER_DEBUG);
263 $URLSearchString = "^\[\]";
265 // Adding these spaces is a quick hack due to my problems with regular expressions :)
266 preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches);
269 preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " ".$body, $matches);
271 // Convert urls without bbcode elements
272 if (!$matches AND $texturl) {
273 preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
275 // Yeah, a hack. I really hate regular expressions :)
277 $matches[1] = $matches[2];
281 $footer = add_page_info($matches[1], $no_photos);
283 // Remove the link from the body if the link is attached at the end of the post
284 if (isset($footer) AND (trim($footer) != "") AND (strpos($footer, $matches[1]))) {
285 $removedlink = trim(str_replace($matches[1], "", $body));
286 if (($removedlink == "") OR strstr($body, $removedlink))
287 $body = $removedlink;
289 $url = str_replace(array('/', '.'), array('\/', '\.'), $matches[1]);
290 $removedlink = preg_replace("/\[url\=".$url."\](.*?)\[\/url\]/ism", '', $body);
291 if (($removedlink == "") OR strstr($body, $removedlink))
292 $body = $removedlink;
295 // Add the page information to the bottom
296 if (isset($footer) AND (trim($footer) != ""))
303 * Adds a "lang" specification in a "postopts" element of given $arr,
304 * if possible and not already present.
305 * Expects "body" element to exist in $arr.
307 * @todo Add a parameter to request forcing override
309 function item_add_language_opt(&$arr) {
311 if (version_compare(PHP_VERSION, '5.3.0', '<')) return; // LanguageDetect.php not available ?
313 if ( x($arr, 'postopts') )
315 if ( strstr($arr['postopts'], 'lang=') )
318 /// @TODO Add parameter to request overriding
321 $postopts = $arr['postopts'];
328 require_once('library/langdet/Text/LanguageDetect.php');
329 $naked_body = preg_replace('/\[(.+?)\]/','',$arr['body']);
330 $l = new Text_LanguageDetect;
331 //$lng = $l->detectConfidence($naked_body);
332 //$arr['postopts'] = (($lng['language']) ? 'lang=' . $lng['language'] . ';' . $lng['confidence'] : '');
333 $lng = $l->detect($naked_body, 3);
335 if (sizeof($lng) > 0) {
336 if ($postopts != "") $postopts .= '&'; // arbitrary separator, to be reviewed
337 $postopts .= 'lang=';
339 foreach ($lng as $language => $score) {
340 $postopts .= $sep . $language.";".$score;
343 $arr['postopts'] = $postopts;
348 * @brief Creates an unique guid out of a given uri
350 * @param string $uri uri of an item entry
351 * @return string unique guid
353 function uri_to_guid($uri) {
355 // Our regular guid routine is using this kind of prefix as well
356 // We have to avoid that different routines could accidentally create the same value
357 $parsed = parse_url($uri);
358 $guid_prefix = hash("crc32", $parsed["host"]);
360 // Remove the scheme to make sure that "https" and "http" doesn't make a difference
361 unset($parsed["scheme"]);
363 $host_id = implode("/", $parsed);
365 // We could use any hash algorithm since it isn't a security issue
366 $host_hash = hash("ripemd128", $host_id);
368 return $guid_prefix.$host_hash;
371 function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) {
373 // If it is a posting where users should get notifications, then define it as wall posting
376 $arr['type'] = 'wall';
378 $arr['last-child'] = 1;
379 $arr['network'] = NETWORK_DFRN;
382 // If a Diaspora signature structure was passed in, pull it out of the
383 // item array and set it aside for later storage.
386 if(x($arr,'dsprsig')) {
387 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
388 unset($arr['dsprsig']);
391 // Converting the plink
392 if ($arr['network'] == NETWORK_OSTATUS) {
393 if (isset($arr['plink']))
394 $arr['plink'] = ostatus::convert_href($arr['plink']);
395 elseif (isset($arr['uri']))
396 $arr['plink'] = ostatus::convert_href($arr['uri']);
399 if(x($arr, 'gravity'))
400 $arr['gravity'] = intval($arr['gravity']);
401 elseif($arr['parent-uri'] === $arr['uri'])
403 elseif(activity_match($arr['verb'],ACTIVITY_POST))
406 $arr['gravity'] = 6; // extensible catchall
409 $arr['type'] = 'remote';
413 /* check for create date and expire time */
414 $uid = intval($arr['uid']);
415 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
417 $expire_interval = $r[0]['expire'];
418 if ($expire_interval>0) {
419 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
420 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
421 if ($created_date < $expire_date) {
422 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
428 // Do we already have this item?
429 // We have to check several networks since Friendica posts could be repeated via OStatus (maybe Diasporsa as well)
430 if (in_array(trim($arr['network']), array(NETWORK_DIASPORA, NETWORK_DFRN, NETWORK_OSTATUS, ""))) {
431 $r = q("SELECT `id`, `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` IN ('%s', '%s', '%s') LIMIT 1",
432 dbesc(trim($arr['uri'])),
434 dbesc(NETWORK_DIASPORA),
436 dbesc(NETWORK_OSTATUS)
439 // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
441 logger("Item with uri ".$arr['uri']." already existed for user ".$uid." with id ".$r[0]["id"]." target network ".$r[0]["network"]." - new network: ".$arr['network']);
446 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
447 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
448 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
449 // $arr['body'] = strip_tags($arr['body']);
451 item_add_language_opt($arr);
455 elseif ((trim($arr['guid']) == "") AND (trim($arr['plink']) != ""))
456 $arr['guid'] = uri_to_guid($arr['plink']);
457 elseif ((trim($arr['guid']) == "") AND (trim($arr['uri']) != ""))
458 $arr['guid'] = uri_to_guid($arr['uri']);
460 $parsed = parse_url($arr["author-link"]);
461 $guid_prefix = hash("crc32", $parsed["host"]);
464 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
465 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
466 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
467 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
468 $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : '');
469 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
470 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
471 $arr['owner-name'] = ((x($arr,'owner-name')) ? trim($arr['owner-name']) : '');
472 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
473 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
474 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
475 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
476 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
477 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
478 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
479 $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : '');
480 $arr['location'] = ((x($arr,'location')) ? trim($arr['location']) : '');
481 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
482 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
483 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
485 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
486 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
487 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
488 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
489 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
490 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
491 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
492 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
493 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
494 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
495 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
496 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
497 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
498 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
499 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
500 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
501 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
502 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
503 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
504 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
505 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
506 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
507 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
508 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
510 // Items cannot be stored before they happen ...
511 if ($arr['created'] > datetime_convert())
512 $arr['created'] = datetime_convert();
514 // We haven't invented time travel by now.
515 if ($arr['edited'] > datetime_convert())
516 $arr['edited'] = datetime_convert();
518 if (($arr['author-link'] == "") AND ($arr['owner-link'] == ""))
519 logger("Both author-link and owner-link are empty. Called by: ".App::callstack(), LOGGER_DEBUG);
521 if ($arr['plink'] == "") {
523 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
526 if ($arr['network'] == "") {
527 $r = q("SELECT `network` FROM `contact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
528 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
529 dbesc(normalise_link($arr['author-link'])),
534 $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
535 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
536 dbesc(normalise_link($arr['author-link']))
540 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
541 intval($arr['contact-id']),
546 $arr['network'] = $r[0]["network"];
548 // Fallback to friendica (why is it empty in some cases?)
549 if ($arr['network'] == "")
550 $arr['network'] = NETWORK_DFRN;
552 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
555 // The contact-id should be set before "item_store" was called - but there seems to be some issues
556 if ($arr["contact-id"] == 0) {
557 // First we are looking for a suitable contact that matches with the author of the post
558 // This is done only for comments (See below explanation at "gcontact-id")
559 if($arr['parent-uri'] != $arr['uri'])
560 $arr["contact-id"] = get_contact($arr['author-link'], $uid);
562 // If not present then maybe the owner was found
563 if ($arr["contact-id"] == 0)
564 $arr["contact-id"] = get_contact($arr['owner-link'], $uid);
566 // Still missing? Then use the "self" contact of the current user
567 if ($arr["contact-id"] == 0) {
568 $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid` = %d", intval($uid));
570 $arr["contact-id"] = $r[0]["id"];
572 logger("Contact-id was missing for post ".$arr["guid"]." from user id ".$uid." - now set to ".$arr["contact-id"], LOGGER_DEBUG);
575 if ($arr["gcontact-id"] == 0) {
576 // The gcontact should mostly behave like the contact. But is is supposed to be global for the system.
577 // This means that wall posts, repeated posts, etc. should have the gcontact id of the owner.
578 // On comments the author is the better choice.
579 if($arr['parent-uri'] === $arr['uri'])
580 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['owner-link'], "network" => $arr['network'],
581 "photo" => $arr['owner-avatar'], "name" => $arr['owner-name']));
583 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
584 "photo" => $arr['author-avatar'], "name" => $arr['author-name']));
587 if ($arr["author-id"] == 0)
588 $arr["author-id"] = get_contact($arr["author-link"], 0);
590 if ($arr["owner-id"] == 0)
591 $arr["owner-id"] = get_contact($arr["owner-link"], 0);
593 if ($arr['guid'] != "") {
594 // Checking if there is already an item with the same guid
595 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
596 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
597 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
600 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
605 // Check for hashtags in the body and repair or add hashtag links
606 item_body_set_hashtags($arr);
608 $arr['thr-parent'] = $arr['parent-uri'];
609 if($arr['parent-uri'] === $arr['uri']) {
612 $allow_cid = $arr['allow_cid'];
613 $allow_gid = $arr['allow_gid'];
614 $deny_cid = $arr['deny_cid'];
615 $deny_gid = $arr['deny_gid'];
616 $notify_type = 'wall-new';
620 // find the parent and snarf the item id and ACLs
621 // and anything else we need to inherit
623 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
624 dbesc($arr['parent-uri']),
630 // is the new message multi-level threaded?
631 // even though we don't support it now, preserve the info
632 // and re-attach to the conversation parent.
634 if($r[0]['uri'] != $r[0]['parent-uri']) {
635 $arr['parent-uri'] = $r[0]['parent-uri'];
636 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
637 ORDER BY `id` ASC LIMIT 1",
638 dbesc($r[0]['parent-uri']),
639 dbesc($r[0]['parent-uri']),
646 $parent_id = $r[0]['id'];
647 $parent_deleted = $r[0]['deleted'];
648 $allow_cid = $r[0]['allow_cid'];
649 $allow_gid = $r[0]['allow_gid'];
650 $deny_cid = $r[0]['deny_cid'];
651 $deny_gid = $r[0]['deny_gid'];
652 $arr['wall'] = $r[0]['wall'];
653 $notify_type = 'comment-new';
655 // if the parent is private, force privacy for the entire conversation
656 // This differs from the above settings as it subtly allows comments from
657 // email correspondents to be private even if the overall thread is not.
660 $arr['private'] = $r[0]['private'];
662 // Edge case. We host a public forum that was originally posted to privately.
663 // The original author commented, but as this is a comment, the permissions
664 // weren't fixed up so it will still show the comment as private unless we fix it here.
666 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
670 // If its a post from myself then tag the thread as "mention"
671 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
672 $u = q("SELECT `nickname` FROM `user` WHERE `uid` = %d", intval($arr['uid']));
675 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
676 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
677 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
678 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
679 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
684 // Allow one to see reply tweets from status.net even when
685 // we don't have or can't see the original post.
688 logger('item_store: $force_parent=true, reply converted to top-level post.');
690 $arr['parent-uri'] = $arr['uri'];
694 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
702 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` IN ('%s', '%s') AND `uid` = %d LIMIT 1",
704 dbesc($arr['network']),
708 if (dbm::is_result($r)) {
709 logger('duplicated item with the same uri found. '.print_r($arr,true));
713 // On Friendica and Diaspora the GUID is unique
714 if (in_array($arr['network'], array(NETWORK_DFRN, NETWORK_DIASPORA))) {
715 $r = q("SELECT `id` FROM `item` WHERE `guid` = '%s' AND `uid` = %d LIMIT 1",
719 if (dbm::is_result($r)) {
720 logger('duplicated item with the same guid found. '.print_r($arr,true));
724 // Check for an existing post with the same content. There seems to be a problem with OStatus.
725 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
727 dbesc($arr['network']),
728 dbesc($arr['created']),
729 intval($arr['contact-id']),
732 if (dbm::is_result($r)) {
733 logger('duplicated item with the same body found. '.print_r($arr,true));
738 // Is this item available in the global items (with uid=0)?
739 if ($arr["uid"] == 0) {
740 $arr["global"] = true;
742 q("UPDATE `item` SET `global` = 1 WHERE `uri` = '%s'", dbesc($arr["uri"]));
744 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `uri` = '%s'", dbesc($arr["uri"]));
746 $arr["global"] = (count($isglobal) > 0);
750 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
753 $private = $arr['private'];
755 $arr["allow_cid"] = $allow_cid;
756 $arr["allow_gid"] = $allow_gid;
757 $arr["deny_cid"] = $deny_cid;
758 $arr["deny_gid"] = $deny_gid;
759 $arr["private"] = $private;
760 $arr["deleted"] = $parent_deleted;
762 // Fill the cache field
763 put_item_in_cache($arr);
766 call_hooks('post_local',$arr);
768 call_hooks('post_remote',$arr);
770 if(x($arr,'cancel')) {
771 logger('item_store: post cancelled by plugin.');
775 // Store the unescaped version
780 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
783 q("START TRANSACTION;");
785 $r = dbq("INSERT INTO `item` (`"
786 . implode("`, `", array_keys($arr))
788 . implode("', '", array_values($arr))
794 // find the item that we just created
795 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' ORDER BY `id` ASC",
798 dbesc($arr['network'])
802 // There are duplicates. Keep the oldest one, delete the others
803 logger('item_store: duplicated post occurred. Removing newer duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
804 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' AND `id` > %d",
807 dbesc($arr['network']),
812 } elseif(count($r)) {
814 $current_post = $r[0]['id'];
815 logger('item_store: created item ' . $current_post);
817 item_set_last_item($arr);
819 logger('item_store: could not locate created item');
824 if(!$parent_id || ($arr['parent-uri'] === $arr['uri']))
825 $parent_id = $current_post;
828 $r = q("UPDATE `item` SET `parent` = %d WHERE `id` = %d",
830 intval($current_post)
833 $arr['id'] = $current_post;
834 $arr['parent'] = $parent_id;
836 // update the commented timestamp on the parent
837 // Only update "commented" if it is really a comment
838 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
839 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
840 dbesc(datetime_convert()),
841 dbesc(datetime_convert()),
845 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
846 dbesc(datetime_convert()),
852 // Friendica servers lower than 3.4.3-2 had double encoded the signature ...
853 // We can check for this condition when we decode and encode the stuff again.
854 if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) {
855 $dsprsig->signature = base64_decode($dsprsig->signature);
856 logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG);
859 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
860 intval($current_post),
861 dbesc($dsprsig->signed_text),
862 dbesc($dsprsig->signature),
863 dbesc($dsprsig->signer)
869 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
872 if($arr['last-child']) {
873 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
876 intval($current_post)
880 $deleted = tag_deliver($arr['uid'],$current_post);
882 // current post can be deleted if is for a community page and no mention are
884 if (!$deleted AND !$dontcache) {
886 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
887 if (count($r) == 1) {
889 call_hooks('post_local_end', $r[0]);
891 call_hooks('post_remote_end', $r[0]);
893 logger('item_store: new item not found in DB, id ' . $current_post);
896 create_tags_from_item($current_post);
897 create_files_from_item($current_post);
901 // Only check for notifications on start posts
902 if ($arr['parent-uri'] === $arr['uri']) {
903 add_thread($current_post);
905 update_thread($parent_id);
906 add_shadow_entry($arr);
909 check_item_notification($current_post, $uid);
912 proc_run(PRIORITY_HIGH, "include/notifier.php", $notify_type, $current_post);
914 return $current_post;
918 * @brief Set "success_update" and "last-item" to the date of the last time we heard from this contact
920 * This can be used to filter for inactive contacts.
921 * Only do this for public postings to avoid privacy problems, since poco data is public.
922 * Don't set this value if it isn't from the owner (could be an author that we don't know)
924 * @param array $arr Contains the just posted item record
926 function item_set_last_item($arr) {
928 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
930 // Is it a forum? Then we don't care about the rules from above
931 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
932 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
933 intval($arr['contact-id']));
939 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
940 dbesc($arr['received']),
941 dbesc($arr['received']),
942 intval($arr['contact-id'])
945 // Now do the same for the system wide contacts with uid=0
946 if (!$arr['private']) {
947 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
948 dbesc($arr['received']),
949 dbesc($arr['received']),
950 intval($arr['owner-id'])
953 if ($arr['owner-id'] != $arr['author-id'])
954 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
955 dbesc($arr['received']),
956 dbesc($arr['received']),
957 intval($arr['author-id'])
962 function item_body_set_hashtags(&$item) {
964 $tags = get_tags($item["body"]);
970 // This sorting is important when there are hashtags that are part of other hashtags
971 // Otherwise there could be problems with hashtags like #test and #test2
976 $URLSearchString = "^\[\]";
978 // All hashtags should point to the home server
979 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
980 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
982 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
983 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
985 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
986 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
988 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
991 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
993 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
996 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
998 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
1001 // Repair recursive urls
1002 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
1003 "#$2", $item["body"]);
1006 foreach($tags as $tag) {
1007 if(strpos($tag,'#') !== 0)
1010 if(strpos($tag,'[url='))
1013 $basetag = str_replace('_',' ',substr($tag,1));
1015 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1017 $item["body"] = str_replace($tag, $newtag, $item["body"]);
1019 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
1020 if(strlen($item["tag"]))
1021 $item["tag"] = ','.$item["tag"];
1022 $item["tag"] = $newtag.$item["tag"];
1026 // Convert back the masked hashtags
1027 $item["body"] = str_replace("#", "#", $item["body"]);
1030 function get_item_guid($id) {
1031 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1033 return($r[0]["guid"]);
1038 function get_item_id($guid, $uid = 0) {
1044 $uid == local_user();
1046 // Does the given user have this item?
1048 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1049 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1050 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1053 $nick = $r[0]["nickname"];
1057 // Or is it anywhere on the server?
1059 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1060 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1061 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1062 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1063 AND `item`.`private` = 0 AND `item`.`wall` = 1
1064 AND `item`.`guid` = '%s'", dbesc($guid));
1067 $nick = $r[0]["nickname"];
1070 return(array("nick" => $nick, "id" => $id));
1074 function get_item_contact($item,$contacts) {
1075 if(! count($contacts) || (! is_array($item)))
1077 foreach($contacts as $contact) {
1078 if($contact['id'] == $item['contact-id']) {
1080 break; // NOTREACHED
1087 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1089 * @param int $item_id
1090 * @return bool true if item was deleted, else false
1092 function tag_deliver($uid,$item_id) {
1100 $u = q("select * from user where uid = %d limit 1",
1106 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1107 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1110 $i = q("select * from item where id = %d and uid = %d limit 1",
1119 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1121 // Diaspora uses their own hardwired link URL in @-tags
1122 // instead of the one we supply with webfinger
1124 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1126 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1128 foreach($matches as $mtch) {
1129 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1131 logger('tag_deliver: mention found: ' . $mtch[2]);
1137 if ( ($community_page || $prvgroup) &&
1138 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1139 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1141 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1142 q("DELETE FROM item WHERE id = %d and uid = %d",
1151 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1153 call_hooks('tagged', $arr);
1155 if((! $community_page) && (! $prvgroup))
1159 // tgroup delivery - setup a second delivery chain
1160 // prevent delivery looping - only proceed
1161 // if the message originated elsewhere and is a top-level post
1163 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1166 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1169 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1170 intval($u[0]['uid'])
1175 // also reset all the privacy bits to the forum default permissions
1177 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1179 $forum_mode = (($prvgroup) ? 2 : 1);
1181 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1182 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1183 intval($forum_mode),
1184 dbesc($c[0]['name']),
1185 dbesc($c[0]['url']),
1186 dbesc($c[0]['thumb']),
1188 dbesc($u[0]['allow_cid']),
1189 dbesc($u[0]['allow_gid']),
1190 dbesc($u[0]['deny_cid']),
1191 dbesc($u[0]['deny_gid']),
1194 update_thread($item_id);
1196 proc_run(PRIORITY_HIGH,'include/notifier.php', 'tgroup', $item_id);
1202 function tgroup_check($uid,$item) {
1208 // check that the message originated elsewhere and is a top-level post
1210 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1214 $u = q("select * from user where uid = %d limit 1",
1220 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1221 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1224 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1226 // Diaspora uses their own hardwired link URL in @-tags
1227 // instead of the one we supply with webfinger
1229 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1231 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1233 foreach($matches as $mtch) {
1234 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1236 logger('tgroup_check: mention found: ' . $mtch[2]);
1244 if((! $community_page) && (! $prvgroup))
1251 This function returns true if $update has an edited timestamp newer
1252 than $existing, i.e. $update contains new data which should override
1253 what's already there. If there is no timestamp yet, the update is
1254 assumed to be newer. If the update has no timestamp, the existing
1255 item is assumed to be up-to-date. If the timestamps are equal it
1256 assumes the update has been seen before and should be ignored.
1258 function edited_timestamp_is_newer($existing, $update) {
1259 if (!x($existing,'edited') || !$existing['edited']) {
1262 if (!x($update,'edited') || !$update['edited']) {
1265 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1266 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1267 return (strcmp($existing_edited, $update_edited) < 0);
1272 * consume_feed - process atom feed and update anything/everything we might need to update
1274 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1276 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1277 * It is this person's stuff that is going to be updated.
1278 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1279 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1280 * have a contact record.
1281 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1282 * might not) try and subscribe to it.
1283 * $datedir sorts in reverse order
1284 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1285 * imported prior to its children being seen in the stream unless we are certain
1286 * of how the feed is arranged/ordered.
1287 * With $pass = 1, we only pull parent items out of the stream.
1288 * With $pass = 2, we only pull children (comments/likes).
1290 * So running this twice, first with pass 1 and then with pass 2 will do the right
1291 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1292 * model where comments can have sub-threads. That would require some massive sorting
1293 * to get all the feed items into a mostly linear ordering, and might still require
1297 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1298 if ($contact['network'] === NETWORK_OSTATUS) {
1300 // Test - remove before flight
1301 //$tempfile = tempnam(get_temppath(), "ostatus2");
1302 //file_put_contents($tempfile, $xml);
1303 logger("Consume OStatus messages ", LOGGER_DEBUG);
1304 ostatus::import($xml,$importer,$contact, $hub);
1309 if ($contact['network'] === NETWORK_FEED) {
1311 logger("Consume feeds", LOGGER_DEBUG);
1312 feed_import($xml,$importer,$contact, $hub);
1317 if ($contact['network'] === NETWORK_DFRN) {
1318 logger("Consume DFRN messages", LOGGER_DEBUG);
1320 $r = q("SELECT `contact`.*, `contact`.`uid` AS `importer_uid`,
1321 `contact`.`pubkey` AS `cpubkey`,
1322 `contact`.`prvkey` AS `cprvkey`,
1323 `contact`.`thumb` AS `thumb`,
1324 `contact`.`url` as `url`,
1325 `contact`.`name` as `senderName`,
1328 LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid`
1329 WHERE `contact`.`id` = %d AND `user`.`uid` = %d",
1330 dbesc($contact["id"]), dbesc($importer["uid"])
1333 logger("Now import the DFRN feed");
1334 dfrn::import($xml,$r[0], true);
1340 function item_is_remote_self($contact, &$datarray) {
1343 if (!$contact['remote_self'])
1346 // Prevent the forwarding of posts that are forwarded
1347 if ($datarray["extid"] == NETWORK_DFRN)
1350 // Prevent to forward already forwarded posts
1351 if ($datarray["app"] == $a->get_hostname())
1354 // Only forward posts
1355 if ($datarray["verb"] != ACTIVITY_POST)
1358 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
1361 $datarray2 = $datarray;
1362 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
1363 if ($contact['remote_self'] == 2) {
1364 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
1365 intval($contact['uid']));
1367 $datarray['contact-id'] = $r[0]["id"];
1369 $datarray['owner-name'] = $r[0]["name"];
1370 $datarray['owner-link'] = $r[0]["url"];
1371 $datarray['owner-avatar'] = $r[0]["thumb"];
1373 $datarray['author-name'] = $datarray['owner-name'];
1374 $datarray['author-link'] = $datarray['owner-link'];
1375 $datarray['author-avatar'] = $datarray['owner-avatar'];
1378 if ($contact['network'] != NETWORK_FEED) {
1379 $datarray["guid"] = get_guid(32);
1380 unset($datarray["plink"]);
1381 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
1382 $datarray["parent-uri"] = $datarray["uri"];
1383 $datarray["extid"] = $contact['network'];
1384 $urlpart = parse_url($datarray2['author-link']);
1385 $datarray["app"] = $urlpart["host"];
1387 $datarray['private'] = 0;
1390 if ($contact['network'] != NETWORK_FEED) {
1391 // Store the original post
1392 $r = item_store($datarray2, false, false);
1393 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
1395 $datarray["app"] = "Feed";
1400 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
1401 $url = notags(trim($datarray['author-link']));
1402 $name = notags(trim($datarray['author-name']));
1403 $photo = notags(trim($datarray['author-avatar']));
1405 if (is_object($item)) {
1406 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
1407 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
1408 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
1412 if(is_array($contact)) {
1413 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
1414 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
1415 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
1416 intval(CONTACT_IS_FRIEND),
1417 intval($contact['id']),
1418 intval($importer['uid'])
1421 // send email notification to owner?
1424 // create contact record
1426 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
1427 `blocked`, `readonly`, `pending`, `writable`)
1428 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
1429 intval($importer['uid']),
1430 dbesc(datetime_convert()),
1432 dbesc(normalise_link($url)),
1436 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
1437 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
1439 $r = q("SELECT `id`, `network` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
1440 intval($importer['uid']),
1444 $contact_record = $r[0];
1445 update_contact_avatar($photo, $importer["uid"], $contact_record["id"], true);
1449 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
1450 intval($importer['uid'])
1453 if(count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
1455 // create notification
1456 $hash = random_string();
1458 if(is_array($contact_record)) {
1459 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
1460 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
1461 intval($importer['uid']),
1462 intval($contact_record['id']),
1464 dbesc(datetime_convert())
1468 $def_gid = get_default_group($importer['uid'], $contact_record["network"]);
1470 if(intval($def_gid))
1471 group_add_member($importer['uid'],'',$contact_record['id'],$def_gid);
1473 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
1474 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
1477 'type' => NOTIFY_INTRO,
1478 'notify_flags' => $r[0]['notify-flags'],
1479 'language' => $r[0]['language'],
1480 'to_name' => $r[0]['username'],
1481 'to_email' => $r[0]['email'],
1482 'uid' => $r[0]['uid'],
1483 'link' => $a->get_baseurl() . '/notifications/intro',
1484 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
1485 'source_link' => $contact_record['url'],
1486 'source_photo' => $contact_record['photo'],
1487 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
1492 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
1493 $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
1494 intval($importer['uid']),
1502 function lose_follower($importer,$contact,$datarray = array(),$item = "") {
1504 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
1505 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
1506 intval(CONTACT_IS_SHARING),
1507 intval($contact['id'])
1511 contact_remove($contact['id']);
1515 function lose_sharer($importer,$contact,$datarray = array(),$item = "") {
1517 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
1518 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
1519 intval(CONTACT_IS_FOLLOWER),
1520 intval($contact['id'])
1524 contact_remove($contact['id']);
1528 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
1532 if(is_array($importer)) {
1533 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
1534 intval($importer['uid'])
1538 // Diaspora has different message-ids in feeds than they do
1539 // through the direct Diaspora protocol. If we try and use
1540 // the feed, we'll get duplicates. So don't.
1542 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
1545 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
1547 // Use a single verify token, even if multiple hubs
1549 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
1551 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
1553 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
1555 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
1556 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
1557 dbesc($verify_token),
1558 intval($contact['id'])
1562 post_url($url,$params);
1564 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
1570 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
1572 if(get_config('system','disable_embedded'))
1577 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
1578 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
1583 $img_start = strpos($orig_body, '[img');
1584 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
1585 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
1586 while( ($img_st_close !== false) && ($img_len !== false) ) {
1588 $img_st_close++; // make it point to AFTER the closing bracket
1589 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
1591 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
1594 if(stristr($image , $site . '/photo/')) {
1595 // Only embed locally hosted photos
1597 $i = basename($image);
1598 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
1599 $x = strpos($i,'-');
1602 $res = substr($i,$x+1);
1603 $i = substr($i,0,$x);
1604 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
1611 // Check to see if we should replace this photo link with an embedded image
1612 // 1. No need to do so if the photo is public
1613 // 2. If there's a contact-id provided, see if they're in the access list
1614 // for the photo. If so, embed it.
1615 // 3. Otherwise, if we have an item, see if the item permissions match the photo
1616 // permissions, regardless of order but first check to see if they're an exact
1617 // match to save some processing overhead.
1619 if(has_permissions($r[0])) {
1621 $recips = enumerate_permissions($r[0]);
1622 if(in_array($cid, $recips)) {
1627 if(compare_permissions($item,$r[0]))
1632 $data = $r[0]['data'];
1633 $type = $r[0]['type'];
1635 // If a custom width and height were specified, apply before embedding
1636 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
1637 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
1639 $width = intval($match[1]);
1640 $height = intval($match[2]);
1642 $ph = new Photo($data, $type);
1643 if($ph->is_valid()) {
1644 $ph->scaleImage(max($width, $height));
1645 $data = $ph->imageString();
1646 $type = $ph->getType();
1650 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
1651 $image = 'data:' . $type . ';base64,' . base64_encode($data);
1652 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
1658 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
1659 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
1660 if($orig_body === false)
1663 $img_start = strpos($orig_body, '[img');
1664 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
1665 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
1668 $new_body = $new_body . $orig_body;
1673 function has_permissions($obj) {
1674 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
1679 function compare_permissions($obj1,$obj2) {
1680 // first part is easy. Check that these are exactly the same.
1681 if(($obj1['allow_cid'] == $obj2['allow_cid'])
1682 && ($obj1['allow_gid'] == $obj2['allow_gid'])
1683 && ($obj1['deny_cid'] == $obj2['deny_cid'])
1684 && ($obj1['deny_gid'] == $obj2['deny_gid']))
1687 // This is harder. Parse all the permissions and compare the resulting set.
1689 $recipients1 = enumerate_permissions($obj1);
1690 $recipients2 = enumerate_permissions($obj2);
1693 if($recipients1 == $recipients2)
1698 // returns an array of contact-ids that are allowed to see this object
1700 function enumerate_permissions($obj) {
1701 $allow_people = expand_acl($obj['allow_cid']);
1702 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
1703 $deny_people = expand_acl($obj['deny_cid']);
1704 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
1705 $recipients = array_unique(array_merge($allow_people,$allow_groups));
1706 $deny = array_unique(array_merge($deny_people,$deny_groups));
1707 $recipients = array_diff($recipients,$deny);
1711 function item_getfeedtags($item) {
1714 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
1716 for($x = 0; $x < $cnt; $x ++) {
1718 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
1722 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
1724 for($x = 0; $x < $cnt; $x ++) {
1726 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
1732 function item_expire($uid, $days, $network = "", $force = false) {
1734 if((! $uid) || ($days < 1))
1737 // $expire_network_only = save your own wall posts
1738 // and just expire conversations started by others
1740 $expire_network_only = get_pconfig($uid,'expire','network_only');
1741 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
1743 if ($network != "") {
1744 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
1745 // There is an index "uid_network_received" but not "uid_network_created"
1746 // This avoids the creation of another index just for one purpose.
1747 // And it doesn't really matter wether to look at "received" or "created"
1748 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
1750 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
1752 $r = q("SELECT `file`, `resource-id`, `starred`, `type`, `id` FROM `item`
1753 WHERE `uid` = %d $range
1764 $expire_items = get_pconfig($uid, 'expire','items');
1765 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
1767 // Forcing expiring of items - but not notes and marked items
1769 $expire_items = true;
1771 $expire_notes = get_pconfig($uid, 'expire','notes');
1772 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
1774 $expire_starred = get_pconfig($uid, 'expire','starred');
1775 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
1777 $expire_photos = get_pconfig($uid, 'expire','photos');
1778 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
1780 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
1782 foreach($r as $item) {
1784 // don't expire filed items
1786 if(strpos($item['file'],'[') !== false)
1789 // Only expire posts, not photos and photo comments
1791 if($expire_photos==0 && strlen($item['resource-id']))
1793 if($expire_starred==0 && intval($item['starred']))
1795 if($expire_notes==0 && $item['type']=='note')
1797 if($expire_items==0 && $item['type']!='note')
1800 drop_item($item['id'],false);
1803 proc_run(PRIORITY_HIGH,"include/notifier.php", "expire", $uid);
1808 function drop_items($items) {
1811 if(! local_user() && ! remote_user())
1815 foreach($items as $item) {
1816 $owner = drop_item($item,false);
1817 if($owner && ! $uid)
1822 // multiple threads may have been deleted, send an expire notification
1825 proc_run(PRIORITY_HIGH,"include/notifier.php", "expire", $uid);
1829 function drop_item($id,$interactive = true) {
1833 // locate item to be deleted
1835 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
1842 notice( t('Item not found.') . EOL);
1843 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
1848 $owner = $item['uid'];
1852 // check if logged in user is either the author or owner of this item
1854 if(is_array($_SESSION['remote'])) {
1855 foreach($_SESSION['remote'] as $visitor) {
1856 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
1857 $cid = $visitor['cid'];
1864 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
1866 // Check if we should do HTML-based delete confirmation
1867 if($_REQUEST['confirm']) {
1868 // <form> can't take arguments in its "action" parameter
1869 // so add any arguments as hidden inputs
1870 $query = explode_querystring($a->query_string);
1872 foreach($query['args'] as $arg) {
1873 if(strpos($arg, 'confirm=') === false) {
1874 $arg_parts = explode('=', $arg);
1875 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
1879 return replace_macros(get_markup_template('confirm.tpl'), array(
1881 '$message' => t('Do you really want to delete this item?'),
1882 '$extra_inputs' => $inputs,
1883 '$confirm' => t('Yes'),
1884 '$confirm_url' => $query['base'],
1885 '$confirm_name' => 'confirmed',
1886 '$cancel' => t('Cancel'),
1889 // Now check how the user responded to the confirmation query
1890 if($_REQUEST['canceled']) {
1891 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
1894 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
1897 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
1898 dbesc(datetime_convert()),
1899 dbesc(datetime_convert()),
1902 create_tags_from_item($item['id']);
1903 create_files_from_item($item['id']);
1904 delete_thread($item['id'], $item['parent-uri']);
1906 // clean up categories and tags so they don't end up as orphans
1909 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
1911 foreach($matches as $mtch) {
1912 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
1918 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
1920 foreach($matches as $mtch) {
1921 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
1925 // If item is a link to a photo resource, nuke all the associated photos
1926 // (visitors will not have photo resources)
1927 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
1928 // generate a resource-id and therefore aren't intimately linked to the item.
1930 if(strlen($item['resource-id'])) {
1931 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
1932 dbesc($item['resource-id']),
1933 intval($item['uid'])
1935 // ignore the result
1938 // If item is a link to an event, nuke the event record.
1940 if(intval($item['event-id'])) {
1941 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
1942 intval($item['event-id']),
1943 intval($item['uid'])
1945 // ignore the result
1948 // If item has attachments, drop them
1950 foreach(explode(",",$item['attach']) as $attach){
1951 preg_match("|attach/(\d+)|", $attach, $matches);
1952 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
1953 intval($matches[1]),
1956 // ignore the result
1960 // clean up item_id and sign meta-data tables
1963 // Old code - caused very long queries and warning entries in the mysql logfiles:
1965 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
1966 intval($item['id']),
1967 intval($item['uid'])
1970 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
1971 intval($item['id']),
1972 intval($item['uid'])
1976 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
1978 // Creating list of parents
1979 $r = q("select id from item where parent = %d and uid = %d",
1980 intval($item['id']),
1981 intval($item['uid'])
1986 foreach ($r AS $row) {
1987 if ($parentid != "")
1990 $parentid .= $row["id"];
1994 if ($parentid != "") {
1995 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
1997 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
2000 // If it's the parent of a comment thread, kill all the kids
2002 if($item['uri'] == $item['parent-uri']) {
2003 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
2004 WHERE `parent-uri` = '%s' AND `uid` = %d ",
2005 dbesc(datetime_convert()),
2006 dbesc(datetime_convert()),
2007 dbesc($item['parent-uri']),
2008 intval($item['uid'])
2010 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
2011 create_files_from_itemuri($item['parent-uri'], $item['uid']);
2012 delete_thread_uri($item['parent-uri'], $item['uid']);
2013 // ignore the result
2016 // ensure that last-child is set in case the comment that had it just got wiped.
2017 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
2018 dbesc(datetime_convert()),
2019 dbesc($item['parent-uri']),
2020 intval($item['uid'])
2022 // who is the last child now?
2023 $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d ORDER BY `edited` DESC LIMIT 1",
2024 dbesc($item['parent-uri']),
2025 intval($item['uid'])
2028 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2034 $drop_id = intval($item['id']);
2036 // send the notification upstream/downstream as the case may be
2038 proc_run(PRIORITY_HIGH,"include/notifier.php", "drop", $drop_id);
2042 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
2048 notice( t('Permission denied.') . EOL);
2049 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
2056 function first_post_date($uid,$wall = false) {
2057 $r = q("select id, created from item
2058 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
2060 order by created asc limit 1",
2062 intval($wall ? 1 : 0)
2065 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
2066 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
2071 /* modified posted_dates() {below} to arrange the list in years */
2072 function list_post_dates($uid, $wall) {
2073 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
2075 $dthen = first_post_date($uid, $wall);
2079 // Set the start and end date to the beginning of the month
2080 $dnow = substr($dnow,0,8).'01';
2081 $dthen = substr($dthen,0,8).'01';
2085 // Starting with the current month, get the first and last days of every
2086 // month down to and including the month of the first post
2087 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
2088 $dyear = intval(substr($dnow,0,4));
2089 $dstart = substr($dnow,0,8) . '01';
2090 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
2091 $start_month = datetime_convert('','',$dstart,'Y-m-d');
2092 $end_month = datetime_convert('','',$dend,'Y-m-d');
2093 $str = day_translate(datetime_convert('','',$dnow,'F'));
2095 $ret[$dyear] = array();
2096 $ret[$dyear][] = array($str,$end_month,$start_month);
2097 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
2102 function posted_dates($uid,$wall) {
2103 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
2105 $dthen = first_post_date($uid,$wall);
2109 // Set the start and end date to the beginning of the month
2110 $dnow = substr($dnow,0,8).'01';
2111 $dthen = substr($dthen,0,8).'01';
2114 // Starting with the current month, get the first and last days of every
2115 // month down to and including the month of the first post
2116 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
2117 $dstart = substr($dnow,0,8) . '01';
2118 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
2119 $start_month = datetime_convert('','',$dstart,'Y-m-d');
2120 $end_month = datetime_convert('','',$dend,'Y-m-d');
2121 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
2122 $ret[] = array($str,$end_month,$start_month);
2123 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
2129 function posted_date_widget($url,$uid,$wall) {
2132 if(! feature_enabled($uid,'archives'))
2135 // For former Facebook folks that left because of "timeline"
2137 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
2140 $visible_years = get_pconfig($uid,'system','archive_visible_years');
2141 if(! $visible_years)
2144 $ret = list_post_dates($uid,$wall);
2149 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
2150 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
2152 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
2153 '$title' => t('Archives'),
2154 '$size' => $visible_years,
2155 '$cutoff_year' => $cutoff_year,
2156 '$cutoff' => $cutoff,
2159 '$showmore' => t('show more')