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 $perfdb = $a->performance["database"];
374 $perfdbw = $a->performance["database_write"];
375 $perfnet = $a->performance["network"];
376 $perffile = $a->performance["file"];
378 logger("Performance: Start", LOGGER_DEBUG);
380 // If it is a posting where users should get notifications, then define it as wall posting
383 $arr['type'] = 'wall';
385 $arr['last-child'] = 1;
386 $arr['network'] = NETWORK_DFRN;
389 // If a Diaspora signature structure was passed in, pull it out of the
390 // item array and set it aside for later storage.
393 if(x($arr,'dsprsig')) {
394 $dsprsig = json_decode(base64_decode($arr['dsprsig']));
395 unset($arr['dsprsig']);
398 // Converting the plink
399 if ($arr['network'] == NETWORK_OSTATUS) {
400 if (isset($arr['plink']))
401 $arr['plink'] = ostatus::convert_href($arr['plink']);
402 elseif (isset($arr['uri']))
403 $arr['plink'] = ostatus::convert_href($arr['uri']);
406 if(x($arr, 'gravity'))
407 $arr['gravity'] = intval($arr['gravity']);
408 elseif($arr['parent-uri'] === $arr['uri'])
410 elseif(activity_match($arr['verb'],ACTIVITY_POST))
413 $arr['gravity'] = 6; // extensible catchall
416 $arr['type'] = 'remote';
420 /* check for create date and expire time */
421 $uid = intval($arr['uid']);
422 $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid));
424 $expire_interval = $r[0]['expire'];
425 if ($expire_interval>0) {
426 $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC'));
427 $created_date = new DateTime($arr['created'], new DateTimeZone('UTC'));
428 if ($created_date < $expire_date) {
429 logger('item-store: item created ('.$arr['created'].') before expiration time ('.$expire_date->format(DateTime::W3C).'). ignored. ' . print_r($arr,true), LOGGER_DEBUG);
435 // Do we already have this item?
436 // We have to check several networks since Friendica posts could be repeated via OStatus (maybe Diasporsa as well)
437 if (in_array(trim($arr['network']), array(NETWORK_DIASPORA, NETWORK_DFRN, NETWORK_OSTATUS, ""))) {
438 $r = q("SELECT `id`, `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` IN ('%s', '%s', '%s') LIMIT 1",
439 dbesc(trim($arr['uri'])),
441 dbesc(NETWORK_DIASPORA),
443 dbesc(NETWORK_OSTATUS)
446 // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
448 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']);
453 // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin.
454 // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<"
455 //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
456 // $arr['body'] = strip_tags($arr['body']);
458 item_add_language_opt($arr);
462 elseif ((trim($arr['guid']) == "") AND (trim($arr['plink']) != ""))
463 $arr['guid'] = uri_to_guid($arr['plink']);
464 elseif ((trim($arr['guid']) == "") AND (trim($arr['uri']) != ""))
465 $arr['guid'] = uri_to_guid($arr['uri']);
467 $parsed = parse_url($arr["author-link"]);
468 $guid_prefix = hash("crc32", $parsed["host"]);
471 $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0);
472 $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix));
473 $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']);
474 $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : '');
475 $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : '');
476 $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : '');
477 $arr['author-avatar'] = ((x($arr,'author-avatar')) ? notags(trim($arr['author-avatar'])) : '');
478 $arr['owner-name'] = ((x($arr,'owner-name')) ? trim($arr['owner-name']) : '');
479 $arr['owner-link'] = ((x($arr,'owner-link')) ? notags(trim($arr['owner-link'])) : '');
480 $arr['owner-avatar'] = ((x($arr,'owner-avatar')) ? notags(trim($arr['owner-avatar'])) : '');
481 $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
482 $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
483 $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
484 $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
485 $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
486 $arr['title'] = ((x($arr,'title')) ? trim($arr['title']) : '');
487 $arr['location'] = ((x($arr,'location')) ? trim($arr['location']) : '');
488 $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
489 $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 );
490 $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 );
492 $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : '');
493 $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : '');
494 $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : '');
495 $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : '');
496 $arr['target-type'] = ((x($arr,'target-type')) ? notags(trim($arr['target-type'])) : '');
497 $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
498 $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
499 $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
500 $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
501 $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
502 $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
503 $arr['private'] = ((x($arr,'private')) ? intval($arr['private']) : 0 );
504 $arr['bookmark'] = ((x($arr,'bookmark')) ? intval($arr['bookmark']) : 0 );
505 $arr['body'] = ((x($arr,'body')) ? trim($arr['body']) : '');
506 $arr['tag'] = ((x($arr,'tag')) ? notags(trim($arr['tag'])) : '');
507 $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
508 $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
509 $arr['origin'] = ((x($arr,'origin')) ? intval($arr['origin']) : 0 );
510 $arr['network'] = ((x($arr,'network')) ? trim($arr['network']) : '');
511 $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
512 $arr['resource-id'] = ((x($arr,'resource-id')) ? trim($arr['resource-id']) : '');
513 $arr['event-id'] = ((x($arr,'event-id')) ? intval($arr['event-id']) : 0 );
514 $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : '');
515 $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : '');
517 // Items cannot be stored before they happen ...
518 if ($arr['created'] > datetime_convert())
519 $arr['created'] = datetime_convert();
521 // We haven't invented time travel by now.
522 if ($arr['edited'] > datetime_convert())
523 $arr['edited'] = datetime_convert();
525 if (($arr['author-link'] == "") AND ($arr['owner-link'] == ""))
526 logger("Both author-link and owner-link are empty. Called by: ".App::callstack(), LOGGER_DEBUG);
528 if ($arr['plink'] == "") {
530 $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']);
533 if ($arr['network'] == "") {
534 $r = q("SELECT `network` FROM `contact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' AND `uid` = %d LIMIT 1",
535 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
536 dbesc(normalise_link($arr['author-link'])),
541 $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1",
542 dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS),
543 dbesc(normalise_link($arr['author-link']))
547 $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
548 intval($arr['contact-id']),
553 $arr['network'] = $r[0]["network"];
555 // Fallback to friendica (why is it empty in some cases?)
556 if ($arr['network'] == "")
557 $arr['network'] = NETWORK_DFRN;
559 logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG);
562 // The contact-id should be set before "item_store" was called - but there seems to be some issues
563 if ($arr["contact-id"] == 0) {
564 // First we are looking for a suitable contact that matches with the author of the post
565 // This is done only for comments (See below explanation at "gcontact-id")
566 if($arr['parent-uri'] != $arr['uri'])
567 $arr["contact-id"] = get_contact($arr['author-link'], $uid);
569 // If not present then maybe the owner was found
570 if ($arr["contact-id"] == 0)
571 $arr["contact-id"] = get_contact($arr['owner-link'], $uid);
573 // Still missing? Then use the "self" contact of the current user
574 if ($arr["contact-id"] == 0) {
575 $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid` = %d", intval($uid));
577 $arr["contact-id"] = $r[0]["id"];
579 logger("Contact-id was missing for post ".$arr["guid"]." from user id ".$uid." - now set to ".$arr["contact-id"], LOGGER_DEBUG);
582 if ($arr["gcontact-id"] == 0) {
583 // The gcontact should mostly behave like the contact. But is is supposed to be global for the system.
584 // This means that wall posts, repeated posts, etc. should have the gcontact id of the owner.
585 // On comments the author is the better choice.
586 if($arr['parent-uri'] === $arr['uri'])
587 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['owner-link'], "network" => $arr['network'],
588 "photo" => $arr['owner-avatar'], "name" => $arr['owner-name']));
590 $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'],
591 "photo" => $arr['author-avatar'], "name" => $arr['author-name']));
594 if ($arr["author-id"] == 0)
595 $arr["author-id"] = get_contact($arr["author-link"], 0);
597 if ($arr["owner-id"] == 0)
598 $arr["owner-id"] = get_contact($arr["owner-link"], 0);
600 if ($arr['guid'] != "") {
601 // Checking if there is already an item with the same guid
602 logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG);
603 $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1",
604 dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid']));
607 logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG);
612 // Check for hashtags in the body and repair or add hashtag links
613 item_body_set_hashtags($arr);
615 $arr['thr-parent'] = $arr['parent-uri'];
616 if($arr['parent-uri'] === $arr['uri']) {
619 $allow_cid = $arr['allow_cid'];
620 $allow_gid = $arr['allow_gid'];
621 $deny_cid = $arr['deny_cid'];
622 $deny_gid = $arr['deny_gid'];
623 $notify_type = 'wall-new';
627 // find the parent and snarf the item id and ACLs
628 // and anything else we need to inherit
630 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d ORDER BY `id` ASC LIMIT 1",
631 dbesc($arr['parent-uri']),
637 // is the new message multi-level threaded?
638 // even though we don't support it now, preserve the info
639 // and re-attach to the conversation parent.
641 if($r[0]['uri'] != $r[0]['parent-uri']) {
642 $arr['parent-uri'] = $r[0]['parent-uri'];
643 $z = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `parent-uri` = '%s' AND `uid` = %d
644 ORDER BY `id` ASC LIMIT 1",
645 dbesc($r[0]['parent-uri']),
646 dbesc($r[0]['parent-uri']),
653 $parent_id = $r[0]['id'];
654 $parent_deleted = $r[0]['deleted'];
655 $allow_cid = $r[0]['allow_cid'];
656 $allow_gid = $r[0]['allow_gid'];
657 $deny_cid = $r[0]['deny_cid'];
658 $deny_gid = $r[0]['deny_gid'];
659 $arr['wall'] = $r[0]['wall'];
660 $notify_type = 'comment-new';
662 // if the parent is private, force privacy for the entire conversation
663 // This differs from the above settings as it subtly allows comments from
664 // email correspondents to be private even if the overall thread is not.
667 $arr['private'] = $r[0]['private'];
669 // Edge case. We host a public forum that was originally posted to privately.
670 // The original author commented, but as this is a comment, the permissions
671 // weren't fixed up so it will still show the comment as private unless we fix it here.
673 if((intval($r[0]['forum_mode']) == 1) && (! $r[0]['private']))
677 // If its a post from myself then tag the thread as "mention"
678 logger("item_store: Checking if parent ".$parent_id." has to be tagged as mention for user ".$arr['uid'], LOGGER_DEBUG);
679 $u = q("select * from user where uid = %d limit 1", intval($arr['uid']));
682 $self = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
683 logger("item_store: 'myself' is ".$self." for parent ".$parent_id." checking against ".$arr['author-link']." and ".$arr['owner-link'], LOGGER_DEBUG);
684 if ((normalise_link($arr['author-link']) == $self) OR (normalise_link($arr['owner-link']) == $self)) {
685 q("UPDATE `thread` SET `mention` = 1 WHERE `iid` = %d", intval($parent_id));
686 logger("item_store: tagged thread ".$parent_id." as mention for user ".$self, LOGGER_DEBUG);
692 // Allow one to see reply tweets from status.net even when
693 // we don't have or can't see the original post.
696 logger('item_store: $force_parent=true, reply converted to top-level post.');
698 $arr['parent-uri'] = $arr['uri'];
702 logger('item_store: item parent '.$arr['parent-uri'].' for '.$arr['uid'].' was not found - ignoring item');
710 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `network` IN ('%s', '%s') AND `uid` = %d LIMIT 1",
712 dbesc($arr['network']),
716 if($r && count($r)) {
717 logger('duplicated item with the same uri found. ' . print_r($arr,true));
721 // Check for an existing post with the same content. There seems to be a problem with OStatus.
722 $r = q("SELECT `id` FROM `item` WHERE `body` = '%s' AND `network` = '%s' AND `created` = '%s' AND `contact-id` = %d AND `uid` = %d LIMIT 1",
724 dbesc($arr['network']),
725 dbesc($arr['created']),
726 intval($arr['contact-id']),
729 if($r && count($r)) {
730 logger('duplicated item with the same body found. ' . print_r($arr,true));
734 // Is this item available in the global items (with uid=0)?
735 if ($arr["uid"] == 0) {
736 $arr["global"] = true;
738 q("UPDATE `item` SET `global` = 1 WHERE `uri` = '%s'", dbesc($arr["uri"]));
740 $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `uri` = '%s'", dbesc($arr["uri"]));
742 $arr["global"] = (count($isglobal) > 0);
745 // Fill the cache field
746 put_item_in_cache($arr);
749 call_hooks('post_local',$arr);
751 call_hooks('post_remote',$arr);
753 if(x($arr,'cancel')) {
754 logger('item_store: post cancelled by plugin.');
758 // Store the unescaped version
763 logger('item_store: ' . print_r($arr,true), LOGGER_DATA);
765 $r = dbq("INSERT INTO `item` (`"
766 . implode("`, `", array_keys($arr))
768 . implode("', '", array_values($arr))
774 // find the item that we just created
775 $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' ORDER BY `id` ASC",
778 dbesc($arr['network'])
782 // There are duplicates. Keep the oldest one, delete the others
783 logger('item_store: duplicated post occurred. Removing newer duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']);
784 q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' AND `id` > %d",
787 dbesc($arr['network']),
791 } elseif(count($r)) {
793 $current_post = $r[0]['id'];
794 logger('item_store: created item ' . $current_post);
796 // Set "success_update" and "last-item" to the date of the last time we heard from this contact
797 // This can be used to filter for inactive contacts.
798 // Only do this for public postings to avoid privacy problems, since poco data is public.
799 // Don't set this value if it isn't from the owner (could be an author that we don't know)
801 $update = (!$arr['private'] AND (($arr["author-link"] === $arr["owner-link"]) OR ($arr["parent-uri"] === $arr["uri"])));
803 // Is it a forum? Then we don't care about the rules from above
804 if (!$update AND ($arr["network"] == NETWORK_DFRN) AND ($arr["parent-uri"] === $arr["uri"])) {
805 $isforum = q("SELECT `forum` FROM `contact` WHERE `id` = %d AND `forum`",
806 intval($arr['contact-id']));
812 q("UPDATE `contact` SET `success_update` = '%s', `last-item` = '%s' WHERE `id` = %d",
813 dbesc($arr['received']),
814 dbesc($arr['received']),
815 intval($arr['contact-id'])
818 logger('item_store: could not locate created item');
822 if((! $parent_id) || ($arr['parent-uri'] === $arr['uri']))
823 $parent_id = $current_post;
825 if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
828 $private = $arr['private'];
830 // Set parent id - and also make sure to inherit the parent's ACLs.
832 $r = q("UPDATE `item` SET `parent` = %d, `allow_cid` = '%s', `allow_gid` = '%s',
833 `deny_cid` = '%s', `deny_gid` = '%s', `private` = %d, `deleted` = %d WHERE `id` = %d",
840 intval($parent_deleted),
841 intval($current_post)
844 $arr['id'] = $current_post;
845 $arr['parent'] = $parent_id;
846 $arr['allow_cid'] = $allow_cid;
847 $arr['allow_gid'] = $allow_gid;
848 $arr['deny_cid'] = $deny_cid;
849 $arr['deny_gid'] = $deny_gid;
850 $arr['private'] = $private;
851 $arr['deleted'] = $parent_deleted;
853 // update the commented timestamp on the parent
854 // Only update "commented" if it is really a comment
855 if (($arr['verb'] == ACTIVITY_POST) OR !get_config("system", "like_no_comment"))
856 q("UPDATE `item` SET `commented` = '%s', `changed` = '%s' WHERE `id` = %d",
857 dbesc(datetime_convert()),
858 dbesc(datetime_convert()),
862 q("UPDATE `item` SET `changed` = '%s' WHERE `id` = %d",
863 dbesc(datetime_convert()),
869 // Friendica servers lower than 3.4.3-2 had double encoded the signature ...
870 // We can check for this condition when we decode and encode the stuff again.
871 if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) {
872 $dsprsig->signature = base64_decode($dsprsig->signature);
873 logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG);
876 q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
877 intval($current_post),
878 dbesc($dsprsig->signed_text),
879 dbesc($dsprsig->signature),
880 dbesc($dsprsig->signer)
886 * If this is now the last-child, force all _other_ children of this parent to *not* be last-child
889 if($arr['last-child']) {
890 $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d",
893 intval($current_post)
897 $deleted = tag_deliver($arr['uid'],$current_post);
899 // current post can be deleted if is for a community page and no mention are
901 if (!$deleted AND !$dontcache) {
903 $r = q('SELECT * FROM `item` WHERE id = %d', intval($current_post));
904 if (count($r) == 1) {
906 call_hooks('post_local_end', $r[0]);
908 call_hooks('post_remote_end', $r[0]);
910 logger('item_store: new item not found in DB, id ' . $current_post);
913 create_tags_from_item($current_post);
914 create_files_from_item($current_post);
916 // Only check for notifications on start posts
917 if ($arr['parent-uri'] === $arr['uri'])
918 add_thread($current_post);
920 update_thread($parent_id);
921 add_shadow_entry($arr);
924 $perfdb = $a->performance["database"] - $perfdb;
925 $perfdbw = $a->performance["database_write"] - $perfdbw;
926 $perfnet = $a->performance["network"] - $perfnet;
927 $perffile = $a->performance["file"] - $perffile;
929 logger("Performance: DB-R: ".round($perfdb - $perfdbw, 2)." - DB-W: ".round($perfdbw, 2)." - Net: ".round($perfnet, 2)." - File: ".round($perffile, 2), LOGGER_DEBUG);
930 //logger("Performance: DB-R: ".round($perfdb - $perfdbw, 2)." - DB-W: ".round($perfdbw, 2)." - Net: ".round($perfnet, 2), LOGGER_DEBUG);
932 check_item_notification($current_post, $uid);
935 proc_run(PRIORITY_HIGH, "include/notifier.php", $notify_type, $current_post);
937 return $current_post;
940 function item_body_set_hashtags(&$item) {
942 $tags = get_tags($item["body"]);
948 // This sorting is important when there are hashtags that are part of other hashtags
949 // Otherwise there could be problems with hashtags like #test and #test2
954 $URLSearchString = "^\[\]";
956 // All hashtags should point to the home server
957 //$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
958 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["body"]);
960 //$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
961 // "#[url=".$a->get_baseurl()."/search?tag=$2]$2[/url]", $item["tag"]);
963 // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
964 $item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
966 return("[url=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/url]");
969 $item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
971 return("[bookmark=".str_replace("#", "#", $match[1])."]".str_replace("#", "#", $match[2])."[/bookmark]");
974 $item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
976 return("[attachment ".str_replace("#", "#", $match[1])."]".$match[2]."[/attachment]");
979 // Repair recursive urls
980 $item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
981 "#$2", $item["body"]);
984 foreach($tags as $tag) {
985 if(strpos($tag,'#') !== 0)
988 if(strpos($tag,'[url='))
991 $basetag = str_replace('_',' ',substr($tag,1));
993 $newtag = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
995 $item["body"] = str_replace($tag, $newtag, $item["body"]);
997 if(!stristr($item["tag"],"/search?tag=".$basetag."]".$basetag."[/url]")) {
998 if(strlen($item["tag"]))
999 $item["tag"] = ','.$item["tag"];
1000 $item["tag"] = $newtag.$item["tag"];
1004 // Convert back the masked hashtags
1005 $item["body"] = str_replace("#", "#", $item["body"]);
1008 function get_item_guid($id) {
1009 $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id));
1011 return($r[0]["guid"]);
1016 function get_item_id($guid, $uid = 0) {
1022 $uid == local_user();
1024 // Does the given user have this item?
1026 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1027 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1028 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid));
1031 $nick = $r[0]["nickname"];
1035 // Or is it anywhere on the server?
1037 $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid`
1038 WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
1039 AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
1040 AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
1041 AND `item`.`private` = 0 AND `item`.`wall` = 1
1042 AND `item`.`guid` = '%s'", dbesc($guid));
1045 $nick = $r[0]["nickname"];
1048 return(array("nick" => $nick, "id" => $id));
1052 function get_item_contact($item,$contacts) {
1053 if(! count($contacts) || (! is_array($item)))
1055 foreach($contacts as $contact) {
1056 if($contact['id'] == $item['contact-id']) {
1058 break; // NOTREACHED
1065 * look for mention tags and setup a second delivery chain for forum/community posts if appropriate
1067 * @param int $item_id
1068 * @return bool true if item was deleted, else false
1070 function tag_deliver($uid,$item_id) {
1078 $u = q("select * from user where uid = %d limit 1",
1084 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1085 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1088 $i = q("select * from item where id = %d and uid = %d limit 1",
1097 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1099 // Diaspora uses their own hardwired link URL in @-tags
1100 // instead of the one we supply with webfinger
1102 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1104 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1106 foreach($matches as $mtch) {
1107 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1109 logger('tag_deliver: mention found: ' . $mtch[2]);
1115 if ( ($community_page || $prvgroup) &&
1116 (!$item['wall']) && (!$item['origin']) && ($item['id'] == $item['parent'])){
1117 // mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
1119 logger("tag_deliver: no-mention top-level post to communuty or private group. delete.");
1120 q("DELETE FROM item WHERE id = %d and uid = %d",
1129 $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]);
1131 call_hooks('tagged', $arr);
1133 if((! $community_page) && (! $prvgroup))
1137 // tgroup delivery - setup a second delivery chain
1138 // prevent delivery looping - only proceed
1139 // if the message originated elsewhere and is a top-level post
1141 if(($item['wall']) || ($item['origin']) || ($item['id'] != $item['parent']))
1144 // now change this copy of the post to a forum head message and deliver to all the tgroup members
1147 $c = q("select name, url, thumb from contact where self = 1 and uid = %d limit 1",
1148 intval($u[0]['uid'])
1153 // also reset all the privacy bits to the forum default permissions
1155 $private = ($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0;
1157 $forum_mode = (($prvgroup) ? 2 : 1);
1159 q("update item set wall = 1, origin = 1, forum_mode = %d, `owner-name` = '%s', `owner-link` = '%s', `owner-avatar` = '%s',
1160 `private` = %d, `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' where id = %d",
1161 intval($forum_mode),
1162 dbesc($c[0]['name']),
1163 dbesc($c[0]['url']),
1164 dbesc($c[0]['thumb']),
1166 dbesc($u[0]['allow_cid']),
1167 dbesc($u[0]['allow_gid']),
1168 dbesc($u[0]['deny_cid']),
1169 dbesc($u[0]['deny_gid']),
1172 update_thread($item_id);
1174 proc_run(PRIORITY_HIGH,'include/notifier.php', 'tgroup', $item_id);
1180 function tgroup_check($uid,$item) {
1186 // check that the message originated elsewhere and is a top-level post
1188 if(($item['wall']) || ($item['origin']) || ($item['uri'] != $item['parent-uri']))
1192 $u = q("select * from user where uid = %d limit 1",
1198 $community_page = (($u[0]['page-flags'] == PAGE_COMMUNITY) ? true : false);
1199 $prvgroup = (($u[0]['page-flags'] == PAGE_PRVGROUP) ? true : false);
1202 $link = normalise_link($a->get_baseurl() . '/profile/' . $u[0]['nickname']);
1204 // Diaspora uses their own hardwired link URL in @-tags
1205 // instead of the one we supply with webfinger
1207 $dlink = normalise_link($a->get_baseurl() . '/u/' . $u[0]['nickname']);
1209 $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism',$item['body'],$matches,PREG_SET_ORDER);
1211 foreach($matches as $mtch) {
1212 if(link_compare($link,$mtch[1]) || link_compare($dlink,$mtch[1])) {
1214 logger('tgroup_check: mention found: ' . $mtch[2]);
1222 if((! $community_page) && (! $prvgroup))
1229 This function returns true if $update has an edited timestamp newer
1230 than $existing, i.e. $update contains new data which should override
1231 what's already there. If there is no timestamp yet, the update is
1232 assumed to be newer. If the update has no timestamp, the existing
1233 item is assumed to be up-to-date. If the timestamps are equal it
1234 assumes the update has been seen before and should be ignored.
1236 function edited_timestamp_is_newer($existing, $update) {
1237 if (!x($existing,'edited') || !$existing['edited']) {
1240 if (!x($update,'edited') || !$update['edited']) {
1243 $existing_edited = datetime_convert('UTC', 'UTC', $existing['edited']);
1244 $update_edited = datetime_convert('UTC', 'UTC', $update['edited']);
1245 return (strcmp($existing_edited, $update_edited) < 0);
1250 * consume_feed - process atom feed and update anything/everything we might need to update
1252 * $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
1254 * $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
1255 * It is this person's stuff that is going to be updated.
1256 * $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
1257 * from an external network and MAY create an appropriate contact record. Otherwise, we MUST
1258 * have a contact record.
1259 * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
1260 * might not) try and subscribe to it.
1261 * $datedir sorts in reverse order
1262 * $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
1263 * imported prior to its children being seen in the stream unless we are certain
1264 * of how the feed is arranged/ordered.
1265 * With $pass = 1, we only pull parent items out of the stream.
1266 * With $pass = 2, we only pull children (comments/likes).
1268 * So running this twice, first with pass 1 and then with pass 2 will do the right
1269 * thing regardless of feed ordering. This won't be adequate in a fully-threaded
1270 * model where comments can have sub-threads. That would require some massive sorting
1271 * to get all the feed items into a mostly linear ordering, and might still require
1275 function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) {
1276 if ($contact['network'] === NETWORK_OSTATUS) {
1278 // Test - remove before flight
1279 //$tempfile = tempnam(get_temppath(), "ostatus2");
1280 //file_put_contents($tempfile, $xml);
1281 logger("Consume OStatus messages ", LOGGER_DEBUG);
1282 ostatus::import($xml,$importer,$contact, $hub);
1287 if ($contact['network'] === NETWORK_FEED) {
1289 logger("Consume feeds", LOGGER_DEBUG);
1290 feed_import($xml,$importer,$contact, $hub);
1295 if ($contact['network'] === NETWORK_DFRN) {
1296 logger("Consume DFRN messages", LOGGER_DEBUG);
1298 $r = q("SELECT `contact`.*, `contact`.`uid` AS `importer_uid`,
1299 `contact`.`pubkey` AS `cpubkey`,
1300 `contact`.`prvkey` AS `cprvkey`,
1301 `contact`.`thumb` AS `thumb`,
1302 `contact`.`url` as `url`,
1303 `contact`.`name` as `senderName`,
1306 LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid`
1307 WHERE `contact`.`id` = %d AND `user`.`uid` = %d",
1308 dbesc($contact["id"]), dbesc($importer["uid"])
1311 logger("Now import the DFRN feed");
1312 dfrn::import($xml,$r[0], true);
1318 function item_is_remote_self($contact, &$datarray) {
1321 if (!$contact['remote_self'])
1324 // Prevent the forwarding of posts that are forwarded
1325 if ($datarray["extid"] == NETWORK_DFRN)
1328 // Prevent to forward already forwarded posts
1329 if ($datarray["app"] == $a->get_hostname())
1332 // Only forward posts
1333 if ($datarray["verb"] != ACTIVITY_POST)
1336 if (($contact['network'] != NETWORK_FEED) AND $datarray['private'])
1339 $datarray2 = $datarray;
1340 logger('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), LOGGER_DEBUG);
1341 if ($contact['remote_self'] == 2) {
1342 $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`",
1343 intval($contact['uid']));
1345 $datarray['contact-id'] = $r[0]["id"];
1347 $datarray['owner-name'] = $r[0]["name"];
1348 $datarray['owner-link'] = $r[0]["url"];
1349 $datarray['owner-avatar'] = $r[0]["thumb"];
1351 $datarray['author-name'] = $datarray['owner-name'];
1352 $datarray['author-link'] = $datarray['owner-link'];
1353 $datarray['author-avatar'] = $datarray['owner-avatar'];
1356 if ($contact['network'] != NETWORK_FEED) {
1357 $datarray["guid"] = get_guid(32);
1358 unset($datarray["plink"]);
1359 $datarray["uri"] = item_new_uri($a->get_hostname(),$contact['uid'], $datarray["guid"]);
1360 $datarray["parent-uri"] = $datarray["uri"];
1361 $datarray["extid"] = $contact['network'];
1362 $urlpart = parse_url($datarray2['author-link']);
1363 $datarray["app"] = $urlpart["host"];
1365 $datarray['private'] = 0;
1368 if ($contact['network'] != NETWORK_FEED) {
1369 // Store the original post
1370 $r = item_store($datarray2, false, false);
1371 logger('remote-self post original item - Contact '.$contact['url'].' return '.$r.' Item '.print_r($datarray2, true), LOGGER_DEBUG);
1373 $datarray["app"] = "Feed";
1378 function new_follower($importer,$contact,$datarray,$item,$sharing = false) {
1379 $url = notags(trim($datarray['author-link']));
1380 $name = notags(trim($datarray['author-name']));
1381 $photo = notags(trim($datarray['author-avatar']));
1383 if (is_object($item)) {
1384 $rawtag = $item->get_item_tags(NAMESPACE_ACTIVITY,'actor');
1385 if($rawtag && $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'])
1386 $nick = $rawtag[0]['child'][NAMESPACE_POCO]['preferredUsername'][0]['data'];
1390 if(is_array($contact)) {
1391 if(($contact['network'] == NETWORK_OSTATUS && $contact['rel'] == CONTACT_IS_SHARING)
1392 || ($sharing && $contact['rel'] == CONTACT_IS_FOLLOWER)) {
1393 $r = q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
1394 intval(CONTACT_IS_FRIEND),
1395 intval($contact['id']),
1396 intval($importer['uid'])
1399 // send email notification to owner?
1402 // create contact record
1404 $r = q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
1405 `blocked`, `readonly`, `pending`, `writable`)
1406 VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
1407 intval($importer['uid']),
1408 dbesc(datetime_convert()),
1410 dbesc(normalise_link($url)),
1414 dbesc(($sharing) ? NETWORK_ZOT : NETWORK_OSTATUS),
1415 intval(($sharing) ? CONTACT_IS_SHARING : CONTACT_IS_FOLLOWER)
1417 $r = q("SELECT `id`, `network` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `pending` = 1 LIMIT 1",
1418 intval($importer['uid']),
1422 $contact_record = $r[0];
1423 update_contact_avatar($photo, $importer["uid"], $contact_record["id"], true);
1427 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
1428 intval($importer['uid'])
1431 if(count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
1433 // create notification
1434 $hash = random_string();
1436 if(is_array($contact_record)) {
1437 $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `hash`, `datetime`)
1438 VALUES ( %d, %d, 0, 0, '%s', '%s' )",
1439 intval($importer['uid']),
1440 intval($contact_record['id']),
1442 dbesc(datetime_convert())
1446 $def_gid = get_default_group($importer['uid'], $contact_record["network"]);
1448 if(intval($def_gid))
1449 group_add_member($importer['uid'],'',$contact_record['id'],$def_gid);
1451 if(($r[0]['notify-flags'] & NOTIFY_INTRO) &&
1452 in_array($r[0]['page-flags'], array(PAGE_NORMAL))) {
1455 'type' => NOTIFY_INTRO,
1456 'notify_flags' => $r[0]['notify-flags'],
1457 'language' => $r[0]['language'],
1458 'to_name' => $r[0]['username'],
1459 'to_email' => $r[0]['email'],
1460 'uid' => $r[0]['uid'],
1461 'link' => $a->get_baseurl() . '/notifications/intro',
1462 'source_name' => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : t('[Name Withheld]')),
1463 'source_link' => $contact_record['url'],
1464 'source_photo' => $contact_record['photo'],
1465 'verb' => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
1470 } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) {
1471 $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1",
1472 intval($importer['uid']),
1480 function lose_follower($importer,$contact,$datarray = array(),$item = "") {
1482 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) {
1483 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
1484 intval(CONTACT_IS_SHARING),
1485 intval($contact['id'])
1489 contact_remove($contact['id']);
1493 function lose_sharer($importer,$contact,$datarray = array(),$item = "") {
1495 if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) {
1496 q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d",
1497 intval(CONTACT_IS_FOLLOWER),
1498 intval($contact['id'])
1502 contact_remove($contact['id']);
1506 function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') {
1510 if(is_array($importer)) {
1511 $r = q("SELECT `nickname` FROM `user` WHERE `uid` = %d LIMIT 1",
1512 intval($importer['uid'])
1516 // Diaspora has different message-ids in feeds than they do
1517 // through the direct Diaspora protocol. If we try and use
1518 // the feed, we'll get duplicates. So don't.
1520 if((! count($r)) || $contact['network'] === NETWORK_DIASPORA)
1523 $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id'];
1525 // Use a single verify token, even if multiple hubs
1527 $verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : random_string());
1529 $params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
1531 logger('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
1533 if(!strlen($contact['hub-verify']) OR ($contact['hub-verify'] != $verify_token)) {
1534 $r = q("UPDATE `contact` SET `hub-verify` = '%s' WHERE `id` = %d",
1535 dbesc($verify_token),
1536 intval($contact['id'])
1540 post_url($url,$params);
1542 logger('subscribe_to_hub: returns: ' . $a->get_curl_code(), LOGGER_DEBUG);
1548 function fix_private_photos($s, $uid, $item = null, $cid = 0) {
1550 if(get_config('system','disable_embedded'))
1555 logger('fix_private_photos: check for photos', LOGGER_DEBUG);
1556 $site = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://'));
1561 $img_start = strpos($orig_body, '[img');
1562 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
1563 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
1564 while( ($img_st_close !== false) && ($img_len !== false) ) {
1566 $img_st_close++; // make it point to AFTER the closing bracket
1567 $image = substr($orig_body, $img_start + $img_st_close, $img_len);
1569 logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG);
1572 if(stristr($image , $site . '/photo/')) {
1573 // Only embed locally hosted photos
1575 $i = basename($image);
1576 $i = str_replace(array('.jpg','.png','.gif'),array('','',''),$i);
1577 $x = strpos($i,'-');
1580 $res = substr($i,$x+1);
1581 $i = substr($i,0,$x);
1582 $r = q("SELECT * FROM `photo` WHERE `resource-id` = '%s' AND `scale` = %d AND `uid` = %d",
1589 // Check to see if we should replace this photo link with an embedded image
1590 // 1. No need to do so if the photo is public
1591 // 2. If there's a contact-id provided, see if they're in the access list
1592 // for the photo. If so, embed it.
1593 // 3. Otherwise, if we have an item, see if the item permissions match the photo
1594 // permissions, regardless of order but first check to see if they're an exact
1595 // match to save some processing overhead.
1597 if(has_permissions($r[0])) {
1599 $recips = enumerate_permissions($r[0]);
1600 if(in_array($cid, $recips)) {
1605 if(compare_permissions($item,$r[0]))
1610 $data = $r[0]['data'];
1611 $type = $r[0]['type'];
1613 // If a custom width and height were specified, apply before embedding
1614 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
1615 logger('fix_private_photos: scaling photo', LOGGER_DEBUG);
1617 $width = intval($match[1]);
1618 $height = intval($match[2]);
1620 $ph = new Photo($data, $type);
1621 if($ph->is_valid()) {
1622 $ph->scaleImage(max($width, $height));
1623 $data = $ph->imageString();
1624 $type = $ph->getType();
1628 logger('fix_private_photos: replacing photo', LOGGER_DEBUG);
1629 $image = 'data:' . $type . ';base64,' . base64_encode($data);
1630 logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA);
1636 $new_body = $new_body . substr($orig_body, 0, $img_start + $img_st_close) . $image . '[/img]';
1637 $orig_body = substr($orig_body, $img_start + $img_st_close + $img_len + strlen('[/img]'));
1638 if($orig_body === false)
1641 $img_start = strpos($orig_body, '[img');
1642 $img_st_close = ($img_start !== false ? strpos(substr($orig_body, $img_start), ']') : false);
1643 $img_len = ($img_start !== false ? strpos(substr($orig_body, $img_start + $img_st_close + 1), '[/img]') : false);
1646 $new_body = $new_body . $orig_body;
1651 function has_permissions($obj) {
1652 if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != ''))
1657 function compare_permissions($obj1,$obj2) {
1658 // first part is easy. Check that these are exactly the same.
1659 if(($obj1['allow_cid'] == $obj2['allow_cid'])
1660 && ($obj1['allow_gid'] == $obj2['allow_gid'])
1661 && ($obj1['deny_cid'] == $obj2['deny_cid'])
1662 && ($obj1['deny_gid'] == $obj2['deny_gid']))
1665 // This is harder. Parse all the permissions and compare the resulting set.
1667 $recipients1 = enumerate_permissions($obj1);
1668 $recipients2 = enumerate_permissions($obj2);
1671 if($recipients1 == $recipients2)
1676 // returns an array of contact-ids that are allowed to see this object
1678 function enumerate_permissions($obj) {
1679 $allow_people = expand_acl($obj['allow_cid']);
1680 $allow_groups = expand_groups(expand_acl($obj['allow_gid']));
1681 $deny_people = expand_acl($obj['deny_cid']);
1682 $deny_groups = expand_groups(expand_acl($obj['deny_gid']));
1683 $recipients = array_unique(array_merge($allow_people,$allow_groups));
1684 $deny = array_unique(array_merge($deny_people,$deny_groups));
1685 $recipients = array_diff($recipients,$deny);
1689 function item_getfeedtags($item) {
1692 $cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
1694 for($x = 0; $x < $cnt; $x ++) {
1696 $ret[$matches[2][$x]] = array('#',$matches[1][$x], $matches[2][$x]);
1700 $cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|',$item['tag'],$matches);
1702 for($x = 0; $x < $cnt; $x ++) {
1704 $ret[] = array('@',$matches[1][$x], $matches[2][$x]);
1710 function item_expire($uid, $days, $network = "", $force = false) {
1712 if((! $uid) || ($days < 1))
1715 // $expire_network_only = save your own wall posts
1716 // and just expire conversations started by others
1718 $expire_network_only = get_pconfig($uid,'expire','network_only');
1719 $sql_extra = ((intval($expire_network_only)) ? " AND wall = 0 " : "");
1721 if ($network != "") {
1722 $sql_extra .= sprintf(" AND network = '%s' ", dbesc($network));
1723 // There is an index "uid_network_received" but not "uid_network_created"
1724 // This avoids the creation of another index just for one purpose.
1725 // And it doesn't really matter wether to look at "received" or "created"
1726 $range = "AND `received` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
1728 $range = "AND `created` < UTC_TIMESTAMP() - INTERVAL %d DAY ";
1730 $r = q("SELECT `file`, `resource-id`, `starred`, `type`, `id` FROM `item`
1731 WHERE `uid` = %d $range
1742 $expire_items = get_pconfig($uid, 'expire','items');
1743 $expire_items = (($expire_items===false)?1:intval($expire_items)); // default if not set: 1
1745 // Forcing expiring of items - but not notes and marked items
1747 $expire_items = true;
1749 $expire_notes = get_pconfig($uid, 'expire','notes');
1750 $expire_notes = (($expire_notes===false)?1:intval($expire_notes)); // default if not set: 1
1752 $expire_starred = get_pconfig($uid, 'expire','starred');
1753 $expire_starred = (($expire_starred===false)?1:intval($expire_starred)); // default if not set: 1
1755 $expire_photos = get_pconfig($uid, 'expire','photos');
1756 $expire_photos = (($expire_photos===false)?0:intval($expire_photos)); // default if not set: 0
1758 logger('expire: # items=' . count($r). "; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos");
1760 foreach($r as $item) {
1762 // don't expire filed items
1764 if(strpos($item['file'],'[') !== false)
1767 // Only expire posts, not photos and photo comments
1769 if($expire_photos==0 && strlen($item['resource-id']))
1771 if($expire_starred==0 && intval($item['starred']))
1773 if($expire_notes==0 && $item['type']=='note')
1775 if($expire_items==0 && $item['type']!='note')
1778 drop_item($item['id'],false);
1781 proc_run(PRIORITY_HIGH,"include/notifier.php", "expire", $uid);
1786 function drop_items($items) {
1789 if(! local_user() && ! remote_user())
1793 foreach($items as $item) {
1794 $owner = drop_item($item,false);
1795 if($owner && ! $uid)
1800 // multiple threads may have been deleted, send an expire notification
1803 proc_run(PRIORITY_HIGH,"include/notifier.php", "expire", $uid);
1807 function drop_item($id,$interactive = true) {
1811 // locate item to be deleted
1813 $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
1820 notice( t('Item not found.') . EOL);
1821 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
1826 $owner = $item['uid'];
1830 // check if logged in user is either the author or owner of this item
1832 if(is_array($_SESSION['remote'])) {
1833 foreach($_SESSION['remote'] as $visitor) {
1834 if($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
1835 $cid = $visitor['cid'];
1842 if((local_user() == $item['uid']) || ($cid) || (! $interactive)) {
1844 // Check if we should do HTML-based delete confirmation
1845 if($_REQUEST['confirm']) {
1846 // <form> can't take arguments in its "action" parameter
1847 // so add any arguments as hidden inputs
1848 $query = explode_querystring($a->query_string);
1850 foreach($query['args'] as $arg) {
1851 if(strpos($arg, 'confirm=') === false) {
1852 $arg_parts = explode('=', $arg);
1853 $inputs[] = array('name' => $arg_parts[0], 'value' => $arg_parts[1]);
1857 return replace_macros(get_markup_template('confirm.tpl'), array(
1859 '$message' => t('Do you really want to delete this item?'),
1860 '$extra_inputs' => $inputs,
1861 '$confirm' => t('Yes'),
1862 '$confirm_url' => $query['base'],
1863 '$confirm_name' => 'confirmed',
1864 '$cancel' => t('Cancel'),
1867 // Now check how the user responded to the confirmation query
1868 if($_REQUEST['canceled']) {
1869 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
1872 logger('delete item: ' . $item['id'], LOGGER_DEBUG);
1875 $r = q("UPDATE `item` SET `deleted` = 1, `title` = '', `body` = '', `edited` = '%s', `changed` = '%s' WHERE `id` = %d",
1876 dbesc(datetime_convert()),
1877 dbesc(datetime_convert()),
1880 create_tags_from_item($item['id']);
1881 create_files_from_item($item['id']);
1882 delete_thread($item['id'], $item['parent-uri']);
1884 // clean up categories and tags so they don't end up as orphans
1887 $cnt = preg_match_all('/<(.*?)>/',$item['file'],$matches,PREG_SET_ORDER);
1889 foreach($matches as $mtch) {
1890 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],true);
1896 $cnt = preg_match_all('/\[(.*?)\]/',$item['file'],$matches,PREG_SET_ORDER);
1898 foreach($matches as $mtch) {
1899 file_tag_unsave_file($item['uid'],$item['id'],$mtch[1],false);
1903 // If item is a link to a photo resource, nuke all the associated photos
1904 // (visitors will not have photo resources)
1905 // This only applies to photos uploaded from the photos page. Photos inserted into a post do not
1906 // generate a resource-id and therefore aren't intimately linked to the item.
1908 if(strlen($item['resource-id'])) {
1909 q("DELETE FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d ",
1910 dbesc($item['resource-id']),
1911 intval($item['uid'])
1913 // ignore the result
1916 // If item is a link to an event, nuke the event record.
1918 if(intval($item['event-id'])) {
1919 q("DELETE FROM `event` WHERE `id` = %d AND `uid` = %d",
1920 intval($item['event-id']),
1921 intval($item['uid'])
1923 // ignore the result
1926 // If item has attachments, drop them
1928 foreach(explode(",",$item['attach']) as $attach){
1929 preg_match("|attach/(\d+)|", $attach, $matches);
1930 q("DELETE FROM `attach` WHERE `id` = %d AND `uid` = %d",
1931 intval($matches[1]),
1934 // ignore the result
1938 // clean up item_id and sign meta-data tables
1941 // Old code - caused very long queries and warning entries in the mysql logfiles:
1943 $r = q("DELETE FROM item_id where iid in (select id from item where parent = %d and uid = %d)",
1944 intval($item['id']),
1945 intval($item['uid'])
1948 $r = q("DELETE FROM sign where iid in (select id from item where parent = %d and uid = %d)",
1949 intval($item['id']),
1950 intval($item['uid'])
1954 // The new code splits the queries since the mysql optimizer really has bad problems with subqueries
1956 // Creating list of parents
1957 $r = q("select id from item where parent = %d and uid = %d",
1958 intval($item['id']),
1959 intval($item['uid'])
1964 foreach ($r AS $row) {
1965 if ($parentid != "")
1968 $parentid .= $row["id"];
1972 if ($parentid != "") {
1973 $r = q("DELETE FROM item_id where iid in (%s)", dbesc($parentid));
1975 $r = q("DELETE FROM sign where iid in (%s)", dbesc($parentid));
1978 // If it's the parent of a comment thread, kill all the kids
1980 if($item['uri'] == $item['parent-uri']) {
1981 $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = ''
1982 WHERE `parent-uri` = '%s' AND `uid` = %d ",
1983 dbesc(datetime_convert()),
1984 dbesc(datetime_convert()),
1985 dbesc($item['parent-uri']),
1986 intval($item['uid'])
1988 create_tags_from_itemuri($item['parent-uri'], $item['uid']);
1989 create_files_from_itemuri($item['parent-uri'], $item['uid']);
1990 delete_thread_uri($item['parent-uri'], $item['uid']);
1991 // ignore the result
1994 // ensure that last-child is set in case the comment that had it just got wiped.
1995 q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ",
1996 dbesc(datetime_convert()),
1997 dbesc($item['parent-uri']),
1998 intval($item['uid'])
2000 // who is the last child now?
2001 $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",
2002 dbesc($item['parent-uri']),
2003 intval($item['uid'])
2006 q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d",
2012 $drop_id = intval($item['id']);
2014 // send the notification upstream/downstream as the case may be
2016 proc_run(PRIORITY_HIGH,"include/notifier.php", "drop", $drop_id);
2020 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
2026 notice( t('Permission denied.') . EOL);
2027 goaway($a->get_baseurl() . '/' . $_SESSION['return_url']);
2034 function first_post_date($uid,$wall = false) {
2035 $r = q("select id, created from item
2036 where uid = %d and wall = %d and deleted = 0 and visible = 1 AND moderated = 0
2038 order by created asc limit 1",
2040 intval($wall ? 1 : 0)
2043 // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA);
2044 return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10);
2049 /* modified posted_dates() {below} to arrange the list in years */
2050 function list_post_dates($uid, $wall) {
2051 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
2053 $dthen = first_post_date($uid, $wall);
2057 // Set the start and end date to the beginning of the month
2058 $dnow = substr($dnow,0,8).'01';
2059 $dthen = substr($dthen,0,8).'01';
2063 // Starting with the current month, get the first and last days of every
2064 // month down to and including the month of the first post
2065 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
2066 $dyear = intval(substr($dnow,0,4));
2067 $dstart = substr($dnow,0,8) . '01';
2068 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
2069 $start_month = datetime_convert('','',$dstart,'Y-m-d');
2070 $end_month = datetime_convert('','',$dend,'Y-m-d');
2071 $str = day_translate(datetime_convert('','',$dnow,'F'));
2073 $ret[$dyear] = array();
2074 $ret[$dyear][] = array($str,$end_month,$start_month);
2075 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
2080 function posted_dates($uid,$wall) {
2081 $dnow = datetime_convert('',date_default_timezone_get(),'now','Y-m-d');
2083 $dthen = first_post_date($uid,$wall);
2087 // Set the start and end date to the beginning of the month
2088 $dnow = substr($dnow,0,8).'01';
2089 $dthen = substr($dthen,0,8).'01';
2092 // Starting with the current month, get the first and last days of every
2093 // month down to and including the month of the first post
2094 while(substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
2095 $dstart = substr($dnow,0,8) . '01';
2096 $dend = substr($dnow,0,8) . get_dim(intval($dnow),intval(substr($dnow,5)));
2097 $start_month = datetime_convert('','',$dstart,'Y-m-d');
2098 $end_month = datetime_convert('','',$dend,'Y-m-d');
2099 $str = day_translate(datetime_convert('','',$dnow,'F Y'));
2100 $ret[] = array($str,$end_month,$start_month);
2101 $dnow = datetime_convert('','',$dnow . ' -1 month', 'Y-m-d');
2107 function posted_date_widget($url,$uid,$wall) {
2110 if(! feature_enabled($uid,'archives'))
2113 // For former Facebook folks that left because of "timeline"
2115 /* if($wall && intval(get_pconfig($uid,'system','no_wall_archive_widget')))
2118 $visible_years = get_pconfig($uid,'system','archive_visible_years');
2119 if(! $visible_years)
2122 $ret = list_post_dates($uid,$wall);
2127 $cutoff_year = intval(datetime_convert('',date_default_timezone_get(),'now','Y')) - $visible_years;
2128 $cutoff = ((array_key_exists($cutoff_year,$ret))? true : false);
2130 $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array(
2131 '$title' => t('Archives'),
2132 '$size' => $visible_years,
2133 '$cutoff_year' => $cutoff_year,
2134 '$cutoff' => $cutoff,
2137 '$showmore' => t('show more')