2018-01-09 21:13:45 +00:00
< ? php
/**
2022-01-02 07:27:47 +00:00
* @ copyright Copyright ( C ) 2010 - 2022 , the Friendica project
2020-02-09 14:45:36 +00:00
*
* @ license GNU AGPL version 3 or any later version
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https :// www . gnu . org / licenses />.
*
2018-01-09 21:13:45 +00:00
*/
namespace Friendica\Model ;
2018-07-20 02:15:21 +00:00
use Friendica\Content\Text\BBCode ;
2018-11-07 02:12:41 +00:00
use Friendica\Content\Text\HTML ;
2018-12-26 06:06:24 +00:00
use Friendica\Core\Hook ;
2018-10-29 21:20:46 +00:00
use Friendica\Core\Logger ;
2018-08-11 20:40:44 +00:00
use Friendica\Core\Protocol ;
2018-11-07 02:12:41 +00:00
use Friendica\Core\Renderer ;
2019-09-28 09:36:41 +00:00
use Friendica\Core\Session ;
2019-10-23 19:38:51 +00:00
use Friendica\Core\System ;
2020-09-11 09:37:18 +00:00
use Friendica\Model\Tag ;
2018-02-01 19:14:11 +00:00
use Friendica\Core\Worker ;
2018-07-20 12:19:26 +00:00
use Friendica\Database\DBA ;
2019-12-15 21:34:11 +00:00
use Friendica\DI ;
2020-11-17 22:33:44 +00:00
use Friendica\Model\Post ;
2019-10-23 00:05:11 +00:00
use Friendica\Protocol\Activity ;
2019-07-21 07:37:50 +00:00
use Friendica\Protocol\ActivityPub ;
2018-02-01 19:14:11 +00:00
use Friendica\Protocol\Diaspora ;
2018-01-27 02:38:34 +00:00
use Friendica\Util\DateTimeFormat ;
2018-11-07 02:12:41 +00:00
use Friendica\Util\Map ;
2019-05-29 17:57:18 +00:00
use Friendica\Util\Network ;
2021-04-28 19:05:46 +00:00
use Friendica\Util\Proxy ;
2018-11-08 13:45:46 +00:00
use Friendica\Util\Strings ;
2019-06-06 04:26:02 +00:00
use Friendica\Worker\Delivery ;
2020-10-03 15:42:21 +00:00
use LanguageDetection\Language ;
2018-01-09 21:13:45 +00:00
2019-12-15 22:28:01 +00:00
class Item
2018-01-09 21:13:45 +00:00
{
2018-07-19 13:52:05 +00:00
// Posting types, inspired by https://www.w3.org/TR/activitystreams-vocabulary/#object-types
const PT_ARTICLE = 0 ;
const PT_NOTE = 1 ;
const PT_PAGE = 2 ;
const PT_IMAGE = 16 ;
const PT_AUDIO = 17 ;
const PT_VIDEO = 18 ;
const PT_DOCUMENT = 19 ;
const PT_EVENT = 32 ;
2022-01-23 04:40:45 +00:00
const PT_POLL = 33 ;
2018-07-19 13:52:05 +00:00
const PT_PERSONAL_NOTE = 128 ;
2021-04-07 06:02:06 +00:00
// Posting reasons (Why had a post been stored for a user?)
const PR_NONE = 0 ;
const PR_TAG = 64 ;
const PR_TO = 65 ;
const PR_CC = 66 ;
const PR_BTO = 67 ;
const PR_BCC = 68 ;
const PR_FOLLOWER = 69 ;
const PR_ANNOUNCEMENT = 70 ;
const PR_COMMENT = 71 ;
const PR_STORED = 72 ;
const PR_GLOBAL = 73 ;
const PR_RELAY = 74 ;
const PR_FETCHED = 75 ;
2022-02-15 20:40:18 +00:00
// system.accept_only_sharer setting values
const COMPLETION_NONE = 1 ;
const COMPLETION_COMMENT = 0 ;
const COMPLETION_LIKE = 2 ;
2018-06-17 06:27:52 +00:00
// Field list that is used to display the items
2018-12-07 05:52:14 +00:00
const DISPLAY_FIELDLIST = [
2021-01-27 10:01:42 +00:00
'uid' , 'id' , 'parent' , 'guid' , 'network' , 'gravity' ,
'uri-id' , 'uri' , 'thr-parent-id' , 'thr-parent' , 'parent-uri-id' , 'parent-uri' ,
2018-12-07 05:52:14 +00:00
'commented' , 'created' , 'edited' , 'received' , 'verb' , 'object-type' , 'postopts' , 'plink' ,
2021-02-17 18:59:19 +00:00
'wall' , 'private' , 'starred' , 'origin' , 'parent-origin' , 'title' , 'body' , 'language' ,
2018-12-07 05:52:14 +00:00
'content-warning' , 'location' , 'coord' , 'app' , 'rendered-hash' , 'rendered-html' , 'object' ,
2022-01-09 17:17:34 +00:00
'allow_cid' , 'allow_gid' , 'deny_cid' , 'deny_gid' , 'mention' , 'global' ,
2018-12-07 05:52:14 +00:00
'author-id' , 'author-link' , 'author-name' , 'author-avatar' , 'author-network' ,
2021-02-20 20:07:25 +00:00
'owner-id' , 'owner-link' , 'owner-name' , 'owner-avatar' , 'owner-network' , 'owner-contact-type' ,
2021-02-17 18:59:19 +00:00
'causer-id' , 'causer-link' , 'causer-name' , 'causer-avatar' , 'causer-contact-type' , 'causer-network' ,
2019-04-24 04:26:23 +00:00
'contact-id' , 'contact-uid' , 'contact-link' , 'contact-name' , 'contact-avatar' ,
2021-01-16 04:13:22 +00:00
'writable' , 'self' , 'cid' , 'alias' ,
2021-02-13 19:56:03 +00:00
'event-created' , 'event-edited' , 'event-start' , 'event-finish' ,
2018-12-07 05:52:14 +00:00
'event-summary' , 'event-desc' , 'event-location' , 'event-type' ,
2021-10-03 17:21:17 +00:00
'event-nofinish' , 'event-ignore' , 'event-id' ,
2020-05-31 15:48:31 +00:00
'delivery_queue_count' , 'delivery_queue_done' , 'delivery_queue_failed'
2018-12-07 05:52:14 +00:00
];
2018-06-17 06:27:52 +00:00
// Field list that is used to deliver items via the protocols
2020-04-26 16:15:39 +00:00
const DELIVER_FIELDLIST = [ 'uid' , 'id' , 'parent' , 'uri-id' , 'uri' , 'thr-parent' , 'parent-uri' , 'guid' ,
2021-02-13 19:56:03 +00:00
'parent-guid' , 'received' , 'created' , 'edited' , 'verb' , 'object-type' , 'object' , 'target' ,
'private' , 'title' , 'body' , 'raw-body' , 'location' , 'coord' , 'app' ,
2021-04-07 06:02:06 +00:00
'inform' , 'deleted' , 'extid' , 'post-type' , 'post-reason' , 'gravity' ,
2018-06-17 06:27:52 +00:00
'allow_cid' , 'allow_gid' , 'deny_cid' , 'deny_gid' ,
2021-03-06 12:47:10 +00:00
'author-id' , 'author-link' , 'author-name' , 'author-avatar' , 'owner-id' , 'owner-link' , 'contact-uid' ,
2022-02-12 18:38:36 +00:00
'signed_text' , 'network' , 'wall' , 'contact-id' , 'plink' , 'origin' ,
2021-03-06 12:47:10 +00:00
'thr-parent-id' , 'parent-uri-id' , 'postopts' , 'pubmail' ,
2021-02-13 19:56:03 +00:00
'event-created' , 'event-edited' , 'event-start' , 'event-finish' ,
'event-summary' , 'event-desc' , 'event-location' , 'event-type' ,
2021-10-03 17:21:17 +00:00
'event-nofinish' , 'event-ignore' , 'event-id' ];
2018-06-17 06:27:52 +00:00
2018-06-25 18:49:36 +00:00
// All fields in the item table
2020-04-16 04:20:59 +00:00
const ITEM_FIELDLIST = [ 'id' , 'uid' , 'parent' , 'uri' , 'parent-uri' , 'thr-parent' ,
2020-05-19 20:32:15 +00:00
'guid' , 'uri-id' , 'parent-uri-id' , 'thr-parent-id' , 'vid' ,
2021-02-13 19:56:03 +00:00
'contact-id' , 'wall' , 'gravity' , 'extid' , 'psid' ,
2018-10-18 21:35:48 +00:00
'created' , 'edited' , 'commented' , 'received' , 'changed' , 'verb' ,
2020-11-07 08:22:59 +00:00
'postopts' , 'plink' , 'resource-id' , 'event-id' , 'inform' ,
2021-04-07 06:02:06 +00:00
'allow_cid' , 'allow_gid' , 'deny_cid' , 'deny_gid' , 'post-type' , 'post-reason' ,
2021-02-13 19:56:03 +00:00
'private' , 'pubmail' , 'visible' , 'starred' ,
2022-02-12 18:38:36 +00:00
'unseen' , 'deleted' , 'origin' , 'mention' , 'global' , 'network' ,
2018-06-25 18:49:36 +00:00
'title' , 'content-warning' , 'body' , 'location' , 'coord' , 'app' ,
'rendered-hash' , 'rendered-html' , 'object-type' , 'object' , 'target-type' , 'target' ,
2019-05-29 19:48:03 +00:00
'author-id' , 'author-link' , 'author-name' , 'author-avatar' , 'author-network' ,
2020-09-22 05:36:01 +00:00
'owner-id' , 'owner-link' , 'owner-name' , 'owner-avatar' , 'causer-id' ];
2018-06-25 18:49:36 +00:00
2020-05-26 05:18:50 +00:00
// List of all verbs that don't need additional content data.
2018-07-06 06:37:33 +00:00
// Never reorder or remove entries from this list. Just add new ones at the end, if needed.
2019-10-23 22:25:43 +00:00
const ACTIVITIES = [
Activity :: LIKE , Activity :: DISLIKE ,
Activity :: ATTEND , Activity :: ATTENDNO , Activity :: ATTENDMAYBE ,
Activity :: FOLLOW ,
Activity :: ANNOUNCE ];
2018-07-05 22:00:38 +00:00
2020-03-02 07:57:23 +00:00
const PUBLIC = 0 ;
const PRIVATE = 1 ;
const UNLISTED = 2 ;
2018-01-09 21:13:45 +00:00
/**
2020-01-19 06:05:23 +00:00
* Update existing item entries
2018-01-09 21:13:45 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param array $fields The fields that are to be changed
2018-01-09 21:13:45 +00:00
* @ param array $condition The condition for finding the item entries
*
2018-01-09 22:16:16 +00:00
* In the future we may have to change permissions as well .
* Then we had to add the user id as third parameter .
*
* A return value of " 0 " doesn ' t mean an error - but that 0 rows had been changed .
*
* @ return integer | boolean number of affected rows - or " false " if there was an error
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-01-09 21:13:45 +00:00
*/
public static function update ( array $fields , array $condition )
{
if ( empty ( $condition ) || empty ( $fields )) {
return false ;
}
2021-04-18 09:57:08 +00:00
if ( isset ( $fields [ 'extid' ])) {
2021-04-18 07:08:16 +00:00
$fields [ 'external-id' ] = ItemURI :: getIdByURI ( $fields [ 'extid' ]);
}
2021-01-28 22:45:54 +00:00
if ( ! empty ( $fields [ 'verb' ])) {
$fields [ 'vid' ] = Verb :: getID ( $fields [ 'verb' ]);
2018-07-07 23:03:28 +00:00
}
2021-06-26 11:31:37 +00:00
if ( ! empty ( $fields [ 'edited' ])) {
2021-06-26 11:29:58 +00:00
$previous = Post :: selectFirst ([ 'edited' ], $condition );
}
2021-01-28 22:45:54 +00:00
$rows = Post :: update ( $fields , $condition );
if ( is_bool ( $rows )) {
return $rows ;
2018-06-24 23:09:13 +00:00
}
2018-01-09 21:13:45 +00:00
2021-01-28 22:45:54 +00:00
// We only need to call the line by line update for specific fields
if ( empty ( $fields [ 'body' ]) && empty ( $fields [ 'file' ]) &&
empty ( $fields [ 'attach' ]) && empty ( $fields [ 'edited' ])) {
return $rows ;
2018-06-30 21:15:24 +00:00
}
2021-01-28 22:45:54 +00:00
Logger :: info ( 'Updating per single row method' , [ 'fields' => $fields , 'condition' => $condition ]);
2018-01-09 21:13:45 +00:00
2021-05-04 05:18:03 +00:00
$items = Post :: select ([ 'id' , 'origin' , 'uri-id' , 'uid' , 'author-network' ], $condition );
2018-01-09 22:16:16 +00:00
2019-08-03 10:36:21 +00:00
$notify_items = [];
2018-07-20 12:19:26 +00:00
while ( $item = DBA :: fetch ( $items )) {
2021-01-28 22:45:54 +00:00
if ( ! empty ( $fields [ 'body' ])) {
2021-04-26 06:50:12 +00:00
Post\Media :: insertFromAttachmentData ( $item [ 'uri-id' ], $fields [ 'body' ]);
2021-01-28 22:45:54 +00:00
$content_fields = [ 'raw-body' => trim ( $fields [ 'raw-body' ] ? ? $fields [ 'body' ])];
2021-04-26 06:50:12 +00:00
2021-01-28 22:45:54 +00:00
// Remove all media attachments from the body and store them in the post-media table
2021-04-30 20:31:24 +00:00
// @todo On shared postings (Diaspora style and commented reshare) don't fetch content from the shared part
2021-01-28 22:45:54 +00:00
$content_fields [ 'raw-body' ] = Post\Media :: insertFromBody ( $item [ 'uri-id' ], $content_fields [ 'raw-body' ]);
$content_fields [ 'raw-body' ] = self :: setHashtags ( $content_fields [ 'raw-body' ]);
2021-05-30 06:13:20 +00:00
if ( $item [ 'author-network' ] != Protocol :: DFRN ) {
Post\Media :: insertFromRelevantUrl ( $item [ 'uri-id' ], $content_fields [ 'raw-body' ]);
}
Post\Content :: update ( $item [ 'uri-id' ], $content_fields );
2018-07-01 19:02:29 +00:00
}
2021-01-28 22:45:54 +00:00
if ( ! empty ( $fields [ 'file' ])) {
Post\Category :: storeTextByURIId ( $item [ 'uri-id' ], $item [ 'uid' ], $fields [ 'file' ]);
2018-06-30 13:54:01 +00:00
}
2020-10-31 13:26:08 +00:00
if ( ! empty ( $fields [ 'attach' ])) {
Post\Media :: insertFromAttachment ( $item [ 'uri-id' ], $fields [ 'attach' ]);
}
2018-05-17 05:49:55 +00:00
// We only need to notfiy others when it is an original entry from us.
2021-06-26 11:29:58 +00:00
// Only call the notifier when the item had been edited and records had been changed.
if ( $item [ 'origin' ] && ! empty ( $fields [ 'edited' ]) && ( $previous [ 'edited' ] != $fields [ 'edited' ])) {
2019-08-03 10:36:21 +00:00
$notify_items [] = $item [ 'id' ];
2018-02-07 20:09:37 +00:00
}
2018-01-09 21:13:45 +00:00
}
2018-07-20 12:19:26 +00:00
DBA :: close ( $items );
2019-08-03 10:36:21 +00:00
foreach ( $notify_items as $notify_item ) {
2021-02-14 14:24:48 +00:00
$post = Post :: selectFirst ([ 'uri-id' , 'uid' ], [ 'id' => $notify_item ]);
Worker :: add ( PRIORITY_HIGH , " Notifier " , Delivery :: POST , ( int ) $post [ 'uri-id' ], ( int ) $post [ 'uid' ]);
2019-08-03 10:36:21 +00:00
}
2018-01-09 22:16:16 +00:00
return $rows ;
2018-01-09 21:13:45 +00:00
}
2018-01-16 22:23:19 +00:00
2018-02-06 12:40:22 +00:00
/**
2020-01-19 06:05:23 +00:00
* Delete an item and notify others about it - if it was ours
2018-02-06 12:40:22 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param array $condition The condition for finding the item entries
* @ param integer $priority Priority for the notification
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-02-06 12:40:22 +00:00
*/
2020-03-03 06:47:28 +00:00
public static function markForDeletion ( $condition , $priority = PRIORITY_HIGH )
2018-02-06 12:40:22 +00:00
{
2021-01-16 04:13:22 +00:00
$items = Post :: select ([ 'id' ], $condition );
while ( $item = Post :: fetch ( $items )) {
2020-03-03 06:47:28 +00:00
self :: markForDeletionById ( $item [ 'id' ], $priority );
2018-02-06 12:40:22 +00:00
}
2018-07-20 12:19:26 +00:00
DBA :: close ( $items );
2018-02-06 12:40:22 +00:00
}
2018-05-29 05:22:57 +00:00
/**
2020-01-19 06:05:23 +00:00
* Delete an item for an user and notify others about it - if it was ours
2018-05-29 05:22:57 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param array $condition The condition for finding the item entries
* @ param integer $uid User who wants to delete this item
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-05-29 05:22:57 +00:00
*/
public static function deleteForUser ( $condition , $uid )
{
if ( $uid == 0 ) {
return ;
}
2021-01-16 04:13:22 +00:00
$items = Post :: select ([ 'id' , 'uid' , 'uri-id' ], $condition );
while ( $item = Post :: fetch ( $items )) {
2021-01-22 08:16:41 +00:00
if ( in_array ( $item [ 'uid' ], [ $uid , 0 ])) {
Post\User :: update ( $item [ 'uri-id' ], $uid , [ 'hidden' => true ], true );
2021-02-09 17:04:41 +00:00
Post\ThreadUser :: update ( $item [ 'uri-id' ], $uid , [ 'hidden' => true ], true );
2021-01-22 08:16:41 +00:00
}
2020-11-17 22:33:44 +00:00
2021-01-31 23:37:34 +00:00
if ( $item [ 'uid' ] == $uid ) {
2020-03-03 06:47:28 +00:00
self :: markForDeletionById ( $item [ 'id' ], PRIORITY_HIGH );
2021-01-31 23:37:34 +00:00
} elseif ( $item [ 'uid' ] != 0 ) {
2021-03-24 22:22:14 +00:00
Logger :: notice ( 'Wrong ownership. Not deleting item' , [ 'id' => $item [ 'id' ]]);
2018-05-29 05:22:57 +00:00
}
2018-02-06 12:40:22 +00:00
}
2018-07-20 12:19:26 +00:00
DBA :: close ( $items );
2018-02-06 12:40:22 +00:00
}
2018-01-17 23:22:01 +00:00
/**
2020-03-03 06:47:28 +00:00
* Mark an item for deletion , delete related data and notify others about it - if it was ours
2018-01-17 23:22:01 +00:00
*
2020-03-03 06:47:28 +00:00
* @ param integer $item_id
2018-02-06 12:40:22 +00:00
* @ param integer $priority Priority for the notification
2018-01-17 23:22:01 +00:00
*
2018-03-17 01:45:02 +00:00
* @ return boolean success
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-01-17 23:22:01 +00:00
*/
2020-03-03 06:47:28 +00:00
public static function markForDeletionById ( $item_id , $priority = PRIORITY_HIGH )
2018-01-16 23:16:53 +00:00
{
2020-03-04 20:59:19 +00:00
Logger :: info ( 'Mark item for deletion by id' , [ 'id' => $item_id , 'callstack' => System :: callstack ()]);
2018-01-16 23:16:53 +00:00
// locate item to be deleted
2021-01-27 10:01:42 +00:00
$fields = [ 'id' , 'uri' , 'uri-id' , 'uid' , 'parent' , 'parent-uri-id' , 'origin' ,
2021-01-21 07:16:41 +00:00
'deleted' , 'resource-id' , 'event-id' ,
2021-01-19 23:26:24 +00:00
'verb' , 'object-type' , 'object' , 'target' , 'contact-id' , 'psid' , 'gravity' ];
2021-01-16 04:13:22 +00:00
$item = Post :: selectFirst ( $fields , [ 'id' => $item_id ]);
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $item )) {
2020-03-04 20:59:19 +00:00
Logger :: info ( 'Item not found.' , [ 'id' => $item_id ]);
2018-01-16 23:16:53 +00:00
return false ;
}
if ( $item [ 'deleted' ]) {
2020-03-04 20:59:19 +00:00
Logger :: info ( 'Item has already been marked for deletion.' , [ 'id' => $item_id ]);
2018-01-16 23:16:53 +00:00
return false ;
}
2021-01-16 04:13:22 +00:00
$parent = Post :: selectFirst ([ 'origin' ], [ 'id' => $item [ 'parent' ]]);
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $parent )) {
2018-01-17 23:22:01 +00:00
$parent = [ 'origin' => false ];
}
2018-01-16 23:16:53 +00:00
// clean up categories and tags so they don't end up as orphans
2021-01-21 07:16:41 +00:00
Post\Category :: deleteByURIId ( $item [ 'uri-id' ], $item [ 'uid' ]);
2018-01-16 23:16:53 +00:00
2018-01-17 07:08:49 +00:00
/*
* If item is a link to a photo resource , nuke all the associated photos
* ( visitors will not have photo resources )
* This only applies to photos uploaded from the photos page . Photos inserted into a post do not
* generate a resource - id and therefore aren ' t intimately linked to the item .
*/
2019-01-02 15:37:55 +00:00
/// @TODO: this should first check if photo is used elsewhere
2018-01-16 23:16:53 +00:00
if ( strlen ( $item [ 'resource-id' ])) {
2018-12-11 19:03:29 +00:00
Photo :: delete ([ 'resource-id' => $item [ 'resource-id' ], 'uid' => $item [ 'uid' ]]);
2018-01-16 23:16:53 +00:00
}
2018-02-06 12:40:22 +00:00
// If item is a link to an event, delete the event.
2018-01-16 23:16:53 +00:00
if ( intval ( $item [ 'event-id' ])) {
2018-03-17 01:45:02 +00:00
Event :: delete ( $item [ 'event-id' ]);
2018-01-16 23:16:53 +00:00
}
// If item has attachments, drop them
2020-11-07 08:22:59 +00:00
$attachments = Post\Media :: getByURIId ( $item [ 'uri-id' ], [ Post\Media :: DOCUMENT ]);
foreach ( $attachments as $attachment ) {
if ( preg_match ( " |attach/( \ d+)| " , $attachment [ 'url' ], $matches )) {
2019-01-02 15:17:29 +00:00
Attach :: delete ([ 'id' => $matches [ 1 ], 'uid' => $item [ 'uid' ]]);
2018-07-01 18:46:45 +00:00
}
2018-01-16 23:16:53 +00:00
}
2018-01-20 22:16:43 +00:00
// Set the item to "deleted"
2018-07-29 03:54:34 +00:00
$item_fields = [ 'deleted' => true , 'edited' => DateTimeFormat :: utcNow (), 'changed' => DateTimeFormat :: utcNow ()];
2021-02-06 13:42:21 +00:00
Post :: update ( $item_fields , [ 'id' => $item [ 'id' ]]);
2018-01-20 22:16:43 +00:00
2020-11-17 22:33:44 +00:00
Post\Category :: storeTextByURIId ( $item [ 'uri-id' ], $item [ 'uid' ], '' );
2018-01-20 22:16:43 +00:00
2021-01-27 10:01:42 +00:00
if ( ! Post :: exists ([ " `uri-id` = ? AND `uid` != 0 AND NOT `deleted` " , $item [ 'uri-id' ]])) {
self :: markForDeletion ([ 'uri-id' => $item [ 'uri-id' ], 'uid' => 0 , 'deleted' => false ], $priority );
2018-05-15 16:40:13 +00:00
}
2020-05-02 19:34:02 +00:00
Post\DeliveryData :: delete ( $item [ 'uri-id' ]);
2018-07-19 21:56:52 +00:00
2018-01-20 22:16:43 +00:00
// If it's the parent of a comment thread, kill all the kids
2020-05-27 12:19:06 +00:00
if ( $item [ 'gravity' ] == GRAVITY_PARENT ) {
2020-03-03 06:47:28 +00:00
self :: markForDeletion ([ 'parent' => $item [ 'parent' ], 'deleted' => false ], $priority );
2018-01-20 22:16:43 +00:00
}
2018-01-16 23:16:53 +00:00
2018-05-15 15:51:58 +00:00
// Is it our comment and/or our thread?
2020-11-28 22:53:58 +00:00
if (( $item [ 'origin' ] || $parent [ 'origin' ]) && ( $item [ 'uid' ] != 0 )) {
2018-05-15 15:51:58 +00:00
// When we delete the original post we will delete all existing copies on the server as well
2021-01-27 10:01:42 +00:00
self :: markForDeletion ([ 'uri-id' => $item [ 'uri-id' ], 'deleted' => false ], $priority );
2018-05-15 15:51:58 +00:00
// send the notification upstream/downstream
2020-11-30 21:40:55 +00:00
if ( $priority ) {
2021-02-14 14:24:48 +00:00
Worker :: add ([ 'priority' => $priority , 'dont_fork' => true ], " Notifier " , Delivery :: DELETION , ( int ) $item [ 'uri-id' ], ( int ) $item [ 'uid' ]);
2020-11-30 21:40:55 +00:00
}
2018-05-26 18:07:27 +00:00
} elseif ( $item [ 'uid' ] != 0 ) {
2020-11-17 22:33:44 +00:00
Post\User :: update ( $item [ 'uri-id' ], $item [ 'uid' ], [ 'hidden' => true ]);
2021-02-09 17:04:41 +00:00
Post\ThreadUser :: update ( $item [ 'uri-id' ], $item [ 'uid' ], [ 'hidden' => true ]);
2018-01-16 23:16:53 +00:00
}
2020-03-04 20:59:19 +00:00
Logger :: info ( 'Item has been marked for deletion.' , [ 'id' => $item_id ]);
2018-05-15 17:50:29 +00:00
2018-01-16 23:16:53 +00:00
return true ;
}
2021-07-30 06:19:25 +00:00
public static function guid ( $item , $notify )
2018-02-21 04:13:13 +00:00
{
2018-07-08 09:37:05 +00:00
if ( ! empty ( $item [ 'guid' ])) {
2021-11-05 19:59:18 +00:00
return trim ( $item [ 'guid' ]);
2018-02-21 04:13:13 +00:00
}
if ( $notify ) {
// We have to avoid duplicates. So we create the GUID in form of a hash of the plink or uri.
// We add the hash of our own host because our host is the original creator of the post.
2019-12-15 23:47:24 +00:00
$prefix_host = DI :: baseUrl () -> getHostname ();
2018-02-21 04:13:13 +00:00
} else {
$prefix_host = '' ;
// We are only storing the post so we create a GUID from the original hostname.
2018-02-21 21:39:07 +00:00
if ( ! empty ( $item [ 'author-link' ])) {
$parsed = parse_url ( $item [ 'author-link' ]);
2018-02-21 04:13:13 +00:00
if ( ! empty ( $parsed [ 'host' ])) {
$prefix_host = $parsed [ 'host' ];
}
}
2018-02-21 21:39:07 +00:00
if ( empty ( $prefix_host ) && ! empty ( $item [ 'plink' ])) {
$parsed = parse_url ( $item [ 'plink' ]);
2018-02-21 04:13:13 +00:00
if ( ! empty ( $parsed [ 'host' ])) {
$prefix_host = $parsed [ 'host' ];
}
}
2018-02-21 21:39:07 +00:00
if ( empty ( $prefix_host ) && ! empty ( $item [ 'uri' ])) {
$parsed = parse_url ( $item [ 'uri' ]);
2018-02-21 04:13:13 +00:00
if ( ! empty ( $parsed [ 'host' ])) {
$prefix_host = $parsed [ 'host' ];
}
}
2018-02-26 11:48:05 +00:00
// Is it in the format data@host.tld? - Used for mail contacts
if ( empty ( $prefix_host ) && ! empty ( $item [ 'author-link' ]) && strstr ( $item [ 'author-link' ], '@' )) {
$mailparts = explode ( '@' , $item [ 'author-link' ]);
$prefix_host = array_pop ( $mailparts );
}
2018-02-21 04:13:13 +00:00
}
2018-02-21 21:39:07 +00:00
if ( ! empty ( $item [ 'plink' ])) {
$guid = self :: guidFromUri ( $item [ 'plink' ], $prefix_host );
} elseif ( ! empty ( $item [ 'uri' ])) {
$guid = self :: guidFromUri ( $item [ 'uri' ], $prefix_host );
2018-02-21 04:13:13 +00:00
} else {
2018-09-27 11:52:15 +00:00
$guid = System :: createUUID ( hash ( 'crc32' , $prefix_host ));
2018-02-21 04:13:13 +00:00
}
return $guid ;
}
2018-02-21 21:39:07 +00:00
private static function contactId ( $item )
2018-02-21 21:08:37 +00:00
{
2019-08-02 16:46:26 +00:00
if ( ! empty ( $item [ 'contact-id' ]) && DBA :: exists ( 'contact' , [ 'self' => true , 'id' => $item [ 'contact-id' ]])) {
return $item [ 'contact-id' ];
2019-08-13 15:54:47 +00:00
} elseif (( $item [ 'gravity' ] == GRAVITY_PARENT ) && ! empty ( $item [ 'uid' ]) && ! empty ( $item [ 'contact-id' ]) && Contact :: isSharing ( $item [ 'contact-id' ], $item [ 'uid' ])) {
return $item [ 'contact-id' ];
2019-08-02 16:46:26 +00:00
} elseif ( ! empty ( $item [ 'uid' ]) && ! Contact :: isSharing ( $item [ 'author-id' ], $item [ 'uid' ])) {
2019-07-30 22:02:32 +00:00
return $item [ 'author-id' ];
} elseif ( ! empty ( $item [ 'contact-id' ])) {
return $item [ 'contact-id' ];
} else {
2018-02-22 07:05:56 +00:00
$contact_id = Contact :: getIdForURL ( $item [ 'author-link' ], $item [ 'uid' ]);
2019-07-30 22:02:32 +00:00
if ( ! empty ( $contact_id )) {
return $contact_id ;
2018-02-21 21:08:37 +00:00
}
}
2019-07-30 22:02:32 +00:00
return $item [ 'author-id' ];
2018-02-21 21:08:37 +00:00
}
2019-07-31 16:07:50 +00:00
/**
* Write an item array into a spool file to be inserted later .
* This command is called whenever there are issues storing an item .
*
* @ param array $item The item fields that are to be inserted
* @ throws \Exception
*/
2019-07-31 14:09:27 +00:00
private static function spool ( $orig_item )
{
// Now we store the data in the spool directory
// We use "microtime" to keep the arrival order and "mt_rand" to avoid duplicates
$file = 'item-' . round ( microtime ( true ) * 10000 ) . '-' . mt_rand () . '.msg' ;
2021-11-04 20:29:59 +00:00
$spoolpath = System :: getSpoolPath ();
2019-07-31 14:09:27 +00:00
if ( $spoolpath != " " ) {
$spool = $spoolpath . '/' . $file ;
file_put_contents ( $spool , json_encode ( $orig_item ));
Logger :: warning ( " Item wasn't stored - Item was spooled into file " , [ 'file' => $file ]);
}
}
2020-05-13 19:26:59 +00:00
/**
* Check if the item array is a duplicate
*
* @ param array $item
* @ return boolean is it a duplicate ?
*/
private static function isDuplicate ( array $item )
2018-01-28 11:18:08 +00:00
{
2020-05-12 20:13:48 +00:00
// Checking if there is already an item with the same guid
$condition = [ 'guid' => $item [ 'guid' ], 'network' => $item [ 'network' ], 'uid' => $item [ 'uid' ]];
2021-01-16 04:13:22 +00:00
if ( Post :: exists ( $condition )) {
2021-07-23 12:39:37 +00:00
Logger :: notice ( 'Found already existing item' , $condition );
2020-05-12 20:13:48 +00:00
return true ;
2018-01-28 11:18:08 +00:00
}
2021-01-27 10:01:42 +00:00
$condition = [ 'uri-id' => $item [ 'uri-id' ], 'uid' => $item [ 'uid' ],
'network' => [ $item [ 'network' ], Protocol :: DFRN ]];
2021-01-16 04:13:22 +00:00
if ( Post :: exists ( $condition )) {
2021-07-23 12:39:37 +00:00
Logger :: notice ( 'duplicated item with the same uri found.' , $condition );
2020-05-12 20:13:48 +00:00
return true ;
2018-05-15 04:33:28 +00:00
}
2020-05-12 20:13:48 +00:00
// On Friendica and Diaspora the GUID is unique
if ( in_array ( $item [ 'network' ], [ Protocol :: DFRN , Protocol :: DIASPORA ])) {
$condition = [ 'guid' => $item [ 'guid' ], 'uid' => $item [ 'uid' ]];
2021-01-16 04:13:22 +00:00
if ( Post :: exists ( $condition )) {
2021-07-23 12:39:37 +00:00
Logger :: notice ( 'duplicated item with the same guid found.' , $condition );
2020-05-12 20:13:48 +00:00
return true ;
}
} elseif ( $item [ 'network' ] == Protocol :: OSTATUS ) {
// Check for an existing post with the same content. There seems to be a problem with OStatus.
$condition = [ " `body` = ? AND `network` = ? AND `created` = ? AND `contact-id` = ? AND `uid` = ? " ,
$item [ 'body' ], $item [ 'network' ], $item [ 'created' ], $item [ 'contact-id' ], $item [ 'uid' ]];
2021-01-16 04:13:22 +00:00
if ( Post :: exists ( $condition )) {
2020-05-12 20:13:48 +00:00
Logger :: notice ( 'duplicated item with the same body found.' , $item );
return true ;
2018-01-28 11:18:08 +00:00
}
}
2020-05-12 20:13:48 +00:00
/*
* 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 .
*/
2021-01-27 10:01:42 +00:00
if (( $item [ 'uid' ] == 0 ) && Post :: exists ([ 'uri-id' => $item [ 'uri-id' ], 'uid' => 0 ])) {
Logger :: notice ( 'Global item already stored.' , [ 'uri-id' => $item [ 'uri-id' ], 'network' => $item [ 'network' ]]);
2020-05-12 20:13:48 +00:00
return true ;
2018-03-22 16:18:49 +00:00
}
2020-05-12 20:13:48 +00:00
return false ;
}
2019-10-23 00:05:11 +00:00
2020-05-13 19:26:59 +00:00
/**
* Check if the item array is valid
*
* @ param array $item
* @ return boolean item is valid
*/
2020-08-15 20:05:08 +00:00
public static function isValid ( array $item )
2020-05-12 20:13:48 +00:00
{
// When there is no content then we don't post it
2021-06-21 03:53:53 +00:00
if (( $item [ 'body' ] . $item [ 'title' ] == '' ) && ( empty ( $item [ 'uri-id' ]) || ! Post\Media :: existsByURIId ( $item [ 'uri-id' ]))) {
2020-05-12 20:13:48 +00:00
Logger :: notice ( 'No body, no title.' );
return false ;
2018-01-28 11:18:08 +00:00
}
2020-12-15 14:41:58 +00:00
if ( ! empty ( $item [ 'uid' ])) {
$owner = User :: getOwnerDataById ( $item [ 'uid' ], false );
if ( ! $owner ) {
Logger :: notice ( 'Missing item user owner data' , [ 'uid' => $item [ 'uid' ]]);
return false ;
}
2020-12-15 19:24:42 +00:00
if ( $owner [ 'account_expired' ] || $owner [ 'account_removed' ]) {
2020-12-15 14:41:58 +00:00
Logger :: notice ( 'Item user has been deleted/expired/removed' , [ 'uid' => $item [ 'uid' ], 'deleted' => $owner [ 'deleted' ], 'account_expired' => $owner [ 'account_expired' ], 'account_removed' => $owner [ 'account_removed' ]]);
return false ;
}
}
2020-08-15 20:05:08 +00:00
if ( ! empty ( $item [ 'author-id' ]) && Contact :: isBlocked ( $item [ 'author-id' ])) {
2020-05-12 20:13:48 +00:00
Logger :: notice ( 'Author is blocked node-wide' , [ 'author-link' => $item [ 'author-link' ], 'item-uri' => $item [ 'uri' ]]);
return false ;
}
if ( ! empty ( $item [ 'author-link' ]) && Network :: isUrlBlocked ( $item [ 'author-link' ])) {
Logger :: notice ( 'Author server is blocked' , [ 'author-link' => $item [ 'author-link' ], 'item-uri' => $item [ 'uri' ]]);
return false ;
}
2020-08-15 20:05:08 +00:00
if ( ! empty ( $item [ 'owner-id' ]) && Contact :: isBlocked ( $item [ 'owner-id' ])) {
2020-05-12 20:13:48 +00:00
Logger :: notice ( 'Owner is blocked node-wide' , [ 'owner-link' => $item [ 'owner-link' ], 'item-uri' => $item [ 'uri' ]]);
return false ;
}
if ( ! empty ( $item [ 'owner-link' ]) && Network :: isUrlBlocked ( $item [ 'owner-link' ])) {
Logger :: notice ( 'Owner server is blocked' , [ 'owner-link' => $item [ 'owner-link' ], 'item-uri' => $item [ 'uri' ]]);
return false ;
}
2020-11-11 07:50:22 +00:00
if ( ! empty ( $item [ 'uid' ]) && ! self :: isAllowedByUser ( $item , $item [ 'uid' ])) {
2020-05-12 20:13:48 +00:00
return false ;
}
if ( $item [ 'verb' ] == Activity :: FOLLOW ) {
if ( ! $item [ 'origin' ] && ( $item [ 'author-id' ] == Contact :: getPublicIdByUserId ( $item [ 'uid' ]))) {
// Our own follow request can be relayed to us. We don't store it to avoid notification chaos.
2020-05-13 05:48:26 +00:00
Logger :: info ( " Follow: Don't store not origin follow request " , [ 'parent-uri' => $item [ 'parent-uri' ]]);
2020-05-12 20:13:48 +00:00
return false ;
}
$condition = [ 'verb' => Activity :: FOLLOW , 'uid' => $item [ 'uid' ],
'parent-uri' => $item [ 'parent-uri' ], 'author-id' => $item [ 'author-id' ]];
2021-01-16 04:13:22 +00:00
if ( Post :: exists ( $condition )) {
2020-05-12 20:13:48 +00:00
// It happens that we receive multiple follow requests by the same author - we only store one.
2020-05-13 05:48:26 +00:00
Logger :: info ( 'Follow: Found existing follow request from author' , [ 'author-id' => $item [ 'author-id' ], 'parent-uri' => $item [ 'parent-uri' ]]);
2020-05-12 20:13:48 +00:00
return false ;
}
}
return true ;
}
2020-11-30 20:32:56 +00:00
/**
* Check if the item array is too old
*
* @ param array $item
* @ return boolean item is too old
*/
2020-11-30 20:59:18 +00:00
public static function isTooOld ( array $item )
2020-11-30 20:32:56 +00:00
{
// check for create date and expire time
$expire_interval = DI :: config () -> get ( 'system' , 'dbclean-expire-days' , 0 );
$user = DBA :: selectFirst ( 'user' , [ 'expire' ], [ 'uid' => $item [ 'uid' ]]);
if ( DBA :: isResult ( $user ) && ( $user [ 'expire' ] > 0 ) && (( $user [ 'expire' ] < $expire_interval ) || ( $expire_interval == 0 ))) {
$expire_interval = $user [ 'expire' ];
}
if (( $expire_interval > 0 ) && ! empty ( $item [ 'created' ])) {
$expire_date = time () - ( $expire_interval * 86400 );
$created_date = strtotime ( $item [ 'created' ]);
if ( $created_date < $expire_date ) {
Logger :: notice ( 'Item created before expiration interval.' , [
'created' => date ( 'c' , $created_date ),
'expired' => date ( 'c' , $expire_date ),
'$item' => $item
]);
return true ;
}
}
return false ;
}
2020-05-13 19:26:59 +00:00
/**
2020-05-14 03:48:26 +00:00
* Return the id of the given item array if it has been stored before
2020-05-13 19:26:59 +00:00
*
* @ param array $item
* @ return integer item id
*/
private static function getDuplicateID ( array $item )
2020-05-12 20:13:48 +00:00
{
2019-07-01 22:14:34 +00:00
if ( empty ( $item [ 'network' ]) || in_array ( $item [ 'network' ], Protocol :: FEDERATED )) {
2021-01-27 10:01:42 +00:00
$condition = [ " `uri-id` = ? AND `uid` = ? AND `network` IN (?, ?, ?, ?) " ,
$item [ 'uri-id' ], $item [ 'uid' ],
2019-09-10 04:59:12 +00:00
Protocol :: ACTIVITYPUB , Protocol :: DIASPORA , Protocol :: DFRN , Protocol :: OSTATUS ];
2021-01-16 04:13:22 +00:00
$existing = Post :: selectFirst ([ 'id' , 'network' ], $condition );
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $existing )) {
2018-01-28 11:18:08 +00:00
// We only log the entries with a different user id than 0. Otherwise we would have too many false positives
2020-05-12 20:13:48 +00:00
if ( $item [ 'uid' ] != 0 ) {
2019-02-23 14:25:21 +00:00
Logger :: notice ( 'Item already existed for user' , [
2021-01-27 10:01:42 +00:00
'uri-id' => $item [ 'uri-id' ],
2020-05-12 20:13:48 +00:00
'uid' => $item [ 'uid' ],
2019-02-23 14:25:21 +00:00
'network' => $item [ 'network' ],
'existing_id' => $existing [ " id " ],
'existing_network' => $existing [ " network " ]
]);
2018-01-28 11:18:08 +00:00
}
2018-02-21 22:55:23 +00:00
return $existing [ " id " ];
2018-01-28 11:18:08 +00:00
}
}
2020-05-12 20:13:48 +00:00
return 0 ;
}
2020-05-13 19:26:59 +00:00
/**
2020-11-11 07:44:43 +00:00
* Fetch top - level parent data for the given item array
2020-05-13 19:26:59 +00:00
*
* @ param array $item
* @ return array item array with parent data
2020-11-11 07:44:43 +00:00
* @ throws \Exception
2020-05-13 19:26:59 +00:00
*/
2020-11-11 07:44:43 +00:00
private static function getTopLevelParent ( array $item )
2020-05-12 20:13:48 +00:00
{
2020-11-11 07:44:43 +00:00
$fields = [ 'uid' , 'uri' , 'parent-uri' , 'id' , 'deleted' ,
2021-01-27 10:01:42 +00:00
'uri-id' , 'parent-uri-id' ,
2020-05-12 20:13:48 +00:00
'allow_cid' , 'allow_gid' , 'deny_cid' , 'deny_gid' ,
2022-02-12 18:38:36 +00:00
'wall' , 'private' , 'origin' , 'author-id' ];
2021-01-27 10:01:42 +00:00
$condition = [ 'uri-id' => $item [ 'thr-parent-id' ], 'uid' => $item [ 'uid' ]];
2020-05-12 20:13:48 +00:00
$params = [ 'order' => [ 'id' => false ]];
2021-01-16 22:37:27 +00:00
$parent = Post :: selectFirst ( $fields , $condition , $params );
2020-05-12 20:13:48 +00:00
2021-09-12 01:52:10 +00:00
if ( ! DBA :: isResult ( $parent ) && $item [ 'origin' ]) {
$stored = Item :: storeForUserByUriId ( $item [ 'thr-parent-id' ], $item [ 'uid' ]);
Logger :: info ( 'Stored thread parent item for user' , [ 'uri-id' => $item [ 'thr-parent-id' ], 'uid' => $item [ 'uid' ], 'stored' => $stored ]);
$parent = Post :: selectFirst ( $fields , $condition , $params );
}
2020-05-12 20:13:48 +00:00
if ( ! DBA :: isResult ( $parent )) {
2021-01-27 10:01:42 +00:00
Logger :: notice ( 'item parent was not found - ignoring item' , [ 'thr-parent-id' => $item [ 'thr-parent-id' ], 'uid' => $item [ 'uid' ]]);
2020-05-12 20:13:48 +00:00
return [];
2020-11-11 07:44:43 +00:00
}
2020-05-12 20:13:48 +00:00
2021-01-27 10:01:42 +00:00
if ( $parent [ 'uri-id' ] == $parent [ 'parent-uri-id' ]) {
2020-11-11 07:44:43 +00:00
return $parent ;
}
2020-05-12 20:13:48 +00:00
2021-01-27 10:01:42 +00:00
$condition = [ 'uri-id' => $parent [ 'parent-uri-id' ],
'parent-uri-id' => $parent [ 'parent-uri-id' ],
2020-11-12 03:59:57 +00:00
'uid' => $parent [ 'uid' ]];
2020-11-14 04:56:19 +00:00
$params = [ 'order' => [ 'id' => false ]];
2021-01-16 22:37:27 +00:00
$toplevel_parent = Post :: selectFirst ( $fields , $condition , $params );
2021-09-12 01:52:10 +00:00
if ( ! DBA :: isResult ( $toplevel_parent ) && $item [ 'origin' ]) {
$stored = Item :: storeForUserByUriId ( $item [ 'parent-uri-id' ], $item [ 'uid' ]);
Logger :: info ( 'Stored parent item for user' , [ 'uri-id' => $item [ 'parent-uri-id' ], 'uid' => $item [ 'uid' ], 'stored' => $stored ]);
$toplevel_parent = Post :: selectFirst ( $fields , $condition , $params );
}
2020-11-11 07:44:43 +00:00
if ( ! DBA :: isResult ( $toplevel_parent )) {
2021-01-27 10:01:42 +00:00
Logger :: notice ( 'item top level parent was not found - ignoring item' , [ 'parent-uri-id' => $parent [ 'parent-uri-id' ], 'uid' => $parent [ 'uid' ]]);
2020-11-11 07:44:43 +00:00
return [];
2020-05-12 20:13:48 +00:00
}
2020-11-11 07:44:43 +00:00
return $toplevel_parent ;
2020-05-12 20:13:48 +00:00
}
2020-05-13 19:26:59 +00:00
/**
* Get the gravity for the given item array
*
* @ param array $item
* @ return integer gravity
*/
private static function getGravity ( array $item )
2020-05-12 20:13:48 +00:00
{
$activity = DI :: activity ();
if ( isset ( $item [ 'gravity' ])) {
return intval ( $item [ 'gravity' ]);
2021-01-27 10:01:42 +00:00
} elseif ( $item [ 'parent-uri-id' ] === $item [ 'uri-id' ]) {
2020-05-12 20:13:48 +00:00
return GRAVITY_PARENT ;
} elseif ( $activity -> match ( $item [ 'verb' ], Activity :: POST )) {
return GRAVITY_COMMENT ;
} elseif ( $activity -> match ( $item [ 'verb' ], Activity :: FOLLOW )) {
return GRAVITY_ACTIVITY ;
2020-08-09 18:42:25 +00:00
} elseif ( $activity -> match ( $item [ 'verb' ], Activity :: ANNOUNCE )) {
return GRAVITY_ACTIVITY ;
2020-05-12 20:13:48 +00:00
}
Logger :: info ( 'Unknown gravity for verb' , [ 'verb' => $item [ 'verb' ]]);
return GRAVITY_UNKNOWN ; // Should not happen
}
2021-08-06 18:49:17 +00:00
public static function insert ( array $item , bool $notify = false , bool $post_local = true )
2020-05-12 20:13:48 +00:00
{
$orig_item = $item ;
$priority = PRIORITY_HIGH ;
// If it is a posting where users should get notifications, then define it as wall posting
if ( $notify ) {
$item [ 'wall' ] = 1 ;
$item [ 'origin' ] = 1 ;
$item [ 'network' ] = Protocol :: DFRN ;
2020-11-29 09:01:51 +00:00
$item [ 'protocol' ] = Conversation :: PARCEL_DIRECT ;
2021-01-09 12:59:30 +00:00
$item [ 'direction' ] = Conversation :: PUSH ;
2020-05-12 20:13:48 +00:00
2020-12-08 21:58:32 +00:00
if ( in_array ( $notify , PRIORITIES )) {
2020-05-12 20:13:48 +00:00
$priority = $notify ;
}
} else {
$item [ 'network' ] = trim (( $item [ 'network' ] ? ? '' ) ? : Protocol :: PHANTOM );
}
$uid = intval ( $item [ 'uid' ]);
$item [ 'guid' ] = self :: guid ( $item , $notify );
2020-11-11 15:07:39 +00:00
$item [ 'uri' ] = substr ( trim ( $item [ 'uri' ] ? ? '' ) ? : self :: newURI ( $item [ 'uid' ], $item [ 'guid' ]), 0 , 255 );
2020-05-12 20:13:48 +00:00
// Store URI data
$item [ 'uri-id' ] = ItemURI :: insert ([ 'uri' => $item [ 'uri' ], 'guid' => $item [ 'guid' ]]);
2020-11-11 07:44:43 +00:00
// Backward compatibility: parent-uri used to be the direct parent uri.
// If it is provided without a thr-parent, it probably is the old behavior.
$item [ 'thr-parent' ] = trim ( $item [ 'thr-parent' ] ? ? $item [ 'parent-uri' ] ? ? $item [ 'uri' ]);
$item [ 'parent-uri' ] = $item [ 'thr-parent' ];
2021-01-27 10:01:42 +00:00
$item [ 'thr-parent-id' ] = $item [ 'parent-uri-id' ] = ItemURI :: getIdByURI ( $item [ 'thr-parent' ]);
2020-05-12 20:13:48 +00:00
// Store conversation data
$item = Conversation :: insert ( $item );
/*
* Do we already have this item ?
* We have to check several networks since Friendica posts could be repeated
* via OStatus ( maybe Diasporsa as well )
*/
$duplicate = self :: getDuplicateID ( $item );
if ( $duplicate ) {
return $duplicate ;
}
// Additional duplicate checks
/// @todo Check why the first duplication check returns the item number and the second a 0
if ( self :: isDuplicate ( $item )) {
return 0 ;
}
2018-01-28 11:18:08 +00:00
2021-04-07 06:02:06 +00:00
if ( ! isset ( $item [ 'post-type' ])) {
$item [ 'post-type' ] = empty ( $item [ 'title' ]) ? self :: PT_NOTE : self :: PT_ARTICLE ;
}
2019-10-16 12:35:14 +00:00
$item [ 'wall' ] = intval ( $item [ 'wall' ] ? ? 0 );
$item [ 'extid' ] = trim ( $item [ 'extid' ] ? ? '' );
$item [ 'author-name' ] = trim ( $item [ 'author-name' ] ? ? '' );
$item [ 'author-link' ] = trim ( $item [ 'author-link' ] ? ? '' );
$item [ 'author-avatar' ] = trim ( $item [ 'author-avatar' ] ? ? '' );
$item [ 'owner-name' ] = trim ( $item [ 'owner-name' ] ? ? '' );
$item [ 'owner-link' ] = trim ( $item [ 'owner-link' ] ? ? '' );
$item [ 'owner-avatar' ] = trim ( $item [ 'owner-avatar' ] ? ? '' );
2018-11-30 14:06:22 +00:00
$item [ 'received' ] = ( isset ( $item [ 'received' ]) ? DateTimeFormat :: utc ( $item [ 'received' ]) : DateTimeFormat :: utcNow ());
$item [ 'created' ] = ( isset ( $item [ 'created' ]) ? DateTimeFormat :: utc ( $item [ 'created' ]) : $item [ 'received' ]);
$item [ 'edited' ] = ( isset ( $item [ 'edited' ]) ? DateTimeFormat :: utc ( $item [ 'edited' ]) : $item [ 'created' ]);
$item [ 'changed' ] = ( isset ( $item [ 'changed' ]) ? DateTimeFormat :: utc ( $item [ 'changed' ]) : $item [ 'created' ]);
$item [ 'commented' ] = ( isset ( $item [ 'commented' ]) ? DateTimeFormat :: utc ( $item [ 'commented' ]) : $item [ 'created' ]);
2020-03-25 07:07:34 +00:00
$item [ 'title' ] = substr ( trim ( $item [ 'title' ] ? ? '' ), 0 , 255 );
2019-10-16 12:35:14 +00:00
$item [ 'location' ] = trim ( $item [ 'location' ] ? ? '' );
$item [ 'coord' ] = trim ( $item [ 'coord' ] ? ? '' );
2018-11-30 14:06:22 +00:00
$item [ 'visible' ] = ( isset ( $item [ 'visible' ]) ? intval ( $item [ 'visible' ]) : 1 );
2018-02-21 21:39:07 +00:00
$item [ 'deleted' ] = 0 ;
2019-10-16 12:35:14 +00:00
$item [ 'verb' ] = trim ( $item [ 'verb' ] ? ? '' );
$item [ 'object-type' ] = trim ( $item [ 'object-type' ] ? ? '' );
$item [ 'object' ] = trim ( $item [ 'object' ] ? ? '' );
$item [ 'target-type' ] = trim ( $item [ 'target-type' ] ? ? '' );
$item [ 'target' ] = trim ( $item [ 'target' ] ? ? '' );
2020-03-25 23:18:07 +00:00
$item [ 'plink' ] = substr ( trim ( $item [ 'plink' ] ? ? '' ), 0 , 255 );
2019-10-16 12:35:14 +00:00
$item [ 'allow_cid' ] = trim ( $item [ 'allow_cid' ] ? ? '' );
$item [ 'allow_gid' ] = trim ( $item [ 'allow_gid' ] ? ? '' );
$item [ 'deny_cid' ] = trim ( $item [ 'deny_cid' ] ? ? '' );
$item [ 'deny_gid' ] = trim ( $item [ 'deny_gid' ] ? ? '' );
2020-03-02 07:57:23 +00:00
$item [ 'private' ] = intval ( $item [ 'private' ] ? ? self :: PUBLIC );
2019-10-16 12:35:14 +00:00
$item [ 'body' ] = trim ( $item [ 'body' ] ? ? '' );
2020-10-29 05:20:26 +00:00
$item [ 'raw-body' ] = trim ( $item [ 'raw-body' ] ? ? $item [ 'body' ]);
2019-10-16 12:35:14 +00:00
$item [ 'app' ] = trim ( $item [ 'app' ] ? ? '' );
$item [ 'origin' ] = intval ( $item [ 'origin' ] ? ? 0 );
$item [ 'postopts' ] = trim ( $item [ 'postopts' ] ? ? '' );
$item [ 'resource-id' ] = trim ( $item [ 'resource-id' ] ? ? '' );
$item [ 'event-id' ] = intval ( $item [ 'event-id' ] ? ? 0 );
$item [ 'inform' ] = trim ( $item [ 'inform' ] ? ? '' );
$item [ 'file' ] = trim ( $item [ 'file' ] ? ? '' );
2018-01-28 11:18:08 +00:00
2022-02-15 07:08:02 +00:00
// Communities aren't working with the Diaspora protoccol
if (( $uid != 0 ) && ( $item [ 'network' ] == Protocol :: DIASPORA )) {
$user = User :: getById ( $uid , [ 'account-type' ]);
if ( $user [ 'account-type' ] == Contact :: TYPE_COMMUNITY ) {
Logger :: info ( 'Community posts are not supported via Diaspora' );
return 0 ;
}
}
2018-01-28 11:18:08 +00:00
// Items cannot be stored before they happen ...
2018-02-21 21:39:07 +00:00
if ( $item [ 'created' ] > DateTimeFormat :: utcNow ()) {
$item [ 'created' ] = DateTimeFormat :: utcNow ();
2018-01-28 11:18:08 +00:00
}
// We haven't invented time travel by now.
2018-02-21 21:39:07 +00:00
if ( $item [ 'edited' ] > DateTimeFormat :: utcNow ()) {
$item [ 'edited' ] = DateTimeFormat :: utcNow ();
2018-01-28 11:18:08 +00:00
}
2019-12-30 22:00:08 +00:00
$item [ 'plink' ] = ( $item [ 'plink' ] ? ? '' ) ? : DI :: baseUrl () . '/display/' . urlencode ( $item [ 'guid' ]);
2018-01-28 11:18:08 +00:00
2020-05-12 20:13:48 +00:00
$item [ 'gravity' ] = self :: getGravity ( $item );
2020-10-03 15:42:21 +00:00
$item [ 'language' ] = self :: getLanguage ( $item );
2018-05-08 20:20:15 +00:00
$default = [ 'url' => $item [ 'author-link' ], 'name' => $item [ 'author-name' ],
2018-05-09 06:53:57 +00:00
'photo' => $item [ 'author-avatar' ], 'network' => $item [ 'network' ]];
2020-07-15 21:08:42 +00:00
$item [ 'author-id' ] = ( $item [ 'author-id' ] ? ? 0 ) ? : Contact :: getIdForURL ( $item [ 'author-link' ], 0 , null , $default );
2018-01-28 11:18:08 +00:00
2018-05-08 20:20:15 +00:00
$default = [ 'url' => $item [ 'owner-link' ], 'name' => $item [ 'owner-name' ],
2018-05-09 06:53:57 +00:00
'photo' => $item [ 'owner-avatar' ], 'network' => $item [ 'network' ]];
2020-07-15 21:08:42 +00:00
$item [ 'owner-id' ] = ( $item [ 'owner-id' ] ? ? 0 ) ? : Contact :: getIdForURL ( $item [ 'owner-link' ], 0 , null , $default );
2019-03-10 21:19:21 +00:00
2020-09-13 14:15:28 +00:00
$actor = ( $item [ 'gravity' ] == GRAVITY_PARENT ) ? $item [ 'owner-id' ] : $item [ 'author-id' ];
2020-09-25 06:47:07 +00:00
if ( ! $item [ 'origin' ] && ( $item [ 'uid' ] != 0 ) && Contact :: isSharing ( $actor , $item [ 'uid' ])) {
2021-04-07 06:02:06 +00:00
$item [ 'post-reason' ] = self :: PR_FOLLOWER ;
2020-09-13 14:15:28 +00:00
}
2020-07-25 11:48:52 +00:00
// Ensure that there is an avatar cache
Contact :: checkAvatarCache ( $item [ 'author-id' ]);
Contact :: checkAvatarCache ( $item [ 'owner-id' ]);
2019-08-02 17:17:51 +00:00
// The contact-id should be set before "self::insert" was called - but there seems to be issues sometimes
$item [ " contact-id " ] = self :: contactId ( $item );
2021-01-11 20:00:51 +00:00
if ( ! empty ( $item [ 'direction' ]) && in_array ( $item [ 'direction' ], [ Conversation :: PUSH , Conversation :: RELAY ]) &&
self :: isTooOld ( $item )) {
Logger :: info ( 'Item is too old' , [ 'item' => $item ]);
return 0 ;
}
2020-05-14 03:48:26 +00:00
if ( ! self :: isValid ( $item )) {
2019-02-10 12:21:16 +00:00
return 0 ;
}
2020-11-11 15:07:39 +00:00
if ( $item [ 'gravity' ] !== GRAVITY_PARENT ) {
2020-11-11 07:44:43 +00:00
$toplevel_parent = self :: getTopLevelParent ( $item );
if ( empty ( $toplevel_parent )) {
return 0 ;
}
2020-05-13 05:48:26 +00:00
2020-11-14 12:41:01 +00:00
// If the thread originated from this node, we check the permission against the thread starter
2021-01-27 10:01:42 +00:00
$condition = [ 'uri-id' => $toplevel_parent [ 'uri-id' ], 'wall' => true ];
2021-01-16 04:13:22 +00:00
$localTopLevelParent = Post :: selectFirst ([ 'uid' ], $condition );
2020-11-14 12:41:01 +00:00
if ( ! empty ( $localTopLevelParent [ 'uid' ]) && ! self :: isAllowedByUser ( $item , $localTopLevelParent [ 'uid' ])) {
return 0 ;
}
2021-01-27 10:01:42 +00:00
$parent_id = $toplevel_parent [ 'id' ];
$item [ 'parent-uri' ] = $toplevel_parent [ 'uri' ];
$item [ 'parent-uri-id' ] = $toplevel_parent [ 'uri-id' ];
$item [ 'deleted' ] = $toplevel_parent [ 'deleted' ];
2022-02-12 18:38:36 +00:00
// Reshares have to keep their permissions to allow forums to work
if ( ! $item [ 'origin' ] || ( $item [ 'verb' ] != Activity :: ANNOUNCE )) {
$item [ 'allow_cid' ] = $toplevel_parent [ 'allow_cid' ];
$item [ 'allow_gid' ] = $toplevel_parent [ 'allow_gid' ];
$item [ 'deny_cid' ] = $toplevel_parent [ 'deny_cid' ];
$item [ 'deny_gid' ] = $toplevel_parent [ 'deny_gid' ];
}
2021-01-27 10:01:42 +00:00
$parent_origin = $toplevel_parent [ 'origin' ];
2020-05-13 05:48:26 +00:00
2020-11-11 07:44:43 +00:00
// Don't federate received participation messages
if ( $item [ 'verb' ] != Activity :: FOLLOW ) {
$item [ 'wall' ] = $toplevel_parent [ 'wall' ];
} else {
$item [ 'wall' ] = false ;
2021-02-21 11:59:59 +00:00
// Participations are technical messages, so they are set to "seen" automatically
$item [ 'unseen' ] = false ;
2020-11-11 07:44:43 +00:00
}
2020-05-13 05:48:26 +00:00
2020-11-11 07:44:43 +00:00
/*
* If the parent is private , force privacy for the entire conversation
* This differs from the above settings as it subtly allows comments from
* email correspondents to be private even if the overall thread is not .
*/
if ( $toplevel_parent [ 'private' ]) {
$item [ 'private' ] = $toplevel_parent [ 'private' ];
}
2019-02-10 11:28:17 +00:00
2020-11-11 07:44:43 +00:00
// If its a post that originated here then tag the thread as "mention"
if ( $item [ 'origin' ] && $item [ 'uid' ]) {
2021-02-04 09:46:29 +00:00
DBA :: update ( 'post-thread-user' , [ 'mention' => true ], [ 'uri-id' => $item [ 'parent-uri-id' ], 'uid' => $item [ 'uid' ]]);
Logger :: info ( 'tagged thread as mention' , [ 'parent' => $parent_id , 'parent-uri-id' => $item [ 'parent-uri-id' ], 'uid' => $item [ 'uid' ]]);
2019-02-10 11:28:17 +00:00
}
2018-02-21 21:08:37 +00:00
2020-11-11 07:44:43 +00:00
// Update the contact relations
Contact\Relation :: store ( $toplevel_parent [ 'author-id' ], $item [ 'author-id' ], $item [ 'created' ]);
2018-01-28 11:18:08 +00:00
} else {
2020-05-12 20:13:48 +00:00
$parent_id = 0 ;
$parent_origin = $item [ 'origin' ];
2019-06-06 04:26:02 +00:00
}
2018-08-05 11:09:59 +00:00
$item [ 'parent-uri-id' ] = ItemURI :: getIdByURI ( $item [ 'parent-uri' ]);
$item [ 'thr-parent-id' ] = ItemURI :: getIdByURI ( $item [ 'thr-parent' ]);
2018-06-03 09:40:32 +00:00
// Is this item available in the global items (with uid=0)?
if ( $item [ " uid " ] == 0 ) {
$item [ " global " ] = true ;
// Set the global flag on all items if this was a global item entry
2021-02-06 13:42:21 +00:00
Post :: update ([ 'global' => true ], [ 'uri-id' => $item [ 'uri-id' ]]);
2018-06-03 09:40:32 +00:00
} else {
2021-01-27 10:01:42 +00:00
$item [ 'global' ] = Post :: exists ([ 'uid' => 0 , 'uri-id' => $item [ 'uri-id' ]]);
2018-06-03 09:40:32 +00:00
}
2018-01-28 11:18:08 +00:00
// ACL settings
2020-05-12 20:13:48 +00:00
if ( ! empty ( $item [ " allow_cid " ] . $item [ " allow_gid " ] . $item [ " deny_cid " ] . $item [ " deny_gid " ])) {
$item [ " private " ] = self :: PRIVATE ;
2018-01-28 11:18:08 +00:00
}
2021-08-06 18:49:17 +00:00
if ( $notify && $post_local ) {
2018-07-10 12:27:56 +00:00
$item [ 'edit' ] = false ;
$item [ 'parent' ] = $parent_id ;
2021-05-16 14:30:15 +00:00
// Trigger automatic reactions for addons
2021-12-20 21:16:00 +00:00
if ( ! isset ( $item [ 'api_source' ])) {
$item [ 'api_source' ] = true ;
}
2021-05-16 14:30:15 +00:00
// We have to tell the hooks who we are - this really should be improved
if ( ! local_user ()) {
$_SESSION [ 'authenticated' ] = true ;
$_SESSION [ 'uid' ] = $uid ;
$dummy_session = true ;
} else {
$dummy_session = false ;
}
2018-12-26 06:06:24 +00:00
Hook :: callAll ( 'post_local' , $item );
2021-05-16 14:30:15 +00:00
if ( $dummy_session ) {
unset ( $_SESSION [ 'authenticated' ]);
unset ( $_SESSION [ 'uid' ]);
}
2021-08-06 18:49:17 +00:00
} elseif ( ! $notify ) {
2018-12-26 06:06:24 +00:00
Hook :: callAll ( 'post_remote' , $item );
2018-01-28 11:18:08 +00:00
}
2018-11-30 14:06:22 +00:00
if ( ! empty ( $item [ 'cancel' ])) {
2021-10-20 18:53:52 +00:00
Logger :: notice ( 'post cancelled by addon.' );
2018-01-28 11:18:08 +00:00
return 0 ;
}
2020-05-12 20:13:48 +00:00
if ( empty ( $item [ 'vid' ]) && ! empty ( $item [ 'verb' ])) {
$item [ 'vid' ] = Verb :: getID ( $item [ 'verb' ]);
2018-06-30 21:15:24 +00:00
}
2018-07-25 23:14:55 +00:00
// Creates or assigns the permission set
2021-10-05 21:30:10 +00:00
$item [ 'psid' ] = DI :: permissionSet () -> selectOrCreate (
DI :: permissionSetFactory () -> createFromString (
$item [ 'uid' ],
$item [ 'allow_cid' ],
$item [ 'allow_gid' ],
$item [ 'deny_cid' ],
$item [ 'deny_gid' ]
)) -> id ;
2019-11-05 13:27:22 +00:00
2021-02-14 09:43:27 +00:00
if ( ! empty ( $item [ 'extid' ])) {
$item [ 'external-id' ] = ItemURI :: getIdByURI ( $item [ 'extid' ]);
}
2020-08-13 06:09:26 +00:00
if ( $item [ 'verb' ] == Activity :: ANNOUNCE ) {
self :: setOwnerforResharedItem ( $item );
}
2020-05-13 18:45:31 +00:00
2021-05-01 15:48:19 +00:00
if ( isset ( $item [ 'attachments' ])) {
foreach ( $item [ 'attachments' ] as $attachment ) {
$attachment [ 'uri-id' ] = $item [ 'uri-id' ];
Post\Media :: insert ( $attachment );
}
unset ( $item [ 'attachments' ]);
}
2021-04-26 06:50:12 +00:00
2021-05-04 05:18:03 +00:00
Post\Media :: insertFromAttachmentData ( $item [ 'uri-id' ], $item [ 'body' ]);
2020-10-29 05:20:26 +00:00
// Remove all media attachments from the body and store them in the post-media table
2020-10-29 08:48:08 +00:00
$item [ 'raw-body' ] = Post\Media :: insertFromBody ( $item [ 'uri-id' ], $item [ 'raw-body' ]);
2020-10-29 05:20:26 +00:00
$item [ 'raw-body' ] = self :: setHashtags ( $item [ 'raw-body' ]);
2021-05-30 06:13:20 +00:00
if ( ! DBA :: exists ( 'contact' , [ 'id' => $item [ 'author-id' ], 'network' => Protocol :: DFRN ])) {
Post\Media :: insertFromRelevantUrl ( $item [ 'uri-id' ], $item [ 'raw-body' ]);
}
2020-05-13 18:45:31 +00:00
// Check for hashtags in the body and repair or add hashtag links
2020-06-05 00:56:50 +00:00
$item [ 'body' ] = self :: setHashtags ( $item [ 'body' ]);
2020-05-31 15:48:31 +00:00
2020-05-13 18:45:31 +00:00
if ( stristr ( $item [ 'verb' ], Activity :: POKE )) {
$notify_type = Delivery :: POKE ;
} else {
$notify_type = Delivery :: POST ;
}
2021-02-01 07:06:01 +00:00
// Filling item related side tables
if ( ! empty ( $item [ 'attach' ])) {
Post\Media :: insertFromAttachment ( $item [ 'uri-id' ], $item [ 'attach' ]);
2020-05-18 21:34:57 +00:00
}
2021-02-13 19:56:03 +00:00
if ( empty ( $item [ 'event-id' ])) {
unset ( $item [ 'event-id' ]);
2021-05-26 09:24:37 +00:00
$ev = Event :: fromBBCode ( $item [ 'body' ]);
if (( ! empty ( $ev [ 'desc' ]) || ! empty ( $ev [ 'summary' ])) && ! empty ( $ev [ 'start' ])) {
Logger :: info ( 'Event found.' );
$ev [ 'cid' ] = $item [ 'contact-id' ];
$ev [ 'uid' ] = $item [ 'uid' ];
$ev [ 'uri' ] = $item [ 'uri' ];
$ev [ 'edited' ] = $item [ 'edited' ];
$ev [ 'private' ] = $item [ 'private' ];
$ev [ 'guid' ] = $item [ 'guid' ];
$ev [ 'plink' ] = $item [ 'plink' ];
$ev [ 'network' ] = $item [ 'network' ];
2021-07-17 20:27:18 +00:00
$ev [ 'protocol' ] = $item [ 'protocol' ] ? ? Conversation :: PARCEL_UNKNOWN ;
$ev [ 'direction' ] = $item [ 'direction' ] ? ? Conversation :: UNKNOWN ;
$ev [ 'source' ] = $item [ 'source' ] ? ? '' ;
2021-05-26 09:24:37 +00:00
$event = DBA :: selectFirst ( 'event' , [ 'id' ], [ 'uri' => $item [ 'uri' ], 'uid' => $item [ 'uid' ]]);
if ( DBA :: isResult ( $event )) {
$ev [ 'id' ] = $event [ 'id' ];
}
2021-07-18 15:05:46 +00:00
$event_id = Event :: store ( $ev );
2021-08-10 10:24:14 +00:00
$item = Event :: getItemArrayForImportedId ( $event_id , $item );
2021-07-18 15:05:46 +00:00
Logger :: info ( 'Event was stored' , [ 'id' => $event_id ]);
2021-05-26 09:24:37 +00:00
}
2021-02-13 19:56:03 +00:00
}
2021-03-01 22:19:47 +00:00
if ( empty ( $item [ 'causer-id' ])) {
unset ( $item [ 'causer-id' ]);
}
2022-03-06 11:49:55 +00:00
if ( in_array ( $item [ 'network' ], [ Protocol :: ACTIVITYPUB , Protocol :: DFRN ])) {
$content_warning = BBCode :: getAbstract ( $item [ 'body' ], Protocol :: ACTIVITYPUB );
if ( ! empty ( $content_warning ) && empty ( $item [ 'content-warning' ])) {
$item [ 'content-warning' ] = $content_warning ;
}
}
2021-02-13 19:56:03 +00:00
Post :: insert ( $item [ 'uri-id' ], $item );
2021-02-04 05:51:25 +00:00
if ( $item [ 'gravity' ] == GRAVITY_PARENT ) {
Post\Thread :: insert ( $item [ 'uri-id' ], $item );
}
2021-02-01 07:06:01 +00:00
if ( ! in_array ( $item [ 'verb' ], self :: ACTIVITIES )) {
Post\Content :: insert ( $item [ 'uri-id' ], $item );
2020-05-13 18:45:31 +00:00
}
2021-05-31 19:39:50 +00:00
// Create Diaspora signature
2021-06-05 06:13:10 +00:00
if ( $item [ 'origin' ] && empty ( $item [ 'diaspora_signed_text' ]) && ( $item [ 'gravity' ] != GRAVITY_PARENT )) {
2021-08-05 08:42:46 +00:00
$signed = Diaspora :: createCommentSignature ( $item );
2021-05-31 19:39:50 +00:00
if ( ! empty ( $signed )) {
$item [ 'diaspora_signed_text' ] = json_encode ( $signed );
}
}
2020-05-12 20:13:48 +00:00
if ( ! empty ( $item [ 'diaspora_signed_text' ])) {
2020-10-23 19:10:17 +00:00
DBA :: replace ( 'diaspora-interaction' , [ 'uri-id' => $item [ 'uri-id' ], 'interaction' => $item [ 'diaspora_signed_text' ]]);
2020-05-12 20:13:48 +00:00
}
2020-05-13 05:48:26 +00:00
// Attached file links
2020-05-13 18:45:31 +00:00
if ( ! empty ( $item [ 'file' ])) {
2020-11-17 22:33:44 +00:00
Post\Category :: storeTextByURIId ( $item [ 'uri-id' ], $item [ 'uid' ], $item [ 'file' ]);
2020-05-12 20:13:48 +00:00
}
2020-05-13 05:48:26 +00:00
// Delivery relevant data
2020-05-12 20:13:48 +00:00
$delivery_data = Post\DeliveryData :: extractFields ( $item );
2018-07-19 21:56:52 +00:00
2020-05-13 18:45:31 +00:00
if ( ! empty ( $item [ 'origin' ]) || ! empty ( $item [ 'wall' ]) || ! empty ( $delivery_data [ 'postopts' ]) || ! empty ( $delivery_data [ 'inform' ])) {
Post\DeliveryData :: insert ( $item [ 'uri-id' ], $delivery_data );
}
2018-08-29 20:46:52 +00:00
2020-05-12 20:13:48 +00:00
// Store tags from the body if this hadn't been handled previously in the protocol classes
if ( ! Tag :: existsForPost ( $item [ 'uri-id' ])) {
2021-02-01 07:06:01 +00:00
Tag :: storeFromBody ( $item [ 'uri-id' ], $item [ 'body' ]);
2020-05-12 20:13:48 +00:00
}
2020-07-09 22:22:26 +00:00
2021-02-13 19:56:03 +00:00
$condition = [ 'uri-id' => $item [ 'uri-id' ], 'uid' => $item [ 'uid' ]];
if ( Post :: exists ( $condition )) {
Logger :: notice ( 'Item is already inserted - aborting' , $condition );
return 0 ;
}
2021-02-14 18:33:15 +00:00
$post_user_id = Post\User :: insert ( $item [ 'uri-id' ], $item [ 'uid' ], $item );
if ( ! $post_user_id ) {
2021-02-01 07:06:01 +00:00
Logger :: notice ( 'Post-User is already inserted - aborting' , [ 'uid' => $item [ 'uid' ], 'uri-id' => $item [ 'uri-id' ]]);
return 0 ;
}
2021-01-31 18:32:22 +00:00
2021-02-01 07:06:01 +00:00
if ( $item [ 'gravity' ] == GRAVITY_PARENT ) {
2021-02-14 18:33:15 +00:00
$item [ 'post-user-id' ] = $post_user_id ;
2021-02-01 07:06:01 +00:00
Post\ThreadUser :: insert ( $item [ 'uri-id' ], $item [ 'uid' ], $item );
}
2018-01-28 11:18:08 +00:00
2021-02-14 20:27:31 +00:00
Logger :: notice ( 'created item' , [ 'post-id' => $post_user_id , 'uid' => $item [ 'uid' ], 'network' => $item [ 'network' ], 'uri-id' => $item [ 'uri-id' ], 'guid' => $item [ 'guid' ]]);
2018-01-28 11:18:08 +00:00
2021-02-14 18:33:15 +00:00
$posted_item = Post :: selectFirst ( self :: ITEM_FIELDLIST , [ 'post-user-id' => $post_user_id ]);
if ( ! DBA :: isResult ( $posted_item )) {
2021-02-14 20:27:31 +00:00
// On failure store the data into a spool file so that the "SpoolPost" worker can try again later.
Logger :: warning ( 'Could not store item. it will be spooled' , [ 'id' => $post_user_id ]);
self :: spool ( $orig_item );
2021-02-14 18:33:15 +00:00
return 0 ;
}
2018-01-28 11:18:08 +00:00
// update the commented timestamp on the parent
2020-10-05 17:57:24 +00:00
if ( DI :: config () -> get ( 'system' , 'like_no_comment' )) {
// Update when it is a comment
2021-02-14 18:33:15 +00:00
$update_commented = in_array ( $posted_item [ 'gravity' ], [ GRAVITY_PARENT , GRAVITY_COMMENT ]);
2020-10-05 17:57:24 +00:00
} else {
// Update when it isn't a follow or tag verb
2021-02-14 18:33:15 +00:00
$update_commented = ! in_array ( $posted_item [ 'verb' ], [ Activity :: FOLLOW , Activity :: TAG ]);
2020-10-05 17:57:24 +00:00
}
if ( $update_commented ) {
2021-02-15 11:03:22 +00:00
$fields = [ 'commented' => DateTimeFormat :: utcNow (), 'changed' => DateTimeFormat :: utcNow ()];
2018-01-28 11:18:08 +00:00
} else {
2021-02-15 11:03:22 +00:00
$fields = [ 'changed' => DateTimeFormat :: utcNow ()];
2018-01-28 11:18:08 +00:00
}
2021-02-15 11:03:22 +00:00
Post :: update ( $fields , [ 'uri-id' => $posted_item [ 'parent-uri-id' ], 'uid' => $posted_item [ 'uid' ]]);
2019-08-03 10:36:21 +00:00
// In that function we check if this is a forum post. Additionally we delete the item under certain circumstances
2021-02-14 18:33:15 +00:00
if ( self :: tagDeliver ( $posted_item [ 'uid' ], $post_user_id )) {
2019-08-03 10:36:21 +00:00
// Get the user information for the logging
$user = User :: getById ( $uid );
2021-02-14 18:33:15 +00:00
Logger :: notice ( 'Item had been deleted' , [ 'id' => $post_user_id , 'user' => $uid , 'account-type' => $user [ 'account-type' ]]);
2019-08-03 10:36:21 +00:00
return 0 ;
}
2021-08-06 18:49:17 +00:00
if ( $notify ) {
if ( ! \Friendica\Content\Feature :: isEnabled ( $posted_item [ 'uid' ], 'explicit_mentions' ) && ( $posted_item [ 'gravity' ] == GRAVITY_COMMENT )) {
Tag :: createImplicitMentions ( $posted_item [ 'uri-id' ], $posted_item [ 'thr-parent-id' ]);
2021-04-26 06:50:12 +00:00
}
2021-08-06 18:49:17 +00:00
Hook :: callAll ( 'post_local_end' , $posted_item );
} else {
Hook :: callAll ( 'post_remote_end' , $posted_item );
2019-08-03 10:36:21 +00:00
}
2021-02-14 18:33:15 +00:00
if ( $posted_item [ 'gravity' ] === GRAVITY_PARENT ) {
self :: addShadow ( $post_user_id );
2018-01-28 11:18:08 +00:00
} else {
2021-02-14 18:33:15 +00:00
self :: addShadowPost ( $post_user_id );
2018-01-28 11:18:08 +00:00
}
2021-02-14 18:33:15 +00:00
self :: updateContact ( $posted_item );
2019-08-03 10:36:21 +00:00
2021-02-14 18:33:15 +00:00
Post\UserNotification :: setNotification ( $posted_item [ 'uri-id' ], $posted_item [ 'uid' ]);
2020-01-05 01:23:40 +00:00
2020-07-21 08:35:57 +00:00
// Distribute items to users who subscribed to their tags
2021-02-14 18:33:15 +00:00
self :: distributeByTags ( $posted_item );
2020-07-21 08:35:57 +00:00
2020-11-28 22:53:58 +00:00
// Automatically reshare the item if the "remote_self" option is selected
2021-02-14 18:33:15 +00:00
self :: autoReshare ( $posted_item );
2020-11-28 22:53:58 +00:00
2021-02-14 18:33:15 +00:00
$transmit = $notify || ( $posted_item [ 'visible' ] && ( $parent_origin || $posted_item [ 'origin' ]));
2020-05-06 20:43:00 +00:00
if ( $transmit ) {
// Don't relay participation messages
2021-04-26 06:50:12 +00:00
if (( $posted_item [ 'verb' ] == Activity :: FOLLOW ) &&
2021-02-14 18:33:15 +00:00
( ! $posted_item [ 'origin' ] || ( $posted_item [ 'author-id' ] != Contact :: getPublicIdByUserId ( $uid )))) {
Logger :: info ( 'Participation messages will not be relayed' , [ 'item' => $posted_item [ 'id' ], 'uri' => $posted_item [ 'uri' ], 'verb' => $posted_item [ 'verb' ]]);
2020-05-06 20:43:00 +00:00
$transmit = false ;
}
}
if ( $transmit ) {
2021-02-14 18:33:15 +00:00
Worker :: add ([ 'priority' => $priority , 'dont_fork' => true ], 'Notifier' , $notify_type , ( int ) $posted_item [ 'uri-id' ], ( int ) $posted_item [ 'uid' ]);
2018-01-28 11:18:08 +00:00
}
2021-02-14 18:33:15 +00:00
return $post_user_id ;
2018-01-28 11:18:08 +00:00
}
2020-08-09 22:46:18 +00:00
/**
2020-08-23 08:39:56 +00:00
* Change the owner of a parent item if it had been shared by a forum
2020-08-09 22:46:18 +00:00
*
2020-08-11 05:23:16 +00:00
* ( public ) forum posts in the new format consist of the regular post by the author
* followed by an announce message sent from the forum account .
2020-08-13 06:09:26 +00:00
* Changing the owner helps in grouping forum posts .
*
2020-08-09 22:46:18 +00:00
* @ param array $item
* @ return void
*/
2020-08-13 06:09:26 +00:00
private static function setOwnerforResharedItem ( array $item )
2020-08-09 22:46:18 +00:00
{
2021-04-07 06:02:06 +00:00
$parent = Post :: selectFirst ([ 'id' , 'causer-id' , 'owner-id' , 'author-id' , 'author-link' , 'origin' , 'post-reason' ],
2020-09-13 14:15:28 +00:00
[ 'uri-id' => $item [ 'thr-parent-id' ], 'uid' => $item [ 'uid' ]]);
2020-08-13 06:09:26 +00:00
if ( ! DBA :: isResult ( $parent )) {
2020-09-13 14:15:28 +00:00
Logger :: error ( 'Parent not found' , [ 'uri-id' => $item [ 'thr-parent-id' ], 'uid' => $item [ 'uid' ]]);
2020-08-09 22:46:18 +00:00
return ;
}
2021-02-14 20:27:31 +00:00
$author = Contact :: selectFirst ([ 'url' , 'contact-type' , 'network' ], [ 'id' => $item [ 'author-id' ]]);
2020-08-13 06:09:26 +00:00
if ( ! DBA :: isResult ( $author )) {
Logger :: error ( 'Author not found' , [ 'id' => $item [ 'author-id' ]]);
2020-08-09 22:46:18 +00:00
return ;
}
2022-02-18 06:12:02 +00:00
$self_contact = Contact :: selectFirst ([ 'id' ], [ 'uid' => $item [ 'uid' ], 'self' => true ]);
$self = ! empty ( $self_contact ) ? $self_contact [ 'id' ] : 0 ;
2022-03-06 11:49:55 +00:00
2020-08-13 06:09:26 +00:00
$cid = Contact :: getIdForURL ( $author [ 'url' ], $item [ 'uid' ]);
2022-02-18 06:12:02 +00:00
if ( empty ( $cid ) || ( ! Contact :: isSharing ( $cid , $item [ 'uid' ]) && ( $cid != $self ))) {
2021-02-14 20:27:31 +00:00
Logger :: info ( 'The resharer is not a following contact: quit' , [ 'resharer' => $author [ 'url' ], 'uid' => $item [ 'uid' ], 'cid' => $cid ]);
2020-08-13 06:09:26 +00:00
return ;
}
2020-09-13 14:15:28 +00:00
if ( $author [ 'contact-type' ] != Contact :: TYPE_COMMUNITY ) {
2021-04-07 06:02:06 +00:00
if ( $parent [ 'post-reason' ] == self :: PR_ANNOUNCEMENT ) {
2020-10-11 17:37:04 +00:00
Logger :: info ( 'The parent is already marked as announced: quit' , [ 'causer' => $parent [ 'causer-id' ], 'owner' => $parent [ 'owner-id' ], 'author' => $parent [ 'author-id' ], 'uid' => $item [ 'uid' ]]);
return ;
}
2020-09-25 06:47:07 +00:00
if ( Contact :: isSharing ( $parent [ 'owner-id' ], $item [ 'uid' ])) {
2020-09-13 21:00:54 +00:00
Logger :: info ( 'The resharer is no forum: quit' , [ 'resharer' => $item [ 'author-id' ], 'owner' => $parent [ 'owner-id' ], 'author' => $parent [ 'author-id' ], 'uid' => $item [ 'uid' ]]);
2020-09-13 14:15:28 +00:00
return ;
}
2020-08-13 06:09:26 +00:00
}
2021-06-05 06:13:10 +00:00
self :: update ([ 'post-reason' => self :: PR_ANNOUNCEMENT , 'causer-id' => $item [ 'author-id' ]], [ 'id' => $parent [ 'id' ]]);
Logger :: info ( 'Set announcement post-reason' , [ 'uri-id' => $item [ 'uri-id' ], 'thr-parent-id' => $item [ 'thr-parent-id' ], 'uid' => $item [ 'uid' ]]);
2020-08-09 22:46:18 +00:00
}
2020-07-21 08:35:57 +00:00
/**
* Distribute the given item to users who subscribed to their tags
*
* @ param array $item Processed item
*/
2020-07-23 03:26:54 +00:00
private static function distributeByTags ( array $item )
2020-07-21 08:35:57 +00:00
{
if (( $item [ 'uid' ] != 0 ) || ( $item [ 'gravity' ] != GRAVITY_PARENT ) || ! in_array ( $item [ 'network' ], Protocol :: FEDERATED )) {
return ;
}
$uids = Tag :: getUIDListByURIId ( $item [ 'uri-id' ]);
foreach ( $uids as $uid ) {
2020-08-23 17:48:44 +00:00
if ( Contact :: isSharing ( $item [ 'author-id' ], $uid )) {
$fields = [];
} else {
2021-04-07 06:02:06 +00:00
$fields = [ 'post-reason' => self :: PR_TAG ];
2020-08-23 17:48:44 +00:00
}
$stored = self :: storeForUserByUriId ( $item [ 'uri-id' ], $uid , $fields );
2020-08-23 17:58:22 +00:00
Logger :: info ( 'Stored item for users' , [ 'uri-id' => $item [ 'uri-id' ], 'uid' => $uid , 'fields' => $fields , 'stored' => $stored ]);
2020-07-21 08:35:57 +00:00
}
}
2018-04-24 13:21:25 +00:00
/**
2020-01-19 06:05:23 +00:00
* Distributes public items to the receivers
2018-04-24 13:21:25 +00:00
*
2018-05-15 04:33:28 +00:00
* @ param integer $itemid Item ID that should be added
* @ param string $signed_text Original text ( for Diaspora signatures ), JSON encoded .
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2018-04-24 13:21:25 +00:00
*/
2018-05-15 04:33:28 +00:00
public static function distribute ( $itemid , $signed_text = '' )
2018-04-24 13:21:25 +00:00
{
2021-02-22 19:47:08 +00:00
$condition = [ " `id` IN (SELECT `parent` FROM `post-user-view` WHERE `id` = ?) " , $itemid ];
2021-01-16 04:13:22 +00:00
$parent = Post :: selectFirst ([ 'owner-id' ], $condition );
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $parent )) {
2021-02-14 18:33:15 +00:00
Logger :: warning ( 'Item not found' , [ 'condition' => $condition ]);
2018-04-24 13:21:25 +00:00
return ;
}
// Only distribute public items from native networks
$condition = [ 'id' => $itemid , 'uid' => 0 ,
2019-07-01 18:00:55 +00:00
'network' => array_merge ( Protocol :: FEDERATED ,[ '' ]),
2021-02-13 19:56:03 +00:00
'visible' => true , 'deleted' => false , 'private' => [ self :: PUBLIC , self :: UNLISTED ]];
2021-01-16 04:13:22 +00:00
$item = Post :: selectFirst ( self :: ITEM_FIELDLIST , $condition );
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $item )) {
2021-02-14 18:33:15 +00:00
Logger :: warning ( 'Item not found' , [ 'condition' => $condition ]);
2018-04-24 13:21:25 +00:00
return ;
}
2018-07-10 12:27:56 +00:00
$origin = $item [ 'origin' ];
2018-04-26 19:47:12 +00:00
$users = [];
2018-09-16 09:06:09 +00:00
/// @todo add a field "pcid" in the contact table that referrs to the public contact id.
2018-09-15 18:54:45 +00:00
$owner = DBA :: selectFirst ( 'contact' , [ 'url' , 'nurl' , 'alias' ], [ 'id' => $parent [ 'owner-id' ]]);
if ( ! DBA :: isResult ( $owner )) {
return ;
}
$condition = [ 'nurl' => $owner [ 'nurl' ], 'rel' => [ Contact :: SHARING , Contact :: FRIEND ]];
$contacts = DBA :: select ( 'contact' , [ 'uid' ], $condition );
while ( $contact = DBA :: fetch ( $contacts )) {
if ( $contact [ 'uid' ] == 0 ) {
continue ;
}
$users [ $contact [ 'uid' ]] = $contact [ 'uid' ];
}
DBA :: close ( $contacts );
$condition = [ 'alias' => $owner [ 'url' ], 'rel' => [ Contact :: SHARING , Contact :: FRIEND ]];
$contacts = DBA :: select ( 'contact' , [ 'uid' ], $condition );
while ( $contact = DBA :: fetch ( $contacts )) {
if ( $contact [ 'uid' ] == 0 ) {
continue ;
}
$users [ $contact [ 'uid' ]] = $contact [ 'uid' ];
}
DBA :: close ( $contacts );
if ( ! empty ( $owner [ 'alias' ])) {
2020-07-18 15:49:10 +00:00
$condition = [ 'nurl' => Strings :: normaliseLink ( $owner [ 'alias' ]), 'rel' => [ Contact :: SHARING , Contact :: FRIEND ]];
2018-09-15 18:54:45 +00:00
$contacts = DBA :: select ( 'contact' , [ 'uid' ], $condition );
while ( $contact = DBA :: fetch ( $contacts )) {
if ( $contact [ 'uid' ] == 0 ) {
continue ;
}
$users [ $contact [ 'uid' ]] = $contact [ 'uid' ];
}
DBA :: close ( $contacts );
}
2018-05-15 04:33:28 +00:00
$origin_uid = 0 ;
2021-01-27 10:01:42 +00:00
if ( $item [ 'uri-id' ] != $item [ 'parent-uri-id' ]) {
$parents = Post :: select ([ 'uid' , 'origin' ], [ " `uri-id` = ? AND `uid` != 0 " , $item [ 'parent-uri-id' ]]);
2021-01-16 04:13:22 +00:00
while ( $parent = Post :: fetch ( $parents )) {
2018-04-26 19:47:12 +00:00
$users [ $parent [ 'uid' ]] = $parent [ 'uid' ];
2018-07-10 12:27:56 +00:00
if ( $parent [ 'origin' ] && ! $origin ) {
2018-05-15 04:33:28 +00:00
$origin_uid = $parent [ 'uid' ];
}
2018-04-26 19:47:12 +00:00
}
2021-01-16 04:13:22 +00:00
DBA :: close ( $parents );
2018-04-26 19:47:12 +00:00
}
foreach ( $users as $uid ) {
2018-05-15 04:33:28 +00:00
if ( $origin_uid == $uid ) {
$item [ 'diaspora_signed_text' ] = $signed_text ;
}
2020-07-21 08:35:57 +00:00
self :: storeForUser ( $item , $uid );
2018-04-24 13:21:25 +00:00
}
}
/**
2020-07-23 03:26:54 +00:00
* Store a public item defined by their URI - ID for the given users
*
2021-05-26 09:24:37 +00:00
* @ param integer $uri_id URI - ID of the given item
* @ param integer $uid The user that will receive the item entry
* @ param array $fields Additional fields to be stored
* @ param integer $source_uid User id of the source post
2020-07-23 03:26:54 +00:00
* @ return integer stored item id
*/
2021-05-26 09:24:37 +00:00
public static function storeForUserByUriId ( int $uri_id , int $uid , array $fields = [], int $source_uid = 0 )
2020-07-23 03:26:54 +00:00
{
2021-05-26 09:24:37 +00:00
if ( $uid == $source_uid ) {
2021-05-26 18:15:07 +00:00
Logger :: warning ( 'target UID must not be be equal to the source UID' , [ 'uri-id' => $uri_id , 'uid' => $uid ]);
2021-05-26 09:24:37 +00:00
return 0 ;
}
$item = Post :: selectFirst ( self :: ITEM_FIELDLIST , [ 'uri-id' => $uri_id , 'uid' => $source_uid ]);
2020-07-28 06:42:12 +00:00
if ( ! DBA :: isResult ( $item )) {
2021-05-26 09:24:37 +00:00
Logger :: warning ( 'Item could not be fetched' , [ 'uri-id' => $uri_id , 'uid' => $source_uid ]);
2020-07-28 06:42:12 +00:00
return 0 ;
}
2021-05-26 09:24:37 +00:00
if (( $source_uid == 0 ) && (( $item [ 'private' ] == self :: PRIVATE ) || ! in_array ( $item [ 'network' ], Protocol :: FEDERATED ))) {
2020-07-28 06:42:12 +00:00
Logger :: notice ( 'Item is private or not from a federated network. It will not be stored for the user.' , [ 'uri-id' => $uri_id , 'uid' => $uid , 'private' => $item [ 'private' ], 'network' => $item [ 'network' ]]);
2020-07-23 03:26:54 +00:00
return 0 ;
}
2020-08-23 17:48:44 +00:00
2021-04-07 06:02:06 +00:00
$item [ 'post-reason' ] = self :: PR_STORED ;
2020-09-13 14:15:28 +00:00
2020-08-23 17:48:44 +00:00
$item = array_merge ( $item , $fields );
2020-07-23 03:26:54 +00:00
2021-05-26 09:24:37 +00:00
$is_reshare = ( $item [ 'gravity' ] == GRAVITY_ACTIVITY ) && ( $item [ 'verb' ] == Activity :: ANNOUNCE );
if ((( $item [ 'gravity' ] == GRAVITY_PARENT ) || $is_reshare ) &&
2022-02-18 14:32:30 +00:00
DI :: pConfig () -> get ( $uid , 'system' , 'accept_only_sharer' ) == self :: COMPLETION_NONE &&
2021-05-26 09:24:37 +00:00
! Contact :: isSharingByURL ( $item [ 'author-link' ], $uid ) &&
! Contact :: isSharingByURL ( $item [ 'owner-link' ], $uid )) {
Logger :: info ( 'Contact is not a follower, thread will not be stored' , [ 'author' => $item [ 'author-link' ], 'uid' => $uid ]);
return 0 ;
}
if ((( $item [ 'gravity' ] == GRAVITY_COMMENT ) || $is_reshare ) && ! Post :: exists ([ 'uri-id' => $item [ 'thr-parent-id' ], 'uid' => $uid ])) {
2022-02-14 22:04:33 +00:00
// Fetch the origin user for the post
$origin_uid = self :: GetOriginUidForUriId ( $item [ 'thr-parent-id' ], $uid );
if ( is_null ( $origin_uid )) {
Logger :: info ( 'Origin item was not found' , [ 'uid' => $uid , 'uri-id' => $item [ 'thr-parent-id' ]]);
return 0 ;
}
2021-05-26 14:21:28 +00:00
$causer = $item [ 'causer-id' ] ? : $item [ 'author-id' ];
2022-02-14 22:04:33 +00:00
$result = self :: storeForUserByUriId ( $item [ 'thr-parent-id' ], $uid , [ 'causer-id' => $causer , 'post-reason' => self :: PR_FETCHED ], $origin_uid );
2021-05-26 14:21:28 +00:00
Logger :: info ( 'Fetched thread parent' , [ 'uri-id' => $item [ 'thr-parent-id' ], 'uid' => $uid , 'causer' => $causer , 'result' => $result ]);
2021-05-26 09:24:37 +00:00
}
2020-07-23 03:26:54 +00:00
$stored = self :: storeForUser ( $item , $uid );
2021-05-26 09:24:37 +00:00
Logger :: info ( 'Item stored for user' , [ 'uri-id' => $item [ 'uri-id' ], 'uid' => $uid , 'source-uid' => $source_uid , 'stored' => $stored ]);
2020-07-23 03:26:54 +00:00
return $stored ;
}
2022-02-14 22:04:33 +00:00
/**
* Returns the origin uid of a post if the given user is allowed to see it .
*
* @ param int $uriid
* @ param int $uid
* @ return int
*/
private static function GetOriginUidForUriId ( int $uriid , int $uid )
{
if ( Post :: exists ([ 'uri-id' => $uriid , 'uid' => $uid ])) {
return $uid ;
}
$post = Post :: selectFirst ([ 'uid' , 'allow_cid' , 'allow_gid' , 'deny_cid' , 'deny_gid' , 'private' ], [ 'uri-id' => $uriid , 'origin' => true ]);
2022-02-15 23:51:13 +00:00
if ( ! empty ( $post )) {
if ( in_array ( $post [ 'private' ], [ Item :: PUBLIC , Item :: UNLISTED ])) {
return $post [ 'uid' ];
}
$pcid = Contact :: getPublicIdByUserId ( $uid );
if ( empty ( $pcid )) {
2022-02-14 22:04:33 +00:00
return null ;
}
2022-02-15 23:51:13 +00:00
foreach ( Item :: enumeratePermissions ( $post , true ) as $receiver ) {
if ( $receiver == $pcid ) {
return $post [ 'uid' ];
}
}
2022-02-14 22:04:33 +00:00
return null ;
}
2022-02-15 23:51:13 +00:00
if ( Post :: exists ([ 'uri-id' => $uriid , 'uid' => 0 ])) {
return 0 ;
}
// When the post belongs to a a forum then all forum users are allowed to access it
2022-02-18 09:12:33 +00:00
foreach ( Tag :: getByURIId ( $uriid , [ Tag :: MENTION , Tag :: EXCLUSIVE_MENTION ]) as $tag ) {
2022-02-15 23:51:13 +00:00
if ( DBA :: exists ( 'contact' , [ 'uid' => $uid , 'nurl' => Strings :: normaliseLink ( $tag [ 'url' ]), 'contact-type' => Contact :: TYPE_COMMUNITY ])) {
$target_uid = User :: getIdForURL ( $tag [ 'url' ]);
if ( ! empty ( $target_uid )) {
return $target_uid ;
}
2022-02-14 22:04:33 +00:00
}
}
return null ;
}
2020-07-23 03:26:54 +00:00
/**
* Store a public item array for the given users
2018-04-24 13:21:25 +00:00
*
2018-04-24 14:58:39 +00:00
* @ param array $item The item entry that will be stored
* @ param integer $uid The user that will receive the item entry
2020-07-21 08:35:57 +00:00
* @ return integer stored item id
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2018-04-24 13:21:25 +00:00
*/
2020-07-23 03:26:54 +00:00
private static function storeForUser ( array $item , int $uid )
2018-04-24 13:21:25 +00:00
{
2021-01-16 04:13:22 +00:00
if ( Post :: exists ([ 'uri-id' => $item [ 'uri-id' ], 'uid' => $uid ])) {
2022-02-05 11:16:50 +00:00
if ( ! empty ( $item [ 'event-id' ])) {
$post = Post :: selectFirst ([ 'event-id' ], [ 'uri-id' => $item [ 'uri-id' ], 'uid' => $uid ]);
if ( ! empty ( $post [ 'event-id' ])) {
$event = DBA :: selectFirst ( 'event' , [ 'edited' , 'start' , 'finish' , 'summary' , 'desc' , 'location' , 'nofinish' , 'adjust' ], [ 'id' => $item [ 'event-id' ]]);
if ( ! empty ( $event )) {
2022-02-05 16:49:21 +00:00
// We aren't using "Event::store" here, since we don't want to trigger any further action
2022-02-05 11:16:50 +00:00
$ret = DBA :: update ( 'event' , $event , [ 'id' => $post [ 'event-id' ]]);
Logger :: info ( 'Event updated' , [ 'uid' => $uid , 'source-event' => $item [ 'event-id' ], 'target-event' => $post [ 'event-id' ], 'ret' => $ret ]);
}
}
}
2020-07-21 08:35:57 +00:00
Logger :: info ( 'Item already exists' , [ 'uri-id' => $item [ 'uri-id' ], 'uid' => $uid ]);
return 0 ;
}
2022-02-15 06:21:46 +00:00
// Data from the "post-user" table
2020-07-21 08:35:57 +00:00
unset ( $item [ 'id' ]);
unset ( $item [ 'mention' ]);
unset ( $item [ 'starred' ]);
2020-07-23 03:26:54 +00:00
unset ( $item [ 'unseen' ]);
unset ( $item [ 'psid' ]);
2021-05-26 09:24:37 +00:00
unset ( $item [ 'pinned' ]);
unset ( $item [ 'ignored' ]);
unset ( $item [ 'pubmail' ]);
unset ( $item [ 'event-id' ]);
unset ( $item [ 'hidden' ]);
unset ( $item [ 'notification-type' ]);
2022-02-15 06:21:46 +00:00
unset ( $item [ 'post-reason' ]);
// Data from the "post-delivery-data" table
unset ( $item [ 'postopts' ]);
unset ( $item [ 'inform' ]);
2020-07-21 08:35:57 +00:00
2018-04-24 14:58:39 +00:00
$item [ 'uid' ] = $uid ;
2018-04-24 13:21:25 +00:00
$item [ 'origin' ] = 0 ;
$item [ 'wall' ] = 0 ;
2020-07-21 08:35:57 +00:00
2020-07-21 19:43:07 +00:00
if ( $item [ 'gravity' ] == GRAVITY_PARENT ) {
2020-07-21 08:35:57 +00:00
$contact = Contact :: getByURLForUser ( $item [ 'owner-link' ], $uid , false , [ 'id' ]);
2018-04-24 13:21:25 +00:00
} else {
2020-07-21 08:35:57 +00:00
$contact = Contact :: getByURLForUser ( $item [ 'author-link' ], $uid , false , [ 'id' ]);
2018-04-24 13:21:25 +00:00
}
2020-07-21 18:53:01 +00:00
if ( ! empty ( $contact [ 'id' ])) {
2020-07-21 08:35:57 +00:00
$item [ 'contact-id' ] = $contact [ 'id' ];
} else {
// Shouldn't happen at all
2020-07-21 18:30:45 +00:00
Logger :: warning ( 'contact-id could not be fetched' , [ 'uid' => $uid , 'item' => $item ]);
2018-07-20 12:19:26 +00:00
$self = DBA :: selectFirst ( 'contact' , [ 'id' ], [ 'self' => true , 'uid' => $uid ]);
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $self )) {
2020-07-21 08:35:57 +00:00
// Shouldn't happen even less
2020-07-21 18:30:45 +00:00
Logger :: warning ( 'self contact could not be fetched' , [ 'uid' => $uid , 'item' => $item ]);
2020-07-21 08:35:57 +00:00
return 0 ;
2018-04-24 13:21:25 +00:00
}
$item [ 'contact-id' ] = $self [ 'id' ];
}
2018-05-04 21:12:13 +00:00
$notify = false ;
2020-07-21 19:43:07 +00:00
if ( $item [ 'gravity' ] == GRAVITY_PARENT ) {
2018-07-20 12:19:26 +00:00
$contact = DBA :: selectFirst ( 'contact' , [], [ 'id' => $item [ 'contact-id' ], 'self' => false ]);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $contact )) {
2018-05-04 21:12:13 +00:00
$notify = self :: isRemoteSelf ( $contact , $item );
}
}
2021-08-06 18:49:17 +00:00
$distributed = self :: insert ( $item , $notify );
2018-04-24 13:21:25 +00:00
if ( ! $distributed ) {
2021-05-26 09:24:37 +00:00
Logger :: info ( " Distributed item wasn't stored " , [ 'uri-id' => $item [ 'uri-id' ], 'user' => $uid ]);
2018-04-24 13:21:25 +00:00
} else {
2021-05-26 09:24:37 +00:00
Logger :: info ( 'Distributed item was stored' , [ 'uri-id' => $item [ 'uri-id' ], 'user' => $uid , 'stored' => $distributed ]);
2018-04-24 13:21:25 +00:00
}
2020-07-21 08:35:57 +00:00
return $distributed ;
2018-04-24 13:21:25 +00:00
}
2018-01-16 22:23:19 +00:00
/**
2020-01-19 06:05:23 +00:00
* Add a shadow entry for a given item id that is a thread starter
2018-01-16 22:23:19 +00:00
*
* We store every public item entry additionally with the user id " 0 " .
* This is used for the community page and for the search .
* It is planned that in the future we will store public item entries only once .
*
* @ param integer $itemid Item ID that should be added
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2018-01-16 22:23:19 +00:00
*/
2020-10-24 08:05:03 +00:00
private static function addShadow ( $itemid )
2018-01-16 22:46:20 +00:00
{
2021-02-13 19:56:03 +00:00
$fields = [ 'uid' , 'private' , 'visible' , 'deleted' , 'network' , 'uri-id' ];
2021-02-14 18:33:15 +00:00
$condition = [ 'id' => $itemid , 'gravity' => GRAVITY_PARENT ];
2021-01-16 04:13:22 +00:00
$item = Post :: selectFirst ( $fields , $condition );
2018-01-16 22:23:19 +00:00
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $item )) {
2018-01-16 22:23:19 +00:00
return ;
}
// is it already a copy?
if (( $itemid == 0 ) || ( $item [ 'uid' ] == 0 )) {
return ;
}
// Is it a visible public post?
2021-02-13 19:56:03 +00:00
if ( ! $item [ " visible " ] || $item [ " deleted " ] || ( $item [ " private " ] == self :: PRIVATE )) {
2018-01-16 22:23:19 +00:00
return ;
}
// is it an entry from a connector? Only add an entry for natively connected networks
2019-07-01 18:00:55 +00:00
if ( ! in_array ( $item [ " network " ], array_merge ( Protocol :: FEDERATED ,[ '' ]))) {
2018-01-16 22:23:19 +00:00
return ;
}
2021-01-27 10:01:42 +00:00
if ( Post :: exists ([ 'uri-id' => $item [ 'uri-id' ], 'uid' => 0 ])) {
2018-07-01 07:57:59 +00:00
return ;
}
2021-01-16 04:13:22 +00:00
$item = Post :: selectFirst ( self :: ITEM_FIELDLIST , [ 'id' => $itemid ]);
2018-06-25 16:11:27 +00:00
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $item )) {
2018-07-01 07:57:59 +00:00
// Preparing public shadow (removing user specific data)
$item [ 'uid' ] = 0 ;
unset ( $item [ 'id' ]);
unset ( $item [ 'parent' ]);
unset ( $item [ 'wall' ]);
unset ( $item [ 'mention' ]);
unset ( $item [ 'origin' ]);
unset ( $item [ 'starred' ]);
2018-07-19 21:56:52 +00:00
unset ( $item [ 'postopts' ]);
unset ( $item [ 'inform' ]);
2021-04-07 06:02:06 +00:00
unset ( $item [ 'post-reason' ]);
2021-01-27 10:01:42 +00:00
if ( $item [ 'uri-id' ] == $item [ 'parent-uri-id' ]) {
2018-07-01 07:57:59 +00:00
$item [ 'contact-id' ] = $item [ 'owner-id' ];
} else {
$item [ 'contact-id' ] = $item [ 'author-id' ];
}
2018-01-16 22:23:19 +00:00
2021-08-06 18:49:17 +00:00
$public_shadow = self :: insert ( $item );
2018-01-16 22:23:19 +00:00
2020-06-28 18:22:29 +00:00
Logger :: info ( 'Stored public shadow' , [ 'thread' => $itemid , 'id' => $public_shadow ]);
2018-01-16 22:23:19 +00:00
}
}
/**
2020-01-19 06:05:23 +00:00
* Add a shadow entry for a given item id that is a comment
2018-01-16 22:23:19 +00:00
*
* This function does the same like the function above - but for comments
*
* @ param integer $itemid Item ID that should be added
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2018-01-16 22:23:19 +00:00
*/
2020-10-24 08:05:03 +00:00
private static function addShadowPost ( $itemid )
2018-01-16 22:46:20 +00:00
{
2021-01-16 04:13:22 +00:00
$item = Post :: selectFirst ( self :: ITEM_FIELDLIST , [ 'id' => $itemid ]);
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $item )) {
2018-01-16 22:23:19 +00:00
return ;
}
// Is it a toplevel post?
2020-05-27 12:19:06 +00:00
if ( $item [ 'gravity' ] == GRAVITY_PARENT ) {
2018-01-16 22:23:19 +00:00
self :: addShadow ( $itemid );
return ;
}
// Is this a shadow entry?
2018-05-10 12:48:27 +00:00
if ( $item [ 'uid' ] == 0 ) {
2018-01-16 22:23:19 +00:00
return ;
2018-05-10 12:48:27 +00:00
}
2018-01-16 22:23:19 +00:00
// Is there a shadow parent?
2021-01-27 10:01:42 +00:00
if ( ! Post :: exists ([ 'uri-id' => $item [ 'parent-uri-id' ], 'uid' => 0 ])) {
2018-01-16 22:23:19 +00:00
return ;
}
// Is there already a shadow entry?
2021-01-27 10:01:42 +00:00
if ( Post :: exists ([ 'uri-id' => $item [ 'uri-id' ], 'uid' => 0 ])) {
2018-01-16 22:23:19 +00:00
return ;
}
2018-04-27 04:11:33 +00:00
// Save "origin" and "parent" state
$origin = $item [ 'origin' ];
$parent = $item [ 'parent' ];
2018-01-16 22:23:19 +00:00
// Preparing public shadow (removing user specific data)
$item [ 'uid' ] = 0 ;
2018-04-27 04:11:33 +00:00
unset ( $item [ 'id' ]);
unset ( $item [ 'parent' ]);
unset ( $item [ 'wall' ]);
unset ( $item [ 'mention' ]);
unset ( $item [ 'origin' ]);
unset ( $item [ 'starred' ]);
2018-07-19 21:56:52 +00:00
unset ( $item [ 'postopts' ]);
unset ( $item [ 'inform' ]);
2021-04-07 06:02:06 +00:00
unset ( $item [ 'post-reason' ]);
2018-03-02 00:53:47 +00:00
$item [ 'contact-id' ] = Contact :: getIdForURL ( $item [ 'author-link' ]);
2018-01-16 22:23:19 +00:00
2021-08-06 18:49:17 +00:00
$public_shadow = self :: insert ( $item );
2018-01-16 22:23:19 +00:00
2021-01-27 10:01:42 +00:00
Logger :: info ( 'Stored public shadow' , [ 'uri-id' => $item [ 'uri-id' ], 'id' => $public_shadow ]);
2018-04-27 04:11:33 +00:00
// If this was a comment to a Diaspora post we don't get our comment back.
// This means that we have to distribute the comment by ourselves.
2021-01-16 04:13:22 +00:00
if ( $origin && Post :: exists ([ 'id' => $parent , 'network' => Protocol :: DIASPORA ])) {
2018-05-10 12:48:27 +00:00
self :: distribute ( $public_shadow );
2018-04-27 04:11:33 +00:00
}
2018-01-16 22:23:19 +00:00
}
2018-01-20 23:52:54 +00:00
2019-01-06 21:06:53 +00:00
/**
2018-06-30 05:18:43 +00:00
* Adds a language specification in a " language " element of given $arr .
2018-01-20 23:52:54 +00:00
* Expects " body " element to exist in $arr .
2019-01-06 21:06:53 +00:00
*
2020-05-13 18:45:31 +00:00
* @ param array $item
* @ return string detected language
2019-01-06 21:06:53 +00:00
* @ throws \Text_LanguageDetect_Exception
2018-01-20 23:52:54 +00:00
*/
2020-05-13 18:45:31 +00:00
private static function getLanguage ( array $item )
2018-01-20 23:52:54 +00:00
{
2021-03-06 08:43:25 +00:00
if ( ! empty ( $item [ 'language' ])) {
return $item [ 'language' ];
}
2020-11-12 16:52:55 +00:00
if ( ! in_array ( $item [ 'gravity' ], [ GRAVITY_PARENT , GRAVITY_COMMENT ]) || empty ( $item [ 'body' ])) {
2020-10-03 15:42:21 +00:00
return '' ;
}
2020-10-05 12:50:18 +00:00
// Convert attachments to links
$naked_body = BBCode :: removeAttachment ( $item [ 'body' ]);
2020-11-12 16:52:55 +00:00
if ( empty ( $naked_body )) {
return '' ;
}
2020-10-05 12:50:18 +00:00
// Remove links and pictures
$naked_body = BBCode :: removeLinks ( $naked_body );
// Convert the title and the body to plain text
$naked_body = trim ( $item [ 'title' ] . " \n " . BBCode :: toPlaintext ( $naked_body ));
// Remove possibly remaining links
$naked_body = preg_replace ( Strings :: autoLinkRegEx (), '' , $naked_body );
2018-01-20 23:52:54 +00:00
2020-10-24 08:05:03 +00:00
if ( empty ( $naked_body )) {
return '' ;
}
2021-07-19 18:00:31 +00:00
$availableLanguages = DI :: l10n () -> getAvailableLanguages ();
// See https://github.com/friendica/friendica/issues/10511
// Persian is manually added to language detection until a persian translation is provided for the interface, at
// which point it will be automatically available through `getAvailableLanguages()` and this should be removed.
$availableLanguages [ 'fa' ] = 'fa' ;
$ld = new Language ( $availableLanguages );
2020-10-03 15:42:21 +00:00
$languages = $ld -> detect ( $naked_body ) -> limit ( 0 , 3 ) -> close ();
2018-06-30 05:18:43 +00:00
if ( is_array ( $languages )) {
2020-05-13 18:45:31 +00:00
return json_encode ( $languages );
2018-01-20 23:52:54 +00:00
}
2020-05-13 18:45:31 +00:00
return '' ;
2018-01-20 23:52:54 +00:00
}
2020-10-04 18:52:28 +00:00
public static function getLanguageMessage ( array $item )
{
2020-10-07 04:15:02 +00:00
$iso639 = new \Matriphe\ISO639\ISO639 ;
2020-10-04 18:52:28 +00:00
$used_languages = '' ;
foreach ( json_decode ( $item [ 'language' ], true ) as $language => $reliability ) {
2020-10-07 04:15:02 +00:00
$used_languages .= $iso639 -> languageByCode1 ( $language ) . ' (' . $language . " ): " . number_format ( $reliability , 5 ) . '\n' ;
2020-10-04 18:52:28 +00:00
}
2020-10-07 04:15:02 +00:00
$used_languages = DI :: l10n () -> t ( 'Detected languages in this post:\n%s' , $used_languages );
2020-10-04 18:52:28 +00:00
return $used_languages ;
}
2018-01-20 23:52:54 +00:00
/**
2020-01-19 06:05:23 +00:00
* Creates an unique guid out of a given uri
2018-01-20 23:52:54 +00:00
*
* @ param string $uri uri of an item entry
2018-02-21 04:13:13 +00:00
* @ param string $host hostname for the GUID prefix
2018-01-20 23:52:54 +00:00
* @ return string unique guid
*/
2018-02-21 04:13:13 +00:00
public static function guidFromUri ( $uri , $host )
2018-01-20 23:52:54 +00:00
{
// 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 );
// We use a hash of the hostname as prefix for the guid
$guid_prefix = hash ( " crc32 " , $host );
// Remove the scheme to make sure that "https" and "http" doesn't make a difference
unset ( $parsed [ " scheme " ]);
// Glue it together to be able to make a hash from it
$host_id = implode ( " / " , $parsed );
// We could use any hash algorithm since it isn't a security issue
$host_hash = hash ( " ripemd128 " , $host_id );
return $guid_prefix . $host_hash ;
}
2018-06-16 06:44:19 +00:00
/**
* generate an unique URI
*
2019-01-06 21:06:53 +00:00
* @ param integer $uid User id
* @ param string $guid An existing GUID ( Otherwise it will be generated )
2018-06-16 06:44:19 +00:00
*
* @ return string
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-06-16 06:44:19 +00:00
*/
public static function newURI ( $uid , $guid = " " )
{
if ( $guid == " " ) {
2018-09-27 11:52:15 +00:00
$guid = System :: createUUID ();
2018-06-16 06:44:19 +00:00
}
2019-12-16 00:05:15 +00:00
return DI :: baseUrl () -> get () . '/objects/' . $guid ;
2018-06-16 06:44:19 +00:00
}
2018-01-20 23:52:54 +00:00
/**
2020-01-19 06:05:23 +00:00
* Set " success_update " and " last-item " to the date of the last time we heard from this contact
2018-01-20 23:52:54 +00:00
*
* This can be used to filter for inactive contacts .
* Only do this for public postings to avoid privacy problems , since poco data is public .
* Don 't set this value if it isn' t from the owner ( could be an author that we don ' t know )
*
* @ param array $arr Contains the just posted item record
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2018-01-20 23:52:54 +00:00
*/
2018-01-28 16:28:59 +00:00
private static function updateContact ( $arr )
{
2018-01-20 23:52:54 +00:00
// Unarchive the author
2018-07-20 12:19:26 +00:00
$contact = DBA :: selectFirst ( 'contact' , [], [ 'id' => $arr [ " author-id " ]]);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $contact )) {
2018-02-14 21:18:16 +00:00
Contact :: unmarkForArchival ( $contact );
2018-01-20 23:52:54 +00:00
}
2018-02-14 21:18:16 +00:00
// Unarchive the contact if it's not our own contact
2018-07-20 12:19:26 +00:00
$contact = DBA :: selectFirst ( 'contact' , [], [ 'id' => $arr [ " contact-id " ], 'self' => false ]);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $contact )) {
2018-02-14 21:18:16 +00:00
Contact :: unmarkForArchival ( $contact );
2018-01-20 23:52:54 +00:00
}
2020-04-24 15:18:34 +00:00
/// @todo On private posts we could obfuscate the date
2020-07-19 10:03:33 +00:00
$update = ( $arr [ 'private' ] != self :: PRIVATE ) || in_array ( $arr [ 'network' ], Protocol :: FEDERATED );
2018-01-20 23:52:54 +00:00
// Is it a forum? Then we don't care about the rules from above
2021-01-27 10:01:42 +00:00
if ( ! $update && in_array ( $arr [ " network " ], [ Protocol :: ACTIVITYPUB , Protocol :: DFRN ]) && ( $arr [ " parent-uri-id " ] === $arr [ " uri-id " ])) {
2018-07-20 12:19:26 +00:00
if ( DBA :: exists ( 'contact' , [ 'id' => $arr [ 'contact-id' ], 'forum' => true ])) {
2018-01-20 23:52:54 +00:00
$update = true ;
}
}
if ( $update ) {
2020-04-24 15:18:34 +00:00
// The "self" contact id is used (for example in the connectors) when the contact is unknown
// So we have to ensure to only update the last item when it had been our own post,
// or it had been done by a "regular" contact.
if ( ! empty ( $arr [ 'wall' ])) {
$condition = [ 'id' => $arr [ 'contact-id' ]];
2021-04-26 06:50:12 +00:00
} else {
2020-04-24 15:18:34 +00:00
$condition = [ 'id' => $arr [ 'contact-id' ], 'self' => false ];
}
2021-09-10 18:21:19 +00:00
Contact :: update ([ 'failed' => false , 'success_update' => $arr [ 'received' ], 'last-item' => $arr [ 'received' ]], $condition );
2018-01-20 23:52:54 +00:00
}
// Now do the same for the system wide contacts with uid=0
2020-03-02 07:57:23 +00:00
if ( $arr [ 'private' ] != self :: PRIVATE ) {
2021-09-10 18:21:19 +00:00
Contact :: update ([ 'failed' => false , 'success_update' => $arr [ 'received' ], 'last-item' => $arr [ 'received' ]],
2018-01-20 23:52:54 +00:00
[ 'id' => $arr [ 'owner-id' ]]);
if ( $arr [ 'owner-id' ] != $arr [ 'author-id' ]) {
2021-09-10 18:21:19 +00:00
Contact :: update ([ 'failed' => false , 'success_update' => $arr [ 'received' ], 'last-item' => $arr [ 'received' ]],
2018-01-20 23:52:54 +00:00
[ 'id' => $arr [ 'author-id' ]]);
}
}
}
2018-01-28 11:18:08 +00:00
2020-06-05 00:56:50 +00:00
public static function setHashtags ( $body )
2018-01-28 16:28:59 +00:00
{
2021-01-22 22:45:28 +00:00
$body = BBCode :: performWithEscapedTags ( $body , [ 'noparse' , 'pre' , 'code' , 'img' ], function ( $body ) {
2020-06-05 00:56:50 +00:00
$tags = BBCode :: getTags ( $body );
2019-01-30 01:25:51 +00:00
2020-06-05 00:56:50 +00:00
// No hashtags?
if ( ! count ( $tags )) {
return $body ;
}
2018-01-28 11:18:08 +00:00
2020-06-05 00:56:50 +00:00
// This sorting is important when there are hashtags that are part of other hashtags
// Otherwise there could be problems with hashtags like #test and #test2
// Because of this we are sorting from the longest to the shortest tag.
usort ( $tags , function ( $a , $b ) {
return strlen ( $b ) <=> strlen ( $a );
});
2018-01-28 11:18:08 +00:00
2020-06-05 00:56:50 +00:00
$URLSearchString = " ^ \ [ \ ] " ;
2018-01-28 11:18:08 +00:00
2020-06-05 00:56:50 +00:00
// All hashtags should point to the home server if "local_tags" is activated
if ( DI :: config () -> get ( 'system' , 'local_tags' )) {
$body = preg_replace ( " /# \ [url \ =([ $URLSearchString ]*) \ ](.*?) \ [ \ /url \ ]/ism " ,
" #[url= " . DI :: baseUrl () . " /search?tag= $ 2] $ 2[/url] " , $body );
}
2018-01-28 11:18:08 +00:00
2020-06-05 00:56:50 +00:00
// mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
$body = preg_replace_callback ( " / \ [url \ =([ $URLSearchString ]*) \ ](.*?) \ [ \ /url \ ]/ism " ,
function ( $match ) {
return ( " [url= " . str_replace ( " # " , " # " , $match [ 1 ]) . " ] " . str_replace ( " # " , " # " , $match [ 2 ]) . " [/url] " );
}, $body );
2018-01-28 11:18:08 +00:00
2020-06-05 00:56:50 +00:00
$body = preg_replace_callback ( " / \ [bookmark \ =([ $URLSearchString ]*) \ ](.*?) \ [ \ /bookmark \ ]/ism " ,
function ( $match ) {
return ( " [bookmark= " . str_replace ( " # " , " # " , $match [ 1 ]) . " ] " . str_replace ( " # " , " # " , $match [ 2 ]) . " [/bookmark] " );
}, $body );
2018-01-28 11:18:08 +00:00
2021-04-29 04:14:44 +00:00
$body = preg_replace_callback ( " / \ [attachment (.*?) \ ](.*?) \ [ \ /attachment \ ]/ism " ,
2020-06-05 00:56:50 +00:00
function ( $match ) {
return ( " [attachment " . str_replace ( " # " , " # " , $match [ 1 ]) . " ] " . $match [ 2 ] . " [/attachment] " );
}, $body );
2018-01-28 11:18:08 +00:00
2020-06-05 00:56:50 +00:00
// Repair recursive urls
$body = preg_replace ( " /# \ [url \ =([ $URLSearchString ]*) \ ](.*?) \ [ \ /url \ ]/ism " ,
" # $ 2 " , $body );
2018-01-28 11:18:08 +00:00
2020-06-05 00:56:50 +00:00
foreach ( $tags as $tag ) {
if (( strpos ( $tag , '#' ) !== 0 ) || strpos ( $tag , '[url=' ) || strlen ( $tag ) < 2 || $tag [ 1 ] == '#' ) {
continue ;
}
2018-01-28 11:18:08 +00:00
2020-06-05 00:56:50 +00:00
$basetag = str_replace ( '_' , ' ' , substr ( $tag , 1 ));
$newtag = '#[url=' . DI :: baseUrl () . '/search?tag=' . $basetag . ']' . $basetag . '[/url]' ;
2018-01-28 11:18:08 +00:00
2020-06-05 00:56:50 +00:00
$body = str_replace ( $tag , $newtag , $body );
}
// Convert back the masked hashtags
$body = str_replace ( " # " , " # " , $body );
2018-01-28 11:18:08 +00:00
2020-06-05 00:56:50 +00:00
return $body ;
});
2019-01-29 20:17:11 +00:00
2020-06-05 00:56:50 +00:00
return $body ;
2018-01-28 11:18:08 +00:00
}
/**
* look for mention tags and setup a second delivery chain for forum / community posts if appropriate
2019-01-06 21:06:53 +00:00
*
2018-01-28 11:18:08 +00:00
* @ param int $uid
* @ param int $item_id
2019-07-31 14:09:27 +00:00
* @ return boolean true if item was deleted , else false
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-01-28 11:18:08 +00:00
*/
private static function tagDeliver ( $uid , $item_id )
{
$mention = false ;
2022-01-29 19:09:18 +00:00
$owner = User :: getOwnerDataById ( $uid );
if ( ! DBA :: isResult ( $owner )) {
2022-02-17 21:44:59 +00:00
Logger :: warning ( 'User not found, quitting here.' , [ 'uid' => $uid ]);
2019-07-31 14:09:27 +00:00
return false ;
2018-01-28 11:18:08 +00:00
}
2022-01-29 19:09:18 +00:00
if ( $owner [ 'contact-type' ] != User :: ACCOUNT_TYPE_COMMUNITY ) {
Logger :: debug ( 'Owner is no community, quitting here.' , [ 'uid' => $uid , 'id' => $item_id ]);
return false ;
}
2018-01-28 11:18:08 +00:00
2022-02-17 21:44:59 +00:00
$item = Post :: selectFirst ( self :: ITEM_FIELDLIST , [ 'id' => $item_id , 'gravity' => [ GRAVITY_PARENT , GRAVITY_COMMENT ], 'origin' => false ]);
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $item )) {
2022-02-17 21:44:59 +00:00
Logger :: debug ( 'Post is an activity or origin or not found at all, quitting here.' , [ 'id' => $item_id ]);
2019-07-31 14:09:27 +00:00
return false ;
2018-01-28 11:18:08 +00:00
}
2022-02-17 21:44:59 +00:00
if ( $item [ 'gravity' ] == GRAVITY_PARENT ) {
$tags = Tag :: getByURIId ( $item [ 'uri-id' ], [ Tag :: MENTION , Tag :: EXCLUSIVE_MENTION ]);
foreach ( $tags as $tag ) {
if ( Strings :: compareLink ( $owner [ 'url' ], $tag [ 'url' ])) {
$mention = true ;
Logger :: info ( 'Mention found in tag.' , [ 'url' => $tag [ 'url' ], 'uri' => $item [ 'uri' ], 'uid' => $uid , 'id' => $item_id , 'uri-id' => $item [ 'uri-id' ], 'guid' => $item [ 'guid' ]]);
}
}
2018-01-28 11:18:08 +00:00
2022-02-17 21:44:59 +00:00
if ( ! $mention ) {
Logger :: info ( 'Top-level post without mention is deleted.' , [ 'uri' => $item [ 'uri' ], $uid , 'id' => $item_id , 'uri-id' => $item [ 'uri-id' ], 'guid' => $item [ 'guid' ]]);
Post\User :: delete ([ 'uri-id' => $item [ 'uri-id' ], 'uid' => $item [ 'uid' ]]);
return true ;
2018-01-28 11:18:08 +00:00
}
2022-02-17 21:44:59 +00:00
$arr = [ 'item' => $item , 'user' => $owner ];
Hook :: callAll ( 'tagged' , $arr );
} else {
$tags = Tag :: getByURIId ( $item [ 'parent-uri-id' ], [ Tag :: MENTION , Tag :: EXCLUSIVE_MENTION ]);
foreach ( $tags as $tag ) {
if ( Strings :: compareLink ( $owner [ 'url' ], $tag [ 'url' ])) {
$mention = true ;
Logger :: info ( 'Mention found in parent tag.' , [ 'url' => $tag [ 'url' ], 'uri' => $item [ 'uri' ], 'uid' => $uid , 'id' => $item_id , 'uri-id' => $item [ 'uri-id' ], 'guid' => $item [ 'guid' ]]);
2020-09-11 09:12:09 +00:00
}
}
2021-04-26 06:50:12 +00:00
2022-02-17 21:44:59 +00:00
if ( ! $mention ) {
Logger :: debug ( 'No mentions found in parent, quitting here.' , [ 'id' => $item_id , 'uri-id' => $item [ 'uri-id' ], 'guid' => $item [ 'guid' ]]);
return false ;
}
2018-01-28 11:18:08 +00:00
}
2022-01-29 19:09:18 +00:00
Logger :: info ( 'Community post will be distributed' , [ 'uri' => $item [ 'uri' ], 'uid' => $uid , 'id' => $item_id , 'uri-id' => $item [ 'uri-id' ], 'guid' => $item [ 'guid' ]]);
2018-01-28 11:18:08 +00:00
2022-02-12 18:38:36 +00:00
if ( $owner [ 'page-flags' ] == User :: PAGE_FLAGS_PRVGROUP ) {
2022-02-16 22:56:55 +00:00
$allow_cid = '' ;
$allow_gid = '<' . Group :: FOLLOWERS . '>' ;
2022-02-14 22:04:33 +00:00
$deny_cid = '' ;
2022-02-12 18:38:36 +00:00
$deny_gid = '' ;
self :: performActivity ( $item [ 'id' ], 'announce' , $uid , $allow_cid , $allow_gid , $deny_cid , $deny_gid );
2021-06-06 09:19:29 +00:00
} else {
2022-02-12 18:38:36 +00:00
self :: performActivity ( $item [ 'id' ], 'announce' , $uid );
2021-06-06 09:19:29 +00:00
}
2018-01-28 11:18:08 +00:00
2022-01-29 19:09:18 +00:00
Logger :: info ( 'Community post had been distributed' , [ 'uri' => $item [ 'uri' ], 'uid' => $uid , 'id' => $item_id , 'uri-id' => $item [ 'uri-id' ], 'guid' => $item [ 'guid' ]]);
2019-07-31 14:09:27 +00:00
return false ;
2018-01-28 11:18:08 +00:00
}
2020-11-28 22:53:58 +00:00
/**
* Automatically reshare the item if the " remote_self " option is selected
*
* @ param array $item
* @ return void
*/
private static function autoReshare ( array $item )
{
if ( $item [ 'gravity' ] != GRAVITY_PARENT ) {
return ;
}
if ( ! DBA :: exists ( 'contact' , [ 'id' => $item [ 'contact-id' ], 'remote_self' => Contact :: MIRROR_NATIVE_RESHARE ])) {
return ;
}
2020-11-29 00:05:46 +00:00
if ( ! in_array ( $item [ 'network' ], [ Protocol :: ACTIVITYPUB , Protocol :: DFRN ])) {
return ;
}
2020-11-28 22:53:58 +00:00
Logger :: info ( 'Automatically reshare item' , [ 'uid' => $item [ 'uid' ], 'id' => $item [ 'id' ], 'guid' => $item [ 'guid' ], 'uri-id' => $item [ 'uri-id' ]]);
2021-01-16 04:13:22 +00:00
self :: performActivity ( $item [ 'id' ], 'announce' , $item [ 'uid' ]);
2020-11-28 22:53:58 +00:00
}
2018-01-28 16:28:59 +00:00
public static function isRemoteSelf ( $contact , & $datarray )
{
2018-01-28 11:18:08 +00:00
if ( ! $contact [ 'remote_self' ]) {
return false ;
}
// Prevent the forwarding of posts that are forwarded
2018-08-11 20:40:44 +00:00
if ( ! empty ( $datarray [ " extid " ]) && ( $datarray [ " extid " ] == Protocol :: DFRN )) {
2020-06-28 18:22:29 +00:00
Logger :: info ( 'Already forwarded' );
2018-01-28 11:18:08 +00:00
return false ;
}
// Prevent to forward already forwarded posts
2019-12-15 23:47:24 +00:00
if ( $datarray [ " app " ] == DI :: baseUrl () -> getHostname ()) {
2020-06-28 18:22:29 +00:00
Logger :: info ( 'Already forwarded (second test)' );
2018-01-28 11:18:08 +00:00
return false ;
}
// Only forward posts
2019-10-23 22:25:43 +00:00
if ( $datarray [ " verb " ] != Activity :: POST ) {
2020-06-28 18:22:29 +00:00
Logger :: info ( 'No post' );
2018-01-28 11:18:08 +00:00
return false ;
}
2020-03-02 07:57:23 +00:00
if (( $contact [ 'network' ] != Protocol :: FEED ) && ( $datarray [ 'private' ] == self :: PRIVATE )) {
2020-06-28 18:22:29 +00:00
Logger :: info ( 'Not public' );
2018-01-28 11:18:08 +00:00
return false ;
}
$datarray2 = $datarray ;
2020-06-28 18:22:29 +00:00
Logger :: info ( 'remote-self start' , [ 'contact' => $contact [ 'url' ], 'remote_self' => $contact [ 'remote_self' ], 'item' => $datarray ]);
2020-11-28 22:53:58 +00:00
if ( $contact [ 'remote_self' ] == Contact :: MIRROR_OWN_POST ) {
2018-07-20 12:19:26 +00:00
$self = DBA :: selectFirst ( 'contact' , [ 'id' , 'name' , 'url' , 'thumb' ],
2018-02-22 06:52:58 +00:00
[ 'uid' => $contact [ 'uid' ], 'self' => true ]);
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $self )) {
2018-02-22 06:52:58 +00:00
$datarray [ 'contact-id' ] = $self [ " id " ];
2018-01-28 11:18:08 +00:00
2018-02-22 06:52:58 +00:00
$datarray [ 'owner-name' ] = $self [ " name " ];
$datarray [ 'owner-link' ] = $self [ " url " ];
$datarray [ 'owner-avatar' ] = $self [ " thumb " ];
2018-01-28 11:18:08 +00:00
$datarray [ 'author-name' ] = $datarray [ 'owner-name' ];
$datarray [ 'author-link' ] = $datarray [ 'owner-link' ];
$datarray [ 'author-avatar' ] = $datarray [ 'owner-avatar' ];
unset ( $datarray [ 'edited' ]);
2018-05-04 21:12:13 +00:00
unset ( $datarray [ 'network' ]);
unset ( $datarray [ 'owner-id' ]);
unset ( $datarray [ 'author-id' ]);
2018-01-28 11:18:08 +00:00
}
2018-08-11 20:40:44 +00:00
if ( $contact [ 'network' ] != Protocol :: FEED ) {
2020-11-07 08:22:59 +00:00
$old_uri_id = $datarray [ " uri-id " ] ? ? 0 ;
2018-09-27 11:52:15 +00:00
$datarray [ " guid " ] = System :: createUUID ();
2018-01-28 11:18:08 +00:00
unset ( $datarray [ " plink " ]);
2018-06-16 06:44:19 +00:00
$datarray [ " uri " ] = self :: newURI ( $contact [ 'uid' ], $datarray [ " guid " ]);
2020-11-07 08:22:59 +00:00
$datarray [ " uri-id " ] = ItemURI :: getIdByURI ( $datarray [ " uri " ]);
2018-08-11 20:40:44 +00:00
$datarray [ " extid " ] = Protocol :: DFRN ;
2018-01-28 11:18:08 +00:00
$urlpart = parse_url ( $datarray2 [ 'author-link' ]);
$datarray [ " app " ] = $urlpart [ " host " ];
2020-11-07 08:22:59 +00:00
if ( ! empty ( $old_uri_id )) {
Post\Media :: copy ( $old_uri_id , $datarray [ " uri-id " ]);
}
2020-11-09 16:13:18 +00:00
unset ( $datarray [ " parent-uri " ]);
unset ( $datarray [ " thr-parent " ]);
2018-01-28 11:18:08 +00:00
} else {
2020-03-02 07:57:23 +00:00
$datarray [ 'private' ] = self :: PUBLIC ;
2018-01-28 11:18:08 +00:00
}
}
2018-08-11 20:40:44 +00:00
if ( $contact [ 'network' ] != Protocol :: FEED ) {
2018-01-28 11:18:08 +00:00
// Store the original post
2020-05-12 21:49:12 +00:00
$result = self :: insert ( $datarray2 );
2020-06-28 18:22:29 +00:00
Logger :: info ( 'remote-self post original item' , [ 'contact' => $contact [ 'url' ], 'result' => $result , 'item' => $datarray2 ]);
2018-01-28 11:18:08 +00:00
} else {
$datarray [ " app " ] = " Feed " ;
2018-05-04 21:12:13 +00:00
$result = true ;
2018-01-28 11:18:08 +00:00
}
2020-12-08 21:58:32 +00:00
return ( bool ) $result ;
2018-01-28 11:18:08 +00:00
}
/**
*
* @ param string $s
* @ param int $uid
* @ param array $item
* @ param int $cid
* @ return string
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-01-28 11:18:08 +00:00
*/
public static function fixPrivatePhotos ( $s , $uid , $item = null , $cid = 0 )
{
2020-01-19 20:21:13 +00:00
if ( DI :: config () -> get ( 'system' , 'disable_embedded' )) {
2018-01-28 11:18:08 +00:00
return $s ;
}
2020-06-28 18:22:29 +00:00
Logger :: info ( 'check for photos' );
2019-12-30 22:00:08 +00:00
$site = substr ( DI :: baseUrl (), strpos ( DI :: baseUrl (), '://' ));
2018-01-28 11:18:08 +00:00
$orig_body = $s ;
$new_body = '' ;
$img_start = strpos ( $orig_body , '[img' );
$img_st_close = ( $img_start !== false ? strpos ( substr ( $orig_body , $img_start ), ']' ) : false );
$img_len = ( $img_start !== false ? strpos ( substr ( $orig_body , $img_start + $img_st_close + 1 ), '[/img]' ) : false );
while (( $img_st_close !== false ) && ( $img_len !== false )) {
$img_st_close ++ ; // make it point to AFTER the closing bracket
$image = substr ( $orig_body , $img_start + $img_st_close , $img_len );
2020-06-28 18:22:29 +00:00
Logger :: info ( 'found photo' , [ 'image' => $image ]);
2018-01-28 11:18:08 +00:00
if ( stristr ( $image , $site . '/photo/' )) {
// Only embed locally hosted photos
$replace = false ;
$i = basename ( $image );
$i = str_replace ([ '.jpg' , '.png' , '.gif' ], [ '' , '' , '' ], $i );
$x = strpos ( $i , '-' );
if ( $x ) {
$res = substr ( $i , $x + 1 );
$i = substr ( $i , 0 , $x );
2018-12-11 19:03:29 +00:00
$photo = Photo :: getPhotoForUser ( $uid , $i , $res );
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $photo )) {
2018-01-28 11:18:08 +00:00
/*
* Check to see if we should replace this photo link with an embedded image
* 1. No need to do so if the photo is public
* 2. If there 's a contact-id provided, see if they' re in the access list
* for the photo . If so , embed it .
* 3. Otherwise , if we have an item , see if the item permissions match the photo
* permissions , regardless of order but first check to see if they ' re an exact
* match to save some processing overhead .
*/
2018-02-22 06:52:58 +00:00
if ( self :: hasPermissions ( $photo )) {
2018-01-28 11:18:08 +00:00
if ( $cid ) {
2018-02-22 06:52:58 +00:00
$recips = self :: enumeratePermissions ( $photo );
2018-01-28 11:18:08 +00:00
if ( in_array ( $cid , $recips )) {
$replace = true ;
}
} elseif ( $item ) {
2019-07-15 01:48:35 +00:00
if ( self :: samePermissions ( $uid , $item , $photo )) {
2018-01-28 11:18:08 +00:00
$replace = true ;
}
}
}
if ( $replace ) {
2018-12-11 19:03:29 +00:00
$photo_img = Photo :: getImageForPhoto ( $photo );
2018-01-28 11:18:08 +00:00
// If a custom width and height were specified, apply before embedding
if ( preg_match ( " / \ [img \ =([0-9]*)x([0-9]*) \ ]/is " , substr ( $orig_body , $img_start , $img_st_close ), $match )) {
2020-06-28 18:22:29 +00:00
Logger :: info ( 'scaling photo' );
2018-01-28 11:18:08 +00:00
$width = intval ( $match [ 1 ]);
$height = intval ( $match [ 2 ]);
2018-12-11 19:03:29 +00:00
$photo_img -> scaleDown ( max ( $width , $height ));
2018-01-28 11:18:08 +00:00
}
2018-12-11 19:03:29 +00:00
$data = $photo_img -> asString ();
$type = $photo_img -> getType ();
2020-06-28 18:22:29 +00:00
Logger :: info ( 'replacing photo' );
2018-01-28 11:18:08 +00:00
$image = 'data:' . $type . ';base64,' . base64_encode ( $data );
2020-06-28 18:22:29 +00:00
Logger :: debug ( 'replaced' , [ 'image' => $image ]);
2018-01-28 11:18:08 +00:00
}
}
}
}
$new_body = $new_body . substr ( $orig_body , 0 , $img_start + $img_st_close ) . $image . '[/img]' ;
$orig_body = substr ( $orig_body , $img_start + $img_st_close + $img_len + strlen ( '[/img]' ));
if ( $orig_body === false ) {
$orig_body = '' ;
}
$img_start = strpos ( $orig_body , '[img' );
$img_st_close = ( $img_start !== false ? strpos ( substr ( $orig_body , $img_start ), ']' ) : false );
$img_len = ( $img_start !== false ? strpos ( substr ( $orig_body , $img_start + $img_st_close + 1 ), '[/img]' ) : false );
}
$new_body = $new_body . $orig_body ;
return $new_body ;
}
2018-01-28 16:28:59 +00:00
private static function hasPermissions ( $obj )
{
2018-07-01 07:57:59 +00:00
return ! empty ( $obj [ 'allow_cid' ]) || ! empty ( $obj [ 'allow_gid' ]) ||
! empty ( $obj [ 'deny_cid' ]) || ! empty ( $obj [ 'deny_gid' ]);
2018-01-28 11:18:08 +00:00
}
2019-07-15 01:48:35 +00:00
private static function samePermissions ( $uid , $obj1 , $obj2 )
2018-01-28 16:28:59 +00:00
{
2018-01-28 11:18:08 +00:00
// first part is easy. Check that these are exactly the same.
if (( $obj1 [ 'allow_cid' ] == $obj2 [ 'allow_cid' ])
&& ( $obj1 [ 'allow_gid' ] == $obj2 [ 'allow_gid' ])
&& ( $obj1 [ 'deny_cid' ] == $obj2 [ 'deny_cid' ])
&& ( $obj1 [ 'deny_gid' ] == $obj2 [ 'deny_gid' ])) {
return true ;
}
// This is harder. Parse all the permissions and compare the resulting set.
$recipients1 = self :: enumeratePermissions ( $obj1 );
$recipients2 = self :: enumeratePermissions ( $obj2 );
sort ( $recipients1 );
sort ( $recipients2 );
/// @TODO Comparison of arrays, maybe use array_diff_assoc() here?
return ( $recipients1 == $recipients2 );
}
2019-08-17 03:59:48 +00:00
/**
* Returns an array of contact - ids that are allowed to see this object
*
* @ param array $obj Item array with at least uid , allow_cid , allow_gid , deny_cid and deny_gid
* @ param bool $check_dead Prunes unavailable contacts from the result
* @ return array
* @ throws \Exception
*/
public static function enumeratePermissions ( array $obj , bool $check_dead = false )
2018-01-28 16:28:59 +00:00
{
2019-12-15 22:28:01 +00:00
$aclFormater = DI :: aclFormatter ();
2019-10-22 22:40:14 +00:00
2019-11-01 13:13:29 +00:00
$allow_people = $aclFormater -> expand ( $obj [ 'allow_cid' ]);
$allow_groups = Group :: expand ( $obj [ 'uid' ], $aclFormater -> expand ( $obj [ 'allow_gid' ]), $check_dead );
$deny_people = $aclFormater -> expand ( $obj [ 'deny_cid' ]);
$deny_groups = Group :: expand ( $obj [ 'uid' ], $aclFormater -> expand ( $obj [ 'deny_gid' ]), $check_dead );
2018-01-28 11:18:08 +00:00
$recipients = array_unique ( array_merge ( $allow_people , $allow_groups ));
$deny = array_unique ( array_merge ( $deny_people , $deny_groups ));
$recipients = array_diff ( $recipients , $deny );
return $recipients ;
}
2020-11-27 11:24:31 +00:00
public static function expire ( int $uid , int $days , string $network = " " , bool $force = false )
2018-01-28 16:28:59 +00:00
{
2018-01-28 11:18:08 +00:00
if ( ! $uid || ( $days < 1 )) {
return ;
}
2020-05-27 12:19:06 +00:00
$condition = [ " `uid` = ? AND NOT `deleted` AND `gravity` = ? " ,
2018-07-01 07:57:59 +00:00
$uid , GRAVITY_PARENT ];
2018-01-28 11:18:08 +00:00
/*
* $expire_network_only = save your own wall posts
* and just expire conversations started by others
*/
2020-01-18 15:50:57 +00:00
$expire_network_only = DI :: pConfig () -> get ( $uid , 'expire' , 'network_only' , false );
2018-07-01 07:57:59 +00:00
if ( $expire_network_only ) {
$condition [ 0 ] .= " AND NOT `wall` " ;
}
2018-01-28 11:18:08 +00:00
if ( $network != " " ) {
2018-07-01 07:57:59 +00:00
$condition [ 0 ] .= " AND `network` = ? " ;
$condition [] = $network ;
2018-01-28 11:18:08 +00:00
}
2021-12-02 14:19:01 +00:00
$condition [ 0 ] .= " AND `received` < ? " ;
$condition [] = DateTimeFormat :: utc ( 'now - ' . $days . ' day' );
2019-07-07 21:30:33 +00:00
2021-02-13 19:56:03 +00:00
$items = Post :: select ([ 'resource-id' , 'starred' , 'id' , 'post-type' , 'uid' , 'uri-id' ], $condition );
2018-01-28 11:18:08 +00:00
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $items )) {
2018-01-28 11:18:08 +00:00
return ;
}
2020-01-18 15:50:57 +00:00
$expire_items = DI :: pConfig () -> get ( $uid , 'expire' , 'items' , true );
2018-01-28 11:18:08 +00:00
// Forcing expiring of items - but not notes and marked items
if ( $force ) {
$expire_items = true ;
}
2020-01-18 15:50:57 +00:00
$expire_notes = DI :: pConfig () -> get ( $uid , 'expire' , 'notes' , true );
$expire_starred = DI :: pConfig () -> get ( $uid , 'expire' , 'starred' , true );
$expire_photos = DI :: pConfig () -> get ( $uid , 'expire' , 'photos' , false );
2018-01-28 11:18:08 +00:00
2018-07-01 09:08:58 +00:00
$expired = 0 ;
2018-01-28 11:18:08 +00:00
2020-11-30 21:40:55 +00:00
$priority = DI :: config () -> get ( 'system' , 'expire-notify-priority' );
2021-01-16 04:13:22 +00:00
while ( $item = Post :: fetch ( $items )) {
2018-01-28 11:18:08 +00:00
// don't expire filed items
2021-01-21 07:16:41 +00:00
if ( DBA :: exists ( 'post-category' , [ 'uri-id' => $item [ 'uri-id' ], 'uid' => $item [ 'uid' ], 'type' => Post\Category :: FILE ])) {
2018-01-28 11:18:08 +00:00
continue ;
}
// Only expire posts, not photos and photo comments
2021-04-07 06:02:06 +00:00
if ( ! $expire_photos && ! empty ( $item [ 'resource-id' ])) {
2018-01-28 11:18:08 +00:00
continue ;
2018-07-01 07:57:59 +00:00
} elseif ( ! $expire_starred && intval ( $item [ 'starred' ])) {
2018-01-28 11:18:08 +00:00
continue ;
2021-02-13 19:56:03 +00:00
} elseif ( ! $expire_notes && ( $item [ 'post-type' ] == self :: PT_PERSONAL_NOTE )) {
2018-01-28 11:18:08 +00:00
continue ;
2021-02-13 19:56:03 +00:00
} elseif ( ! $expire_items && ( $item [ 'post-type' ] != self :: PT_PERSONAL_NOTE )) {
2018-01-28 11:18:08 +00:00
continue ;
}
2020-11-30 21:40:55 +00:00
self :: markForDeletionById ( $item [ 'id' ], $priority );
2018-07-01 09:08:58 +00:00
++ $expired ;
2018-01-28 11:18:08 +00:00
}
2018-07-20 12:19:26 +00:00
DBA :: close ( $items );
2021-10-20 18:53:52 +00:00
Logger :: notice ( 'User ' . $uid . " : expired $expired items; expire items: $expire_items , expire notes: $expire_notes , expire starred: $expire_starred , expire photos: $expire_photos " );
2018-01-28 11:18:08 +00:00
}
2018-01-28 16:28:59 +00:00
public static function firstPostDate ( $uid , $wall = false )
{
2021-03-07 10:46:46 +00:00
$user = User :: getById ( $uid , [ 'register_date' ]);
if ( empty ( $user )) {
return false ;
}
$condition = [ " `uid` = ? AND `wall` = ? AND NOT `deleted` AND `visible` AND `received` >= ? " ,
$uid , $wall , $user [ 'register_date' ]];
2019-07-07 21:30:33 +00:00
$params = [ 'order' => [ 'received' => false ]];
2021-03-07 10:46:46 +00:00
$thread = Post :: selectFirstThread ([ 'received' ], $condition , $params );
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $thread )) {
2021-02-17 18:59:19 +00:00
$postdate = substr ( DateTimeFormat :: local ( $thread [ 'received' ]), 0 , 10 );
return $postdate ;
2018-01-28 11:18:08 +00:00
}
return false ;
}
2018-02-01 19:14:11 +00:00
/**
2020-01-19 06:05:23 +00:00
* add / remove activity to an item
2018-02-01 19:14:11 +00:00
*
* Toggle activities as like , dislike , attend of an item
*
2022-02-12 18:38:36 +00:00
* @ param int $item_id
2018-02-01 19:14:11 +00:00
* @ param string $verb
2019-01-06 21:06:53 +00:00
* Activity verb . One of
* like , unlike , dislike , undislike , attendyes , unattendyes ,
2020-08-09 18:42:25 +00:00
* attendno , unattendno , attendmaybe , unattendmaybe ,
* announce , unannouce
2022-02-12 18:38:36 +00:00
* @ param int $uid
* @ param string $allow_cid
* @ param string $allow_gid
* @ param string $deny_cid
* @ param string $deny_gid
2019-01-06 21:06:53 +00:00
* @ return bool
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
* @ hook 'post_local_end'
* array $arr
* 'post_id' => ID of posted item
2018-02-01 19:14:11 +00:00
*/
2022-02-12 18:38:36 +00:00
public static function performActivity ( int $item_id , string $verb , int $uid , string $allow_cid = null , string $allow_gid = null , string $deny_cid = null , string $deny_gid = null )
2018-02-01 19:14:11 +00:00
{
2020-08-09 18:42:25 +00:00
if ( empty ( $uid )) {
2018-02-01 19:14:11 +00:00
return false ;
}
2020-08-09 18:42:25 +00:00
Logger :: notice ( 'Start create activity' , [ 'verb' => $verb , 'item' => $item_id , 'user' => $uid ]);
2018-02-01 19:14:11 +00:00
2021-01-16 04:13:22 +00:00
$item = Post :: selectFirst ( self :: ITEM_FIELDLIST , [ 'id' => $item_id ]);
2018-07-21 12:46:04 +00:00
if ( ! DBA :: isResult ( $item )) {
2021-10-20 18:53:52 +00:00
Logger :: notice ( 'like: unknown item' , [ 'id' => $item_id ]);
2018-02-01 19:14:11 +00:00
return false ;
}
2021-01-27 10:01:42 +00:00
$uri_id = $item [ 'uri-id' ];
2018-07-07 10:43:43 +00:00
2020-08-09 18:42:25 +00:00
if ( ! in_array ( $item [ 'uid' ], [ 0 , $uid ])) {
2018-02-01 19:14:11 +00:00
return false ;
}
2021-01-16 04:13:22 +00:00
if ( ! Post :: exists ([ 'uri-id' => $item [ 'parent-uri-id' ], 'uid' => $uid ])) {
2020-07-23 03:26:54 +00:00
$stored = self :: storeForUserByUriId ( $item [ 'parent-uri-id' ], $uid );
2020-08-09 18:42:25 +00:00
if (( $item [ 'parent-uri-id' ] == $item [ 'uri-id' ]) && ! empty ( $stored )) {
2021-01-16 04:13:22 +00:00
$item = Post :: selectFirst ( self :: ITEM_FIELDLIST , [ 'id' => $stored ]);
2020-08-09 18:42:25 +00:00
if ( ! DBA :: isResult ( $item )) {
2021-01-27 10:01:42 +00:00
Logger :: info ( 'Could not fetch just created item - should not happen' , [ 'stored' => $stored , 'uid' => $uid , 'uri-id' => $uri_id ]);
2020-08-09 18:42:25 +00:00
return false ;
}
}
2020-07-22 05:16:57 +00:00
}
2018-02-01 19:14:11 +00:00
// Retrieves the local post owner
2020-08-09 18:42:25 +00:00
$owner = User :: getOwnerDataById ( $uid );
if ( empty ( $owner )) {
Logger :: info ( 'Empty owner for user' , [ 'uid' => $uid ]);
2018-02-01 19:14:11 +00:00
return false ;
}
// Retrieve the current logged in user's public contact
2020-08-09 18:42:25 +00:00
$author_id = Contact :: getIdForURL ( $owner [ 'url' ]);
if ( empty ( $author_id )) {
Logger :: info ( 'Empty public contact' );
2018-02-01 19:14:11 +00:00
return false ;
}
2020-05-29 13:01:37 +00:00
$activity = null ;
2020-05-27 12:40:00 +00:00
switch ( $verb ) {
case 'like' :
case 'unlike' :
$activity = Activity :: LIKE ;
break ;
case 'dislike' :
case 'undislike' :
$activity = Activity :: DISLIKE ;
break ;
case 'attendyes' :
case 'unattendyes' :
$activity = Activity :: ATTEND ;
break ;
case 'attendno' :
case 'unattendno' :
$activity = Activity :: ATTENDNO ;
break ;
case 'attendmaybe' :
case 'unattendmaybe' :
$activity = Activity :: ATTENDMAYBE ;
break ;
case 'follow' :
case 'unfollow' :
$activity = Activity :: FOLLOW ;
break ;
2020-08-09 18:42:25 +00:00
case 'announce' :
case 'unannounce' :
$activity = Activity :: ANNOUNCE ;
break ;
2020-05-27 12:40:00 +00:00
default :
2020-08-09 18:42:25 +00:00
Logger :: notice ( 'unknown verb' , [ 'verb' => $verb , 'item' => $item_id ]);
2020-05-27 12:40:00 +00:00
return false ;
}
$mode = Strings :: startsWith ( $verb , 'un' ) ? 'delete' : 'create' ;
// Enable activity toggling instead of on/off
$event_verb_flag = $activity === Activity :: ATTEND || $activity === Activity :: ATTENDNO || $activity === Activity :: ATTENDMAYBE ;
2018-02-01 19:14:11 +00:00
// Look for an existing verb row
2020-05-27 12:40:00 +00:00
// Event participation activities are mutually exclusive, only one of them can exist at all times.
2018-02-01 19:14:11 +00:00
if ( $event_verb_flag ) {
2019-10-23 22:25:43 +00:00
$verbs = [ Activity :: ATTEND , Activity :: ATTENDNO , Activity :: ATTENDMAYBE ];
2018-07-07 10:43:43 +00:00
// Translate to the index based activity index
2020-05-26 05:18:50 +00:00
$vids = [];
2018-07-07 10:43:43 +00:00
foreach ( $verbs as $verb ) {
2020-05-26 05:18:50 +00:00
$vids [] = Verb :: getID ( $verb );
2018-07-07 10:43:43 +00:00
}
2018-02-01 19:14:11 +00:00
} else {
2020-05-26 05:18:50 +00:00
$vids = Verb :: getID ( $activity );
2018-06-27 19:37:13 +00:00
}
2018-02-01 19:14:11 +00:00
2020-05-26 05:18:50 +00:00
$condition = [ 'vid' => $vids , 'deleted' => false , 'gravity' => GRAVITY_ACTIVITY ,
2021-01-27 10:01:42 +00:00
'author-id' => $author_id , 'uid' => $item [ 'uid' ], 'thr-parent-id' => $uri_id ];
2021-01-16 04:13:22 +00:00
$like_item = Post :: selectFirst ([ 'id' , 'guid' , 'verb' ], $condition );
2018-02-01 19:14:11 +00:00
2018-07-21 12:46:04 +00:00
if ( DBA :: isResult ( $like_item )) {
2020-05-27 12:40:00 +00:00
/**
* Truth table for existing activities
*
* | Inputs || Outputs |
* |----------------------------||-------------------|
* | Mode | Event | Same verb || Delete ? | Return ? |
* |--------|-------|-----------||---------|---------|
* | create | Yes | Yes || No | Yes |
* | create | Yes | No || Yes | No |
* | create | No | Yes || No | Yes |
* | create | No | No || N / A† |
* | delete | Yes | Yes || Yes | N / A‡ |
* | delete | Yes | No || No | N / A‡ |
* | delete | No | Yes || Yes | N / A‡ |
* | delete | No | No || N / A† |
* |--------|-------|-----------||---------|---------|
* | A | B | C || A xor C | ! B or C |
*
* † Can 't happen: It' s impossible to find an existing non - event activity without
* the same verb because we are only looking for this single verb .
*
* ‡ The " mode = delete " is returning early whether an existing activity was found or not .
*/
if ( $mode == 'create' xor $like_item [ 'verb' ] == $activity ) {
self :: markForDeletionById ( $like_item [ 'id' ]);
}
2018-02-01 19:14:11 +00:00
if ( ! $event_verb_flag || $like_item [ 'verb' ] == $activity ) {
return true ;
}
}
2020-05-27 12:40:00 +00:00
// No need to go further if we aren't creating anything
if ( $mode == 'delete' ) {
2018-02-01 19:14:11 +00:00
return true ;
}
2019-10-24 22:10:20 +00:00
$objtype = $item [ 'resource-id' ] ? Activity\ObjectType :: IMAGE : Activity\ObjectType :: NOTE ;
2018-02-01 19:14:11 +00:00
$new_item = [
2018-09-27 11:52:15 +00:00
'guid' => System :: createUUID (),
2018-06-16 06:44:19 +00:00
'uri' => self :: newURI ( $item [ 'uid' ]),
2018-02-01 19:14:11 +00:00
'uid' => $item [ 'uid' ],
2020-08-09 18:42:25 +00:00
'contact-id' => $owner [ 'id' ],
2018-02-01 19:14:11 +00:00
'wall' => $item [ 'wall' ],
'origin' => 1 ,
2018-08-11 20:40:44 +00:00
'network' => Protocol :: DFRN ,
2021-01-09 12:59:30 +00:00
'protocol' => Conversation :: PARCEL_DIRECT ,
'direction' => Conversation :: PUSH ,
2018-06-27 18:09:33 +00:00
'gravity' => GRAVITY_ACTIVITY ,
2018-02-01 19:14:11 +00:00
'parent' => $item [ 'id' ],
'thr-parent' => $item [ 'uri' ],
2018-10-06 08:51:52 +00:00
'owner-id' => $author_id ,
2018-07-07 10:43:43 +00:00
'author-id' => $author_id ,
'body' => $activity ,
2018-02-01 19:14:11 +00:00
'verb' => $activity ,
'object-type' => $objtype ,
2022-02-15 15:44:44 +00:00
'allow_cid' => $allow_cid ? ? $item [ 'allow_cid' ],
'allow_gid' => $allow_gid ? ? $item [ 'allow_gid' ],
'deny_cid' => $deny_cid ? ? $item [ 'deny_cid' ],
'deny_gid' => $deny_gid ? ? $item [ 'deny_gid' ],
2018-02-01 19:14:11 +00:00
'visible' => 1 ,
'unseen' => 1 ,
];
2018-10-29 21:15:37 +00:00
$signed = Diaspora :: createLikeSignature ( $uid , $new_item );
2018-10-27 11:09:23 +00:00
if ( ! empty ( $signed )) {
2018-10-27 14:35:22 +00:00
$new_item [ 'diaspora_signed_text' ] = json_encode ( $signed );
2018-10-27 11:09:23 +00:00
}
2018-02-06 12:40:22 +00:00
$new_item_id = self :: insert ( $new_item );
2018-02-01 19:14:11 +00:00
2018-02-06 12:40:22 +00:00
// If the parent item isn't visible then set it to visible
if ( ! $item [ 'visible' ]) {
self :: update ([ 'visible' => true ], [ 'id' => $item [ 'id' ]]);
2018-02-01 19:14:11 +00:00
}
$new_item [ 'id' ] = $new_item_id ;
2018-12-26 06:06:24 +00:00
Hook :: callAll ( 'post_local_end' , $new_item );
2018-02-01 19:14:11 +00:00
return true ;
}
2018-02-05 12:37:32 +00:00
2020-11-03 19:24:47 +00:00
/**
* Fetch the SQL condition for the given user id
*
* @ param integer $owner_id User ID for which the permissions should be fetched
* @ return array condition
*/
public static function getPermissionsConditionArrayByUserId ( int $owner_id )
{
$local_user = local_user ();
$remote_user = Session :: getRemoteContactID ( $owner_id );
// default permissions - anonymous user
$condition = [ " `private` != ? " , self :: PRIVATE ];
if ( $local_user && ( $local_user == $owner_id )) {
// Profile owner - everything is visible
$condition = [];
} elseif ( $remote_user ) {
// Authenticated visitor - fetch the matching permissionsets
2021-10-05 21:30:10 +00:00
$permissionSets = DI :: permissionSet () -> selectByContactId ( $remote_user , $owner_id );
2020-11-03 19:24:47 +00:00
if ( ! empty ( $set )) {
2020-11-03 20:30:59 +00:00
$condition = [ " (`private` != ? OR (`private` = ? AND `wall`
AND `psid` IN ( " . implode(', ', array_fill(0, count( $set ), '?')) . " ))) " ,
2021-01-16 04:13:22 +00:00
self :: PRIVATE , self :: PRIVATE ];
2021-10-05 21:30:10 +00:00
$condition = array_merge ( $condition , $permissionSets -> column ( 'id' ));
2020-11-03 19:24:47 +00:00
}
}
return $condition ;
}
2021-01-18 20:19:13 +00:00
/**
* Get a permission SQL string for the given user
2021-04-26 06:50:12 +00:00
*
* @ param int $owner_id
* @ param string $table
* @ return string
2021-01-18 20:19:13 +00:00
*/
public static function getPermissionsSQLByUserId ( int $owner_id , string $table = '' )
2018-10-17 19:30:41 +00:00
{
$local_user = local_user ();
2019-09-28 09:36:41 +00:00
$remote_user = Session :: getRemoteContactID ( $owner_id );
2019-09-27 05:49:23 +00:00
2021-01-18 20:19:13 +00:00
if ( ! empty ( $table )) {
$table = DBA :: quoteIdentifier ( $table ) . '.' ;
}
2018-10-17 19:30:41 +00:00
/*
* Construct permissions
*
* default permissions - anonymous user
*/
2021-01-18 20:19:13 +00:00
$sql = sprintf ( " AND " . $table . " `private` != %d " , self :: PRIVATE );
2018-10-17 19:30:41 +00:00
// Profile owner - everything is visible
if ( $local_user && ( $local_user == $owner_id )) {
$sql = '' ;
} elseif ( $remote_user ) {
/*
* Authenticated visitor . Unless pre - verified ,
* check that the contact belongs to this $owner_id
* and load the groups the visitor belongs to .
* If pre - verified , the caller is expected to have already
* done this and passed the groups into this function .
*/
2021-10-05 21:30:10 +00:00
$permissionSets = DI :: permissionSet () -> selectByContactId ( $remote_user , $owner_id );
2018-10-17 19:30:41 +00:00
if ( ! empty ( $set )) {
2021-10-05 21:30:10 +00:00
$sql_set = sprintf ( " OR ( " . $table . " `private` = %d AND " . $table . " `wall` AND " . $table . " `psid` IN ( " , self :: PRIVATE ) . implode ( ',' , $permissionSets -> column ( 'id' )) . " )) " ;
2018-10-17 19:30:41 +00:00
} else {
$sql_set = '' ;
}
2021-01-18 20:19:13 +00:00
$sql = sprintf ( " AND ( " . $table . " `private` != %d " , self :: PRIVATE ) . $sql_set . " ) " ;
2018-10-17 19:30:41 +00:00
}
return $sql ;
}
2018-11-07 02:12:41 +00:00
/**
* get translated item type
*
2021-03-07 07:39:13 +00:00
* @ param array $item
* @ param \Friendica\Core\L10n $l10n
2018-11-07 02:12:41 +00:00
* @ return string
*/
2021-03-07 07:39:13 +00:00
public static function postType ( array $item , \Friendica\Core\L10n $l10n )
2018-11-07 02:12:41 +00:00
{
if ( ! empty ( $item [ 'event-id' ])) {
2021-03-07 07:39:13 +00:00
return $l10n -> t ( 'event' );
2018-11-07 02:12:41 +00:00
} elseif ( ! empty ( $item [ 'resource-id' ])) {
2021-03-07 07:39:13 +00:00
return $l10n -> t ( 'photo' );
2020-05-27 12:19:06 +00:00
} elseif ( $item [ 'gravity' ] == GRAVITY_ACTIVITY ) {
2021-03-07 07:39:13 +00:00
return $l10n -> t ( 'activity' );
2020-05-27 12:19:06 +00:00
} elseif ( $item [ 'gravity' ] == GRAVITY_COMMENT ) {
2021-03-07 07:39:13 +00:00
return $l10n -> t ( 'comment' );
2018-11-07 02:12:41 +00:00
}
2021-03-07 07:39:13 +00:00
return $l10n -> t ( 'post' );
2018-11-07 02:12:41 +00:00
}
/**
* Sets the " rendered-html " field of the provided item
*
* Body is preserved to avoid side - effects as we modify it just - in - time for spoilers and private image links
*
* @ param array $item
*
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-11-07 02:12:41 +00:00
* @ todo Remove reference , simply return " rendered-html " and " rendered-hash "
*/
2021-02-20 20:07:25 +00:00
public static function putInCache ( & $item )
2018-11-07 02:12:41 +00:00
{
2020-12-04 12:29:08 +00:00
// Save original body to prevent addons to modify it
$body = $item [ 'body' ];
2018-11-07 02:12:41 +00:00
2019-10-16 12:35:14 +00:00
$rendered_hash = $item [ 'rendered-hash' ] ? ? '' ;
$rendered_html = $item [ 'rendered-html' ] ? ? '' ;
2018-11-07 02:12:41 +00:00
if ( $rendered_hash == ''
2020-12-04 12:29:08 +00:00
|| $rendered_html == ''
2020-12-04 12:55:48 +00:00
|| $rendered_hash != hash ( 'md5' , BBCode :: VERSION . '::' . $body )
2020-12-04 12:29:08 +00:00
|| DI :: config () -> get ( 'system' , 'ignore_cache' )
2018-11-07 02:12:41 +00:00
) {
2019-10-23 19:38:51 +00:00
self :: addRedirToImageTags ( $item );
2018-11-07 02:12:41 +00:00
2021-07-09 06:29:24 +00:00
$item [ 'rendered-html' ] = BBCode :: convertForUriId ( $item [ 'uri-id' ], $item [ 'body' ]);
2020-12-04 12:55:48 +00:00
$item [ 'rendered-hash' ] = hash ( 'md5' , BBCode :: VERSION . '::' . $body );
2018-11-07 02:12:41 +00:00
$hook_data = [ 'item' => $item , 'rendered-html' => $item [ 'rendered-html' ], 'rendered-hash' => $item [ 'rendered-hash' ]];
2018-12-26 06:06:24 +00:00
Hook :: callAll ( 'put_item_in_cache' , $hook_data );
2018-11-07 02:12:41 +00:00
$item [ 'rendered-html' ] = $hook_data [ 'rendered-html' ];
2020-12-04 12:55:48 +00:00
$item [ 'rendered-hash' ] = $hook_data [ 'rendered-hash' ];
2018-11-07 02:12:41 +00:00
unset ( $hook_data );
2021-02-20 20:07:25 +00:00
// Update if the generated values differ from the existing ones
if ((( $rendered_hash != $item [ 'rendered-hash' ]) || ( $rendered_html != $item [ 'rendered-html' ])) && ! empty ( $item [ 'id' ])) {
2018-11-07 02:12:41 +00:00
self :: update (
[
2020-12-04 12:29:08 +00:00
'rendered-html' => $item [ 'rendered-html' ],
'rendered-hash' => $item [ 'rendered-hash' ]
2018-11-07 02:12:41 +00:00
],
2020-12-04 12:29:08 +00:00
[ 'id' => $item [ 'id' ]]
2018-11-07 02:12:41 +00:00
);
}
}
2020-12-04 12:29:08 +00:00
$item [ 'body' ] = $body ;
2018-11-07 02:12:41 +00:00
}
2019-10-22 22:20:44 +00:00
/**
2020-01-19 06:05:23 +00:00
* Find any non - embedded images in private items and add redir links to them
2019-10-22 22:20:44 +00:00
*
* @ param array & $item The field array of an item row
*/
2019-10-23 19:38:51 +00:00
private static function addRedirToImageTags ( array & $item )
2019-10-22 22:20:44 +00:00
{
2019-12-15 21:34:11 +00:00
$app = DI :: app ();
2019-10-22 22:20:44 +00:00
$matches = [];
$cnt = preg_match_all ( '|\[img\](http[^\[]*?/photo/[a-fA-F0-9]+?(-[0-9]\.[\w]+?)?)\[\/img\]|' , $item [ 'body' ], $matches , PREG_SET_ORDER );
if ( $cnt ) {
foreach ( $matches as $mtch ) {
if ( strpos ( $mtch [ 1 ], '/redir' ) !== false ) {
continue ;
}
2021-07-24 20:34:07 +00:00
if (( local_user () == $item [ 'uid' ]) && ( $item [ 'private' ] == self :: PRIVATE ) && ( $item [ 'contact-id' ] != $app -> getContactId ()) && ( $item [ 'network' ] == Protocol :: DFRN )) {
2019-10-22 22:20:44 +00:00
$img_url = 'redir/' . $item [ 'contact-id' ] . '?url=' . urlencode ( $mtch [ 1 ]);
$item [ 'body' ] = str_replace ( $mtch [ 0 ], '[img]' . $img_url . '[/img]' , $item [ 'body' ]);
}
}
}
}
2018-11-07 02:12:41 +00:00
/**
2020-01-19 06:05:23 +00:00
* Given an item array , convert the body element from bbcode to html and add smilie icons .
2018-11-07 02:12:41 +00:00
* If attach is true , also add icons for item attachments .
*
* @ param array $item
* @ param boolean $attach
* @ param boolean $is_preview
* @ return string item body html
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
* @ hook prepare_body_init item array before any work
* @ hook prepare_body_content_filter ( 'item' => item array , 'filter_reasons' => string array ) before first bbcode to html
* @ hook prepare_body ( 'item' => item array , 'html' => body string , 'is_preview' => boolean , 'filter_reasons' => string array ) after first bbcode to html
* @ hook prepare_body_final ( 'item' => item array , 'html' => body string ) after attach icons and blockquote special case handling ( spoiler , author )
2018-11-07 02:12:41 +00:00
*/
public static function prepareBody ( array & $item , $attach = false , $is_preview = false )
{
2019-12-15 21:34:11 +00:00
$a = DI :: app ();
2018-12-26 06:06:24 +00:00
Hook :: callAll ( 'prepare_body_init' , $item );
2018-11-07 02:12:41 +00:00
// In order to provide theme developers more possibilities, event items
// are treated differently.
2019-10-24 22:10:20 +00:00
if ( $item [ 'object-type' ] === Activity\ObjectType :: EVENT && isset ( $item [ 'event-id' ])) {
2018-11-07 02:12:41 +00:00
$ev = Event :: getItemHTML ( $item );
return $ev ;
}
2020-05-01 06:01:22 +00:00
$tags = Tag :: populateFromItem ( $item );
2018-11-07 02:12:41 +00:00
$item [ 'tags' ] = $tags [ 'tags' ];
$item [ 'hashtags' ] = $tags [ 'hashtags' ];
$item [ 'mentions' ] = $tags [ 'mentions' ];
2021-04-30 04:45:31 +00:00
$body = $item [ 'body' ] ? ? '' ;
2021-06-28 04:53:20 +00:00
$shared = BBCode :: fetchShareAttributes ( $body );
if ( ! empty ( $shared [ 'guid' ])) {
$shared_item = Post :: selectFirst ([ 'uri-id' , 'plink' ], [ 'guid' => $shared [ 'guid' ]]);
$shared_uri_id = $shared_item [ 'uri-id' ] ? ? 0 ;
$shared_links = [ strtolower ( $shared_item [ 'plink' ] ? ? '' )];
$shared_attachments = Post\Media :: splitAttachments ( $shared_uri_id , $shared [ 'guid' ]);
$shared_links = array_merge ( $shared_links , array_column ( $shared_attachments [ 'visual' ], 'url' ));
$shared_links = array_merge ( $shared_links , array_column ( $shared_attachments [ 'link' ], 'url' ));
$shared_links = array_merge ( $shared_links , array_column ( $shared_attachments [ 'additional' ], 'url' ));
$item [ 'body' ] = self :: replaceVisualAttachments ( $shared_attachments , $item [ 'body' ]);
} else {
$shared_uri_id = 0 ;
$shared_links = [];
}
$attachments = Post\Media :: splitAttachments ( $item [ 'uri-id' ], $item [ 'guid' ] ? ? '' , $shared_links );
$item [ 'body' ] = self :: replaceVisualAttachments ( $attachments , $item [ 'body' ] ? ? '' );
2021-05-02 17:33:32 +00:00
$item [ 'body' ] = preg_replace ( " / \ s* \ [attachment .*? \ ].*? \ [ \ /attachment \ ] \ s*/ism " , " \n " , $item [ 'body' ]);
2021-02-20 20:07:25 +00:00
self :: putInCache ( $item );
2021-04-29 05:45:35 +00:00
$item [ 'body' ] = $body ;
2018-11-07 02:12:41 +00:00
$s = $item [ " rendered-html " ];
2021-07-27 12:24:22 +00:00
// Compile eventual content filter reasons
$filter_reasons = [];
if ( ! $is_preview && public_contact () != $item [ 'author-id' ]) {
if ( ! empty ( $item [ 'content-warning' ]) && ( ! local_user () || ! DI :: pConfig () -> get ( local_user (), 'system' , 'disable_cw' , false ))) {
$filter_reasons [] = DI :: l10n () -> t ( 'Content warning: %s' , $item [ 'content-warning' ]);
}
2021-08-16 09:28:08 +00:00
$item [ 'attachments' ] = $attachments ;
2021-07-27 12:24:22 +00:00
$hook_data = [
'item' => $item ,
'filter_reasons' => $filter_reasons
];
Hook :: callAll ( 'prepare_body_content_filter' , $hook_data );
$filter_reasons = $hook_data [ 'filter_reasons' ];
unset ( $hook_data );
}
2018-11-07 02:12:41 +00:00
$hook_data = [
'item' => $item ,
'html' => $s ,
'preview' => $is_preview ,
'filter_reasons' => $filter_reasons
];
2018-12-26 06:06:24 +00:00
Hook :: callAll ( 'prepare_body' , $hook_data );
2018-11-07 02:12:41 +00:00
$s = $hook_data [ 'html' ];
unset ( $hook_data );
if ( ! $attach ) {
// Replace the blockquotes with quotes that are used in mails.
$mailquote = '<blockquote type="cite" class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">' ;
$s = str_replace ([ '<blockquote>' , '<blockquote class="spoiler">' , '<blockquote class="author">' ], [ $mailquote , $mailquote , $mailquote ], $s );
return $s ;
}
2021-06-28 04:53:20 +00:00
if ( ! empty ( $shared_attachments )) {
$s = self :: addVisualAttachments ( $shared_attachments , $item , $s , true );
2021-07-06 04:36:45 +00:00
$s = self :: addLinkAttachment ( $shared_uri_id ? : $item [ 'uri-id' ], $shared_attachments , $body , $s , true , []);
2021-06-28 04:53:20 +00:00
$s = self :: addNonVisualAttachments ( $shared_attachments , $item , $s , true );
2021-04-29 05:45:35 +00:00
$body = preg_replace ( " / \ s* \ [share .*? \ ].*? \ [ \ /share \ ] \ s*/ism " , '' , $body );
2021-04-26 06:50:12 +00:00
}
$s = self :: addVisualAttachments ( $attachments , $item , $s , false );
2021-07-05 18:45:49 +00:00
$s = self :: addLinkAttachment ( $item [ 'uri-id' ], $attachments , $body , $s , false , $shared_links );
2021-04-26 06:50:12 +00:00
$s = self :: addNonVisualAttachments ( $attachments , $item , $s , false );
2021-04-05 09:15:36 +00:00
// Map.
if ( strpos ( $s , '<div class="map">' ) !== false && ! empty ( $item [ 'coord' ])) {
$x = Map :: byCoordinates ( trim ( $item [ 'coord' ]));
if ( $x ) {
$s = preg_replace ( '/\<div class\=\"map\"\>/' , '$0' . $x , $s );
}
}
// Replace friendica image url size with theme preference.
2021-07-25 19:39:10 +00:00
if ( ! empty ( $a -> getThemeInfoValue ( 'item_image_size' ))) {
$ps = $a -> getThemeInfoValue ( 'item_image_size' );
2021-04-05 09:15:36 +00:00
$s = preg_replace ( '|(<img[^>]+src="[^"]+/photo/[0-9a-f]+)-[0-9]|' , " $ 1- " . $ps , $s );
}
$s = HTML :: applyContentFilter ( $s , $filter_reasons );
$hook_data = [ 'item' => $item , 'html' => $s ];
Hook :: callAll ( 'prepare_body_final' , $hook_data );
return $hook_data [ 'html' ];
}
/**
2021-04-26 06:50:12 +00:00
* Check if the body contains a link
2021-04-05 09:15:36 +00:00
*
2021-04-26 15:09:32 +00:00
* @ param string $body
* @ param string $url
2021-05-23 12:35:05 +00:00
* @ param int $type
2021-04-26 15:09:32 +00:00
* @ return bool
2021-04-26 06:50:12 +00:00
*/
2021-05-23 12:35:05 +00:00
public static function containsLink ( string $body , string $url , int $type = 0 )
2021-04-26 06:50:12 +00:00
{
2021-05-02 17:33:32 +00:00
// Make sure that for example site parameters aren't used when testing if the link is contained in the body
$urlparts = parse_url ( $url );
unset ( $urlparts [ 'query' ]);
unset ( $urlparts [ 'fragment' ]);
$url = Network :: unparseURL ( $urlparts );
2021-05-23 12:35:05 +00:00
// Remove media links to only search in embedded content
// @todo Check images for image link, audio for audio links, ...
if ( in_array ( $type , [ Post\Media :: AUDIO , Post\Media :: VIDEO , Post\Media :: IMAGE ])) {
$body = preg_replace ( " / \ [url=[^ \ [ \ ]]* \ ](.*) \ [ \ /url \ ]/Usi " , ' $1 ' , $body );
}
2021-04-26 06:50:12 +00:00
if ( strpos ( $body , $url )) {
return true ;
}
foreach ([ 0 , 1 , 2 ] as $size ) {
2021-04-26 15:09:32 +00:00
if ( preg_match ( '#/photo/.*-' . $size . '\.#ism' , $url ) &&
2021-04-26 06:50:12 +00:00
strpos ( preg_replace ( '#(/photo/.*)-[012]\.#ism' , '$1-' . $size . '.' , $body ), $url )) {
return true ;
}
}
return false ;
}
2021-06-28 04:53:20 +00:00
/**
* Replace visual attachments in the body
*
* @ param array $attachments
* @ param string $body
* @ return string modified body
*/
private static function replaceVisualAttachments ( array $attachments , string $body )
{
2021-07-27 04:57:29 +00:00
DI :: profiler () -> startRecording ( 'rendering' );
2021-06-28 04:53:20 +00:00
foreach ( $attachments [ 'visual' ] as $attachment ) {
if ( ! empty ( $attachment [ 'preview' ])) {
$body = str_replace ( $attachment [ 'preview' ], Post\Media :: getPreviewUrlForId ( $attachment [ 'id' ], Proxy :: SIZE_LARGE ), $body );
} elseif ( $attachment [ 'filetype' ] == 'image' ) {
$body = str_replace ( $attachment [ 'url' ], Post\Media :: getUrlForId ( $attachment [ 'id' ]), $body );
}
}
2021-07-27 04:57:29 +00:00
DI :: profiler () -> stopRecording ();
2021-06-28 04:53:20 +00:00
return $body ;
}
2021-04-26 06:50:12 +00:00
/**
* Add visual attachments to the content
*
* @ param array $attachments
2021-04-26 15:09:32 +00:00
* @ param array $item
2021-04-05 09:15:36 +00:00
* @ param string $content
2021-04-26 06:50:12 +00:00
* @ return string modified content
2021-04-05 09:15:36 +00:00
*/
2021-04-26 06:50:12 +00:00
private static function addVisualAttachments ( array $attachments , array $item , string $content , bool $shared )
2021-04-05 09:15:36 +00:00
{
2021-07-27 04:57:29 +00:00
DI :: profiler () -> startRecording ( 'rendering' );
2021-04-05 11:44:43 +00:00
$leading = '' ;
$trailing = '' ;
2021-04-26 06:50:12 +00:00
2021-04-30 06:11:13 +00:00
// @todo In the future we should make a single for the template engine with all media in it. This allows more flexibilty.
2021-04-26 06:50:12 +00:00
foreach ( $attachments [ 'visual' ] as $attachment ) {
2021-07-17 07:55:19 +00:00
if ( self :: containsLink ( $item [ 'body' ], $attachment [ 'preview' ] ? ? $attachment [ 'url' ], $attachment [ 'type' ])) {
2021-04-14 19:12:01 +00:00
continue ;
}
2021-04-26 15:09:32 +00:00
2021-04-28 19:05:46 +00:00
if ( ! empty ( $attachment [ 'preview' ])) {
2021-06-28 04:53:20 +00:00
$preview_url = Post\Media :: getPreviewUrlForId ( $attachment [ 'id' ], Proxy :: SIZE_LARGE );
2021-04-28 19:05:46 +00:00
} else {
$preview_url = '' ;
}
2021-04-26 06:50:12 +00:00
if (( $attachment [ 'filetype' ] == 'video' )) {
2021-04-05 11:44:43 +00:00
/// @todo Move the template to /content as well
2021-04-07 06:02:06 +00:00
$media = Renderer :: replaceMacros ( Renderer :: getMarkupTemplate ( 'video_top.tpl' ), [
2021-04-05 09:15:36 +00:00
'$video' => [
2021-04-28 19:05:46 +00:00
'id' => $attachment [ 'id' ],
2021-06-27 11:50:10 +00:00
'src' => $attachment [ 'url' ],
2021-04-28 19:05:46 +00:00
'name' => $attachment [ 'name' ] ? : $attachment [ 'url' ],
'preview' => $preview_url ,
'mime' => $attachment [ 'mimetype' ],
2021-04-05 09:15:36 +00:00
],
]);
2021-05-31 05:39:04 +00:00
if (( $item [ 'post-type' ] ? ? null ) == Item :: PT_VIDEO ) {
2021-04-07 06:02:06 +00:00
$leading .= $media ;
} else {
$trailing .= $media ;
}
2021-04-26 06:50:12 +00:00
} elseif ( $attachment [ 'filetype' ] == 'audio' ) {
2021-04-07 06:02:06 +00:00
$media = Renderer :: replaceMacros ( Renderer :: getMarkupTemplate ( 'content/audio.tpl' ), [
2021-04-05 09:15:36 +00:00
'$audio' => [
2021-04-28 19:05:46 +00:00
'id' => $attachment [ 'id' ],
2021-06-27 11:50:10 +00:00
'src' => $attachment [ 'url' ],
'name' => $attachment [ 'name' ] ? : $attachment [ 'url' ],
2021-04-26 06:50:12 +00:00
'mime' => $attachment [ 'mimetype' ],
2021-04-05 09:15:36 +00:00
],
]);
2021-06-02 17:43:30 +00:00
if (( $item [ 'post-type' ] ? ? null ) == Item :: PT_AUDIO ) {
2021-04-07 06:02:06 +00:00
$leading .= $media ;
} else {
$trailing .= $media ;
}
2021-04-26 06:50:12 +00:00
} elseif ( $attachment [ 'filetype' ] == 'image' ) {
$media = Renderer :: replaceMacros ( Renderer :: getMarkupTemplate ( 'content/image.tpl' ), [
2021-06-28 04:53:20 +00:00
'$image' => [
2021-06-27 11:50:10 +00:00
'src' => Post\Media :: getUrlForId ( $attachment [ 'id' ]),
'preview' => Post\Media :: getPreviewUrlForId ( $attachment [ 'id' ], ( $attachment [ 'width' ] > $attachment [ 'height' ]) ? Proxy :: SIZE_MEDIUM : Proxy :: SIZE_LARGE ),
'attachment' => $attachment ,
2021-04-26 06:50:12 +00:00
],
]);
2021-04-29 19:29:29 +00:00
// On Diaspora posts the attached pictures are leading
if ( $item [ 'network' ] == Protocol :: DIASPORA ) {
$leading .= $media ;
} else {
$trailing .= $media ;
}
2021-04-26 06:50:12 +00:00
}
}
if ( $shared ) {
2021-04-29 19:29:29 +00:00
$content = str_replace ( BBCode :: TOP_ANCHOR , '<div class="body-attach">' . $leading . '<div class="clear"></div></div>' . BBCode :: TOP_ANCHOR , $content );
$content = str_replace ( BBCode :: BOTTOM_ANCHOR , '<div class="body-attach">' . $trailing . '<div class="clear"></div></div>' . BBCode :: BOTTOM_ANCHOR , $content );
2021-04-26 06:50:12 +00:00
} else {
if ( $leading != '' ) {
$content = '<div class="body-attach">' . $leading . '<div class="clear"></div></div>' . $content ;
}
2021-04-26 15:09:32 +00:00
2021-04-26 06:50:12 +00:00
if ( $trailing != '' ) {
$content .= '<div class="body-attach">' . $trailing . '<div class="clear"></div></div>' ;
}
}
2021-07-27 04:57:29 +00:00
DI :: profiler () -> stopRecording ();
2021-04-26 06:50:12 +00:00
return $content ;
}
/**
* Add link attachment to the content
*
* @ param array $attachments
2021-04-29 05:45:35 +00:00
* @ param string $body
2021-04-26 06:50:12 +00:00
* @ param string $content
* @ param bool $shared
2021-04-30 20:31:24 +00:00
* @ param array $ignore_links A list of URLs to ignore
2021-04-26 06:50:12 +00:00
* @ return string modified content
*/
2021-07-05 18:45:49 +00:00
private static function addLinkAttachment ( int $uriid , array $attachments , string $body , string $content , bool $shared , array $ignore_links )
2021-04-26 06:50:12 +00:00
{
2021-07-27 04:57:29 +00:00
DI :: profiler () -> startRecording ( 'rendering' );
2021-12-04 04:03:18 +00:00
// Don't show a preview when there is a visual attachment (audio or video)
$types = array_column ( $attachments [ 'visual' ], 'type' );
$preview = ! in_array ( Post\Media :: IMAGE , $types ) && ! in_array ( Post\Media :: VIDEO , $types );
2018-11-07 02:12:41 +00:00
2021-04-26 06:50:12 +00:00
if ( ! empty ( $attachments [ 'link' ])) {
foreach ( $attachments [ 'link' ] as $link ) {
2021-04-29 19:02:43 +00:00
$found = false ;
foreach ( $ignore_links as $ignore_link ) {
if ( Strings :: compareLink ( $link [ 'url' ], $ignore_link )) {
$found = true ;
}
}
2021-05-05 16:46:55 +00:00
// @todo Judge between the links to use the one with most information
2021-05-07 06:26:41 +00:00
if ( ! $found && ( empty ( $attachment ) || ! empty ( $link [ 'author-name' ]) ||
( empty ( $attachment [ 'name' ]) && ! empty ( $link [ 'name' ])) ||
( empty ( $attachment [ 'description' ]) && ! empty ( $link [ 'description' ])) ||
( empty ( $attachment [ 'preview' ]) && ! empty ( $link [ 'preview' ])))) {
2021-04-26 06:50:12 +00:00
$attachment = $link ;
2021-04-05 09:15:36 +00:00
}
2021-04-26 06:50:12 +00:00
}
}
if ( ! empty ( $attachment )) {
$data = [
2021-04-27 12:29:54 +00:00
'after' => '' ,
'author_name' => $attachment [ 'author-name' ] ? ? '' ,
'author_url' => $attachment [ 'author-url' ] ? ? '' ,
'description' => $attachment [ 'description' ] ? ? '' ,
'image' => '' ,
'preview' => '' ,
'provider_name' => $attachment [ 'publisher-name' ] ? ? '' ,
'provider_url' => $attachment [ 'publisher-url' ] ? ? '' ,
'text' => '' ,
'title' => $attachment [ 'name' ] ? ? '' ,
'type' => 'link' ,
'url' => $attachment [ 'url' ]];
2021-06-27 11:50:10 +00:00
if ( $preview && ! empty ( $attachment [ 'preview' ])) {
2021-04-27 12:29:54 +00:00
if ( $attachment [ 'preview-width' ] >= 500 ) {
2021-06-28 04:53:20 +00:00
$data [ 'image' ] = Post\Media :: getPreviewUrlForId ( $attachment [ 'id' ], Proxy :: SIZE_MEDIUM );
2021-04-27 12:29:54 +00:00
} else {
2021-06-27 11:50:10 +00:00
$data [ 'preview' ] = Post\Media :: getPreviewUrlForId ( $attachment [ 'id' ], Proxy :: SIZE_MEDIUM );
2021-04-27 12:29:54 +00:00
}
2021-04-26 06:50:12 +00:00
}
2021-05-04 05:18:03 +00:00
if ( ! empty ( $data [ 'description' ]) && ! empty ( $content )) {
similar_text ( $data [ 'description' ], $content , $percent );
} else {
$percent = 0 ;
}
if ( ! empty ( $data [ 'description' ]) && (( $data [ 'title' ] == $data [ 'description' ]) || ( $percent > 95 ) || ( strpos ( $content , $data [ 'description' ]) !== false ))) {
$data [ 'description' ] = '' ;
}
2021-05-04 06:08:40 +00:00
2021-05-13 11:26:56 +00:00
if (( $data [ 'author_name' ] ? ? '' ) == ( $data [ 'provider_name' ] ? ? '' )) {
2021-05-04 06:08:40 +00:00
$data [ 'author_name' ] = '' ;
}
2021-05-13 15:01:35 +00:00
if (( $data [ 'author_url' ] ? ? '' ) == ( $data [ 'provider_url' ] ? ? '' )) {
2021-05-04 06:08:40 +00:00
$data [ 'author_url' ] = '' ;
}
2021-04-29 05:45:35 +00:00
} elseif ( preg_match ( " /.*( \ [attachment.*? \ ].*? \ [ \ /attachment \ ]).*/ism " , $body , $match )) {
$data = BBCode :: getAttachmentData ( $match [ 1 ]);
2021-04-26 06:50:12 +00:00
}
2021-07-27 04:57:29 +00:00
DI :: profiler () -> stopRecording ();
2018-11-07 02:12:41 +00:00
2021-04-30 20:31:24 +00:00
if ( isset ( $data [ 'url' ]) && ! in_array ( $data [ 'url' ], $ignore_links )) {
2021-05-11 19:15:05 +00:00
if ( ! empty ( $data [ 'description' ]) || ! empty ( $data [ 'image' ]) || ! empty ( $data [ 'preview' ])) {
2021-05-04 05:18:03 +00:00
$parts = parse_url ( $data [ 'url' ]);
if ( ! empty ( $parts [ 'scheme' ]) && ! empty ( $parts [ 'host' ])) {
if ( empty ( $data [ 'provider_name' ])) {
$data [ 'provider_name' ] = $parts [ 'host' ];
}
if ( empty ( $data [ 'provider_url' ]) || empty ( parse_url ( $data [ 'provider_url' ], PHP_URL_SCHEME ))) {
$data [ 'provider_url' ] = $parts [ 'scheme' ] . '://' . $parts [ 'host' ];
2021-05-01 17:03:50 +00:00
2021-05-04 05:18:03 +00:00
if ( ! empty ( $parts [ 'port' ])) {
$data [ 'provider_url' ] .= ':' . $parts [ 'port' ];
}
2021-05-01 17:03:50 +00:00
}
}
2021-05-04 05:18:03 +00:00
// @todo Use a template
2021-07-06 08:57:49 +00:00
$rendered = BBCode :: convertAttachment ( '' , BBCode :: INTERNAL , false , $data , $uriid );
2021-05-23 12:35:05 +00:00
} elseif ( ! self :: containsLink ( $content , $data [ 'url' ], Post\Media :: HTML )) {
2021-05-04 05:18:03 +00:00
$rendered = Renderer :: replaceMacros ( Renderer :: getMarkupTemplate ( 'content/link.tpl' ), [
'$url' => $data [ 'url' ],
'$title' => $data [ 'title' ],
]);
} else {
return $content ;
2021-05-01 17:03:50 +00:00
}
2021-04-26 06:50:12 +00:00
if ( $shared ) {
2021-04-29 19:29:29 +00:00
return str_replace ( BBCode :: BOTTOM_ANCHOR , BBCode :: BOTTOM_ANCHOR . $rendered , $content );
2021-04-26 06:50:12 +00:00
} else {
return $content . $rendered ;
2018-11-07 02:12:41 +00:00
}
}
2021-04-26 06:50:12 +00:00
return $content ;
}
/**
* Add non visual attachments to the content
*
* @ param array $attachments
2021-04-26 15:09:32 +00:00
* @ param array $item
2021-04-26 06:50:12 +00:00
* @ param string $content
* @ return string modified content
*/
private static function addNonVisualAttachments ( array $attachments , array $item , string $content )
{
2021-07-27 04:57:29 +00:00
DI :: profiler () -> startRecording ( 'rendering' );
2021-04-26 06:50:12 +00:00
$trailing = '' ;
foreach ( $attachments [ 'additional' ] as $attachment ) {
if ( strpos ( $item [ 'body' ], $attachment [ 'url' ])) {
continue ;
}
$author = [ 'uid' => 0 , 'id' => $item [ 'author-id' ],
'network' => $item [ 'author-network' ], 'url' => $item [ 'author-link' ]];
$the_url = Contact :: magicLinkByContact ( $author , $attachment [ 'url' ]);
$title = Strings :: escapeHtml ( trim (( $attachment [ 'description' ] ? ? '' ) ? : $attachment [ 'url' ]));
2018-11-07 02:12:41 +00:00
2021-04-26 06:50:12 +00:00
if ( ! empty ( $attachment [ 'size' ])) {
$title .= ' ' . $attachment [ 'size' ] . ' ' . DI :: l10n () -> t ( 'bytes' );
}
/// @todo Use a template
$icon = '<div class="attachtype icon s22 type-' . $attachment [ 'filetype' ] . ' subtype-' . $attachment [ 'subtype' ] . '"></div>' ;
$trailing .= '<a href="' . strip_tags ( $the_url ) . '" title="' . $title . '" class="attachlink" target="_blank" rel="noopener noreferrer" >' . $icon . '</a>' ;
2021-04-05 11:44:43 +00:00
}
if ( $trailing != '' ) {
$content .= '<div class="body-attach">' . $trailing . '<div class="clear"></div></div>' ;
2018-11-07 02:12:41 +00:00
}
2021-07-27 04:57:29 +00:00
DI :: profiler () -> stopRecording ();
2021-04-05 09:15:36 +00:00
return $content ;
2018-11-07 02:12:41 +00:00
}
2018-11-07 12:19:39 +00:00
/**
* get private link for item
2019-01-06 21:06:53 +00:00
*
2018-11-07 12:19:39 +00:00
* @ param array $item
* @ return boolean | array False if item has not plink , otherwise array ( 'href' => plink url , 'title' => translated title )
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2018-11-07 12:19:39 +00:00
*/
public static function getPlink ( $item )
{
2020-06-19 11:05:58 +00:00
if ( local_user ()) {
2018-11-07 12:19:39 +00:00
$ret = [
2018-11-07 19:16:59 +00:00
'href' => " display/ " . $item [ 'guid' ],
'orig' => " display/ " . $item [ 'guid' ],
2020-01-18 19:52:34 +00:00
'title' => DI :: l10n () -> t ( 'View on separate page' ),
2021-05-10 14:47:58 +00:00
'orig_title' => DI :: l10n () -> t ( 'View on separate page' ),
2018-11-07 19:16:59 +00:00
];
2018-11-07 12:19:39 +00:00
2018-11-07 19:16:59 +00:00
if ( ! empty ( $item [ 'plink' ])) {
2021-05-10 22:59:09 +00:00
$ret [ 'href' ] = DI :: baseUrl () -> remove ( $item [ 'plink' ]);
2021-05-10 14:47:58 +00:00
$ret [ 'title' ] = DI :: l10n () -> t ( 'Link to source' );
2018-11-07 12:19:39 +00:00
}
2020-03-02 07:57:23 +00:00
} elseif ( ! empty ( $item [ 'plink' ]) && ( $item [ 'private' ] != self :: PRIVATE )) {
2018-11-07 12:19:39 +00:00
$ret = [
2018-11-07 19:16:59 +00:00
'href' => $item [ 'plink' ],
'orig' => $item [ 'plink' ],
2021-05-10 14:47:58 +00:00
'title' => DI :: l10n () -> t ( 'Link to source' ),
2021-05-10 22:59:09 +00:00
'orig_title' => DI :: l10n () -> t ( 'Link to source' ),
2018-11-07 19:16:59 +00:00
];
2018-11-07 12:19:39 +00:00
} else {
$ret = [];
}
return $ret ;
}
2019-03-14 18:44:41 +00:00
/**
2022-02-16 22:56:55 +00:00
* Does the given uri - id belongs to a post that is sent as starting post to a forum ?
2019-03-14 18:44:41 +00:00
*
2022-02-16 22:56:55 +00:00
* @ param int $uri_id
2019-03-14 18:44:41 +00:00
*
* @ return boolean " true " when it is a forum post
*/
2022-02-16 22:56:55 +00:00
public static function isForumPost ( int $uri_id )
2019-03-14 18:44:41 +00:00
{
2022-02-16 22:56:55 +00:00
foreach ( Tag :: getByURIId ( $uri_id , [ Tag :: EXCLUSIVE_MENTION ]) as $tag ) {
if ( DBA :: exists ( 'contact' , [ 'uid' => 0 , 'nurl' => Strings :: normaliseLink ( $tag [ 'url' ]), 'contact-type' => Contact :: TYPE_COMMUNITY ])) {
return true ;
2019-03-14 18:44:41 +00:00
}
}
2022-02-16 22:56:55 +00:00
return false ;
2019-03-14 18:44:41 +00:00
}
2019-07-18 06:11:02 +00:00
/**
* Search item id for given URI or plink
*
* @ param string $uri
* @ param integer $uid
*
* @ return integer item id
*/
public static function searchByLink ( $uri , $uid = 0 )
{
$ssl_uri = str_replace ( 'http://' , 'https://' , $uri );
$uris = [ $uri , $ssl_uri , Strings :: normaliseLink ( $uri )];
2021-01-27 10:01:42 +00:00
$item = Post :: selectFirst ([ 'id' ], [ 'uri' => $uris , 'uid' => $uid ]);
2019-07-18 06:11:02 +00:00
if ( DBA :: isResult ( $item )) {
return $item [ 'id' ];
}
2021-01-27 10:01:42 +00:00
$item = Post :: selectFirst ([ 'id' ], [ 'plink' => $uris , 'uid' => $uid ]);
2019-07-18 06:11:02 +00:00
if ( DBA :: isResult ( $item )) {
return $item [ 'id' ];
}
return 0 ;
}
2020-02-02 19:59:14 +00:00
/**
2021-04-26 06:50:12 +00:00
* Return the URI for a link to the post
*
2020-02-02 19:59:14 +00:00
* @ param string $uri URI or link to post
*
* @ return string URI
*/
public static function getURIByLink ( string $uri )
{
$ssl_uri = str_replace ( 'http://' , 'https://' , $uri );
$uris = [ $uri , $ssl_uri , Strings :: normaliseLink ( $uri )];
2021-01-27 10:01:42 +00:00
$item = Post :: selectFirst ([ 'uri' ], [ 'uri' => $uris ]);
2020-02-02 19:59:14 +00:00
if ( DBA :: isResult ( $item )) {
return $item [ 'uri' ];
}
2021-01-27 10:01:42 +00:00
$item = Post :: selectFirst ([ 'uri' ], [ 'plink' => $uris ]);
if ( DBA :: isResult ( $item )) {
return $item [ 'uri' ];
2020-02-02 19:59:14 +00:00
}
return '' ;
}
2019-07-18 06:11:02 +00:00
/**
* Fetches item for given URI or plink
*
* @ param string $uri
* @ param integer $uid
*
* @ return integer item id
*/
2020-03-07 12:39:09 +00:00
public static function fetchByLink ( string $uri , int $uid = 0 )
2019-07-18 06:11:02 +00:00
{
2020-07-29 05:12:16 +00:00
Logger :: info ( 'Trying to fetch link' , [ 'uid' => $uid , 'uri' => $uri ]);
2019-07-18 06:11:02 +00:00
$item_id = self :: searchByLink ( $uri , $uid );
if ( ! empty ( $item_id )) {
2020-07-29 05:12:16 +00:00
Logger :: info ( 'Link found' , [ 'uid' => $uid , 'uri' => $uri , 'id' => $item_id ]);
2019-07-18 06:11:02 +00:00
return $item_id ;
}
2021-11-23 22:47:02 +00:00
$hookData = [
'uri' => $uri ,
'uid' => $uid ,
'item_id' => null ,
];
Hook :: callAll ( 'item_by_link' , $hookData );
if ( isset ( $hookData [ 'item_id' ])) {
return is_numeric ( $hookData [ 'item_id' ]) ? $hookData [ 'item_id' ] : 0 ;
}
2020-01-20 22:30:34 +00:00
if ( $fetched_uri = ActivityPub\Processor :: fetchMissingActivity ( $uri )) {
$item_id = self :: searchByLink ( $fetched_uri , $uid );
2019-07-21 07:37:50 +00:00
} else {
$item_id = Diaspora :: fetchByURL ( $uri );
}
2019-07-18 06:20:54 +00:00
2019-07-18 06:11:02 +00:00
if ( ! empty ( $item_id )) {
2020-07-29 05:12:16 +00:00
Logger :: info ( 'Link fetched' , [ 'uid' => $uid , 'uri' => $uri , 'id' => $item_id ]);
2019-07-18 06:11:02 +00:00
return $item_id ;
}
2020-07-29 05:12:16 +00:00
Logger :: info ( 'Link not found' , [ 'uid' => $uid , 'uri' => $uri ]);
2019-07-18 06:11:02 +00:00
return 0 ;
}
2019-12-04 22:57:09 +00:00
/**
* Return share data from an item array ( if the item is shared item )
* We are providing the complete Item array , because at some time in the future
* we hopefully will define these values not in the body anymore but in some item fields .
* This function is meant to replace all similar functions in the system .
*
* @ param array $item
*
* @ return array with share information
*/
public static function getShareArray ( $item )
{
if ( ! preg_match ( " /(.*?) \ [share(.*?) \ ] \ s?(.*?) \ s? \ [ \ /share \ ] \ s?/ism " , $item [ 'body' ], $matches )) {
return [];
}
$attribute_string = $matches [ 2 ];
$attributes = [ 'comment' => trim ( $matches [ 1 ]), 'shared' => trim ( $matches [ 3 ])];
2019-12-05 05:28:28 +00:00
foreach ([ 'author' , 'profile' , 'avatar' , 'guid' , 'posted' , 'link' ] as $field ) {
if ( preg_match ( " / $field =([' \" ])(.+?) \\ 1/ism " , $attribute_string , $matches )) {
$attributes [ $field ] = trim ( html_entity_decode ( $matches [ 2 ] ? ? '' , ENT_QUOTES , 'UTF-8' ));
}
2019-12-04 22:57:09 +00:00
}
return $attributes ;
}
/**
* Fetch item information for shared items from the original items and adds it .
*
* @ param array $item
*
* @ return array item array with data from the original item
*/
2020-03-07 12:39:09 +00:00
public static function addShareDataFromOriginal ( array $item )
2019-12-04 22:57:09 +00:00
{
$shared = self :: getShareArray ( $item );
if ( empty ( $shared )) {
return $item ;
}
// Real reshares always have got a GUID.
if ( empty ( $shared [ 'guid' ])) {
return $item ;
}
$uid = $item [ 'uid' ] ? ? 0 ;
// first try to fetch the item via the GUID. This will work for all reshares that had been created on this system
2021-01-16 04:13:22 +00:00
$shared_item = Post :: selectFirst ([ 'title' , 'body' ], [ 'guid' => $shared [ 'guid' ], 'uid' => [ 0 , $uid ]]);
2019-12-04 22:57:09 +00:00
if ( ! DBA :: isResult ( $shared_item )) {
2020-03-07 01:49:43 +00:00
if ( empty ( $shared [ 'link' ])) {
return $item ;
}
2019-12-04 22:57:09 +00:00
// Otherwhise try to find (and possibly fetch) the item via the link. This should work for Diaspora and ActivityPub posts
2020-03-07 12:39:09 +00:00
$id = self :: fetchByLink ( $shared [ 'link' ] ? ? '' , $uid );
2019-12-04 22:57:09 +00:00
if ( empty ( $id )) {
2020-03-07 12:39:09 +00:00
Logger :: info ( 'Original item not found' , [ 'url' => $shared [ 'link' ] ? ? '' , 'callstack' => System :: callstack ()]);
2019-12-04 22:57:09 +00:00
return $item ;
}
2021-01-16 04:13:22 +00:00
$shared_item = Post :: selectFirst ([ 'title' , 'body' ], [ 'id' => $id ]);
2019-12-04 22:57:09 +00:00
if ( ! DBA :: isResult ( $shared_item )) {
return $item ;
}
2019-12-05 05:28:28 +00:00
Logger :: info ( 'Got shared data from url' , [ 'url' => $shared [ 'link' ], 'callstack' => System :: callstack ()]);
} else {
Logger :: info ( 'Got shared data from guid' , [ 'guid' => $shared [ 'guid' ], 'callstack' => System :: callstack ()]);
2019-12-04 22:57:09 +00:00
}
2019-12-04 23:49:07 +00:00
if ( ! empty ( $shared_item [ 'title' ])) {
$body = '[h3]' . $shared_item [ 'title' ] . " [/h3] \n " . $shared_item [ 'body' ];
unset ( $shared_item [ 'title' ]);
} else {
$body = $shared_item [ 'body' ];
}
2021-11-26 17:38:34 +00:00
$item [ 'body' ] = preg_replace ( " / \ [share ([^ \ [ \ ]]*) \ ].* \ [ \ /share \ ]/ism " , '[share $1]' . str_replace ( '$' , '\$' , $body ) . '[/share]' , $item [ 'body' ]);
2019-12-04 22:57:09 +00:00
unset ( $shared_item [ 'body' ]);
return array_merge ( $item , $shared_item );
}
2020-11-11 07:50:22 +00:00
/**
* Check a prospective item array against user - level permissions
*
* @ param array $item Expected keys : uri , gravity , and
* author - link if is author - id is set ,
* owner - link if is owner - id is set ,
* causer - link if is causer - id is set .
* @ param int $user_id Local user ID
* @ return bool
* @ throws \Exception
*/
protected static function isAllowedByUser ( array $item , int $user_id )
{
if ( ! empty ( $item [ 'author-id' ]) && Contact\User :: isBlocked ( $item [ 'author-id' ], $user_id )) {
Logger :: notice ( 'Author is blocked by user' , [ 'author-link' => $item [ 'author-link' ], 'uid' => $user_id , 'item-uri' => $item [ 'uri' ]]);
return false ;
}
if ( ! empty ( $item [ 'owner-id' ]) && Contact\User :: isBlocked ( $item [ 'owner-id' ], $user_id )) {
Logger :: notice ( 'Owner is blocked by user' , [ 'owner-link' => $item [ 'owner-link' ], 'uid' => $user_id , 'item-uri' => $item [ 'uri' ]]);
return false ;
}
// The causer is set during a thread completion, for example because of a reshare. It countains the responsible actor.
if ( ! empty ( $item [ 'causer-id' ]) && Contact\User :: isBlocked ( $item [ 'causer-id' ], $user_id )) {
2020-11-17 23:06:16 +00:00
Logger :: notice ( 'Causer is blocked by user' , [ 'causer-link' => $item [ 'causer-link' ] ? ? $item [ 'causer-id' ], 'uid' => $user_id , 'item-uri' => $item [ 'uri' ]]);
2020-11-11 07:50:22 +00:00
return false ;
}
if ( ! empty ( $item [ 'causer-id' ]) && ( $item [ 'gravity' ] === GRAVITY_PARENT ) && Contact\User :: isIgnored ( $item [ 'causer-id' ], $user_id )) {
2020-11-17 23:06:16 +00:00
Logger :: notice ( 'Causer is ignored by user' , [ 'causer-link' => $item [ 'causer-link' ] ? ? $item [ 'causer-id' ], 'uid' => $user_id , 'item-uri' => $item [ 'uri' ]]);
2020-11-11 07:50:22 +00:00
return false ;
}
return true ;
}
2021-05-07 06:26:41 +00:00
/**
* Improve the data in shared posts
*
* @ param array $item
* @ return string body
*/
public static function improveSharedDataInBody ( array $item )
{
$shared = BBCode :: fetchShareAttributes ( $item [ 'body' ]);
2021-05-07 11:41:10 +00:00
if ( empty ( $shared [ 'link' ])) {
2021-05-26 09:24:37 +00:00
return $item [ 'body' ];
2021-05-07 11:41:10 +00:00
}
2021-05-26 09:24:37 +00:00
2021-05-07 11:41:10 +00:00
$id = self :: fetchByLink ( $shared [ 'link' ]);
Logger :: info ( 'Fetched shared post' , [ 'uri-id' => $item [ 'uri-id' ], 'id' => $id , 'author' => $shared [ 'profile' ], 'url' => $shared [ 'link' ], 'guid' => $shared [ 'guid' ], 'callstack' => System :: callstack ()]);
if ( ! $id ) {
2021-05-26 09:24:37 +00:00
return $item [ 'body' ];
2021-05-07 11:41:10 +00:00
}
$shared_item = Post :: selectFirst ([ 'author-name' , 'author-link' , 'author-avatar' , 'plink' , 'created' , 'guid' , 'title' , 'body' ], [ 'id' => $id ]);
if ( ! DBA :: isResult ( $shared_item )) {
2021-05-26 09:24:37 +00:00
return $item [ 'body' ];
2021-05-07 11:41:10 +00:00
}
2021-05-07 06:26:41 +00:00
2021-05-07 11:41:10 +00:00
$shared_content = BBCode :: getShareOpeningTag ( $shared_item [ 'author-name' ], $shared_item [ 'author-link' ], $shared_item [ 'author-avatar' ], $shared_item [ 'plink' ], $shared_item [ 'created' ], $shared_item [ 'guid' ]);
2021-05-07 06:26:41 +00:00
2021-05-07 11:41:10 +00:00
if ( ! empty ( $shared_item [ 'title' ])) {
$shared_content .= '[h3]' . $shared_item [ 'title' ] . '[/h3]' . " \n " ;
2021-05-07 06:26:41 +00:00
}
2021-05-07 11:41:10 +00:00
$shared_content .= $shared_item [ 'body' ];
$item [ 'body' ] = preg_replace ( " / \ [share.*? \ ](.*) \ [ \ /share \ ]/ism " , $shared_content . '[/share]' , $item [ 'body' ]);
Logger :: info ( 'New shared data' , [ 'uri-id' => $item [ 'uri-id' ], 'id' => $id , 'shared_item' => $shared_item ]);
2021-05-07 06:26:41 +00:00
return $item [ 'body' ];
}
2018-01-09 21:13:45 +00:00
}