X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=include%2Fitems.php;h=da9147faddddd4da00cc2d7050b74cfe6c2d7dd2;hb=e54ee2e5091eafe7fa22baa213a798f9e6f58b01;hp=1ee048e48c2c0437a27415f7511f32406b60842a;hpb=8e50254bb994592b28e04299a4bfc11f4c8c979a;p=friendica.git diff --git a/include/items.php b/include/items.php index 1ee048e48c..da9147fadd 100644 --- a/include/items.php +++ b/include/items.php @@ -1,5 +1,11 @@ 0)) + is_string($data["text"]) AND (sizeof($data["images"]) > 0)) { $data["type"] = "link"; + } - if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $url)) - return(""); + if ((($data["type"] != "link") AND ($data["type"] != "video") AND ($data["type"] != "photo")) OR ($data["title"] == $data["url"])) { + return ""; + } - if ($no_photos AND ($data["type"] == "photo")) - return(""); + if ($no_photos AND ($data["type"] == "photo")) { + return ""; + } - if (sizeof($data["images"]) > 0) + if (sizeof($data["images"]) > 0) { $preview = $data["images"][0]; - else + } else { $preview = ""; + } // Escape some bad characters $data["url"] = str_replace(array("[", "]"), array("[", "]"), htmlentities($data["url"], ENT_QUOTES, 'UTF-8', false)); @@ -167,19 +177,33 @@ function add_page_info_data($data) { $text = "[attachment type='".$data["type"]."'"; - if ($data["url"] != "") + if ($data["text"] == "") { + $data["text"] = $data["title"]; + } + + if ($data["text"] == "") { + $data["text"] = $data["url"]; + } + + if ($data["url"] != "") { $text .= " url='".$data["url"]."'"; - if ($data["title"] != "") + } + + if ($data["title"] != "") { $text .= " title='".$data["title"]."'"; + } + if (sizeof($data["images"]) > 0) { $preview = str_replace(array("[", "]"), array("[", "]"), htmlentities($data["images"][0]["src"], ENT_QUOTES, 'UTF-8', false)); // if the preview picture is larger than 500 pixels then show it in a larger mode // But only, if the picture isn't higher than large (To prevent huge posts) - if (($data["images"][0]["width"] >= 500) AND ($data["images"][0]["width"] >= $data["images"][0]["height"])) + if (($data["images"][0]["width"] >= 500) AND ($data["images"][0]["width"] >= $data["images"][0]["height"])) { $text .= " image='".$preview."'"; - else + } else { $text .= " preview='".$preview."'"; + } } + $text .= "]".$data["text"]."[/attachment]"; $hashtags = ""; @@ -198,9 +222,8 @@ function add_page_info_data($data) { } function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") { - require_once("mod/parse_url.php"); - $data = parseurl_getsiteinfo_cached($url, true); + $data = ParseUrl::getSiteinfoCached($url, true); if ($photo != "") $data["images"][0]["src"] = $photo; @@ -257,6 +280,10 @@ function add_page_info_to_body($body, $texturl = false, $no_photos = false) { $URLSearchString = "^\[\]"; + // Fix for Mastodon where the mentions are in a different format + $body = preg_replace("/\[url\=([$URLSearchString]*)\]([#!@])(.*?)\[\/url\]/ism", + '$2[url=$1]$3[/url]', $body); + // Adding these spaces is a quick hack due to my problems with regular expressions :) preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " ".$body, $matches); @@ -341,14 +368,20 @@ function item_add_language_opt(&$arr) { * @brief Creates an unique guid out of a given uri * * @param string $uri uri of an item entry + * @param string $host (Optional) hostname for the GUID prefix * @return string unique guid */ -function uri_to_guid($uri) { +function uri_to_guid($uri, $host = "") { // Our regular guid routine is using this kind of prefix as well // We have to avoid that different routines could accidentally create the same value $parsed = parse_url($uri); - $guid_prefix = hash("crc32", $parsed["host"]); + + if ($host == "") { + $host = $parsed["host"]; + } + + $guid_prefix = hash("crc32", $host); // Remove the scheme to make sure that "https" and "http" doesn't make a difference unset($parsed["scheme"]); @@ -363,6 +396,8 @@ function uri_to_guid($uri) { function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) { + $a = get_app(); + // If it is a posting where users should get notifications, then define it as wall posting if ($notify) { $arr['wall'] = 1; @@ -370,6 +405,15 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa $arr['origin'] = 1; $arr['last-child'] = 1; $arr['network'] = NETWORK_DFRN; + + // We have to avoid duplicates. So we create the GUID in form of a hash of the plink or uri. + // In difference to the call to "uri_to_guid" several lines below we add the hash of our own host. + // This is done because our host is the original creator of the post. + if (isset($arr['plink'])) { + $arr['guid'] = uri_to_guid($arr['plink'], $a->get_hostname()); + } elseif (isset($arr['uri'])) { + $arr['guid'] = uri_to_guid($arr['uri'], $a->get_hostname()); + } } // If a Diaspora signature structure was passed in, pull it out of the @@ -377,6 +421,7 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa $dsprsig = null; if (x($arr,'dsprsig')) { + $encoded_signature = $arr['dsprsig']; $dsprsig = json_decode(base64_decode($arr['dsprsig'])); unset($arr['dsprsig']); } @@ -406,7 +451,7 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa /* check for create date and expire time */ $uid = intval($arr['uid']); $r = q("SELECT expire FROM user WHERE uid = %d", intval($uid)); - if (count($r)) { + if (dbm::is_result($r)) { $expire_interval = $r[0]['expire']; if ($expire_interval>0) { $expire_date = new DateTime( '- '.$expire_interval.' days', new DateTimeZone('UTC')); @@ -456,7 +501,7 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa $arr['wall'] = ((x($arr,'wall')) ? intval($arr['wall']) : 0); $arr['guid'] = ((x($arr,'guid')) ? notags(trim($arr['guid'])) : get_guid(32, $guid_prefix)); - $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : $arr['guid']); + $arr['uri'] = ((x($arr,'uri')) ? notags(trim($arr['uri'])) : item_new_uri($a->get_hostname(), $uid, $arr['guid'])); $arr['extid'] = ((x($arr,'extid')) ? notags(trim($arr['extid'])) : ''); $arr['author-name'] = ((x($arr,'author-name')) ? trim($arr['author-name']) : ''); $arr['author-link'] = ((x($arr,'author-link')) ? notags(trim($arr['author-link'])) : ''); @@ -475,7 +520,7 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa $arr['last-child'] = ((x($arr,'last-child')) ? intval($arr['last-child']) : 0 ); $arr['visible'] = ((x($arr,'visible') !== false) ? intval($arr['visible']) : 1 ); $arr['deleted'] = 0; - $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : ''); + $arr['parent-uri'] = ((x($arr,'parent-uri')) ? notags(trim($arr['parent-uri'])) : $arr['uri']); $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : ''); $arr['object-type'] = ((x($arr,'object-type')) ? notags(trim($arr['object-type'])) : ''); $arr['object'] = ((x($arr,'object')) ? trim($arr['object']) : ''); @@ -523,19 +568,19 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa intval($arr['uid']) ); - if (!count($r)) + if (!dbm::is_result($r)) $r = q("SELECT `network` FROM `gcontact` WHERE `network` IN ('%s', '%s', '%s') AND `nurl` = '%s' LIMIT 1", dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS), dbesc(normalise_link($arr['author-link'])) ); - if (!count($r)) + if (!dbm::is_result($r)) $r = q("SELECT `network` FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1", intval($arr['contact-id']), intval($arr['uid']) ); - if (count($r)) + if (dbm::is_result($r)) $arr['network'] = $r[0]["network"]; // Fallback to friendica (why is it empty in some cases?) @@ -589,7 +634,7 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa $r = q("SELECT `guid` FROM `item` WHERE `guid` = '%s' AND `network` = '%s' AND `uid` = '%d' LIMIT 1", dbesc($arr['guid']), dbesc($arr['network']), intval($arr['uid'])); - if (count($r)) { + if (dbm::is_result($r)) { logger('found item with guid '.$arr['guid'].' for user '.$arr['uid'].' on network '.$arr['network'], LOGGER_DEBUG); return 0; } @@ -617,7 +662,7 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa intval($arr['uid']) ); - if (count($r)) { + if (dbm::is_result($r)) { // is the new message multi-level threaded? // even though we don't support it now, preserve the info @@ -730,6 +775,7 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa if ($arr["uid"] == 0) { $arr["global"] = true; + // Set the global flag on all items if this was a global item entry q("UPDATE `item` SET `global` = 1 WHERE `uri` = '%s'", dbesc($arr["uri"])); } else { $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `uri` = '%s'", dbesc($arr["uri"])); @@ -763,6 +809,17 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa return 0; } + // Check for already added items. + // There is a timing issue here that sometimes creates double postings. + // An unique index would help - but the limitations of MySQL (maximum size of index values) prevent this. + if ($arr["uid"] == 0) { + $r = qu("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = 0 LIMIT 1", dbesc(trim($arr['uri']))); + if (dbm::is_result($r)) { + logger('Global item already stored. URI: '.$arr['uri'].' on network '.$arr['network'], LOGGER_DEBUG); + return 0; + } + } + // Store the unescaped version $unescaped = $arr; @@ -777,41 +834,79 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa . implode("`, `", array_keys($arr)) . "`) VALUES ('" . implode("', '", array_values($arr)) - . "')" ); + . "')"); // And restore it $arr = $unescaped; - // find the item that we just created - $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' ORDER BY `id` ASC", + // When the item was successfully stored we fetch the ID of the item. + if (dbm::is_result($r)) { + $r = q("SELECT LAST_INSERT_ID() AS `item-id`"); + if (dbm::is_result($r)) { + $current_post = $r[0]['item-id']; + } else { + // This shouldn't happen + $current_post = 0; + } + } else { + // This can happen - for example - if there are locking timeouts. + q("ROLLBACK"); + + // Store the data into a spool file so that we can try again later. + + // At first we restore the Diaspora signature that we removed above. + if (isset($encoded_signature)) { + $arr['dsprsig'] = $encoded_signature; + } + + // Now we store the data in the spool directory + $file = 'item-'.round(microtime(true) * 10000).".msg"; + $spool = get_spoolpath().'/'.$file; + file_put_contents($spool, json_encode($arr)); + logger("Item wasn't stored - Item was spooled into file ".$file, LOGGER_DEBUG); + return 0; + } + + if ($current_post == 0) { + // This is one of these error messages that never should occur. + logger("couldn't find created item - we better quit now."); + q("ROLLBACK"); + return 0; + } + + // How much entries have we created? + // We wouldn't need this query when we could use an unique index - but MySQL has length problems with them. + $r = q("SELECT COUNT(*) AS `entries` FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s'", dbesc($arr['uri']), intval($arr['uid']), dbesc($arr['network']) ); - if (count($r) > 1) { - // There are duplicates. Keep the oldest one, delete the others - logger('item_store: duplicated post occurred. Removing newer duplicates. uri = '.$arr['uri'].' uid = '.$arr['uid']); - q("DELETE FROM `item` WHERE `uri` = '%s' AND `uid` = %d AND `network` = '%s' AND `id` > %d", - dbesc($arr['uri']), - intval($arr['uid']), - dbesc($arr['network']), - intval($r[0]["id"]) - ); - q("COMMIT"); + if (!dbm::is_result($r)) { + // This shouldn't happen, since COUNT always works when the database connection is there. + logger("We couldn't count the stored entries. Very strange ..."); + q("ROLLBACK"); return 0; - } elseif (count($r)) { + } - $current_post = $r[0]['id']; - logger('item_store: created item ' . $current_post); + if ($r[0]["entries"] > 1) { + // There are duplicates. We delete our just created entry. + logger('Duplicated post occurred. uri = '.$arr['uri'].' uid = '.$arr['uid']); - item_set_last_item($arr); - } else { - logger('item_store: could not locate created item'); + // Yes, we could do a rollback here - but we are having many users with MyISAM. + q("DELETE FROM `item` WHERE `id` = %d", intval($current_post)); q("COMMIT"); return 0; + } elseif ($r[0]["entries"] == 0) { + // This really should never happen since we quit earlier if there were problems. + logger("Something is terribly wrong. We haven't found our created entry."); + q("ROLLBACK"); + return 0; } + logger('item_store: created item '.$current_post); + item_set_last_item($arr); + if (!$parent_id || ($arr['parent-uri'] === $arr['uri'])) $parent_id = $current_post; @@ -855,19 +950,6 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa ); } - - /** - * If this is now the last-child, force all _other_ children of this parent to *not* be last-child - */ - - if ($arr['last-child']) { - $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d", - dbesc($arr['uri']), - intval($arr['uid']), - intval($current_post) - ); - } - $deleted = tag_deliver($arr['uid'],$current_post); // current post can be deleted if is for a community page and no mention are @@ -884,20 +966,33 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa logger('item_store: new item not found in DB, id ' . $current_post); } + if ($arr['parent-uri'] === $arr['uri']) { + add_thread($current_post); + } else { + update_thread($parent_id); + } + + q("COMMIT"); + + // Due to deadlock issues with the "term" table we are doing these steps after the commit. + // This is not perfect - but a workable solution until we found the reason for the problem. create_tags_from_item($current_post); create_files_from_item($current_post); - // Only check for notifications on start posts - if ($arr['parent-uri'] === $arr['uri']) { - add_thread($current_post); - q("COMMIT"); + // If this is now the last-child, force all _other_ children of this parent to *not* be last-child + // It is done after the transaction to avoid dead locks. + if ($arr['last-child']) { + $r = q("UPDATE `item` SET `last-child` = 0 WHERE `parent-uri` = '%s' AND `uid` = %d AND `id` != %d", + dbesc($arr['uri']), + intval($arr['uid']), + intval($current_post) + ); + } + if ($arr['parent-uri'] === $arr['uri']) { add_shadow_thread($current_post); } else { - update_thread($parent_id); - q("COMMIT"); - - add_shadow_entry($arr); + add_shadow_entry($current_post); } check_item_notification($current_post, $uid); @@ -1025,7 +1120,7 @@ function item_body_set_hashtags(&$item) { function get_item_guid($id) { $r = q("SELECT `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($id)); - if (count($r)) + if (dbm::is_result($r)) return($r[0]["guid"]); else return(""); @@ -1044,7 +1139,7 @@ function get_item_id($guid, $uid = 0) { $r = q("SELECT `item`.`id`, `user`.`nickname` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid` WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0 AND `item`.`guid` = '%s' AND `item`.`uid` = %d", dbesc($guid), intval($uid)); - if (count($r)) { + if (dbm::is_result($r)) { $id = $r[0]["id"]; $nick = $r[0]["nickname"]; } @@ -1058,7 +1153,7 @@ function get_item_id($guid, $uid = 0) { AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' AND `item`.`private` = 0 AND `item`.`wall` = 1 AND `item`.`guid` = '%s'", dbesc($guid)); - if (count($r)) { + if (dbm::is_result($r)) { $id = $r[0]["id"]; $nick = $r[0]["nickname"]; } @@ -1359,7 +1454,7 @@ function item_is_remote_self($contact, &$datarray) { if ($contact['remote_self'] == 2) { $r = q("SELECT `id`,`url`,`name`,`thumb` FROM `contact` WHERE `uid` = %d AND `self`", intval($contact['uid'])); - if (count($r)) { + if (dbm::is_result($r)) { $datarray['contact-id'] = $r[0]["id"]; $datarray['owner-name'] = $r[0]["name"]; @@ -1436,7 +1531,7 @@ function new_follower($importer,$contact,$datarray,$item,$sharing = false) { intval($importer['uid']), dbesc($url) ); - if (count($r)) { + if (dbm::is_result($r)) { $contact_record = $r[0]; update_contact_avatar($photo, $importer["uid"], $contact_record["id"], true); } @@ -1446,7 +1541,8 @@ function new_follower($importer,$contact,$datarray,$item,$sharing = false) { intval($importer['uid']) ); $a = get_app(); - if (count($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) { + + if (dbm::is_result($r) AND !in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) { // create notification $hash = random_string(); @@ -1485,7 +1581,7 @@ function new_follower($importer,$contact,$datarray,$item,$sharing = false) { )); } - } elseif (count($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) { + } elseif (dbm::is_result($r) AND in_array($r[0]['page-flags'], array(PAGE_SOAPBOX, PAGE_FREELOVE))) { $r = q("UPDATE `contact` SET `pending` = 0 WHERE `uid` = %d AND `url` = '%s' AND `pending` LIMIT 1", intval($importer['uid']), dbesc($url) @@ -1533,7 +1629,7 @@ function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') { // through the direct Diaspora protocol. If we try and use // the feed, we'll get duplicates. So don't. - if ((! count($r)) || $contact['network'] === NETWORK_DIASPORA) + if ((! dbm::is_result($r)) || $contact['network'] === NETWORK_DIASPORA) return; $push_url = get_config('system','url') . '/pubsub/' . $r[0]['nickname'] . '/' . $contact['id']; @@ -1751,7 +1847,7 @@ function item_expire($uid, $days, $network = "", $force = false) { intval($days) ); - if (! count($r)) + if (! dbm::is_result($r)) return; $expire_items = get_pconfig($uid, 'expire','items'); @@ -1829,7 +1925,7 @@ function drop_item($id,$interactive = true) { intval($id) ); - if (! count($r)) { + if (! dbm::is_result($r)) { if (! $interactive) return 0; notice( t('Item not found.') . EOL); @@ -1840,21 +1936,21 @@ function drop_item($id,$interactive = true) { $owner = $item['uid']; - $cid = 0; + $contact_id = 0; // check if logged in user is either the author or owner of this item if (is_array($_SESSION['remote'])) { foreach($_SESSION['remote'] as $visitor) { if ($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) { - $cid = $visitor['cid']; + $contact_id = $visitor['cid']; break; } } } - if ((local_user() == $item['uid']) || ($cid) || (! $interactive)) { + if ((local_user() == $item['uid']) || ($contact_id) || (! $interactive)) { // Check if we should do HTML-based delete confirmation if ($_REQUEST['confirm']) { @@ -2016,7 +2112,7 @@ function drop_item($id,$interactive = true) { dbesc($item['parent-uri']), intval($item['uid']) ); - if (count($r)) { + if (dbm::is_result($r)) { q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d", intval($r[0]['id']) ); @@ -2052,7 +2148,7 @@ function first_post_date($uid,$wall = false) { intval($uid), intval($wall ? 1 : 0) ); - if (count($r)) { + if (dbm::is_result($r)) { // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA); return substr(datetime_convert('',date_default_timezone_get(),$r[0]['created']),0,10); }