2018-01-09 16:13:45 -05:00
< ? php
/**
* @ file src / Model / Item . php
*/
namespace Friendica\Model ;
2018-02-01 14:14:11 -05:00
use Friendica\BaseObject ;
2018-07-19 22:15:21 -04:00
use Friendica\Content\Text\BBCode ;
2018-11-06 21:12:41 -05:00
use Friendica\Content\Text\HTML ;
2018-01-28 06:18:08 -05:00
use Friendica\Core\Addon ;
use Friendica\Core\Config ;
2018-07-05 14:57:31 -04:00
use Friendica\Core\Lock ;
2018-10-29 17:20:46 -04:00
use Friendica\Core\Logger ;
2018-11-06 21:12:41 -05:00
use Friendica\Core\L10n ;
2018-01-28 06:18:08 -05:00
use Friendica\Core\PConfig ;
2018-08-11 16:40:44 -04:00
use Friendica\Core\Protocol ;
2018-11-06 21:12:41 -05:00
use Friendica\Core\Renderer ;
2018-01-28 06:18:08 -05:00
use Friendica\Core\System ;
2018-02-01 14:14:11 -05:00
use Friendica\Core\Worker ;
2018-07-20 08:19:26 -04:00
use Friendica\Database\DBA ;
2018-07-24 22:53:46 -04:00
use Friendica\Model\Contact ;
2018-11-06 21:12:41 -05:00
use Friendica\Model\Event ;
2018-10-30 14:51:45 -04:00
use Friendica\Model\FileTag ;
2018-07-28 13:11:46 -04:00
use Friendica\Model\PermissionSet ;
2018-11-06 21:12:41 -05:00
use Friendica\Model\Term ;
2018-08-05 07:09:59 -04:00
use Friendica\Model\ItemURI ;
2018-02-01 14:14:11 -05:00
use Friendica\Object\Image ;
use Friendica\Protocol\Diaspora ;
2018-01-28 06:18:08 -05:00
use Friendica\Protocol\OStatus ;
2018-01-26 21:38:34 -05:00
use Friendica\Util\DateTimeFormat ;
2018-11-06 21:12:41 -05:00
use Friendica\Util\Map ;
2018-03-08 15:48:00 -05:00
use Friendica\Util\XML ;
2018-10-17 16:35:49 -04:00
use Friendica\Util\Security ;
2018-01-20 18:52:54 -05:00
use Text_LanguageDetect ;
2018-01-09 16:13:45 -05:00
2018-02-01 14:14:11 -05:00
require_once 'boot.php' ;
2018-01-16 18:20:07 -05:00
require_once 'include/items.php' ;
2018-01-28 06:18:08 -05:00
require_once 'include/text.php' ;
2018-01-09 16:13:45 -05:00
2018-02-01 14:14:11 -05:00
class Item extends BaseObject
2018-01-09 16:13:45 -05:00
{
2018-07-19 09:52:05 -04: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 ;
const PT_PERSONAL_NOTE = 128 ;
2018-06-17 02:27:52 -04:00
// Field list that is used to display the items
2018-06-24 06:48:29 -04:00
const DISPLAY_FIELDLIST = [ 'uid' , 'id' , 'parent' , 'uri' , 'thr-parent' , 'parent-uri' , 'guid' , 'network' ,
2018-06-17 02:27:52 -04:00
'commented' , 'created' , 'edited' , 'received' , 'verb' , 'object-type' , 'postopts' , 'plink' ,
2018-06-30 01:18:43 -04:00
'wall' , 'private' , 'starred' , 'origin' , 'title' , 'body' , 'file' , 'attach' , 'language' ,
2018-06-17 02:27:52 -04:00
'content-warning' , 'location' , 'coord' , 'app' , 'rendered-hash' , 'rendered-html' , 'object' ,
2018-06-18 16:36:34 -04:00
'allow_cid' , 'allow_gid' , 'deny_cid' , 'deny_gid' , 'item_id' ,
2018-07-02 01:41:55 -04:00
'author-id' , 'author-link' , 'author-name' , 'author-avatar' , 'author-network' ,
'owner-id' , 'owner-link' , 'owner-name' , 'owner-avatar' , 'owner-network' ,
2018-06-17 02:27:52 -04:00
'contact-id' , 'contact-link' , 'contact-name' , 'contact-avatar' ,
2018-06-24 06:48:29 -04:00
'writable' , 'self' , 'cid' , 'alias' ,
2018-06-17 02:27:52 -04:00
'event-id' , 'event-created' , 'event-edited' , 'event-start' , 'event-finish' ,
'event-summary' , 'event-desc' , 'event-location' , 'event-type' ,
'event-nofinish' , 'event-adjust' , 'event-ignore' , 'event-id' ];
// Field list that is used to deliver items via the protocols
const DELIVER_FIELDLIST = [ 'uid' , 'id' , 'parent' , 'uri' , 'thr-parent' , 'parent-uri' , 'guid' ,
'created' , 'edited' , 'verb' , 'object-type' , 'object' , 'target' ,
'private' , 'title' , 'body' , 'location' , 'coord' , 'app' ,
2018-07-19 09:52:05 -04:00
'attach' , 'tag' , 'deleted' , 'extid' , 'post-type' ,
2018-06-17 02:27:52 -04:00
'allow_cid' , 'allow_gid' , 'deny_cid' , 'deny_gid' ,
'author-id' , 'author-link' , 'owner-link' , 'contact-uid' ,
2018-07-08 07:46:05 -04:00
'signed_text' , 'signature' , 'signer' , 'network' ];
2018-06-17 02:27:52 -04:00
2018-06-25 14:49:36 -04:00
// Field list for "item-content" table that is mixed with the item table
2018-06-30 01:18:43 -04:00
const MIXED_CONTENT_FIELDLIST = [ 'title' , 'content-warning' , 'body' , 'location' ,
2018-06-27 15:37:13 -04:00
'coord' , 'app' , 'rendered-hash' , 'rendered-html' , 'verb' ,
2018-06-29 02:20:04 -04:00
'object-type' , 'object' , 'target-type' , 'target' , 'plink' ];
2018-06-24 17:41:49 -04:00
2018-06-30 01:18:43 -04:00
// Field list for "item-content" table that is not present in the "item" table
const CONTENT_FIELDLIST = [ 'language' ];
2018-07-19 17:56:52 -04:00
// Field list for additional delivery data
const DELIVERY_DATA_FIELDLIST = [ 'postopts' , 'inform' ];
2018-06-25 14:49:36 -04:00
// All fields in the item table
const ITEM_FIELDLIST = [ 'id' , 'uid' , 'parent' , 'uri' , 'parent-uri' , 'thr-parent' , 'guid' ,
2018-07-19 09:52:05 -04:00
'contact-id' , 'type' , 'wall' , 'gravity' , 'extid' , 'icid' , 'iaid' , 'psid' ,
2018-10-18 17:35:48 -04:00
'created' , 'edited' , 'commented' , 'received' , 'changed' , 'verb' ,
2018-06-25 14:49:36 -04:00
'postopts' , 'plink' , 'resource-id' , 'event-id' , 'tag' , 'attach' , 'inform' ,
2018-07-19 09:52:05 -04:00
'file' , 'allow_cid' , 'allow_gid' , 'deny_cid' , 'deny_gid' , 'post-type' ,
2018-06-25 14:49:36 -04:00
'private' , 'pubmail' , 'moderated' , 'visible' , 'starred' , 'bookmark' ,
'unseen' , 'deleted' , 'origin' , 'forum_mode' , 'mention' , 'global' , 'network' ,
'title' , 'content-warning' , 'body' , 'location' , 'coord' , 'app' ,
'rendered-hash' , 'rendered-html' , 'object-type' , 'object' , 'target-type' , 'target' ,
'author-id' , 'author-link' , 'author-name' , 'author-avatar' ,
2018-07-18 17:26:14 -04:00
'owner-id' , 'owner-link' , 'owner-name' , 'owner-avatar' ];
2018-06-25 14:49:36 -04:00
2018-07-06 02:37:33 -04:00
// Never reorder or remove entries from this list. Just add new ones at the end, if needed.
// The item-activity table only stores the index and needs this array to know the matching activity.
2018-07-05 18:00:38 -04:00
const ACTIVITIES = [ ACTIVITY_LIKE , ACTIVITY_DISLIKE , ACTIVITY_ATTEND , ACTIVITY_ATTENDNO , ACTIVITY_ATTENDMAYBE ];
2018-07-15 14:36:20 -04:00
private static $legacy_mode = null ;
public static function isLegacyMode ()
{
if ( is_null ( self :: $legacy_mode )) {
2018-07-19 17:56:52 -04:00
self :: $legacy_mode = ( Config :: get ( " system " , " post_update_version " ) < 1279 );
2018-07-15 14:36:20 -04:00
}
return self :: $legacy_mode ;
}
2018-07-05 18:00:38 -04:00
/**
* @ brief returns an activity index from an activity string
*
* @ param string $activity activity string
* @ return integer Activity index
*/
2018-08-19 08:46:11 -04:00
public static function activityToIndex ( $activity )
2018-07-05 18:00:38 -04:00
{
$index = array_search ( $activity , self :: ACTIVITIES );
if ( is_bool ( $index )) {
$index = - 1 ;
}
return $index ;
}
/**
* @ brief returns an activity string from an activity index
*
* @ param integer $index activity index
* @ return string Activity string
*/
2018-07-06 01:16:40 -04:00
private static function indexToActivity ( $index )
2018-07-05 18:00:38 -04:00
{
2018-07-07 19:31:30 -04:00
if ( is_null ( $index ) || ! array_key_exists ( $index , self :: ACTIVITIES )) {
2018-07-05 18:00:38 -04:00
return '' ;
}
return self :: ACTIVITIES [ $index ];
}
2018-06-21 11:14:01 -04:00
/**
* @ brief Fetch a single item row
*
* @ param mixed $stmt statement object
* @ return array current row
*/
public static function fetch ( $stmt )
{
2018-07-20 08:19:26 -04:00
$row = DBA :: fetch ( $stmt );
2018-06-21 11:14:01 -04:00
2018-07-05 18:00:38 -04:00
if ( is_bool ( $row )) {
return $row ;
2018-06-24 17:41:49 -04:00
}
2018-07-05 18:00:38 -04:00
// ---------------------- Transform item structure data ----------------------
2018-06-24 06:48:29 -04:00
// We prefer the data from the user's contact over the public one
if ( ! empty ( $row [ 'author-link' ]) && ! empty ( $row [ 'contact-link' ]) &&
( $row [ 'author-link' ] == $row [ 'contact-link' ])) {
if ( isset ( $row [ 'author-avatar' ]) && ! empty ( $row [ 'contact-avatar' ])) {
$row [ 'author-avatar' ] = $row [ 'contact-avatar' ];
}
if ( isset ( $row [ 'author-name' ]) && ! empty ( $row [ 'contact-name' ])) {
$row [ 'author-name' ] = $row [ 'contact-name' ];
}
}
if ( ! empty ( $row [ 'owner-link' ]) && ! empty ( $row [ 'contact-link' ]) &&
( $row [ 'owner-link' ] == $row [ 'contact-link' ])) {
if ( isset ( $row [ 'owner-avatar' ]) && ! empty ( $row [ 'contact-avatar' ])) {
$row [ 'owner-avatar' ] = $row [ 'contact-avatar' ];
}
if ( isset ( $row [ 'owner-name' ]) && ! empty ( $row [ 'contact-name' ])) {
$row [ 'owner-name' ] = $row [ 'contact-name' ];
}
}
2018-07-05 18:00:38 -04:00
// We can always comment on posts from these networks
if ( array_key_exists ( 'writable' , $row ) &&
2018-09-14 12:51:32 -04:00
in_array ( $row [ 'internal-network' ], [ Protocol :: ACTIVITYPUB , Protocol :: DFRN , Protocol :: DIASPORA , Protocol :: OSTATUS ])) {
2018-07-05 18:00:38 -04:00
$row [ 'writable' ] = true ;
2018-06-30 09:54:01 -04:00
}
2018-07-05 18:00:38 -04:00
// ---------------------- Transform item content data ----------------------
// Fetch data from the item-content table whenever there is content there
2018-07-15 14:36:20 -04:00
if ( self :: isLegacyMode ()) {
2018-07-19 17:56:52 -04:00
$legacy_fields = array_merge ( self :: DELIVERY_DATA_FIELDLIST , self :: MIXED_CONTENT_FIELDLIST );
foreach ( $legacy_fields as $field ) {
2018-07-15 14:36:20 -04:00
if ( empty ( $row [ $field ]) && ! empty ( $row [ 'internal-item-' . $field ])) {
$row [ $field ] = $row [ 'internal-item-' . $field ];
}
unset ( $row [ 'internal-item-' . $field ]);
2018-07-05 18:00:38 -04:00
}
2018-07-01 03:57:59 -04:00
}
2018-06-30 17:15:24 -04:00
2018-07-05 18:00:38 -04:00
if ( ! empty ( $row [ 'internal-iaid' ]) && array_key_exists ( 'verb' , $row )) {
$row [ 'verb' ] = self :: indexToActivity ( $row [ 'internal-activity' ]);
if ( array_key_exists ( 'title' , $row )) {
$row [ 'title' ] = '' ;
}
if ( array_key_exists ( 'body' , $row )) {
$row [ 'body' ] = $row [ 'verb' ];
}
if ( array_key_exists ( 'object' , $row )) {
$row [ 'object' ] = '' ;
}
if ( array_key_exists ( 'object-type' , $row )) {
$row [ 'object-type' ] = ACTIVITY_OBJ_NOTE ;
}
2018-07-08 00:55:45 -04:00
} elseif ( array_key_exists ( 'verb' , $row ) && in_array ( $row [ 'verb' ], [ '' , ACTIVITY_POST , ACTIVITY_SHARE ])) {
2018-07-05 18:00:38 -04:00
// Posts don't have an object or target - but having tags or files.
// We safe some performance by building tag and file strings only here.
// We remove object and target since they aren't used for this type.
if ( array_key_exists ( 'object' , $row )) {
$row [ 'object' ] = '' ;
}
if ( array_key_exists ( 'target' , $row )) {
$row [ 'target' ] = '' ;
}
2018-07-08 00:55:45 -04:00
}
2018-07-05 18:00:38 -04:00
2018-07-08 01:44:35 -04:00
if ( ! array_key_exists ( 'verb' , $row ) || in_array ( $row [ 'verb' ], [ '' , ACTIVITY_POST , ACTIVITY_SHARE ])) {
// Build the tag string out of the term entries
if ( array_key_exists ( 'tag' , $row ) && empty ( $row [ 'tag' ])) {
$row [ 'tag' ] = Term :: tagTextFromItemId ( $row [ 'internal-iid' ]);
}
2018-07-08 00:55:45 -04:00
2018-07-08 01:44:35 -04:00
// Build the file string out of the term entries
if ( array_key_exists ( 'file' , $row ) && empty ( $row [ 'file' ])) {
$row [ 'file' ] = Term :: fileTextFromItemId ( $row [ 'internal-iid' ]);
}
2018-06-24 06:48:29 -04:00
}
2018-10-15 17:42:55 -04:00
if ( array_key_exists ( 'signed_text' , $row ) && array_key_exists ( 'interaction' , $row ) && ! is_null ( $row [ 'interaction' ])) {
$row [ 'signed_text' ] = $row [ 'interaction' ];
}
2018-08-08 16:32:11 -04:00
if ( array_key_exists ( 'ignored' , $row ) && array_key_exists ( 'internal-user-ignored' , $row ) && ! is_null ( $row [ 'internal-user-ignored' ])) {
$row [ 'ignored' ] = $row [ 'internal-user-ignored' ];
}
2018-07-05 18:00:38 -04:00
// Remove internal fields
unset ( $row [ 'internal-activity' ]);
unset ( $row [ 'internal-network' ]);
unset ( $row [ 'internal-iid' ]);
unset ( $row [ 'internal-iaid' ]);
unset ( $row [ 'internal-icid' ]);
2018-08-08 16:32:11 -04:00
unset ( $row [ 'internal-user-ignored' ]);
2018-10-15 17:42:55 -04:00
unset ( $row [ 'interaction' ]);
2018-07-05 18:00:38 -04:00
2018-06-21 11:14:01 -04:00
return $row ;
}
/**
* @ brief Fills an array with data from an item query
*
* @ param object $stmt statement object
* @ return array Data array
*/
public static function inArray ( $stmt , $do_close = true ) {
if ( is_bool ( $stmt )) {
return $stmt ;
}
$data = [];
while ( $row = self :: fetch ( $stmt )) {
$data [] = $row ;
}
if ( $do_close ) {
2018-07-20 08:19:26 -04:00
DBA :: close ( $stmt );
2018-06-21 11:14:01 -04:00
}
return $data ;
}
2018-06-27 15:37:13 -04:00
/**
* @ brief Check if item data exists
*
* @ param array $condition array of fields for condition
*
* @ return boolean Are there rows for that condition ?
*/
public static function exists ( $condition ) {
$stmt = self :: select ([ 'id' ], $condition , [ 'limit' => 1 ]);
if ( is_bool ( $stmt )) {
$retval = $stmt ;
} else {
2018-07-20 22:05:12 -04:00
$retval = ( DBA :: numRows ( $stmt ) > 0 );
2018-06-27 15:37:13 -04:00
}
2018-07-20 08:19:26 -04:00
DBA :: close ( $stmt );
2018-06-27 15:37:13 -04:00
return $retval ;
}
2018-06-09 15:12:13 -04:00
/**
2018-06-17 13:05:17 -04:00
* Retrieve a single record from the item table for a given user and returns it in an associative array
2018-06-09 15:12:13 -04:00
*
* @ brief Retrieve a single record from a table
* @ param integer $uid User ID
* @ param array $fields
* @ param array $condition
* @ param array $params
* @ return bool | array
2018-07-20 08:19:26 -04:00
* @ see DBA :: select
2018-06-09 15:12:13 -04:00
*/
2018-06-17 13:05:17 -04:00
public static function selectFirstForUser ( $uid , array $selected = [], array $condition = [], $params = [])
{
$params [ 'uid' ] = $uid ;
if ( empty ( $selected )) {
$selected = Item :: DISPLAY_FIELDLIST ;
}
return self :: selectFirst ( $selected , $condition , $params );
}
/**
* @ brief Select rows from the item table for a given user
*
* @ param integer $uid User ID
* @ param array $selected Array of selected fields , empty for all
* @ param array $condition Array of fields for condition
* @ param array $params Array of several parameters
*
* @ return boolean | object
*/
public static function selectForUser ( $uid , array $selected = [], array $condition = [], $params = [])
{
$params [ 'uid' ] = $uid ;
if ( empty ( $selected )) {
$selected = Item :: DISPLAY_FIELDLIST ;
}
return self :: select ( $selected , $condition , $params );
}
/**
* Retrieve a single record from the item table and returns it in an associative array
*
* @ brief Retrieve a single record from a table
* @ param array $fields
* @ param array $condition
* @ param array $params
* @ return bool | array
2018-07-20 08:19:26 -04:00
* @ see DBA :: select
2018-06-17 13:05:17 -04:00
*/
public static function selectFirst ( array $fields = [], array $condition = [], $params = [])
2018-06-07 02:03:12 -04:00
{
2018-06-09 12:56:37 -04:00
$params [ 'limit' ] = 1 ;
2018-06-17 13:05:17 -04:00
$result = self :: select ( $fields , $condition , $params );
2018-06-09 12:56:37 -04:00
if ( is_bool ( $result )) {
return $result ;
} else {
2018-06-21 11:14:01 -04:00
$row = self :: fetch ( $result );
2018-07-20 08:19:26 -04:00
DBA :: close ( $result );
2018-06-09 12:56:37 -04:00
return $row ;
}
}
2018-06-07 02:03:12 -04:00
2018-06-09 15:12:13 -04:00
/**
* @ brief Select rows from the item table
*
2018-06-17 13:05:17 -04:00
* @ param array $selected Array of selected fields , empty for all
2018-06-09 15:12:13 -04:00
* @ param array $condition Array of fields for condition
* @ param array $params Array of several parameters
*
* @ return boolean | object
*/
2018-06-17 13:05:17 -04:00
public static function select ( array $selected = [], array $condition = [], $params = [])
2018-06-09 12:56:37 -04:00
{
2018-06-17 13:05:17 -04:00
$uid = 0 ;
$usermode = false ;
if ( isset ( $params [ 'uid' ])) {
$uid = $params [ 'uid' ];
$usermode = true ;
}
2018-10-16 18:30:24 -04:00
$fields = self :: fieldlist ( $usermode );
2018-06-07 02:03:12 -04:00
2018-06-09 12:56:37 -04:00
$select_fields = self :: constructSelectFields ( $fields , $selected );
2018-06-07 02:03:12 -04:00
2018-07-20 08:19:26 -04:00
$condition_string = DBA :: buildCondition ( $condition );
2018-06-07 02:03:12 -04:00
2018-06-09 12:56:37 -04:00
$condition_string = self :: addTablesToFields ( $condition_string , $fields );
2018-06-17 13:05:17 -04:00
if ( $usermode ) {
$condition_string = $condition_string . ' AND ' . self :: condition ( false );
}
2018-06-09 12:56:37 -04:00
2018-07-20 08:19:26 -04:00
$param_string = self :: addTablesToFields ( DBA :: buildParameter ( $params ), $fields );
2018-06-07 02:03:12 -04:00
2018-07-02 14:22:27 -04:00
$table = " `item` " . self :: constructJoins ( $uid , $select_fields . $condition_string . $param_string , false , $usermode );
2018-06-07 02:03:12 -04:00
2018-06-09 12:56:37 -04:00
$sql = " SELECT " . $select_fields . " FROM " . $table . $condition_string . $param_string ;
2018-06-15 18:30:49 -04:00
2018-07-20 08:19:26 -04:00
return DBA :: p ( $sql , $condition );
2018-06-09 12:56:37 -04:00
}
2018-06-19 01:39:56 -04:00
/**
* @ brief Select rows from the starting post in the item table
*
* @ param integer $uid User ID
* @ param array $fields Array of selected fields , empty for all
* @ param array $condition Array of fields for condition
* @ param array $params Array of several parameters
*
* @ return boolean | object
*/
public static function selectThreadForUser ( $uid , array $selected = [], array $condition = [], $params = [])
{
$params [ 'uid' ] = $uid ;
if ( empty ( $selected )) {
$selected = Item :: DISPLAY_FIELDLIST ;
}
return self :: selectThread ( $selected , $condition , $params );
}
2018-06-09 15:12:13 -04:00
/**
* Retrieve a single record from the starting post in the item table and returns it in an associative array
*
* @ brief Retrieve a single record from a table
* @ param integer $uid User ID
2018-06-19 01:39:56 -04:00
* @ param array $selected
* @ param array $condition
* @ param array $params
* @ return bool | array
2018-07-20 08:19:26 -04:00
* @ see DBA :: select
2018-06-19 01:39:56 -04:00
*/
public static function selectFirstThreadForUser ( $uid , array $selected = [], array $condition = [], $params = [])
{
$params [ 'uid' ] = $uid ;
if ( empty ( $selected )) {
$selected = Item :: DISPLAY_FIELDLIST ;
}
return self :: selectFirstThread ( $selected , $condition , $params );
}
/**
* Retrieve a single record from the starting post in the item table and returns it in an associative array
*
* @ brief Retrieve a single record from a table
2018-06-09 15:12:13 -04:00
* @ param array $fields
* @ param array $condition
* @ param array $params
* @ return bool | array
2018-07-20 08:19:26 -04:00
* @ see DBA :: select
2018-06-09 15:12:13 -04:00
*/
2018-06-19 01:39:56 -04:00
public static function selectFirstThread ( array $fields = [], array $condition = [], $params = [])
2018-06-09 12:56:37 -04:00
{
$params [ 'limit' ] = 1 ;
2018-06-19 01:39:56 -04:00
$result = self :: selectThread ( $fields , $condition , $params );
2018-06-09 12:56:37 -04:00
if ( is_bool ( $result )) {
return $result ;
} else {
2018-06-21 11:14:01 -04:00
$row = self :: fetch ( $result );
2018-07-20 08:19:26 -04:00
DBA :: close ( $result );
2018-06-09 12:56:37 -04:00
return $row ;
2018-06-07 02:03:12 -04:00
}
2018-06-09 12:56:37 -04:00
}
2018-06-09 15:12:13 -04:00
/**
* @ brief Select rows from the starting post in the item table
*
2018-06-19 01:39:56 -04:00
* @ param array $selected Array of selected fields , empty for all
2018-06-09 15:12:13 -04:00
* @ param array $condition Array of fields for condition
* @ param array $params Array of several parameters
*
* @ return boolean | object
*/
2018-06-19 01:39:56 -04:00
public static function selectThread ( array $selected = [], array $condition = [], $params = [])
2018-06-09 12:56:37 -04:00
{
2018-06-19 01:39:56 -04:00
$uid = 0 ;
$usermode = false ;
if ( isset ( $params [ 'uid' ])) {
$uid = $params [ 'uid' ];
$usermode = true ;
2018-06-17 13:05:17 -04:00
}
2018-10-16 18:30:24 -04:00
$fields = self :: fieldlist ( $usermode );
2018-08-08 16:32:11 -04:00
$fields [ 'thread' ] = [ 'mention' , 'ignored' , 'iid' ];
2018-06-09 12:56:37 -04:00
$threadfields = [ 'thread' => [ 'iid' , 'uid' , 'contact-id' , 'owner-id' , 'author-id' ,
'created' , 'edited' , 'commented' , 'received' , 'changed' , 'wall' , 'private' ,
2018-07-19 09:52:05 -04:00
'pubmail' , 'moderated' , 'visible' , 'starred' , 'ignored' , 'post-type' ,
2018-06-09 12:56:37 -04:00
'unseen' , 'deleted' , 'origin' , 'forum_mode' , 'mention' , 'network' ]];
$select_fields = self :: constructSelectFields ( $fields , $selected );
2018-07-20 08:19:26 -04:00
$condition_string = DBA :: buildCondition ( $condition );
2018-06-09 12:56:37 -04:00
$condition_string = self :: addTablesToFields ( $condition_string , $threadfields );
$condition_string = self :: addTablesToFields ( $condition_string , $fields );
2018-06-19 01:39:56 -04:00
if ( $usermode ) {
$condition_string = $condition_string . ' AND ' . self :: condition ( true );
}
2018-06-07 02:03:12 -04:00
2018-07-20 08:19:26 -04:00
$param_string = DBA :: buildParameter ( $params );
2018-06-09 12:56:37 -04:00
$param_string = self :: addTablesToFields ( $param_string , $threadfields );
$param_string = self :: addTablesToFields ( $param_string , $fields );
2018-06-07 02:03:12 -04:00
2018-07-02 14:22:27 -04:00
$table = " `thread` " . self :: constructJoins ( $uid , $select_fields . $condition_string . $param_string , true , $usermode );
2018-06-07 02:03:12 -04:00
$sql = " SELECT " . $select_fields . " FROM " . $table . $condition_string . $param_string ;
2018-07-20 08:19:26 -04:00
return DBA :: p ( $sql , $condition );
2018-06-09 12:56:37 -04:00
}
2018-06-09 15:12:13 -04:00
/**
* @ brief Returns a list of fields that are associated with the item table
*
* @ return array field list
*/
2018-10-16 18:30:24 -04:00
private static function fieldlist ( $usermode )
2018-06-09 12:56:37 -04:00
{
2018-06-17 13:14:52 -04:00
$fields = [];
2018-06-17 17:35:33 -04:00
$fields [ 'item' ] = [ 'id' , 'uid' , 'parent' , 'uri' , 'parent-uri' , 'thr-parent' , 'guid' ,
'contact-id' , 'owner-id' , 'author-id' , 'type' , 'wall' , 'gravity' , 'extid' ,
2018-10-18 17:35:48 -04:00
'created' , 'edited' , 'commented' , 'received' , 'changed' , 'psid' ,
2018-07-25 19:14:55 -04:00
'resource-id' , 'event-id' , 'tag' , 'attach' , 'post-type' , 'file' ,
2018-06-17 17:35:33 -04:00
'private' , 'pubmail' , 'moderated' , 'visible' , 'starred' , 'bookmark' ,
2018-06-25 00:56:32 -04:00
'unseen' , 'deleted' , 'origin' , 'forum_mode' , 'mention' , 'global' ,
2018-07-05 18:00:38 -04:00
'id' => 'item_id' , 'network' , 'icid' , 'iaid' , 'id' => 'internal-iid' ,
'network' => 'internal-network' , 'icid' => 'internal-icid' ,
'iaid' => 'internal-iaid' ];
2018-08-08 16:32:11 -04:00
if ( $usermode ) {
$fields [ 'user-item' ] = [ 'ignored' => 'internal-user-ignored' ];
}
2018-07-07 06:43:43 -04:00
$fields [ 'item-activity' ] = [ 'activity' , 'activity' => 'internal-activity' ];
2018-06-16 18:32:57 -04:00
2018-06-30 01:18:43 -04:00
$fields [ 'item-content' ] = array_merge ( self :: CONTENT_FIELDLIST , self :: MIXED_CONTENT_FIELDLIST );
2018-06-25 00:56:32 -04:00
2018-07-19 17:56:52 -04:00
$fields [ 'item-delivery-data' ] = self :: DELIVERY_DATA_FIELDLIST ;
2018-07-25 19:14:55 -04:00
$fields [ 'permissionset' ] = [ 'allow_cid' , 'allow_gid' , 'deny_cid' , 'deny_gid' ];
2018-06-18 16:36:34 -04:00
$fields [ 'author' ] = [ 'url' => 'author-link' , 'name' => 'author-name' ,
2018-07-02 01:41:55 -04:00
'thumb' => 'author-avatar' , 'nick' => 'author-nick' , 'network' => 'author-network' ];
2018-06-17 13:14:52 -04:00
2018-06-18 16:36:34 -04:00
$fields [ 'owner' ] = [ 'url' => 'owner-link' , 'name' => 'owner-name' ,
2018-07-02 01:41:55 -04:00
'thumb' => 'owner-avatar' , 'nick' => 'owner-nick' , 'network' => 'owner-network' ];
2018-06-17 13:14:52 -04:00
$fields [ 'contact' ] = [ 'url' => 'contact-link' , 'name' => 'contact-name' , 'thumb' => 'contact-avatar' ,
2018-06-24 06:48:29 -04:00
'writable' , 'self' , 'id' => 'cid' , 'alias' , 'uid' => 'contact-uid' ,
'photo' , 'name-date' , 'uri-date' , 'avatar-date' , 'thumb' , 'dfrn-id' ];
2018-06-09 12:56:37 -04:00
2018-06-18 16:36:34 -04:00
$fields [ 'parent-item' ] = [ 'guid' => 'parent-guid' , 'network' => 'parent-network' ];
2018-06-17 13:14:52 -04:00
$fields [ 'parent-item-author' ] = [ 'url' => 'parent-author-link' , 'name' => 'parent-author-name' ];
$fields [ 'event' ] = [ 'created' => 'event-created' , 'edited' => 'event-edited' ,
2018-06-09 12:56:37 -04:00
'start' => 'event-start' , 'finish' => 'event-finish' ,
'summary' => 'event-summary' , 'desc' => 'event-desc' ,
'location' => 'event-location' , 'type' => 'event-type' ,
'nofinish' => 'event-nofinish' , 'adjust' => 'event-adjust' ,
'ignore' => 'event-ignore' , 'id' => 'event-id' ];
2018-06-17 02:27:52 -04:00
$fields [ 'sign' ] = [ 'signed_text' , 'signature' , 'signer' ];
2018-06-15 01:50:28 -04:00
2018-10-15 17:42:55 -04:00
$fields [ 'diaspora-interaction' ] = [ 'interaction' ];
2018-06-15 01:50:28 -04:00
return $fields ;
2018-06-09 12:56:37 -04:00
}
2018-06-09 15:12:13 -04:00
/**
* @ brief Returns SQL condition for the " select " functions
*
* @ param boolean $thread_mode Called for the items ( false ) or for the threads ( true )
*
* @ return string SQL condition
*/
private static function condition ( $thread_mode )
2018-06-09 12:56:37 -04:00
{
2018-06-09 15:12:13 -04:00
if ( $thread_mode ) {
$master_table = " `thread` " ;
} else {
$master_table = " `item` " ;
}
2018-08-25 09:48:00 -04:00
return sprintf ( " $master_table .`visible` AND NOT $master_table .`deleted` AND NOT $master_table .`moderated`
AND ( `user-item` . `hidden` IS NULL OR NOT `user-item` . `hidden` )
AND ( `user-author` . `blocked` IS NULL OR NOT `user-author` . `blocked` )
AND ( `user-author` . `ignored` IS NULL OR NOT `user-author` . `ignored` OR `item` . `gravity` != % d )
AND ( `user-owner` . `blocked` IS NULL OR NOT `user-owner` . `blocked` )
AND ( `user-owner` . `ignored` IS NULL OR NOT `user-owner` . `ignored` OR `item` . `gravity` != % d ) " ,
GRAVITY_PARENT , GRAVITY_PARENT );
2018-06-09 12:56:37 -04:00
}
2018-06-09 15:12:13 -04:00
/**
* @ brief Returns all needed " JOIN " commands for the " select " functions
*
* @ param integer $uid User ID
* @ param string $sql_commands The parts of the built SQL commands in the " select " functions
* @ param boolean $thread_mode Called for the items ( false ) or for the threads ( true )
*
* @ return string The SQL joins for the " select " functions
*/
2018-07-02 14:22:27 -04:00
private static function constructJoins ( $uid , $sql_commands , $thread_mode , $user_mode )
2018-06-09 12:56:37 -04:00
{
if ( $thread_mode ) {
$master_table = " `thread` " ;
$master_table_key = " `thread`.`iid` " ;
$joins = " STRAIGHT_JOIN `item` ON `item`.`id` = `thread`.`iid` " ;
} else {
$master_table = " `item` " ;
$master_table_key = " `item`.`id` " ;
$joins = '' ;
}
2018-07-02 14:22:27 -04:00
if ( $user_mode ) {
$joins .= sprintf ( " STRAIGHT_JOIN `contact` ON `contact`.`id` = $master_table .`contact-id`
AND NOT `contact` . `blocked`
AND (( NOT `contact` . `readonly` AND NOT `contact` . `pending` AND ( `contact` . `rel` IN ( % s , % s )))
2018-07-15 14:36:20 -04:00
OR `contact` . `self` OR `item` . `gravity` != % d OR `contact` . `uid` = 0 )
2018-07-02 14:22:27 -04:00
STRAIGHT_JOIN `contact` AS `author` ON `author` . `id` = $master_table . `author-id` AND NOT `author` . `blocked`
STRAIGHT_JOIN `contact` AS `owner` ON `owner` . `id` = $master_table . `owner-id` AND NOT `owner` . `blocked`
2018-08-25 09:48:00 -04:00
LEFT JOIN `user-item` ON `user-item` . `iid` = $master_table_key AND `user-item` . `uid` = % d
LEFT JOIN `user-contact` AS `user-author` ON `user-author` . `cid` = $master_table . `author-id` AND `user-author` . `uid` = % d
LEFT JOIN `user-contact` AS `user-owner` ON `user-owner` . `cid` = $master_table . `owner-id` AND `user-owner` . `uid` = % d " ,
Contact :: SHARING , Contact :: FRIEND , GRAVITY_PARENT , intval ( $uid ), intval ( $uid ), intval ( $uid ));
2018-07-02 14:22:27 -04:00
} else {
if ( strpos ( $sql_commands , " `contact`. " ) !== false ) {
2018-07-15 14:36:20 -04:00
$joins .= " LEFT JOIN `contact` ON `contact`.`id` = $master_table .`contact-id` " ;
2018-07-02 14:22:27 -04:00
}
if ( strpos ( $sql_commands , " `author`. " ) !== false ) {
2018-07-15 14:36:20 -04:00
$joins .= " LEFT JOIN `contact` AS `author` ON `author`.`id` = $master_table .`author-id` " ;
2018-07-02 14:22:27 -04:00
}
if ( strpos ( $sql_commands , " `owner`. " ) !== false ) {
2018-07-15 14:36:20 -04:00
$joins .= " LEFT JOIN `contact` AS `owner` ON `owner`.`id` = $master_table .`owner-id` " ;
2018-07-02 14:22:27 -04:00
}
}
2018-06-09 12:56:37 -04:00
if ( strpos ( $sql_commands , " `group_member`. " ) !== false ) {
$joins .= " STRAIGHT_JOIN `group_member` ON `group_member`.`contact-id` = $master_table .`contact-id` " ;
}
if ( strpos ( $sql_commands , " `user`. " ) !== false ) {
$joins .= " STRAIGHT_JOIN `user` ON `user`.`uid` = $master_table .`uid` " ;
}
if ( strpos ( $sql_commands , " `event`. " ) !== false ) {
$joins .= " LEFT JOIN `event` ON `event-id` = `event`.`id` " ;
}
2018-06-16 18:32:57 -04:00
if ( strpos ( $sql_commands , " `sign`. " ) !== false ) {
$joins .= " LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id` " ;
}
2018-10-15 17:42:55 -04:00
if ( strpos ( $sql_commands , " `diaspora-interaction`. " ) !== false ) {
$joins .= " LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `item`.`uri-id` " ;
}
2018-07-05 18:00:38 -04:00
if ( strpos ( $sql_commands , " `item-activity`. " ) !== false ) {
2018-10-17 14:34:24 -04:00
$joins .= " LEFT JOIN `item-activity` ON `item-activity`.`uri-id` = `item`.`uri-id` " ;
2018-07-05 18:00:38 -04:00
}
2018-06-24 17:41:49 -04:00
if ( strpos ( $sql_commands , " `item-content`. " ) !== false ) {
2018-10-17 14:34:24 -04:00
$joins .= " LEFT JOIN `item-content` ON `item-content`.`uri-id` = `item`.`uri-id` " ;
2018-06-24 17:41:49 -04:00
}
2018-07-19 17:56:52 -04:00
if ( strpos ( $sql_commands , " `item-delivery-data`. " ) !== false ) {
$joins .= " LEFT JOIN `item-delivery-data` ON `item-delivery-data`.`iid` = `item`.`id` " ;
}
2018-07-25 19:14:55 -04:00
if ( strpos ( $sql_commands , " `permissionset`. " ) !== false ) {
$joins .= " LEFT JOIN `permissionset` ON `permissionset`.`id` = `item`.`psid` " ;
}
2018-06-15 01:50:28 -04:00
if (( strpos ( $sql_commands , " `parent-item`. " ) !== false ) || ( strpos ( $sql_commands , " `parent-author`. " ) !== false )) {
$joins .= " STRAIGHT_JOIN `item` AS `parent-item` ON `parent-item`.`id` = `item`.`parent` " ;
}
if ( strpos ( $sql_commands , " `parent-item-author`. " ) !== false ) {
$joins .= " STRAIGHT_JOIN `contact` AS `parent-item-author` ON `parent-item-author`.`id` = `parent-item`.`author-id` " ;
}
2018-06-09 12:56:37 -04:00
return $joins ;
}
2018-06-09 15:12:13 -04:00
/**
* @ brief Add the field list for the " select " functions
*
* @ param array $fields The field definition array
* @ param array $selected The array with the selected fields from the " select " functions
*
* @ return string The field list
*/
2018-06-09 12:56:37 -04:00
private static function constructSelectFields ( $fields , $selected )
{
2018-07-05 18:00:38 -04:00
if ( ! empty ( $selected )) {
$selected [] = 'internal-iid' ;
$selected [] = 'internal-iaid' ;
$selected [] = 'internal-icid' ;
$selected [] = 'internal-network' ;
2018-06-30 09:54:01 -04:00
}
2018-07-05 18:00:38 -04:00
if ( in_array ( 'verb' , $selected )) {
$selected [] = 'internal-activity' ;
2018-07-01 03:57:59 -04:00
}
2018-06-30 17:15:24 -04:00
2018-08-08 16:32:11 -04:00
if ( in_array ( 'ignored' , $selected )) {
$selected [] = 'internal-user-ignored' ;
}
2018-10-15 17:42:55 -04:00
if ( in_array ( 'signed_text' , $selected )) {
$selected [] = 'interaction' ;
}
2018-06-09 12:56:37 -04:00
$selection = [];
foreach ( $fields as $table => $table_fields ) {
foreach ( $table_fields as $field => $select ) {
if ( empty ( $selected ) || in_array ( $select , $selected )) {
2018-07-19 17:56:52 -04:00
$legacy_fields = array_merge ( self :: DELIVERY_DATA_FIELDLIST , self :: MIXED_CONTENT_FIELDLIST );
if ( self :: isLegacyMode () && in_array ( $select , $legacy_fields )) {
2018-07-05 18:00:38 -04:00
$selection [] = " `item`.` " . $select . " ` AS `internal-item- " . $select . " ` " ;
2018-06-24 17:41:49 -04:00
}
2018-06-09 12:56:37 -04:00
if ( is_int ( $field )) {
2018-06-24 17:41:49 -04:00
$selection [] = " ` " . $table . " `.` " . $select . " ` " ;
2018-06-09 12:56:37 -04:00
} else {
2018-06-24 17:41:49 -04:00
$selection [] = " ` " . $table . " `.` " . $field . " ` AS ` " . $select . " ` " ;
2018-06-09 12:56:37 -04:00
}
}
}
}
return implode ( " , " , $selection );
}
2018-06-09 15:12:13 -04:00
/**
* @ brief add table definition to fields in an SQL query
*
* @ param string $query SQL query
* @ param array $fields The field definition array
*
* @ return string the changed SQL query
*/
2018-06-09 12:56:37 -04:00
private static function addTablesToFields ( $query , $fields )
{
foreach ( $fields as $table => $table_fields ) {
foreach ( $table_fields as $alias => $field ) {
if ( is_int ( $alias )) {
$replace_field = $field ;
} else {
$replace_field = $alias ;
}
$search = " /([^ \ .])` " . $field . " `/i " ;
$replace = " $ 1` " . $table . " `.` " . $replace_field . " ` " ;
$query = preg_replace ( $search , $replace , $query );
}
}
return $query ;
2018-06-07 02:03:12 -04:00
}
2018-01-09 16:13:45 -05:00
/**
* @ brief Update existing item entries
*
* @ param array $fields The fields that are to be changed
* @ param array $condition The condition for finding the item entries
*
2018-01-09 17:16:16 -05: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
2018-01-09 16:13:45 -05:00
*/
public static function update ( array $fields , array $condition )
{
if ( empty ( $condition ) || empty ( $fields )) {
return false ;
}
2018-05-08 13:50:06 -04:00
// To ensure the data integrity we do it in an transaction
2018-07-20 08:19:26 -04:00
DBA :: transaction ();
2018-05-08 13:50:06 -04:00
// We cannot simply expand the condition to check for origin entries
// The condition needn't to be a simple array but could be a complex condition.
// And we have to execute this query before the update to ensure to fetch the same data.
2018-10-18 17:35:48 -04:00
$items = DBA :: select ( 'item' , [ 'id' , 'origin' , 'uri' , 'uri-id' , 'iaid' , 'icid' , 'tag' , 'file' ], $condition );
2018-05-08 13:50:06 -04:00
2018-06-24 19:09:13 -04:00
$content_fields = [];
2018-06-30 01:18:43 -04:00
foreach ( array_merge ( self :: CONTENT_FIELDLIST , self :: MIXED_CONTENT_FIELDLIST ) as $field ) {
2018-06-24 19:09:13 -04:00
if ( isset ( $fields [ $field ])) {
$content_fields [ $field ] = $fields [ $field ];
2018-07-15 14:36:20 -04:00
if ( in_array ( $field , self :: CONTENT_FIELDLIST ) || ! self :: isLegacyMode ()) {
2018-07-07 19:03:28 -04:00
unset ( $fields [ $field ]);
} else {
$fields [ $field ] = null ;
}
}
}
2018-07-19 09:52:05 -04:00
$clear_fields = [ 'bookmark' , 'type' , 'author-name' , 'author-avatar' , 'author-link' , 'owner-name' , 'owner-avatar' , 'owner-link' ];
foreach ( $clear_fields as $field ) {
2018-07-10 08:27:56 -04:00
if ( array_key_exists ( $field , $fields )) {
2018-07-07 19:03:28 -04:00
$fields [ $field ] = null ;
2018-06-24 19:09:13 -04:00
}
}
2018-01-09 16:13:45 -05:00
2018-06-30 11:21:32 -04:00
if ( array_key_exists ( 'tag' , $fields )) {
$tags = $fields [ 'tag' ];
2018-07-10 08:27:56 -04:00
$fields [ 'tag' ] = null ;
2018-06-30 11:21:32 -04:00
} else {
2018-10-23 05:40:20 -04:00
$tags = null ;
2018-06-30 11:21:32 -04:00
}
2018-06-30 17:15:24 -04:00
if ( array_key_exists ( 'file' , $fields )) {
$files = $fields [ 'file' ];
2018-07-10 08:27:56 -04:00
$fields [ 'file' ] = null ;
2018-06-30 17:15:24 -04:00
} else {
$files = '' ;
}
2018-07-19 17:56:52 -04:00
$delivery_data = [ 'postopts' => defaults ( $fields , 'postopts' , '' ),
'inform' => defaults ( $fields , 'inform' , '' )];
$fields [ 'postopts' ] = null ;
$fields [ 'inform' ] = null ;
2018-06-24 19:09:13 -04:00
if ( ! empty ( $fields )) {
2018-07-20 08:19:26 -04:00
$success = DBA :: update ( 'item' , $fields , $condition );
2018-06-24 19:09:13 -04:00
if ( ! $success ) {
2018-07-20 08:19:26 -04:00
DBA :: close ( $items );
DBA :: rollback ();
2018-06-24 19:09:13 -04:00
return false ;
}
2018-01-09 16:13:45 -05:00
}
2018-06-24 19:09:13 -04:00
// When there is no content for the "old" item table, this will count the fetched items
2018-07-20 21:58:15 -04:00
$rows = DBA :: affectedRows ();
2018-01-09 17:16:16 -05:00
2018-07-20 08:19:26 -04:00
while ( $item = DBA :: fetch ( $items )) {
2018-07-07 15:39:00 -04:00
if ( ! empty ( $item [ 'iaid' ]) || ( ! empty ( $content_fields [ 'verb' ]) && ( self :: activityToIndex ( $content_fields [ 'verb' ]) >= 0 ))) {
2018-10-18 17:35:48 -04:00
self :: updateActivity ( $content_fields , [ 'uri-id' => $item [ 'uri-id' ]]);
2018-07-07 12:38:01 -04:00
2018-07-05 18:50:33 -04:00
if ( empty ( $item [ 'iaid' ])) {
2018-10-18 17:35:48 -04:00
$item_activity = DBA :: selectFirst ( 'item-activity' , [ 'id' ], [ 'uri-id' => $item [ 'uri-id' ]]);
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $item_activity )) {
2018-07-06 01:16:40 -04:00
$item_fields = [ 'iaid' => $item_activity [ 'id' ], 'icid' => null ];
foreach ( self :: MIXED_CONTENT_FIELDLIST as $field ) {
2018-07-15 14:36:20 -04:00
if ( self :: isLegacyMode ()) {
$item_fields [ $field ] = null ;
} else {
unset ( $item_fields [ $field ]);
}
2018-07-06 01:16:40 -04:00
}
2018-07-20 08:19:26 -04:00
DBA :: update ( 'item' , $item_fields , [ 'id' => $item [ 'id' ]]);
2018-07-05 18:50:33 -04:00
2018-07-20 08:19:26 -04:00
if ( ! empty ( $item [ 'icid' ]) && ! DBA :: exists ( 'item' , [ 'icid' => $item [ 'icid' ]])) {
DBA :: delete ( 'item-content' , [ 'id' => $item [ 'icid' ]]);
2018-07-05 18:50:33 -04:00
}
}
2018-07-07 15:39:00 -04:00
} elseif ( ! empty ( $item [ 'icid' ])) {
2018-07-20 08:19:26 -04:00
DBA :: update ( 'item' , [ 'icid' => null ], [ 'id' => $item [ 'id' ]]);
2018-07-07 15:39:00 -04:00
2018-07-20 08:19:26 -04:00
if ( ! DBA :: exists ( 'item' , [ 'icid' => $item [ 'icid' ]])) {
DBA :: delete ( 'item-content' , [ 'id' => $item [ 'icid' ]]);
2018-07-07 15:39:00 -04:00
}
2018-07-05 18:50:33 -04:00
}
} else {
2018-10-18 17:35:48 -04:00
self :: updateContent ( $content_fields , [ 'uri-id' => $item [ 'uri-id' ]]);
2018-07-05 18:00:38 -04:00
if ( empty ( $item [ 'icid' ])) {
2018-10-18 17:35:48 -04:00
$item_content = DBA :: selectFirst ( 'item-content' , [], [ 'uri-id' => $item [ 'uri-id' ]]);
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $item_content )) {
2018-07-05 18:00:38 -04:00
$item_fields = [ 'icid' => $item_content [ 'id' ]];
// Clear all fields in the item table that have a content in the item-content table
foreach ( $item_content as $field => $content ) {
if ( in_array ( $field , self :: MIXED_CONTENT_FIELDLIST ) && ! empty ( $item_content [ $field ])) {
2018-07-15 14:36:20 -04:00
if ( self :: isLegacyMode ()) {
$item_fields [ $field ] = null ;
} else {
unset ( $item_fields [ $field ]);
}
2018-07-05 18:00:38 -04:00
}
2018-07-01 15:02:29 -04:00
}
2018-07-20 08:19:26 -04:00
DBA :: update ( 'item' , $item_fields , [ 'id' => $item [ 'id' ]]);
2018-07-01 15:02:29 -04:00
}
}
}
2018-10-23 05:40:20 -04:00
if ( ! is_null ( $tags )) {
2018-10-23 13:29:59 -04:00
Term :: insertFromTagFieldByItemId ( $item [ 'id' ], $tags );
if ( ! empty ( $item [ 'tag' ])) {
DBA :: update ( 'item' , [ 'tag' => '' ], [ 'id' => $item [ 'id' ]]);
2018-10-23 07:38:31 -04:00
}
2018-06-30 09:54:01 -04:00
}
2018-06-30 17:15:24 -04:00
if ( ! empty ( $files )) {
Term :: insertFromFileFieldByItemId ( $item [ 'id' ], $files );
2018-07-07 19:03:28 -04:00
if ( ! empty ( $item [ 'file' ])) {
2018-07-20 08:19:26 -04:00
DBA :: update ( 'item' , [ 'file' => '' ], [ 'id' => $item [ 'id' ]]);
2018-07-07 19:03:28 -04:00
}
2018-06-30 09:54:01 -04:00
}
2018-07-19 17:56:52 -04:00
self :: updateDeliveryData ( $item [ 'id' ], $delivery_data );
2018-02-05 07:47:06 -05:00
self :: updateThread ( $item [ 'id' ]);
2018-01-09 16:13:45 -05:00
2018-05-17 01:49:55 -04:00
// We only need to notfiy others when it is an original entry from us.
// Only call the notifier when the item has some content relevant change.
if ( $item [ 'origin' ] && in_array ( 'edited' , array_keys ( $fields ))) {
2018-02-07 15:09:37 -05:00
Worker :: add ( PRIORITY_HIGH , " Notifier " , 'edit_post' , $item [ 'id' ]);
}
2018-01-09 16:13:45 -05:00
}
2018-07-20 08:19:26 -04:00
DBA :: close ( $items );
DBA :: commit ();
2018-01-09 17:16:16 -05:00
return $rows ;
2018-01-09 16:13:45 -05:00
}
2018-01-16 17:23:19 -05:00
2018-02-06 07:40:22 -05:00
/**
* @ brief Delete an item and notify others about it - if it was ours
*
* @ param array $condition The condition for finding the item entries
* @ param integer $priority Priority for the notification
*/
public static function delete ( $condition , $priority = PRIORITY_HIGH )
{
2018-08-15 00:41:49 -04:00
$items = self :: select ([ 'id' ], $condition );
while ( $item = self :: fetch ( $items )) {
2018-02-06 07:40:22 -05:00
self :: deleteById ( $item [ 'id' ], $priority );
}
2018-07-20 08:19:26 -04:00
DBA :: close ( $items );
2018-02-06 07:40:22 -05:00
}
2018-05-29 01:22:57 -04:00
/**
* @ brief Delete an item for an user and notify others about it - if it was ours
*
* @ param array $condition The condition for finding the item entries
* @ param integer $uid User who wants to delete this item
*/
public static function deleteForUser ( $condition , $uid )
{
if ( $uid == 0 ) {
return ;
}
2018-08-15 00:41:49 -04:00
$items = self :: select ([ 'id' , 'uid' ], $condition );
while ( $item = self :: fetch ( $items )) {
2018-05-29 01:22:57 -04:00
// "Deleting" global items just means hiding them
if ( $item [ 'uid' ] == 0 ) {
2018-07-20 08:19:26 -04:00
DBA :: update ( 'user-item' , [ 'hidden' => true ], [ 'iid' => $item [ 'id' ], 'uid' => $uid ], true );
2018-05-29 01:22:57 -04:00
} elseif ( $item [ 'uid' ] == $uid ) {
self :: deleteById ( $item [ 'id' ], PRIORITY_HIGH );
} else {
2018-10-29 17:20:46 -04:00
Logger :: log ( 'Wrong ownership. Not deleting item ' . $item [ 'id' ]);
2018-05-29 01:22:57 -04:00
}
2018-02-06 07:40:22 -05:00
}
2018-07-20 08:19:26 -04:00
DBA :: close ( $items );
2018-02-06 07:40:22 -05:00
}
2018-01-17 18:22:01 -05:00
/**
* @ brief Delete an item and notify others about it - if it was ours
*
* @ param integer $item_id Item ID that should be delete
2018-02-06 07:40:22 -05:00
* @ param integer $priority Priority for the notification
2018-01-17 18:22:01 -05:00
*
2018-03-16 21:45:02 -04:00
* @ return boolean success
2018-01-17 18:22:01 -05:00
*/
2018-10-26 00:27:23 -04:00
public static function deleteById ( $item_id , $priority = PRIORITY_HIGH )
2018-01-16 18:16:53 -05:00
{
// locate item to be deleted
2018-05-15 11:51:58 -04:00
$fields = [ 'id' , 'uri' , 'uid' , 'parent' , 'parent-uri' , 'origin' ,
'deleted' , 'file' , 'resource-id' , 'event-id' , 'attach' ,
2018-07-28 23:54:34 -04:00
'verb' , 'object-type' , 'object' , 'target' , 'contact-id' ,
'icid' , 'iaid' , 'psid' ];
2018-06-30 17:15:24 -04:00
$item = self :: selectFirst ( $fields , [ 'id' => $item_id ]);
2018-07-21 08:46:04 -04:00
if ( ! DBA :: isResult ( $item )) {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Item with ID ' . $item_id . " hasn't been found. " , Logger :: DEBUG );
2018-01-16 18:16:53 -05:00
return false ;
}
if ( $item [ 'deleted' ]) {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Item with ID ' . $item_id . ' has already been deleted.' , Logger :: DEBUG );
2018-01-16 18:16:53 -05:00
return false ;
}
2018-06-30 17:15:24 -04:00
$parent = self :: selectFirst ([ 'origin' ], [ 'id' => $item [ 'parent' ]]);
2018-07-21 08:46:04 -04:00
if ( ! DBA :: isResult ( $parent )) {
2018-01-17 18:22:01 -05:00
$parent = [ 'origin' => false ];
}
2018-01-16 18:16:53 -05:00
// clean up categories and tags so they don't end up as orphans
$matches = false ;
$cnt = preg_match_all ( '/<(.*?)>/' , $item [ 'file' ], $matches , PREG_SET_ORDER );
2018-10-30 14:51:45 -04:00
if ( $cnt )
{
foreach ( $matches as $mtch )
{
FileTag :: unsaveFile ( $item [ 'uid' ], $item [ 'id' ], $mtch [ 1 ], true );
2018-01-16 18:16:53 -05:00
}
}
$matches = false ;
$cnt = preg_match_all ( '/\[(.*?)\]/' , $item [ 'file' ], $matches , PREG_SET_ORDER );
2018-10-30 14:51:45 -04:00
if ( $cnt )
{
foreach ( $matches as $mtch )
{
FileTag :: unsaveFile ( $item [ 'uid' ], $item [ 'id' ], $mtch [ 1 ], false );
2018-01-16 18:16:53 -05:00
}
}
2018-01-17 02:08:49 -05: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 .
*/
2018-01-16 18:16:53 -05:00
if ( strlen ( $item [ 'resource-id' ])) {
2018-07-20 08:19:26 -04:00
DBA :: delete ( 'photo' , [ 'resource-id' => $item [ 'resource-id' ], 'uid' => $item [ 'uid' ]]);
2018-01-16 18:16:53 -05:00
}
2018-02-06 07:40:22 -05:00
// If item is a link to an event, delete the event.
2018-01-16 18:16:53 -05:00
if ( intval ( $item [ 'event-id' ])) {
2018-03-16 21:45:02 -04:00
Event :: delete ( $item [ 'event-id' ]);
2018-01-16 18:16:53 -05:00
}
// If item has attachments, drop them
foreach ( explode ( " , " , $item [ 'attach' ]) as $attach ) {
preg_match ( " |attach/( \ d+)| " , $attach , $matches );
2018-07-01 14:46:45 -04:00
if ( is_array ( $matches ) && count ( $matches ) > 1 ) {
2018-07-20 08:19:26 -04:00
DBA :: delete ( 'attach' , [ 'id' => $matches [ 1 ], 'uid' => $item [ 'uid' ]]);
2018-07-01 14:46:45 -04:00
}
2018-01-16 18:16:53 -05:00
}
2018-02-06 07:40:22 -05:00
// Delete tags that had been attached to other items
self :: deleteTagsFromItem ( $item );
2018-01-20 17:16:43 -05:00
// Set the item to "deleted"
2018-07-28 23:54:34 -04:00
$item_fields = [ 'deleted' => true , 'edited' => DateTimeFormat :: utcNow (), 'changed' => DateTimeFormat :: utcNow ()];
2018-07-20 08:19:26 -04:00
DBA :: update ( 'item' , $item_fields , [ 'id' => $item [ 'id' ]]);
2018-01-20 17:16:43 -05:00
2018-06-30 11:21:32 -04:00
Term :: insertFromTagFieldByItemId ( $item [ 'id' ], '' );
2018-06-30 17:15:24 -04:00
Term :: insertFromFileFieldByItemId ( $item [ 'id' ], '' );
2018-02-05 07:47:06 -05:00
self :: deleteThread ( $item [ 'id' ], $item [ 'parent-uri' ]);
2018-01-20 17:16:43 -05:00
2018-06-27 15:37:13 -04:00
if ( ! self :: exists ([ " `uri` = ? AND `uid` != 0 AND NOT `deleted` " , $item [ 'uri' ]])) {
2018-05-15 12:40:13 -04:00
self :: delete ([ 'uri' => $item [ 'uri' ], 'uid' => 0 , 'deleted' => false ], $priority );
}
2018-07-20 08:19:26 -04:00
DBA :: delete ( 'item-delivery-data' , [ 'iid' => $item [ 'id' ]]);
2018-07-19 17:56:52 -04:00
2018-09-30 03:21:57 -04:00
// We don't delete the item-activity here, since we need some of the data for ActivityPub
2018-08-15 00:41:49 -04:00
if ( ! empty ( $item [ 'icid' ]) && ! self :: exists ([ 'icid' => $item [ 'icid' ], 'deleted' => false ])) {
2018-08-05 00:35:11 -04:00
DBA :: delete ( 'item-content' , [ 'id' => $item [ 'icid' ]], [ 'cascade' => false ]);
2018-07-28 23:54:34 -04:00
}
// When the permission set will be used in photo and events as well,
// this query here needs to be extended.
2018-08-15 00:41:49 -04:00
if ( ! empty ( $item [ 'psid' ]) && ! self :: exists ([ 'psid' => $item [ 'psid' ], 'deleted' => false ])) {
2018-08-05 05:02:36 -04:00
DBA :: delete ( 'permissionset' , [ 'id' => $item [ 'psid' ]], [ 'cascade' => false ]);
2018-07-28 23:54:34 -04:00
}
2018-01-20 17:16:43 -05:00
// If it's the parent of a comment thread, kill all the kids
if ( $item [ 'id' ] == $item [ 'parent' ]) {
2018-05-15 15:25:35 -04:00
self :: delete ([ 'parent' => $item [ 'parent' ], 'deleted' => false ], $priority );
2018-01-20 17:16:43 -05:00
}
2018-01-16 18:16:53 -05:00
2018-05-15 11:51:58 -04:00
// Is it our comment and/or our thread?
2018-01-20 17:16:43 -05:00
if ( $item [ 'origin' ] || $parent [ 'origin' ]) {
2018-05-15 11:51:58 -04:00
// When we delete the original post we will delete all existing copies on the server as well
self :: delete ([ 'uri' => $item [ 'uri' ], 'deleted' => false ], $priority );
// send the notification upstream/downstream
2018-01-17 02:08:49 -05:00
Worker :: add ([ 'priority' => $priority , 'dont_fork' => true ], " Notifier " , " drop " , intval ( $item [ 'id' ]));
2018-05-26 14:07:27 -04:00
} elseif ( $item [ 'uid' ] != 0 ) {
2018-05-26 16:07:30 -04:00
// When we delete just our local user copy of an item, we have to set a marker to hide it
2018-06-30 17:15:24 -04:00
$global_item = self :: selectFirst ([ 'id' ], [ 'uri' => $item [ 'uri' ], 'uid' => 0 , 'deleted' => false ]);
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $global_item )) {
2018-07-20 08:19:26 -04:00
DBA :: update ( 'user-item' , [ 'hidden' => true ], [ 'iid' => $global_item [ 'id' ], 'uid' => $item [ 'uid' ]], true );
2018-05-26 14:07:27 -04:00
}
2018-01-16 18:16:53 -05:00
}
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Item with ID ' . $item_id . " has been deleted. " , Logger :: DEBUG );
2018-05-15 13:50:29 -04:00
2018-01-16 18:16:53 -05:00
return true ;
}
2018-02-06 07:40:22 -05:00
private static function deleteTagsFromItem ( $item )
{
if (( $item [ " verb " ] != ACTIVITY_TAG ) || ( $item [ " object-type " ] != ACTIVITY_OBJ_TAGTERM )) {
return ;
}
$xo = XML :: parseString ( $item [ " object " ], false );
$xt = XML :: parseString ( $item [ " target " ], false );
if ( $xt -> type != ACTIVITY_OBJ_NOTE ) {
return ;
}
2018-06-30 17:15:24 -04:00
$i = self :: selectFirst ([ 'id' , 'contact-id' , 'tag' ], [ 'uri' => $xt -> id , 'uid' => $item [ 'uid' ]]);
2018-07-21 08:46:04 -04:00
if ( ! DBA :: isResult ( $i )) {
2018-02-06 07:40:22 -05:00
return ;
}
// For tags, the owner cannot remove the tag on the author's copy of the post.
$owner_remove = ( $item [ " contact-id " ] == $i [ " contact-id " ]);
$author_copy = $item [ " origin " ];
if (( $owner_remove && $author_copy ) || ! $owner_remove ) {
return ;
}
$tags = explode ( ',' , $i [ " tag " ]);
$newtags = [];
if ( count ( $tags )) {
foreach ( $tags as $tag ) {
if ( trim ( $tag ) !== trim ( $xo -> body )) {
$newtags [] = trim ( $tag );
}
}
}
self :: update ([ 'tag' => implode ( ',' , $newtags )], [ 'id' => $i [ " id " ]]);
}
2018-02-21 16:39:07 -05:00
private static function guid ( $item , $notify )
2018-02-20 23:13:13 -05:00
{
2018-07-08 05:37:05 -04:00
if ( ! empty ( $item [ 'guid' ])) {
return notags ( trim ( $item [ 'guid' ]));
2018-02-20 23:13:13 -05: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.
2018-10-09 13:58:58 -04:00
$prefix_host = get_app () -> getHostName ();
2018-02-20 23:13:13 -05:00
} else {
$prefix_host = '' ;
// We are only storing the post so we create a GUID from the original hostname.
2018-02-21 16:39:07 -05:00
if ( ! empty ( $item [ 'author-link' ])) {
$parsed = parse_url ( $item [ 'author-link' ]);
2018-02-20 23:13:13 -05:00
if ( ! empty ( $parsed [ 'host' ])) {
$prefix_host = $parsed [ 'host' ];
}
}
2018-02-21 16:39:07 -05:00
if ( empty ( $prefix_host ) && ! empty ( $item [ 'plink' ])) {
$parsed = parse_url ( $item [ 'plink' ]);
2018-02-20 23:13:13 -05:00
if ( ! empty ( $parsed [ 'host' ])) {
$prefix_host = $parsed [ 'host' ];
}
}
2018-02-21 16:39:07 -05:00
if ( empty ( $prefix_host ) && ! empty ( $item [ 'uri' ])) {
$parsed = parse_url ( $item [ 'uri' ]);
2018-02-20 23:13:13 -05:00
if ( ! empty ( $parsed [ 'host' ])) {
$prefix_host = $parsed [ 'host' ];
}
}
2018-02-26 06:48:05 -05: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-20 23:13:13 -05:00
}
2018-02-21 16:39:07 -05: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-20 23:13:13 -05:00
} else {
2018-09-27 07:52:15 -04:00
$guid = System :: createUUID ( hash ( 'crc32' , $prefix_host ));
2018-02-20 23:13:13 -05:00
}
return $guid ;
}
2018-02-21 16:39:07 -05:00
private static function contactId ( $item )
2018-02-21 16:08:37 -05:00
{
2018-02-21 16:39:07 -05:00
$contact_id = ( int ) $item [ " contact-id " ];
2018-02-21 16:08:37 -05:00
if ( ! empty ( $contact_id )) {
return $contact_id ;
}
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Missing contact-id. Called by: ' . System :: callstack (), Logger :: DEBUG );
2018-02-21 16:08:37 -05:00
/*
* First we are looking for a suitable contact that matches with the author of the post
* This is done only for comments
*/
2018-02-21 16:39:07 -05:00
if ( $item [ 'parent-uri' ] != $item [ 'uri' ]) {
2018-02-22 02:05:56 -05:00
$contact_id = Contact :: getIdForURL ( $item [ 'author-link' ], $item [ 'uid' ]);
2018-02-21 16:08:37 -05:00
}
// If not present then maybe the owner was found
if ( $contact_id == 0 ) {
2018-02-22 02:05:56 -05:00
$contact_id = Contact :: getIdForURL ( $item [ 'owner-link' ], $item [ 'uid' ]);
2018-02-21 16:08:37 -05:00
}
// Still missing? Then use the "self" contact of the current user
if ( $contact_id == 0 ) {
2018-07-20 08:19:26 -04:00
$self = DBA :: selectFirst ( 'contact' , [ 'id' ], [ 'self' => true , 'uid' => $item [ 'uid' ]]);
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $self )) {
2018-02-21 16:08:37 -05:00
$contact_id = $self [ " id " ];
}
}
2018-10-30 09:58:45 -04:00
Logger :: log ( " Contact-id was missing for post " . $item [ 'guid' ] . " from user id " . $item [ 'uid' ] . " - now set to " . $contact_id , Logger :: DEBUG );
2018-02-21 16:08:37 -05:00
return $contact_id ;
}
2018-07-20 14:07:54 -04:00
// This function will finally cover most of the preparation functionality in mod/item.php
public static function prepare ( & $item )
{
$data = BBCode :: getAttachmentData ( $item [ 'body' ]);
if (( preg_match_all ( " / \ [bookmark \ =([^ \ ]]*) \ ](.*?) \ [ \ /bookmark \ ]/ism " , $item [ 'body' ], $match , PREG_SET_ORDER ) || isset ( $data [ " type " ]))
&& ( $posttype != Item :: PT_PERSONAL_NOTE )) {
$posttype = Item :: PT_PAGE ;
$objecttype = ACTIVITY_OBJ_BOOKMARK ;
}
}
2018-02-21 16:39:07 -05:00
public static function insert ( $item , $force_parent = false , $notify = false , $dontcache = false )
2018-01-28 06:18:08 -05:00
{
$a = get_app ();
// If it is a posting where users should get notifications, then define it as wall posting
if ( $notify ) {
2018-02-21 16:39:07 -05:00
$item [ 'wall' ] = 1 ;
$item [ 'origin' ] = 1 ;
2018-08-11 16:40:44 -04:00
$item [ 'network' ] = Protocol :: DFRN ;
2018-08-05 06:23:57 -04:00
$item [ 'protocol' ] = Conversation :: PARCEL_DFRN ;
2018-05-15 15:29:14 -04:00
if ( is_int ( $notify )) {
$priority = $notify ;
} else {
$priority = PRIORITY_HIGH ;
}
2018-01-28 06:18:08 -05:00
} else {
2018-08-11 16:40:44 -04:00
$item [ 'network' ] = trim ( defaults ( $item , 'network' , Protocol :: PHANTOM ));
2018-01-28 06:18:08 -05:00
}
2018-02-21 16:39:07 -05:00
$item [ 'guid' ] = self :: guid ( $item , $notify );
2018-06-16 02:44:19 -04:00
$item [ 'uri' ] = notags ( trim ( defaults ( $item , 'uri' , self :: newURI ( $item [ 'uid' ], $item [ 'guid' ]))));
2018-01-28 06:18:08 -05:00
2018-08-05 07:09:59 -04:00
// Store URI data
$item [ 'uri-id' ] = ItemURI :: insert ([ 'uri' => $item [ 'uri' ], 'guid' => $item [ 'guid' ]]);
2018-01-28 06:18:08 -05:00
// Store conversation data
2018-02-21 16:39:07 -05:00
$item = Conversation :: insert ( $item );
2018-01-28 06:18:08 -05:00
/*
* If a Diaspora signature structure was passed in , pull it out of the
* item array and set it aside for later storage .
*/
$dsprsig = null ;
2018-10-12 16:52:22 -04:00
if ( isset ( $item [ 'dsprsig' ])) {
2018-02-21 16:39:07 -05:00
$encoded_signature = $item [ 'dsprsig' ];
$dsprsig = json_decode ( base64_decode ( $item [ 'dsprsig' ]));
2018-10-12 16:52:22 -04:00
unset ( $item [ 'dsprsig' ]);
2018-01-28 06:18:08 -05:00
}
2018-10-12 16:52:22 -04:00
$diaspora_signed_text = '' ;
if ( isset ( $item [ 'diaspora_signed_text' ])) {
2018-05-15 00:33:28 -04:00
$diaspora_signed_text = $item [ 'diaspora_signed_text' ];
2018-10-12 16:52:22 -04:00
unset ( $item [ 'diaspora_signed_text' ]);
2018-05-15 00:33:28 -04:00
}
2018-01-28 06:18:08 -05:00
// Converting the plink
/// @TODO Check if this is really still needed
2018-08-11 16:40:44 -04:00
if ( $item [ 'network' ] == Protocol :: OSTATUS ) {
2018-02-21 16:39:07 -05:00
if ( isset ( $item [ 'plink' ])) {
$item [ 'plink' ] = OStatus :: convertHref ( $item [ 'plink' ]);
} elseif ( isset ( $item [ 'uri' ])) {
$item [ 'plink' ] = OStatus :: convertHref ( $item [ 'uri' ]);
2018-01-28 06:18:08 -05:00
}
}
2018-03-22 12:18:49 -04:00
if ( ! empty ( $item [ 'thr-parent' ])) {
$item [ 'parent-uri' ] = $item [ 'thr-parent' ];
}
2018-06-27 14:09:33 -04:00
if ( isset ( $item [ 'gravity' ])) {
2018-02-21 16:39:07 -05:00
$item [ 'gravity' ] = intval ( $item [ 'gravity' ]);
} elseif ( $item [ 'parent-uri' ] === $item [ 'uri' ]) {
2018-06-27 14:09:33 -04:00
$item [ 'gravity' ] = GRAVITY_PARENT ;
} elseif ( activity_match ( $item [ 'verb' ], ACTIVITY_POST )) {
$item [ 'gravity' ] = GRAVITY_COMMENT ;
2018-01-28 06:18:08 -05:00
} else {
2018-06-27 14:09:33 -04:00
$item [ 'gravity' ] = GRAVITY_UNKNOWN ; // Should not happen
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Unknown gravity for verb: ' . $item [ 'verb' ], Logger :: DEBUG );
2018-01-28 06:18:08 -05:00
}
2018-02-21 16:39:07 -05:00
$uid = intval ( $item [ 'uid' ]);
2018-01-28 06:18:08 -05:00
// check for create date and expire time
$expire_interval = Config :: get ( 'system' , 'dbclean-expire-days' , 0 );
2018-07-20 08:19:26 -04:00
$user = DBA :: selectFirst ( 'user' , [ 'expire' ], [ 'uid' => $uid ]);
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $user ) && ( $user [ 'expire' ] > 0 ) && (( $user [ 'expire' ] < $expire_interval ) || ( $expire_interval == 0 ))) {
2018-01-28 06:18:08 -05:00
$expire_interval = $user [ 'expire' ];
}
2018-02-21 16:39:07 -05:00
if (( $expire_interval > 0 ) && ! empty ( $item [ 'created' ])) {
2018-01-28 06:18:08 -05:00
$expire_date = time () - ( $expire_interval * 86400 );
2018-02-21 16:39:07 -05:00
$created_date = strtotime ( $item [ 'created' ]);
2018-01-28 06:18:08 -05:00
if ( $created_date < $expire_date ) {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'item-store: item created (' . date ( 'c' , $created_date ) . ') before expiration time (' . date ( 'c' , $expire_date ) . '). ignored. ' . print_r ( $item , true ), Logger :: DEBUG );
2018-01-28 06:18:08 -05:00
return 0 ;
}
}
/*
* Do we already have this item ?
* We have to check several networks since Friendica posts could be repeated
* via OStatus ( maybe Diasporsa as well )
*/
2018-09-14 12:51:32 -04:00
if ( in_array ( $item [ 'network' ], [ Protocol :: ACTIVITYPUB , Protocol :: DIASPORA , Protocol :: DFRN , Protocol :: OSTATUS , " " ])) {
2018-02-21 17:55:23 -05:00
$condition = [ " `uri` = ? AND `uid` = ? AND `network` IN (?, ?, ?) " ,
2018-02-21 16:39:07 -05:00
trim ( $item [ 'uri' ]), $item [ 'uid' ],
2018-08-11 16:40:44 -04:00
Protocol :: DIASPORA , Protocol :: DFRN , Protocol :: OSTATUS ];
2018-06-30 17:15:24 -04:00
$existing = self :: selectFirst ([ 'id' , 'network' ], $condition );
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $existing )) {
2018-01-28 06:18:08 -05:00
// We only log the entries with a different user id than 0. Otherwise we would have too many false positives
if ( $uid != 0 ) {
2018-10-29 17:20:46 -04:00
Logger :: log ( " Item with uri " . $item [ 'uri' ] . " already existed for user " . $uid . " with id " . $existing [ " id " ] . " target network " . $existing [ " network " ] . " - new network: " . $item [ 'network' ]);
2018-01-28 06:18:08 -05:00
}
2018-02-21 17:55:23 -05:00
return $existing [ " id " ];
2018-01-28 06:18:08 -05:00
}
}
2018-02-21 16:39:07 -05:00
$item [ 'wall' ] = intval ( defaults ( $item , 'wall' , 0 ));
$item [ 'extid' ] = trim ( defaults ( $item , 'extid' , '' ));
$item [ 'author-name' ] = trim ( defaults ( $item , 'author-name' , '' ));
$item [ 'author-link' ] = trim ( defaults ( $item , 'author-link' , '' ));
$item [ 'author-avatar' ] = trim ( defaults ( $item , 'author-avatar' , '' ));
$item [ 'owner-name' ] = trim ( defaults ( $item , 'owner-name' , '' ));
$item [ 'owner-link' ] = trim ( defaults ( $item , 'owner-link' , '' ));
$item [ 'owner-avatar' ] = trim ( defaults ( $item , 'owner-avatar' , '' ));
$item [ 'received' ] = (( x ( $item , 'received' ) !== false ) ? DateTimeFormat :: utc ( $item [ 'received' ]) : DateTimeFormat :: utcNow ());
$item [ 'created' ] = (( x ( $item , 'created' ) !== false ) ? DateTimeFormat :: utc ( $item [ 'created' ]) : $item [ 'received' ]);
$item [ 'edited' ] = (( x ( $item , 'edited' ) !== false ) ? DateTimeFormat :: utc ( $item [ 'edited' ]) : $item [ 'created' ]);
$item [ 'changed' ] = (( x ( $item , 'changed' ) !== false ) ? DateTimeFormat :: utc ( $item [ 'changed' ]) : $item [ 'created' ]);
$item [ 'commented' ] = (( x ( $item , 'commented' ) !== false ) ? DateTimeFormat :: utc ( $item [ 'commented' ]) : $item [ 'created' ]);
$item [ 'title' ] = trim ( defaults ( $item , 'title' , '' ));
$item [ 'location' ] = trim ( defaults ( $item , 'location' , '' ));
$item [ 'coord' ] = trim ( defaults ( $item , 'coord' , '' ));
$item [ 'visible' ] = (( x ( $item , 'visible' ) !== false ) ? intval ( $item [ 'visible' ]) : 1 );
$item [ 'deleted' ] = 0 ;
$item [ 'parent-uri' ] = trim ( defaults ( $item , 'parent-uri' , $item [ 'uri' ]));
2018-07-19 09:52:05 -04:00
$item [ 'post-type' ] = defaults ( $item , 'post-type' , self :: PT_ARTICLE );
2018-02-21 16:39:07 -05:00
$item [ 'verb' ] = trim ( defaults ( $item , 'verb' , '' ));
$item [ 'object-type' ] = trim ( defaults ( $item , 'object-type' , '' ));
$item [ 'object' ] = trim ( defaults ( $item , 'object' , '' ));
$item [ 'target-type' ] = trim ( defaults ( $item , 'target-type' , '' ));
$item [ 'target' ] = trim ( defaults ( $item , 'target' , '' ));
$item [ 'plink' ] = trim ( defaults ( $item , 'plink' , '' ));
$item [ 'allow_cid' ] = trim ( defaults ( $item , 'allow_cid' , '' ));
$item [ 'allow_gid' ] = trim ( defaults ( $item , 'allow_gid' , '' ));
$item [ 'deny_cid' ] = trim ( defaults ( $item , 'deny_cid' , '' ));
$item [ 'deny_gid' ] = trim ( defaults ( $item , 'deny_gid' , '' ));
$item [ 'private' ] = intval ( defaults ( $item , 'private' , 0 ));
$item [ 'body' ] = trim ( defaults ( $item , 'body' , '' ));
$item [ 'tag' ] = trim ( defaults ( $item , 'tag' , '' ));
$item [ 'attach' ] = trim ( defaults ( $item , 'attach' , '' ));
$item [ 'app' ] = trim ( defaults ( $item , 'app' , '' ));
$item [ 'origin' ] = intval ( defaults ( $item , 'origin' , 0 ));
$item [ 'postopts' ] = trim ( defaults ( $item , 'postopts' , '' ));
$item [ 'resource-id' ] = trim ( defaults ( $item , 'resource-id' , '' ));
$item [ 'event-id' ] = intval ( defaults ( $item , 'event-id' , 0 ));
$item [ 'inform' ] = trim ( defaults ( $item , 'inform' , '' ));
$item [ 'file' ] = trim ( defaults ( $item , 'file' , '' ));
2018-01-28 06:18:08 -05:00
// When there is no content then we don't post it
2018-02-21 16:39:07 -05:00
if ( $item [ 'body' ] . $item [ 'title' ] == '' ) {
2018-10-29 17:20:46 -04:00
Logger :: log ( 'No body, no title.' );
2018-01-28 06:18:08 -05:00
return 0 ;
}
2018-07-24 08:52:25 -04:00
self :: addLanguageToItemArray ( $item );
2018-01-28 06:18:08 -05:00
// Items cannot be stored before they happen ...
2018-02-21 16:39:07 -05:00
if ( $item [ 'created' ] > DateTimeFormat :: utcNow ()) {
$item [ 'created' ] = DateTimeFormat :: utcNow ();
2018-01-28 06:18:08 -05:00
}
// We haven't invented time travel by now.
2018-02-21 16:39:07 -05:00
if ( $item [ 'edited' ] > DateTimeFormat :: utcNow ()) {
$item [ 'edited' ] = DateTimeFormat :: utcNow ();
2018-01-28 06:18:08 -05:00
}
2018-02-21 16:39:07 -05:00
$item [ 'plink' ] = defaults ( $item , 'plink' , System :: baseUrl () . '/display/' . urlencode ( $item [ 'guid' ]));
2018-01-28 06:18:08 -05:00
2018-02-21 16:08:37 -05:00
// The contact-id should be set before "self::insert" was called - but there seems to be issues sometimes
2018-02-21 16:39:07 -05:00
$item [ " contact-id " ] = self :: contactId ( $item );
2018-01-28 06:18:08 -05:00
2018-05-08 16:20:15 -04:00
$default = [ 'url' => $item [ 'author-link' ], 'name' => $item [ 'author-name' ],
2018-05-09 02:53:57 -04:00
'photo' => $item [ 'author-avatar' ], 'network' => $item [ 'network' ]];
2018-05-08 16:20:15 -04:00
$item [ 'author-id' ] = defaults ( $item , 'author-id' , Contact :: getIdForURL ( $item [ " author-link " ], 0 , false , $default ));
2018-01-28 06:18:08 -05:00
2018-02-21 16:39:07 -05:00
if ( Contact :: isBlocked ( $item [ " author-id " ])) {
2018-10-29 17:20:46 -04:00
Logger :: log ( 'Contact ' . $item [ " author-id " ] . ' is blocked, item ' . $item [ " uri " ] . ' will not be stored' );
2018-01-28 06:18:08 -05:00
return 0 ;
}
2018-05-08 16:20:15 -04:00
$default = [ 'url' => $item [ 'owner-link' ], 'name' => $item [ 'owner-name' ],
2018-05-09 02:53:57 -04:00
'photo' => $item [ 'owner-avatar' ], 'network' => $item [ 'network' ]];
2018-05-08 16:20:15 -04:00
$item [ 'owner-id' ] = defaults ( $item , 'owner-id' , Contact :: getIdForURL ( $item [ " owner-link " ], 0 , false , $default ));
2018-01-28 06:18:08 -05:00
2018-02-21 16:39:07 -05:00
if ( Contact :: isBlocked ( $item [ " owner-id " ])) {
2018-10-29 17:20:46 -04:00
Logger :: log ( 'Contact ' . $item [ " owner-id " ] . ' is blocked, item ' . $item [ " uri " ] . ' will not be stored' );
2018-01-28 06:18:08 -05:00
return 0 ;
}
2018-08-11 16:40:44 -04:00
if ( $item [ 'network' ] == Protocol :: PHANTOM ) {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Missing network. Called by: ' . System :: callstack (), Logger :: DEBUG );
2018-02-22 04:51:59 -05:00
2018-08-11 16:40:44 -04:00
$item [ 'network' ] = Protocol :: DFRN ;
2018-10-30 09:58:45 -04:00
Logger :: log ( " Set network to " . $item [ " network " ] . " for " . $item [ " uri " ], Logger :: DEBUG );
2018-02-21 16:08:37 -05:00
}
// Checking if there is already an item with the same guid
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Checking for an item for user ' . $item [ 'uid' ] . ' on network ' . $item [ 'network' ] . ' with the guid ' . $item [ 'guid' ], Logger :: DEBUG );
2018-02-21 16:39:07 -05:00
$condition = [ 'guid' => $item [ 'guid' ], 'network' => $item [ 'network' ], 'uid' => $item [ 'uid' ]];
2018-06-27 15:37:13 -04:00
if ( self :: exists ( $condition )) {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'found item with guid ' . $item [ 'guid' ] . ' for user ' . $item [ 'uid' ] . ' on network ' . $item [ 'network' ], Logger :: DEBUG );
2018-02-21 16:08:37 -05:00
return 0 ;
2018-01-28 06:18:08 -05:00
}
// Check for hashtags in the body and repair or add hashtag links
2018-02-21 16:39:07 -05:00
self :: setHashtags ( $item );
2018-01-28 06:18:08 -05:00
2018-02-21 16:39:07 -05:00
$item [ 'thr-parent' ] = $item [ 'parent-uri' ];
2018-01-28 06:18:08 -05:00
2018-02-14 00:05:00 -05:00
$notify_type = '' ;
$allow_cid = '' ;
$allow_gid = '' ;
$deny_cid = '' ;
$deny_gid = '' ;
2018-02-21 16:08:37 -05:00
2018-02-21 16:39:07 -05:00
if ( $item [ 'parent-uri' ] === $item [ 'uri' ]) {
2018-01-28 06:18:08 -05:00
$parent_id = 0 ;
$parent_deleted = 0 ;
2018-02-21 16:39:07 -05:00
$allow_cid = $item [ 'allow_cid' ];
$allow_gid = $item [ 'allow_gid' ];
$deny_cid = $item [ 'deny_cid' ];
$deny_gid = $item [ 'deny_gid' ];
2018-01-28 06:18:08 -05:00
$notify_type = 'wall-new' ;
} else {
// find the parent and snarf the item id and ACLs
// and anything else we need to inherit
2018-02-21 16:08:37 -05:00
$fields = [ 'uri' , 'parent-uri' , 'id' , 'deleted' ,
'allow_cid' , 'allow_gid' , 'deny_cid' , 'deny_gid' ,
2018-05-15 00:33:28 -04:00
'wall' , 'private' , 'forum_mode' , 'origin' ];
2018-02-21 16:39:07 -05:00
$condition = [ 'uri' => $item [ 'parent-uri' ], 'uid' => $item [ 'uid' ]];
2018-02-21 16:08:37 -05:00
$params = [ 'order' => [ 'id' => false ]];
2018-06-30 17:15:24 -04:00
$parent = self :: selectFirst ( $fields , $condition , $params );
2018-01-28 06:18:08 -05:00
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $parent )) {
2018-01-28 06:18:08 -05:00
// is the new message multi-level threaded?
// even though we don't support it now, preserve the info
// and re-attach to the conversation parent.
2018-02-21 17:55:23 -05:00
if ( $parent [ 'uri' ] != $parent [ 'parent-uri' ]) {
$item [ 'parent-uri' ] = $parent [ 'parent-uri' ];
2018-02-21 16:08:37 -05:00
2018-02-21 16:39:07 -05:00
$condition = [ 'uri' => $item [ 'parent-uri' ],
'parent-uri' => $item [ 'parent-uri' ],
'uid' => $item [ 'uid' ]];
2018-02-21 16:08:37 -05:00
$params = [ 'order' => [ 'id' => false ]];
2018-06-30 17:15:24 -04:00
$toplevel_parent = self :: selectFirst ( $fields , $condition , $params );
2018-01-28 06:18:08 -05:00
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $toplevel_parent )) {
2018-02-21 17:55:23 -05:00
$parent = $toplevel_parent ;
2018-01-28 06:18:08 -05:00
}
}
2018-02-21 17:55:23 -05:00
$parent_id = $parent [ 'id' ];
$parent_deleted = $parent [ 'deleted' ];
$allow_cid = $parent [ 'allow_cid' ];
$allow_gid = $parent [ 'allow_gid' ];
$deny_cid = $parent [ 'deny_cid' ];
$deny_gid = $parent [ 'deny_gid' ];
$item [ 'wall' ] = $parent [ 'wall' ];
2018-01-28 06:18:08 -05:00
$notify_type = 'comment-new' ;
/*
* 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 .
*/
2018-02-21 17:55:23 -05:00
if ( $parent [ 'private' ]) {
$item [ 'private' ] = $parent [ 'private' ];
2018-01-28 06:18:08 -05:00
}
/*
* Edge case . We host a public forum that was originally posted to privately .
* The original author commented , but as this is a comment , the permissions
* weren ' t fixed up so it will still show the comment as private unless we fix it here .
*/
2018-02-21 17:55:23 -05:00
if (( intval ( $parent [ 'forum_mode' ]) == 1 ) && $parent [ 'private' ]) {
2018-02-21 16:39:07 -05:00
$item [ 'private' ] = 0 ;
2018-01-28 06:18:08 -05:00
}
// If its a post from myself then tag the thread as "mention"
2018-10-30 09:58:45 -04:00
Logger :: log ( " Checking if parent " . $parent_id . " has to be tagged as mention for user " . $item [ 'uid' ], Logger :: DEBUG );
2018-07-20 08:19:26 -04:00
$user = DBA :: selectFirst ( 'user' , [ 'nickname' ], [ 'uid' => $item [ 'uid' ]]);
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $user )) {
2018-02-21 18:10:29 -05:00
$self = normalise_link ( System :: baseUrl () . '/profile/' . $user [ 'nickname' ]);
2018-06-24 06:48:29 -04:00
$self_id = Contact :: getIdForURL ( $self , 0 , true );
2018-10-30 09:58:45 -04:00
Logger :: log ( " 'myself' is " . $self_id . " for parent " . $parent_id . " checking against " . $item [ 'author-id' ] . " and " . $item [ 'owner-id' ], Logger :: DEBUG );
2018-06-24 06:48:29 -04:00
if (( $item [ 'author-id' ] == $self_id ) || ( $item [ 'owner-id' ] == $self_id )) {
2018-07-20 08:19:26 -04:00
DBA :: update ( 'thread' , [ 'mention' => true ], [ 'iid' => $parent_id ]);
2018-10-30 09:58:45 -04:00
Logger :: log ( " tagged thread " . $parent_id . " as mention for user " . $self , Logger :: DEBUG );
2018-01-28 06:18:08 -05:00
}
}
} else {
/*
* Allow one to see reply tweets from status . net even when
* we don 't have or can' t see the original post .
*/
if ( $force_parent ) {
2018-10-29 17:20:46 -04:00
Logger :: log ( '$force_parent=true, reply converted to top-level post.' );
2018-01-28 06:18:08 -05:00
$parent_id = 0 ;
2018-02-21 16:39:07 -05:00
$item [ 'parent-uri' ] = $item [ 'uri' ];
2018-06-27 14:09:33 -04:00
$item [ 'gravity' ] = GRAVITY_PARENT ;
2018-01-28 06:18:08 -05:00
} else {
2018-10-29 17:20:46 -04:00
Logger :: log ( 'item parent ' . $item [ 'parent-uri' ] . ' for ' . $item [ 'uid' ] . ' was not found - ignoring item' );
2018-01-28 06:18:08 -05:00
return 0 ;
}
$parent_deleted = 0 ;
}
}
2018-08-05 07:09:59 -04:00
$item [ 'parent-uri-id' ] = ItemURI :: getIdByURI ( $item [ 'parent-uri' ]);
$item [ 'thr-parent-id' ] = ItemURI :: getIdByURI ( $item [ 'thr-parent' ]);
2018-02-21 16:08:37 -05:00
$condition = [ " `uri` = ? AND `network` IN (?, ?) AND `uid` = ? " ,
2018-08-11 16:40:44 -04:00
$item [ 'uri' ], $item [ 'network' ], Protocol :: DFRN , $item [ 'uid' ]];
2018-06-27 15:37:13 -04:00
if ( self :: exists ( $condition )) {
2018-10-29 17:20:46 -04:00
Logger :: log ( 'duplicated item with the same uri found. ' . print_r ( $item , true ));
2018-01-28 06:18:08 -05:00
return 0 ;
}
// On Friendica and Diaspora the GUID is unique
2018-08-11 16:40:44 -04:00
if ( in_array ( $item [ 'network' ], [ Protocol :: DFRN , Protocol :: DIASPORA ])) {
2018-02-21 16:39:07 -05:00
$condition = [ 'guid' => $item [ 'guid' ], 'uid' => $item [ 'uid' ]];
2018-06-27 15:37:13 -04:00
if ( self :: exists ( $condition )) {
2018-10-29 17:20:46 -04:00
Logger :: log ( 'duplicated item with the same guid found. ' . print_r ( $item , true ));
2018-01-28 06:18:08 -05:00
return 0 ;
}
} else {
// Check for an existing post with the same content. There seems to be a problem with OStatus.
2018-02-21 16:08:37 -05:00
$condition = [ " `body` = ? AND `network` = ? AND `created` = ? AND `contact-id` = ? AND `uid` = ? " ,
2018-02-21 16:39:07 -05:00
$item [ 'body' ], $item [ 'network' ], $item [ 'created' ], $item [ 'contact-id' ], $item [ 'uid' ]];
2018-06-27 15:37:13 -04:00
if ( self :: exists ( $condition )) {
2018-10-29 17:20:46 -04:00
Logger :: log ( 'duplicated item with the same body found. ' . print_r ( $item , true ));
2018-01-28 06:18:08 -05:00
return 0 ;
}
}
2018-06-03 05:40:32 -04: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
2018-08-15 00:41:49 -04:00
self :: update ([ 'global' => true ], [ 'uri' => $item [ " uri " ]]);
2018-06-03 05:40:32 -04:00
} else {
2018-06-27 15:37:13 -04:00
$item [ " global " ] = self :: exists ([ 'uid' => 0 , 'uri' => $item [ " uri " ]]);
2018-06-03 05:40:32 -04:00
}
2018-01-28 06:18:08 -05:00
// ACL settings
if ( strlen ( $allow_cid ) || strlen ( $allow_gid ) || strlen ( $deny_cid ) || strlen ( $deny_gid )) {
$private = 1 ;
} else {
2018-02-21 16:39:07 -05:00
$private = $item [ 'private' ];
2018-01-28 06:18:08 -05:00
}
2018-02-21 16:39:07 -05:00
$item [ " allow_cid " ] = $allow_cid ;
$item [ " allow_gid " ] = $allow_gid ;
$item [ " deny_cid " ] = $deny_cid ;
$item [ " deny_gid " ] = $deny_gid ;
$item [ " private " ] = $private ;
$item [ " deleted " ] = $parent_deleted ;
2018-01-28 06:18:08 -05:00
// Fill the cache field
2018-11-06 21:12:41 -05:00
self :: putInCache ( $item );
2018-01-28 06:18:08 -05:00
if ( $notify ) {
2018-07-10 08:27:56 -04:00
$item [ 'edit' ] = false ;
$item [ 'parent' ] = $parent_id ;
2018-02-21 16:39:07 -05:00
Addon :: callHooks ( 'post_local' , $item );
2018-07-10 08:27:56 -04:00
unset ( $item [ 'edit' ]);
unset ( $item [ 'parent' ]);
2018-01-28 06:18:08 -05:00
} else {
2018-02-21 16:39:07 -05:00
Addon :: callHooks ( 'post_remote' , $item );
2018-01-28 06:18:08 -05:00
}
// This array field is used to trigger some automatic reactions
// It is mainly used in the "post_local" hook.
2018-02-21 16:39:07 -05:00
unset ( $item [ 'api_source' ]);
2018-01-28 06:18:08 -05:00
2018-02-21 16:39:07 -05:00
if ( x ( $item , 'cancel' )) {
2018-10-29 17:20:46 -04:00
Logger :: log ( 'post cancelled by addon.' );
2018-01-28 06:18:08 -05:00
return 0 ;
}
/*
* Check for already added items .
* There is a timing issue here that sometimes creates double postings .
* An unique index would help - but the limitations of MySQL ( maximum size of index values ) prevent this .
*/
2018-02-21 16:39:07 -05:00
if ( $item [ " uid " ] == 0 ) {
2018-06-27 15:37:13 -04:00
if ( self :: exists ([ 'uri' => trim ( $item [ 'uri' ]), 'uid' => 0 ])) {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Global item already stored. URI: ' . $item [ 'uri' ] . ' on network ' . $item [ 'network' ], Logger :: DEBUG );
2018-01-28 06:18:08 -05:00
return 0 ;
}
}
2018-10-30 09:58:45 -04:00
Logger :: log ( '' . print_r ( $item , true ), Logger :: DATA );
2018-01-28 06:18:08 -05:00
2018-06-30 11:21:32 -04:00
if ( array_key_exists ( 'tag' , $item )) {
$tags = $item [ 'tag' ];
unset ( $item [ 'tag' ]);
} else {
$tags = '' ;
}
2018-06-30 17:15:24 -04:00
if ( array_key_exists ( 'file' , $item )) {
$files = $item [ 'file' ];
2018-07-01 03:57:59 -04:00
unset ( $item [ 'file' ]);
2018-06-30 17:15:24 -04:00
} else {
$files = '' ;
}
2018-07-25 19:14:55 -04:00
// Creates or assigns the permission set
2018-07-19 09:52:05 -04:00
$item [ 'psid' ] = PermissionSet :: fetchIDForPost ( $item );
2018-06-29 07:10:36 -04:00
// We are doing this outside of the transaction to avoid timing problems
2018-07-05 18:00:38 -04:00
if ( ! self :: insertActivity ( $item )) {
self :: insertContent ( $item );
}
2018-06-29 07:10:36 -04:00
2018-07-19 17:56:52 -04:00
$delivery_data = [ 'postopts' => defaults ( $item , 'postopts' , '' ),
'inform' => defaults ( $item , 'inform' , '' )];
unset ( $item [ 'postopts' ]);
unset ( $item [ 'inform' ]);
2018-08-29 16:46:52 -04:00
// These fields aren't stored anymore in the item table, they are fetched upon request
unset ( $item [ 'author-link' ]);
unset ( $item [ 'author-name' ]);
unset ( $item [ 'author-avatar' ]);
unset ( $item [ 'owner-link' ]);
unset ( $item [ 'owner-name' ]);
unset ( $item [ 'owner-avatar' ]);
2018-07-20 08:19:26 -04:00
DBA :: transaction ();
$ret = DBA :: insert ( 'item' , $item );
2018-01-28 06:18:08 -05:00
// When the item was successfully stored we fetch the ID of the item.
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $ret )) {
2018-07-20 08:19:26 -04:00
$current_post = DBA :: lastInsertId ();
2018-01-28 06:18:08 -05:00
} else {
// This can happen - for example - if there are locking timeouts.
2018-07-20 08:19:26 -04:00
DBA :: rollback ();
2018-01-28 06:18:08 -05:00
// Store the data into a spool file so that we can try again later.
// At first we restore the Diaspora signature that we removed above.
if ( isset ( $encoded_signature )) {
2018-02-21 16:39:07 -05:00
$item [ 'dsprsig' ] = $encoded_signature ;
2018-01-28 06:18:08 -05:00
}
// 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' ;
$spoolpath = get_spoolpath ();
if ( $spoolpath != " " ) {
$spool = $spoolpath . '/' . $file ;
2018-07-19 17:56:52 -04:00
// Ensure to have the removed data from above again in the item array
$item = array_merge ( $item , $delivery_data );
2018-02-21 16:39:07 -05:00
file_put_contents ( $spool , json_encode ( $item ));
2018-10-30 09:58:45 -04:00
Logger :: log ( " Item wasn't stored - Item was spooled into file " . $file , Logger :: DEBUG );
2018-01-28 06:18:08 -05:00
}
return 0 ;
}
if ( $current_post == 0 ) {
// This is one of these error messages that never should occur.
2018-10-29 17:20:46 -04:00
Logger :: log ( " couldn't find created item - we better quit now. " );
2018-07-20 08:19:26 -04:00
DBA :: rollback ();
2018-01-28 06:18:08 -05:00
return 0 ;
}
// How much entries have we created?
// We wouldn't need this query when we could use an unique index - but MySQL has length problems with them.
2018-07-20 08:19:26 -04:00
$entries = DBA :: count ( 'item' , [ 'uri' => $item [ 'uri' ], 'uid' => $item [ 'uid' ], 'network' => $item [ 'network' ]]);
2018-01-28 06:18:08 -05:00
2018-02-21 17:55:23 -05:00
if ( $entries > 1 ) {
2018-01-28 06:18:08 -05:00
// There are duplicates. We delete our just created entry.
2018-10-29 17:20:46 -04:00
Logger :: log ( 'Duplicated post occurred. uri = ' . $item [ 'uri' ] . ' uid = ' . $item [ 'uid' ]);
2018-01-28 06:18:08 -05:00
// Yes, we could do a rollback here - but we are having many users with MyISAM.
2018-07-20 08:19:26 -04:00
DBA :: delete ( 'item' , [ 'id' => $current_post ]);
DBA :: commit ();
2018-01-28 06:18:08 -05:00
return 0 ;
2018-02-21 17:55:23 -05:00
} elseif ( $entries == 0 ) {
2018-01-28 06:18:08 -05:00
// This really should never happen since we quit earlier if there were problems.
2018-10-29 17:20:46 -04:00
Logger :: log ( " Something is terribly wrong. We haven't found our created entry. " );
2018-07-20 08:19:26 -04:00
DBA :: rollback ();
2018-01-28 06:18:08 -05:00
return 0 ;
}
2018-10-29 17:20:46 -04:00
Logger :: log ( 'created item ' . $current_post );
2018-02-21 16:39:07 -05:00
self :: updateContact ( $item );
2018-01-28 06:18:08 -05:00
2018-02-21 16:39:07 -05:00
if ( ! $parent_id || ( $item [ 'parent-uri' ] === $item [ 'uri' ])) {
2018-01-28 06:18:08 -05:00
$parent_id = $current_post ;
}
// Set parent id
2018-08-15 00:41:49 -04:00
self :: update ([ 'parent' => $parent_id ], [ 'id' => $current_post ]);
2018-01-28 06:18:08 -05:00
2018-02-21 16:39:07 -05:00
$item [ 'id' ] = $current_post ;
$item [ 'parent' ] = $parent_id ;
2018-01-28 06:18:08 -05:00
// update the commented timestamp on the parent
// Only update "commented" if it is really a comment
2018-06-29 02:20:04 -04:00
if (( $item [ 'gravity' ] != GRAVITY_ACTIVITY ) || ! Config :: get ( " system " , " like_no_comment " )) {
2018-08-15 00:41:49 -04:00
self :: update ([ 'commented' => DateTimeFormat :: utcNow (), 'changed' => DateTimeFormat :: utcNow ()], [ 'id' => $parent_id ]);
2018-01-28 06:18:08 -05:00
} else {
2018-08-15 00:41:49 -04:00
self :: update ([ 'changed' => DateTimeFormat :: utcNow ()], [ 'id' => $parent_id ]);
2018-01-28 06:18:08 -05:00
}
if ( $dsprsig ) {
/*
* Friendica servers lower than 3.4 . 3 - 2 had double encoded the signature ...
* We can check for this condition when we decode and encode the stuff again .
*/
if ( base64_encode ( base64_decode ( base64_decode ( $dsprsig -> signature ))) == base64_decode ( $dsprsig -> signature )) {
$dsprsig -> signature = base64_decode ( $dsprsig -> signature );
2018-10-30 09:58:45 -04:00
Logger :: log ( " Repaired double encoded signature from handle " . $dsprsig -> signer , Logger :: DEBUG );
2018-01-28 06:18:08 -05:00
}
2018-10-15 17:42:55 -04:00
if ( ! empty ( $dsprsig -> signed_text ) && empty ( $dsprsig -> signature ) && empty ( $dsprsig -> signer )) {
DBA :: insert ( 'diaspora-interaction' , [ 'uri-id' => $item [ 'uri-id' ], 'interaction' => $dsprsig -> signed_text ], true );
} else {
// The other fields are used by very old Friendica servers, so we currently store them differently
DBA :: insert ( 'sign' , [ 'iid' => $current_post , 'signed_text' => $dsprsig -> signed_text ,
'signature' => $dsprsig -> signature , 'signer' => $dsprsig -> signer ]);
}
2018-01-28 06:18:08 -05:00
}
2018-05-15 00:33:28 -04:00
if ( ! empty ( $diaspora_signed_text )) {
2018-10-15 17:42:55 -04:00
DBA :: insert ( 'diaspora-interaction' , [ 'uri-id' => $item [ 'uri-id' ], 'interaction' => $diaspora_signed_text ], true );
2018-05-15 00:33:28 -04:00
}
2018-02-21 16:39:07 -05:00
$deleted = self :: tagDeliver ( $item [ 'uid' ], $current_post );
2018-01-28 06:18:08 -05:00
/*
* current post can be deleted if is for a community page and no mention are
* in it .
*/
if ( ! $deleted && ! $dontcache ) {
2018-06-30 17:15:24 -04:00
$posted_item = self :: selectFirst ( self :: ITEM_FIELDLIST , [ 'id' => $current_post ]);
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $posted_item )) {
2018-01-28 06:18:08 -05:00
if ( $notify ) {
2018-02-21 17:55:23 -05:00
Addon :: callHooks ( 'post_local_end' , $posted_item );
2018-01-28 06:18:08 -05:00
} else {
2018-02-21 17:55:23 -05:00
Addon :: callHooks ( 'post_remote_end' , $posted_item );
2018-01-28 06:18:08 -05:00
}
} else {
2018-10-29 17:20:46 -04:00
Logger :: log ( 'new item not found in DB, id ' . $current_post );
2018-01-28 06:18:08 -05:00
}
}
2018-02-21 16:39:07 -05:00
if ( $item [ 'parent-uri' ] === $item [ 'uri' ]) {
2018-02-05 07:47:06 -05:00
self :: addThread ( $current_post );
2018-01-28 06:18:08 -05:00
} else {
2018-02-05 07:47:06 -05:00
self :: updateThread ( $parent_id );
2018-01-28 06:18:08 -05:00
}
2018-07-19 17:56:52 -04:00
$delivery_data [ 'iid' ] = $current_post ;
self :: insertDeliveryData ( $delivery_data );
2018-07-20 08:19:26 -04:00
DBA :: commit ();
2018-01-28 06:18:08 -05:00
/*
* Due to deadlock issues with the " term " table we are doing these steps after the commit .
* This is not perfect - but a workable solution until we found the reason for the problem .
*/
2018-06-30 11:21:32 -04:00
if ( ! empty ( $tags )) {
Term :: insertFromTagFieldByItemId ( $current_post , $tags );
2018-06-30 09:54:01 -04:00
}
2018-06-30 17:15:24 -04:00
if ( ! empty ( $files )) {
Term :: insertFromFileFieldByItemId ( $current_post , $files );
2018-06-30 09:54:01 -04:00
}
2018-01-28 06:18:08 -05:00
2018-02-21 16:39:07 -05:00
if ( $item [ 'parent-uri' ] === $item [ 'uri' ]) {
2018-01-28 06:18:08 -05:00
self :: addShadow ( $current_post );
} else {
self :: addShadowPost ( $current_post );
}
check_user_notification ( $current_post );
if ( $notify ) {
2018-10-27 07:09:23 -04:00
Worker :: add ([ 'priority' => $priority , 'dont_fork' => true ], 'Notifier' , $notify_type , $current_post );
} elseif ( $item [ 'visible' ] && (( ! empty ( $parent ) && $parent [ 'origin' ]) || $item [ 'origin' ])) {
if ( $item [ 'gravity' ] == GRAVITY_ACTIVITY ) {
$cmd = $item [ 'origin' ] ? 'activity-new' : 'activity-import' ;
} elseif ( $item [ 'gravity' ] == GRAVITY_COMMENT ) {
$cmd = $item [ 'origin' ] ? 'comment-new' : 'comment-import' ;
} else {
$cmd = 'wall-new' ;
}
Worker :: add ([ 'priority' => PRIORITY_HIGH , 'dont_fork' => true ], 'Notifier' , $cmd , $current_post );
2018-01-28 06:18:08 -05:00
}
return $current_post ;
}
2018-07-19 17:56:52 -04:00
/**
* @ brief Insert a new item delivery data entry
*
* @ param array $item The item fields that are to be inserted
*/
private static function insertDeliveryData ( $delivery_data )
{
if ( empty ( $delivery_data [ 'iid' ]) || ( empty ( $delivery_data [ 'postopts' ]) && empty ( $delivery_data [ 'inform' ]))) {
return ;
}
2018-07-20 08:19:26 -04:00
DBA :: insert ( 'item-delivery-data' , $delivery_data );
2018-07-19 17:56:52 -04:00
}
/**
* @ brief Update an existing item delivery data entry
*
* @ param integer $id The item id that is to be updated
* @ param array $item The item fields that are to be inserted
*/
private static function updateDeliveryData ( $id , $delivery_data )
{
if ( empty ( $id ) || ( empty ( $delivery_data [ 'postopts' ]) && empty ( $delivery_data [ 'inform' ]))) {
return ;
}
2018-07-20 08:19:26 -04:00
DBA :: update ( 'item-delivery-data' , $delivery_data , [ 'iid' => $id ], true );
2018-07-19 17:56:52 -04:00
}
2018-07-05 18:00:38 -04:00
/**
* @ brief Insert a new item content entry
*
* @ param array $item The item fields that are to be inserted
*/
private static function insertActivity ( & $item )
{
$activity_index = self :: activityToIndex ( $item [ 'verb' ]);
if ( $activity_index < 0 ) {
return false ;
}
2018-10-18 17:35:48 -04:00
$fields = [ 'activity' => $activity_index , 'uri-hash' => ( string ) $item [ 'uri-id' ], 'uri-id' => $item [ 'uri-id' ]];
2018-07-05 18:00:38 -04:00
// We just remove everything that is content
foreach ( array_merge ( self :: CONTENT_FIELDLIST , self :: MIXED_CONTENT_FIELDLIST ) as $field ) {
unset ( $item [ $field ]);
}
// To avoid timing problems, we are using locks.
2018-07-08 04:32:50 -04:00
$locked = Lock :: acquire ( 'item_insert_activity' );
2018-07-05 18:00:38 -04:00
if ( ! $locked ) {
2018-10-29 17:20:46 -04:00
Logger :: log ( " Couldn't acquire lock for URI " . $item [ 'uri' ] . " - proceeding anyway. " );
2018-07-05 18:00:38 -04:00
}
// Do we already have this content?
2018-10-18 17:35:48 -04:00
$item_activity = DBA :: selectFirst ( 'item-activity' , [ 'id' ], [ 'uri-id' => $item [ 'uri-id' ]]);
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $item_activity )) {
2018-07-05 18:00:38 -04:00
$item [ 'iaid' ] = $item_activity [ 'id' ];
2018-10-29 17:20:46 -04:00
Logger :: log ( 'Fetched activity for URI ' . $item [ 'uri' ] . ' (' . $item [ 'iaid' ] . ')' );
2018-07-20 08:19:26 -04:00
} elseif ( DBA :: insert ( 'item-activity' , $fields )) {
$item [ 'iaid' ] = DBA :: lastInsertId ();
2018-10-29 17:20:46 -04:00
Logger :: log ( 'Inserted activity for URI ' . $item [ 'uri' ] . ' (' . $item [ 'iaid' ] . ')' );
2018-07-05 18:00:38 -04:00
} else {
2018-07-15 14:36:20 -04:00
// This shouldn't happen.
2018-10-29 17:20:46 -04:00
Logger :: log ( 'Could not insert activity for URI ' . $item [ 'uri' ] . ' - should not happen' );
2018-09-06 02:11:18 -04:00
Lock :: release ( 'item_insert_activity' );
2018-07-05 18:00:38 -04:00
return false ;
}
if ( $locked ) {
2018-07-08 04:32:50 -04:00
Lock :: release ( 'item_insert_activity' );
2018-07-05 18:00:38 -04:00
}
return true ;
}
2018-06-24 17:41:49 -04:00
/**
* @ brief Insert a new item content entry
*
* @ param array $item The item fields that are to be inserted
*/
private static function insertContent ( & $item )
{
2018-10-18 17:35:48 -04:00
$fields = [ 'uri-plink-hash' => ( string ) $item [ 'uri-id' ], 'uri-id' => $item [ 'uri-id' ]];
2018-06-24 17:41:49 -04:00
2018-06-30 01:18:43 -04:00
foreach ( array_merge ( self :: CONTENT_FIELDLIST , self :: MIXED_CONTENT_FIELDLIST ) as $field ) {
2018-06-24 19:09:13 -04:00
if ( isset ( $item [ $field ])) {
$fields [ $field ] = $item [ $field ];
unset ( $item [ $field ]);
}
}
2018-06-29 02:20:04 -04:00
// To avoid timing problems, we are using locks.
2018-07-05 14:57:31 -04:00
$locked = Lock :: acquire ( 'item_insert_content' );
2018-06-29 02:20:04 -04:00
if ( ! $locked ) {
2018-10-29 17:20:46 -04:00
Logger :: log ( " Couldn't acquire lock for URI " . $item [ 'uri' ] . " - proceeding anyway. " );
2018-06-25 12:11:27 -04:00
}
2018-06-29 02:20:04 -04:00
// Do we already have this content?
2018-10-18 17:35:48 -04:00
$item_content = DBA :: selectFirst ( 'item-content' , [ 'id' ], [ 'uri-id' => $item [ 'uri-id' ]]);
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $item_content )) {
2018-06-25 00:56:32 -04:00
$item [ 'icid' ] = $item_content [ 'id' ];
2018-10-29 17:20:46 -04:00
Logger :: log ( 'Fetched content for URI ' . $item [ 'uri' ] . ' (' . $item [ 'icid' ] . ')' );
2018-07-20 08:19:26 -04:00
} elseif ( DBA :: insert ( 'item-content' , $fields )) {
$item [ 'icid' ] = DBA :: lastInsertId ();
2018-10-29 17:20:46 -04:00
Logger :: log ( 'Inserted content for URI ' . $item [ 'uri' ] . ' (' . $item [ 'icid' ] . ')' );
2018-06-29 02:20:04 -04:00
} else {
2018-07-15 14:36:20 -04:00
// This shouldn't happen.
2018-10-29 17:20:46 -04:00
Logger :: log ( 'Could not insert content for URI ' . $item [ 'uri' ] . ' - should not happen' );
2018-06-29 02:20:04 -04:00
}
if ( $locked ) {
2018-07-05 14:57:31 -04:00
Lock :: release ( 'item_insert_content' );
2018-06-29 02:20:04 -04:00
}
}
2018-07-05 18:00:38 -04:00
/**
* @ brief Update existing item content entries
*
* @ param array $item The item fields that are to be changed
* @ param array $condition The condition for finding the item content entries
*/
private static function updateActivity ( $item , $condition )
{
if ( empty ( $item [ 'verb' ])) {
return false ;
}
$activity_index = self :: activityToIndex ( $item [ 'verb' ]);
2018-07-06 01:16:40 -04:00
if ( $activity_index < 0 ) {
2018-07-05 18:00:38 -04:00
return false ;
}
2018-07-15 14:36:20 -04:00
$fields = [ 'activity' => $activity_index ];
2018-07-05 18:00:38 -04:00
2018-10-29 17:20:46 -04:00
Logger :: log ( 'Update activity for ' . json_encode ( $condition ));
2018-07-05 18:00:38 -04:00
2018-07-20 08:19:26 -04:00
DBA :: update ( 'item-activity' , $fields , $condition , true );
2018-07-05 18:00:38 -04:00
return true ;
}
2018-06-24 17:41:49 -04:00
/**
* @ brief Update existing item content entries
*
* @ param array $item The item fields that are to be changed
* @ param array $condition The condition for finding the item content entries
*/
private static function updateContent ( $item , $condition )
{
// We have to select only the fields from the "item-content" table
$fields = [];
2018-06-30 01:18:43 -04:00
foreach ( array_merge ( self :: CONTENT_FIELDLIST , self :: MIXED_CONTENT_FIELDLIST ) as $field ) {
2018-06-24 17:41:49 -04:00
if ( isset ( $item [ $field ])) {
$fields [ $field ] = $item [ $field ];
}
}
if ( empty ( $fields )) {
2018-07-06 18:08:41 -04:00
// when there are no fields at all, just use the condition
// This is to ensure that we always store content.
$fields = $condition ;
2018-06-24 17:41:49 -04:00
}
2018-10-29 17:20:46 -04:00
Logger :: log ( 'Update content for ' . json_encode ( $condition ));
2018-06-24 17:41:49 -04:00
2018-07-20 08:19:26 -04:00
DBA :: update ( 'item-content' , $fields , $condition , true );
2018-06-24 17:41:49 -04:00
}
2018-04-24 09:21:25 -04:00
/**
* @ brief Distributes public items to the receivers
*
2018-05-15 00:33:28 -04:00
* @ param integer $itemid Item ID that should be added
* @ param string $signed_text Original text ( for Diaspora signatures ), JSON encoded .
2018-04-24 09:21:25 -04:00
*/
2018-05-15 00:33:28 -04:00
public static function distribute ( $itemid , $signed_text = '' )
2018-04-24 09:21:25 -04:00
{
$condition = [ " `id` IN (SELECT `parent` FROM `item` WHERE `id` = ?) " , $itemid ];
2018-06-30 17:15:24 -04:00
$parent = self :: selectFirst ([ 'owner-id' ], $condition );
2018-07-21 08:46:04 -04:00
if ( ! DBA :: isResult ( $parent )) {
2018-04-24 09:21:25 -04:00
return ;
}
// Only distribute public items from native networks
$condition = [ 'id' => $itemid , 'uid' => 0 ,
2018-09-14 12:51:32 -04:00
'network' => [ Protocol :: ACTIVITYPUB , Protocol :: DFRN , Protocol :: DIASPORA , Protocol :: OSTATUS , " " ],
2018-04-24 09:21:25 -04:00
'visible' => true , 'deleted' => false , 'moderated' => false , 'private' => false ];
2018-06-25 14:49:36 -04:00
$item = self :: selectFirst ( self :: ITEM_FIELDLIST , [ 'id' => $itemid ]);
2018-07-21 08:46:04 -04:00
if ( ! DBA :: isResult ( $item )) {
2018-04-24 09:21:25 -04:00
return ;
}
2018-07-10 08:27:56 -04:00
$origin = $item [ 'origin' ];
2018-04-24 09:21:25 -04:00
unset ( $item [ 'id' ]);
2018-04-26 16:11:29 -04:00
unset ( $item [ 'parent' ]);
unset ( $item [ 'mention' ]);
unset ( $item [ 'wall' ]);
unset ( $item [ 'origin' ]);
unset ( $item [ 'starred' ]);
2018-04-24 09:21:25 -04:00
2018-04-26 15:47:12 -04:00
$users = [];
2018-09-16 05:06:09 -04:00
/// @todo add a field "pcid" in the contact table that referrs to the public contact id.
2018-09-15 14:54:45 -04: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' ])) {
$condition = [ 'url' => $owner [ 'alias' ], '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 );
}
2018-05-15 00:33:28 -04:00
$origin_uid = 0 ;
2018-04-26 15:47:12 -04:00
if ( $item [ 'uri' ] != $item [ 'parent-uri' ]) {
2018-06-30 17:15:24 -04:00
$parents = self :: select ([ 'uid' , 'origin' ], [ " `uri` = ? AND `uid` != 0 " , $item [ 'parent-uri' ]]);
2018-08-15 00:41:49 -04:00
while ( $parent = self :: fetch ( $parents )) {
2018-04-26 15:47:12 -04:00
$users [ $parent [ 'uid' ]] = $parent [ 'uid' ];
2018-07-10 08:27:56 -04:00
if ( $parent [ 'origin' ] && ! $origin ) {
2018-05-15 00:33:28 -04:00
$origin_uid = $parent [ 'uid' ];
}
2018-04-26 15:47:12 -04:00
}
}
foreach ( $users as $uid ) {
2018-05-15 00:33:28 -04:00
if ( $origin_uid == $uid ) {
$item [ 'diaspora_signed_text' ] = $signed_text ;
}
2018-04-26 15:47:12 -04:00
self :: storeForUser ( $itemid , $item , $uid );
2018-04-24 09:21:25 -04:00
}
}
/**
* @ brief Store public items for the receivers
*
2018-04-24 10:58:39 -04:00
* @ param integer $itemid Item ID that should be added
* @ param array $item The item entry that will be stored
* @ param integer $uid The user that will receive the item entry
2018-04-24 09:21:25 -04:00
*/
2018-04-24 10:58:39 -04:00
private static function storeForUser ( $itemid , $item , $uid )
2018-04-24 09:21:25 -04:00
{
2018-04-24 10:58:39 -04:00
$item [ 'uid' ] = $uid ;
2018-04-24 09:21:25 -04:00
$item [ 'origin' ] = 0 ;
$item [ 'wall' ] = 0 ;
if ( $item [ 'uri' ] == $item [ 'parent-uri' ]) {
2018-04-24 10:58:39 -04:00
$item [ 'contact-id' ] = Contact :: getIdForURL ( $item [ 'owner-link' ], $uid );
2018-04-24 09:21:25 -04:00
} else {
2018-04-24 10:58:39 -04:00
$item [ 'contact-id' ] = Contact :: getIdForURL ( $item [ 'author-link' ], $uid );
2018-04-24 09:21:25 -04:00
}
if ( empty ( $item [ 'contact-id' ])) {
2018-07-20 08:19:26 -04:00
$self = DBA :: selectFirst ( 'contact' , [ 'id' ], [ 'self' => true , 'uid' => $uid ]);
2018-07-21 08:46:04 -04:00
if ( ! DBA :: isResult ( $self )) {
2018-04-24 09:21:25 -04:00
return ;
}
$item [ 'contact-id' ] = $self [ 'id' ];
}
/// @todo Handling of "event-id"
2018-05-04 17:12:13 -04:00
$notify = false ;
if ( $item [ 'uri' ] == $item [ 'parent-uri' ]) {
2018-07-20 08:19:26 -04:00
$contact = DBA :: selectFirst ( 'contact' , [], [ 'id' => $item [ 'contact-id' ], 'self' => false ]);
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $contact )) {
2018-05-04 17:12:13 -04:00
$notify = self :: isRemoteSelf ( $contact , $item );
}
}
$distributed = self :: insert ( $item , false , $notify , true );
2018-04-24 09:21:25 -04:00
if ( ! $distributed ) {
2018-10-30 09:58:45 -04:00
Logger :: log ( " Distributed public item " . $itemid . " for user " . $uid . " wasn't stored " , Logger :: DEBUG );
2018-04-24 09:21:25 -04:00
} else {
2018-10-30 09:58:45 -04:00
Logger :: log ( " Distributed public item " . $itemid . " for user " . $uid . " with id " . $distributed , Logger :: DEBUG );
2018-04-24 09:21:25 -04:00
}
}
2018-01-16 17:23:19 -05:00
/**
* @ brief Add a shadow entry for a given item id that is a thread starter
*
* 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
*/
2018-01-16 17:46:20 -05:00
public static function addShadow ( $itemid )
{
2018-07-01 03:57:59 -04:00
$fields = [ 'uid' , 'private' , 'moderated' , 'visible' , 'deleted' , 'network' , 'uri' ];
2018-04-23 07:14:25 -04:00
$condition = [ 'id' => $itemid , 'parent' => [ 0 , $itemid ]];
2018-06-30 17:15:24 -04:00
$item = self :: selectFirst ( $fields , $condition );
2018-01-16 17:23:19 -05:00
2018-07-21 08:46:04 -04:00
if ( ! DBA :: isResult ( $item )) {
2018-01-16 17:23:19 -05:00
return ;
}
// is it already a copy?
if (( $itemid == 0 ) || ( $item [ 'uid' ] == 0 )) {
return ;
}
// Is it a visible public post?
if ( ! $item [ " visible " ] || $item [ " deleted " ] || $item [ " moderated " ] || $item [ " private " ]) {
return ;
}
// is it an entry from a connector? Only add an entry for natively connected networks
2018-09-14 12:51:32 -04:00
if ( ! in_array ( $item [ " network " ], [ Protocol :: ACTIVITYPUB , Protocol :: DFRN , Protocol :: DIASPORA , Protocol :: OSTATUS , " " ])) {
2018-01-16 17:23:19 -05:00
return ;
}
2018-07-01 03:57:59 -04:00
if ( self :: exists ([ 'uri' => $item [ 'uri' ], 'uid' => 0 ])) {
return ;
}
2018-06-25 14:49:36 -04:00
$item = self :: selectFirst ( self :: ITEM_FIELDLIST , [ 'id' => $itemid ]);
2018-06-25 12:11:27 -04:00
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $item )) {
2018-07-01 03:57:59 -04: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 17:56:52 -04:00
unset ( $item [ 'postopts' ]);
unset ( $item [ 'inform' ]);
2018-07-01 03:57:59 -04:00
if ( $item [ 'uri' ] == $item [ 'parent-uri' ]) {
$item [ 'contact-id' ] = $item [ 'owner-id' ];
} else {
$item [ 'contact-id' ] = $item [ 'author-id' ];
}
2018-01-16 17:23:19 -05:00
2018-07-01 03:57:59 -04:00
$public_shadow = self :: insert ( $item , false , false , true );
2018-01-16 17:23:19 -05:00
2018-10-30 09:58:45 -04:00
Logger :: log ( " Stored public shadow for thread " . $itemid . " under id " . $public_shadow , Logger :: DEBUG );
2018-01-16 17:23:19 -05:00
}
}
/**
* @ brief Add a shadow entry for a given item id that is a comment
*
* This function does the same like the function above - but for comments
*
* @ param integer $itemid Item ID that should be added
*/
2018-01-28 09:06:02 -05:00
public static function addShadowPost ( $itemid )
2018-01-16 17:46:20 -05:00
{
2018-06-25 16:38:32 -04:00
$item = self :: selectFirst ( self :: ITEM_FIELDLIST , [ 'id' => $itemid ]);
2018-07-21 08:46:04 -04:00
if ( ! DBA :: isResult ( $item )) {
2018-01-16 17:23:19 -05:00
return ;
}
// Is it a toplevel post?
if ( $item [ 'id' ] == $item [ 'parent' ]) {
self :: addShadow ( $itemid );
return ;
}
// Is this a shadow entry?
2018-05-10 08:48:27 -04:00
if ( $item [ 'uid' ] == 0 ) {
2018-01-16 17:23:19 -05:00
return ;
2018-05-10 08:48:27 -04:00
}
2018-01-16 17:23:19 -05:00
// Is there a shadow parent?
2018-06-27 15:37:13 -04:00
if ( ! self :: exists ([ 'uri' => $item [ 'parent-uri' ], 'uid' => 0 ])) {
2018-01-16 17:23:19 -05:00
return ;
}
// Is there already a shadow entry?
2018-06-27 15:37:13 -04:00
if ( self :: exists ([ 'uri' => $item [ 'uri' ], 'uid' => 0 ])) {
2018-01-16 17:23:19 -05:00
return ;
}
2018-04-27 00:11:33 -04:00
// Save "origin" and "parent" state
$origin = $item [ 'origin' ];
$parent = $item [ 'parent' ];
2018-01-16 17:23:19 -05:00
// Preparing public shadow (removing user specific data)
$item [ 'uid' ] = 0 ;
2018-04-27 00:11:33 -04:00
unset ( $item [ 'id' ]);
unset ( $item [ 'parent' ]);
unset ( $item [ 'wall' ]);
unset ( $item [ 'mention' ]);
unset ( $item [ 'origin' ]);
unset ( $item [ 'starred' ]);
2018-07-19 17:56:52 -04:00
unset ( $item [ 'postopts' ]);
unset ( $item [ 'inform' ]);
2018-03-01 19:53:47 -05:00
$item [ 'contact-id' ] = Contact :: getIdForURL ( $item [ 'author-link' ]);
2018-01-16 17:23:19 -05:00
2018-01-28 06:18:08 -05:00
$public_shadow = self :: insert ( $item , false , false , true );
2018-01-16 17:23:19 -05:00
2018-10-30 09:58:45 -04:00
Logger :: log ( " Stored public shadow for comment " . $item [ 'uri' ] . " under id " . $public_shadow , Logger :: DEBUG );
2018-04-27 00:11:33 -04: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.
2018-08-11 16:40:44 -04:00
if ( $origin && self :: exists ([ 'id' => $parent , 'network' => Protocol :: DIASPORA ])) {
2018-05-10 08:48:27 -04:00
self :: distribute ( $public_shadow );
2018-04-27 00:11:33 -04:00
}
2018-01-16 17:23:19 -05:00
}
2018-01-20 18:52:54 -05:00
/**
2018-06-30 01:18:43 -04:00
* Adds a language specification in a " language " element of given $arr .
2018-01-20 18:52:54 -05:00
* Expects " body " element to exist in $arr .
*/
2018-06-30 01:18:43 -04:00
private static function addLanguageToItemArray ( & $item )
2018-01-20 18:52:54 -05:00
{
2018-07-19 22:15:21 -04:00
$naked_body = BBCode :: toPlaintext ( $item [ 'body' ], false );
2018-01-20 18:52:54 -05:00
2018-06-30 01:18:43 -04:00
$ld = new Text_LanguageDetect ();
$ld -> setNameMode ( 2 );
$languages = $ld -> detect ( $naked_body , 3 );
2018-01-20 18:52:54 -05:00
2018-06-30 01:18:43 -04:00
if ( is_array ( $languages )) {
$item [ 'language' ] = json_encode ( $languages );
2018-01-20 18:52:54 -05:00
}
}
/**
* @ brief Creates an unique guid out of a given uri
*
* @ param string $uri uri of an item entry
2018-02-20 23:13:13 -05:00
* @ param string $host hostname for the GUID prefix
2018-01-20 18:52:54 -05:00
* @ return string unique guid
*/
2018-02-20 23:13:13 -05:00
public static function guidFromUri ( $uri , $host )
2018-01-20 18:52:54 -05: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 02:44:19 -04:00
/**
* generate an unique URI
*
* @ param integer $uid User id
* @ param string $guid An existing GUID ( Otherwise it will be generated )
*
* @ return string
*/
public static function newURI ( $uid , $guid = " " )
{
if ( $guid == " " ) {
2018-09-27 07:52:15 -04:00
$guid = System :: createUUID ();
2018-06-16 02:44:19 -04:00
}
2018-10-09 13:58:58 -04:00
return self :: getApp () -> getBaseURL () . '/objects/' . $guid ;
2018-06-16 02:44:19 -04:00
}
2018-01-20 18:52:54 -05:00
/**
* @ brief Set " success_update " and " last-item " to the date of the last time we heard from this contact
*
* 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
*/
2018-01-28 11:28:59 -05:00
private static function updateContact ( $arr )
{
2018-01-20 18:52:54 -05:00
// Unarchive the author
2018-07-20 08:19:26 -04:00
$contact = DBA :: selectFirst ( 'contact' , [], [ 'id' => $arr [ " author-id " ]]);
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $contact )) {
2018-02-14 16:18:16 -05:00
Contact :: unmarkForArchival ( $contact );
2018-01-20 18:52:54 -05:00
}
2018-02-14 16:18:16 -05:00
// Unarchive the contact if it's not our own contact
2018-07-20 08:19:26 -04:00
$contact = DBA :: selectFirst ( 'contact' , [], [ 'id' => $arr [ " contact-id " ], 'self' => false ]);
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $contact )) {
2018-02-14 16:18:16 -05:00
Contact :: unmarkForArchival ( $contact );
2018-01-20 18:52:54 -05:00
}
2018-07-01 04:03:57 -04:00
$update = ( ! $arr [ 'private' ] && (( defaults ( $arr , 'author-link' , '' ) === defaults ( $arr , 'owner-link' , '' )) || ( $arr [ " parent-uri " ] === $arr [ " uri " ])));
2018-01-20 18:52:54 -05:00
// Is it a forum? Then we don't care about the rules from above
2018-08-11 16:40:44 -04:00
if ( ! $update && ( $arr [ " network " ] == Protocol :: DFRN ) && ( $arr [ " parent-uri " ] === $arr [ " uri " ])) {
2018-07-20 08:19:26 -04:00
if ( DBA :: exists ( 'contact' , [ 'id' => $arr [ 'contact-id' ], 'forum' => true ])) {
2018-01-20 18:52:54 -05:00
$update = true ;
}
}
if ( $update ) {
2018-07-20 08:19:26 -04:00
DBA :: update ( 'contact' , [ 'success_update' => $arr [ 'received' ], 'last-item' => $arr [ 'received' ]],
2018-01-20 18:52:54 -05:00
[ 'id' => $arr [ 'contact-id' ]]);
}
// Now do the same for the system wide contacts with uid=0
if ( ! $arr [ 'private' ]) {
2018-07-20 08:19:26 -04:00
DBA :: update ( 'contact' , [ 'success_update' => $arr [ 'received' ], 'last-item' => $arr [ 'received' ]],
2018-01-20 18:52:54 -05:00
[ 'id' => $arr [ 'owner-id' ]]);
if ( $arr [ 'owner-id' ] != $arr [ 'author-id' ]) {
2018-07-20 08:19:26 -04:00
DBA :: update ( 'contact' , [ 'success_update' => $arr [ 'received' ], 'last-item' => $arr [ 'received' ]],
2018-01-20 18:52:54 -05:00
[ 'id' => $arr [ 'author-id' ]]);
}
}
}
2018-01-28 06:18:08 -05:00
2018-05-19 10:55:27 -04:00
public static function setHashtags ( & $item )
2018-01-28 11:28:59 -05:00
{
2018-01-28 06:18:08 -05:00
$tags = get_tags ( $item [ " body " ]);
// No hashtags?
if ( ! count ( $tags )) {
return false ;
}
// 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
rsort ( $tags );
$URLSearchString = " ^ \ [ \ ] " ;
// All hashtags should point to the home server if "local_tags" is activated
if ( Config :: get ( 'system' , 'local_tags' )) {
$item [ " body " ] = preg_replace ( " /# \ [url \ =([ $URLSearchString ]*) \ ](.*?) \ [ \ /url \ ]/ism " ,
" #[url= " . System :: baseUrl () . " /search?tag= $ 2] $ 2[/url] " , $item [ " body " ]);
$item [ " tag " ] = preg_replace ( " /# \ [url \ =([ $URLSearchString ]*) \ ](.*?) \ [ \ /url \ ]/ism " ,
" #[url= " . System :: baseUrl () . " /search?tag= $ 2] $ 2[/url] " , $item [ " tag " ]);
}
// mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
$item [ " body " ] = preg_replace_callback ( " / \ [url \ =([ $URLSearchString ]*) \ ](.*?) \ [ \ /url \ ]/ism " ,
function ( $match ) {
return ( " [url= " . str_replace ( " # " , " # " , $match [ 1 ]) . " ] " . str_replace ( " # " , " # " , $match [ 2 ]) . " [/url] " );
}, $item [ " body " ]);
$item [ " body " ] = preg_replace_callback ( " / \ [bookmark \ =([ $URLSearchString ]*) \ ](.*?) \ [ \ /bookmark \ ]/ism " ,
function ( $match ) {
return ( " [bookmark= " . str_replace ( " # " , " # " , $match [ 1 ]) . " ] " . str_replace ( " # " , " # " , $match [ 2 ]) . " [/bookmark] " );
}, $item [ " body " ]);
$item [ " body " ] = preg_replace_callback ( " / \ [attachment (.*) \ ](.*?) \ [ \ /attachment \ ]/ism " ,
function ( $match ) {
return ( " [attachment " . str_replace ( " # " , " # " , $match [ 1 ]) . " ] " . $match [ 2 ] . " [/attachment] " );
}, $item [ " body " ]);
// Repair recursive urls
$item [ " body " ] = preg_replace ( " /# \ [url \ =([ $URLSearchString ]*) \ ](.*?) \ [ \ /url \ ]/ism " ,
" # $ 2 " , $item [ " body " ]);
foreach ( $tags as $tag ) {
if (( strpos ( $tag , '#' ) !== 0 ) || strpos ( $tag , '[url=' )) {
continue ;
}
$basetag = str_replace ( '_' , ' ' , substr ( $tag , 1 ));
$newtag = '#[url=' . System :: baseUrl () . '/search?tag=' . rawurlencode ( $basetag ) . ']' . $basetag . '[/url]' ;
$item [ " body " ] = str_replace ( $tag , $newtag , $item [ " body " ]);
if ( ! stristr ( $item [ " tag " ], " /search?tag= " . $basetag . " ] " . $basetag . " [/url] " )) {
if ( strlen ( $item [ " tag " ])) {
$item [ " tag " ] = ',' . $item [ " tag " ];
}
$item [ " tag " ] = $newtag . $item [ " tag " ];
}
}
// Convert back the masked hashtags
$item [ " body " ] = str_replace ( " # " , " # " , $item [ " body " ]);
}
2018-01-28 11:28:59 -05:00
public static function getGuidById ( $id )
{
2018-06-30 17:15:24 -04:00
$item = self :: selectFirst ([ 'guid' ], [ 'id' => $id ]);
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $item )) {
2018-02-22 01:52:58 -05:00
return $item [ 'guid' ];
2018-01-28 06:18:08 -05:00
} else {
2018-02-22 01:52:58 -05:00
return '' ;
2018-01-28 06:18:08 -05:00
}
}
2018-08-19 08:46:11 -04:00
/**
* This function is only used for the old Friendica app on Android that doesn ' t like paths with guid
* @ param string $guid item guid
* @ param int $uid user id
* @ return array with id and nick of the item with the given guid
*/
2018-01-28 11:28:59 -05:00
public static function getIdAndNickByGuid ( $guid , $uid = 0 )
{
2018-01-28 06:18:08 -05:00
$nick = " " ;
$id = 0 ;
if ( $uid == 0 ) {
$uid == local_user ();
}
// Does the given user have this item?
if ( $uid ) {
2018-08-19 08:46:11 -04:00
$item = self :: selectFirst ([ 'id' ], [ 'guid' => $guid , 'uid' => $uid ]);
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $item )) {
2018-08-19 08:46:11 -04:00
$user = DBA :: selectFirst ( 'user' , [ 'nickname' ], [ 'uid' => $uid ]);
if ( ! DBA :: isResult ( $user )) {
return ;
}
$id = $item [ 'id' ];
$nick = $user [ 'nickname' ];
2018-01-28 06:18:08 -05:00
}
}
// Or is it anywhere on the server?
if ( $nick == " " ) {
2018-08-19 08:46:11 -04:00
$condition = [ " `guid` = ? AND `uid` != 0 " , $guid ];
$item = self :: selectFirst ([ 'id' , 'uid' ], $condition );
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $item )) {
2018-08-19 08:46:11 -04:00
$user = DBA :: selectFirst ( 'user' , [ 'nickname' ], [ 'uid' => $item [ 'uid' ]]);
if ( ! DBA :: isResult ( $user )) {
return ;
}
$id = $item [ 'id' ];
$nick = $user [ 'nickname' ];
2018-01-28 06:18:08 -05:00
}
}
return [ " nick " => $nick , " id " => $id ];
}
/**
* look for mention tags and setup a second delivery chain for forum / community posts if appropriate
* @ param int $uid
* @ param int $item_id
* @ return bool true if item was deleted , else false
*/
private static function tagDeliver ( $uid , $item_id )
{
$mention = false ;
2018-07-20 08:19:26 -04:00
$user = DBA :: selectFirst ( 'user' , [], [ 'uid' => $uid ]);
2018-07-21 08:46:04 -04:00
if ( ! DBA :: isResult ( $user )) {
2018-01-28 06:18:08 -05:00
return ;
}
2018-07-27 19:25:57 -04:00
$community_page = (( $user [ 'page-flags' ] == Contact :: PAGE_COMMUNITY ) ? true : false );
$prvgroup = (( $user [ 'page-flags' ] == Contact :: PAGE_PRVGROUP ) ? true : false );
2018-01-28 06:18:08 -05:00
2018-06-30 17:15:24 -04:00
$item = self :: selectFirst ( self :: ITEM_FIELDLIST , [ 'id' => $item_id ]);
2018-07-21 08:46:04 -04:00
if ( ! DBA :: isResult ( $item )) {
2018-01-28 06:18:08 -05:00
return ;
}
2018-02-22 01:52:58 -05:00
$link = normalise_link ( System :: baseUrl () . '/profile/' . $user [ 'nickname' ]);
2018-01-28 06:18:08 -05:00
/*
* Diaspora uses their own hardwired link URL in @- tags
* instead of the one we supply with webfinger
*/
2018-02-22 01:52:58 -05:00
$dlink = normalise_link ( System :: baseUrl () . '/u/' . $user [ 'nickname' ]);
2018-01-28 06:18:08 -05:00
$cnt = preg_match_all ( '/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism' , $item [ 'body' ], $matches , PREG_SET_ORDER );
if ( $cnt ) {
foreach ( $matches as $mtch ) {
if ( link_compare ( $link , $mtch [ 1 ]) || link_compare ( $dlink , $mtch [ 1 ])) {
$mention = true ;
2018-10-29 17:20:46 -04:00
Logger :: log ( 'mention found: ' . $mtch [ 2 ]);
2018-01-28 06:18:08 -05:00
}
}
}
if ( ! $mention ) {
if (( $community_page || $prvgroup ) &&
! $item [ 'wall' ] && ! $item [ 'origin' ] && ( $item [ 'id' ] == $item [ 'parent' ])) {
// mmh.. no mention.. community page or private group... no wall.. no origin.. top-post (not a comment)
// delete it!
2018-10-29 17:20:46 -04:00
Logger :: log ( " no-mention top-level post to community or private group. delete. " );
2018-07-20 08:19:26 -04:00
DBA :: delete ( 'item' , [ 'id' => $item_id ]);
2018-01-28 06:18:08 -05:00
return true ;
}
return ;
}
2018-02-22 01:52:58 -05:00
$arr = [ 'item' => $item , 'user' => $user ];
2018-01-28 06:18:08 -05:00
Addon :: callHooks ( 'tagged' , $arr );
if ( ! $community_page && ! $prvgroup ) {
return ;
}
/*
* tgroup delivery - setup a second delivery chain
* prevent delivery looping - only proceed
* if the message originated elsewhere and is a top - level post
*/
if ( $item [ 'wall' ] || $item [ 'origin' ] || ( $item [ 'id' ] != $item [ 'parent' ])) {
return ;
}
// now change this copy of the post to a forum head message and deliver to all the tgroup members
2018-07-20 08:19:26 -04:00
$self = DBA :: selectFirst ( 'contact' , [ 'id' , 'name' , 'url' , 'thumb' ], [ 'uid' => $uid , 'self' => true ]);
2018-07-21 08:46:04 -04:00
if ( ! DBA :: isResult ( $self )) {
2018-01-28 06:18:08 -05:00
return ;
}
2018-03-11 14:12:38 -04:00
$owner_id = Contact :: getIdForURL ( $self [ 'url' ]);
2018-01-28 06:18:08 -05:00
// also reset all the privacy bits to the forum default permissions
2018-02-22 01:52:58 -05:00
$private = ( $user [ 'allow_cid' ] || $user [ 'allow_gid' ] || $user [ 'deny_cid' ] || $user [ 'deny_gid' ]) ? 1 : 0 ;
2018-01-28 06:18:08 -05:00
2018-08-15 16:13:10 -04:00
$psid = PermissionSet :: fetchIDForPost ( $user );
2018-02-22 01:52:58 -05:00
$forum_mode = ( $prvgroup ? 2 : 1 );
2018-01-28 06:18:08 -05:00
2018-03-11 14:12:38 -04:00
$fields = [ 'wall' => true , 'origin' => true , 'forum_mode' => $forum_mode , 'contact-id' => $self [ 'id' ],
2018-08-15 16:13:10 -04:00
'owner-id' => $owner_id , 'private' => $private , 'psid' => $psid ];
2018-08-15 00:41:49 -04:00
self :: update ( $fields , [ 'id' => $item_id ]);
2018-02-21 16:08:37 -05:00
2018-02-05 07:47:06 -05:00
self :: updateThread ( $item_id );
2018-01-28 06:18:08 -05:00
Worker :: add ([ 'priority' => PRIORITY_HIGH , 'dont_fork' => true ], 'Notifier' , 'tgroup' , $item_id );
}
2018-01-28 11:28:59 -05:00
public static function isRemoteSelf ( $contact , & $datarray )
{
2018-01-28 06:18:08 -05:00
$a = get_app ();
if ( ! $contact [ 'remote_self' ]) {
return false ;
}
// Prevent the forwarding of posts that are forwarded
2018-08-11 16:40:44 -04:00
if ( ! empty ( $datarray [ " extid " ]) && ( $datarray [ " extid " ] == Protocol :: DFRN )) {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Already forwarded' , Logger :: DEBUG );
2018-01-28 06:18:08 -05:00
return false ;
}
// Prevent to forward already forwarded posts
2018-10-09 13:58:58 -04:00
if ( $datarray [ " app " ] == $a -> getHostName ()) {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Already forwarded (second test)' , Logger :: DEBUG );
2018-01-28 06:18:08 -05:00
return false ;
}
// Only forward posts
if ( $datarray [ " verb " ] != ACTIVITY_POST ) {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'No post' , Logger :: DEBUG );
2018-01-28 06:18:08 -05:00
return false ;
}
2018-08-11 16:40:44 -04:00
if (( $contact [ 'network' ] != Protocol :: FEED ) && $datarray [ 'private' ]) {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Not public' , Logger :: DEBUG );
2018-01-28 06:18:08 -05:00
return false ;
}
$datarray2 = $datarray ;
2018-10-30 09:58:45 -04:00
Logger :: log ( 'remote-self start - Contact ' . $contact [ 'url' ] . ' - ' . $contact [ 'remote_self' ] . ' Item ' . print_r ( $datarray , true ), Logger :: DEBUG );
2018-01-28 06:18:08 -05:00
if ( $contact [ 'remote_self' ] == 2 ) {
2018-07-20 08:19:26 -04:00
$self = DBA :: selectFirst ( 'contact' , [ 'id' , 'name' , 'url' , 'thumb' ],
2018-02-22 01:52:58 -05:00
[ 'uid' => $contact [ 'uid' ], 'self' => true ]);
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $self )) {
2018-02-22 01:52:58 -05:00
$datarray [ 'contact-id' ] = $self [ " id " ];
2018-01-28 06:18:08 -05:00
2018-02-22 01:52:58 -05:00
$datarray [ 'owner-name' ] = $self [ " name " ];
$datarray [ 'owner-link' ] = $self [ " url " ];
$datarray [ 'owner-avatar' ] = $self [ " thumb " ];
2018-01-28 06:18:08 -05:00
$datarray [ 'author-name' ] = $datarray [ 'owner-name' ];
$datarray [ 'author-link' ] = $datarray [ 'owner-link' ];
$datarray [ 'author-avatar' ] = $datarray [ 'owner-avatar' ];
unset ( $datarray [ 'created' ]);
unset ( $datarray [ 'edited' ]);
2018-05-04 17:12:13 -04:00
unset ( $datarray [ 'network' ]);
unset ( $datarray [ 'owner-id' ]);
unset ( $datarray [ 'author-id' ]);
2018-01-28 06:18:08 -05:00
}
2018-08-11 16:40:44 -04:00
if ( $contact [ 'network' ] != Protocol :: FEED ) {
2018-09-27 07:52:15 -04:00
$datarray [ " guid " ] = System :: createUUID ();
2018-01-28 06:18:08 -05:00
unset ( $datarray [ " plink " ]);
2018-06-16 02:44:19 -04:00
$datarray [ " uri " ] = self :: newURI ( $contact [ 'uid' ], $datarray [ " guid " ]);
2018-01-28 06:18:08 -05:00
$datarray [ " parent-uri " ] = $datarray [ " uri " ];
2018-05-04 17:12:13 -04:00
$datarray [ " thr-parent " ] = $datarray [ " uri " ];
2018-08-11 16:40:44 -04:00
$datarray [ " extid " ] = Protocol :: DFRN ;
2018-01-28 06:18:08 -05:00
$urlpart = parse_url ( $datarray2 [ 'author-link' ]);
$datarray [ " app " ] = $urlpart [ " host " ];
} else {
$datarray [ 'private' ] = 0 ;
}
}
2018-08-11 16:40:44 -04:00
if ( $contact [ 'network' ] != Protocol :: FEED ) {
2018-01-28 06:18:08 -05:00
// Store the original post
2018-05-04 17:12:13 -04:00
$result = self :: insert ( $datarray2 , false , false );
2018-10-30 09:58:45 -04:00
Logger :: log ( 'remote-self post original item - Contact ' . $contact [ 'url' ] . ' return ' . $result . ' Item ' . print_r ( $datarray2 , true ), Logger :: DEBUG );
2018-01-28 06:18:08 -05:00
} else {
$datarray [ " app " ] = " Feed " ;
2018-05-04 17:12:13 -04:00
$result = true ;
2018-01-28 06:18:08 -05:00
}
// Trigger automatic reactions for addons
$datarray [ 'api_source' ] = true ;
// We have to tell the hooks who we are - this really should be improved
$_SESSION [ " authenticated " ] = true ;
$_SESSION [ " uid " ] = $contact [ 'uid' ];
2018-05-04 17:12:13 -04:00
return $result ;
2018-01-28 06:18:08 -05:00
}
/**
*
* @ param string $s
* @ param int $uid
* @ param array $item
* @ param int $cid
* @ return string
*/
public static function fixPrivatePhotos ( $s , $uid , $item = null , $cid = 0 )
{
if ( Config :: get ( 'system' , 'disable_embedded' )) {
return $s ;
}
2018-10-30 09:58:45 -04:00
Logger :: log ( 'check for photos' , Logger :: DEBUG );
2018-01-28 06:18:08 -05:00
$site = substr ( System :: baseUrl (), strpos ( System :: baseUrl (), '://' ));
$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 );
2018-10-30 09:58:45 -04:00
Logger :: log ( 'found photo ' . $image , Logger :: DEBUG );
2018-01-28 06:18:08 -05: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-02-22 01:52:58 -05:00
$fields = [ 'data' , 'type' , 'allow_cid' , 'allow_gid' , 'deny_cid' , 'deny_gid' ];
2018-07-20 08:19:26 -04:00
$photo = DBA :: selectFirst ( 'photo' , $fields , [ 'resource-id' => $i , 'scale' => $res , 'uid' => $uid ]);
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $photo )) {
2018-01-28 06:18:08 -05: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 01:52:58 -05:00
if ( self :: hasPermissions ( $photo )) {
2018-01-28 06:18:08 -05:00
if ( $cid ) {
2018-02-22 01:52:58 -05:00
$recips = self :: enumeratePermissions ( $photo );
2018-01-28 06:18:08 -05:00
if ( in_array ( $cid , $recips )) {
$replace = true ;
}
} elseif ( $item ) {
2018-02-22 01:52:58 -05:00
if ( self :: samePermissions ( $item , $photo )) {
2018-01-28 06:18:08 -05:00
$replace = true ;
}
}
}
if ( $replace ) {
2018-02-22 01:52:58 -05:00
$data = $photo [ 'data' ];
$type = $photo [ 'type' ];
2018-01-28 06:18:08 -05: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 )) {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'scaling photo' , Logger :: DEBUG );
2018-01-28 06:18:08 -05:00
$width = intval ( $match [ 1 ]);
$height = intval ( $match [ 2 ]);
$Image = new Image ( $data , $type );
if ( $Image -> isValid ()) {
$Image -> scaleDown ( max ( $width , $height ));
$data = $Image -> asString ();
$type = $Image -> getType ();
}
}
2018-10-30 09:58:45 -04:00
Logger :: log ( 'replacing photo' , Logger :: DEBUG );
2018-01-28 06:18:08 -05:00
$image = 'data:' . $type . ';base64,' . base64_encode ( $data );
2018-10-30 09:58:45 -04:00
Logger :: log ( 'replaced: ' . $image , Logger :: DATA );
2018-01-28 06:18:08 -05: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 11:28:59 -05:00
private static function hasPermissions ( $obj )
{
2018-07-01 03:57:59 -04:00
return ! empty ( $obj [ 'allow_cid' ]) || ! empty ( $obj [ 'allow_gid' ]) ||
! empty ( $obj [ 'deny_cid' ]) || ! empty ( $obj [ 'deny_gid' ]);
2018-01-28 06:18:08 -05:00
}
2018-01-28 11:28:59 -05:00
private static function samePermissions ( $obj1 , $obj2 )
{
2018-01-28 06:18:08 -05: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 );
}
// returns an array of contact-ids that are allowed to see this object
2018-09-17 17:13:08 -04:00
public static function enumeratePermissions ( $obj )
2018-01-28 11:28:59 -05:00
{
2018-01-28 06:18:08 -05:00
$allow_people = expand_acl ( $obj [ 'allow_cid' ]);
$allow_groups = Group :: expand ( expand_acl ( $obj [ 'allow_gid' ]));
$deny_people = expand_acl ( $obj [ 'deny_cid' ]);
$deny_groups = Group :: expand ( expand_acl ( $obj [ 'deny_gid' ]));
$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 ;
}
2018-01-28 11:28:59 -05:00
public static function getFeedTags ( $item )
{
2018-01-28 06:18:08 -05:00
$ret = [];
$matches = false ;
$cnt = preg_match_all ( '|\#\[url\=(.*?)\](.*?)\[\/url\]|' , $item [ 'tag' ], $matches );
if ( $cnt ) {
for ( $x = 0 ; $x < $cnt ; $x ++ ) {
if ( $matches [ 1 ][ $x ]) {
$ret [ $matches [ 2 ][ $x ]] = [ '#' , $matches [ 1 ][ $x ], $matches [ 2 ][ $x ]];
}
}
}
$matches = false ;
$cnt = preg_match_all ( '|\@\[url\=(.*?)\](.*?)\[\/url\]|' , $item [ 'tag' ], $matches );
if ( $cnt ) {
for ( $x = 0 ; $x < $cnt ; $x ++ ) {
if ( $matches [ 1 ][ $x ]) {
$ret [] = [ '@' , $matches [ 1 ][ $x ], $matches [ 2 ][ $x ]];
}
}
}
return $ret ;
}
2018-01-28 11:28:59 -05:00
public static function expire ( $uid , $days , $network = " " , $force = false )
{
2018-01-28 06:18:08 -05:00
if ( ! $uid || ( $days < 1 )) {
return ;
}
2018-07-01 03:57:59 -04:00
$condition = [ " `uid` = ? AND NOT `deleted` AND `id` = `parent` AND `gravity` = ? " ,
$uid , GRAVITY_PARENT ];
2018-01-28 06:18:08 -05:00
/*
* $expire_network_only = save your own wall posts
* and just expire conversations started by others
*/
2018-07-01 03:57:59 -04:00
$expire_network_only = PConfig :: get ( $uid , 'expire' , 'network_only' , false );
if ( $expire_network_only ) {
$condition [ 0 ] .= " AND NOT `wall` " ;
}
2018-01-28 06:18:08 -05:00
if ( $network != " " ) {
2018-07-01 03:57:59 -04:00
$condition [ 0 ] .= " AND `network` = ? " ;
$condition [] = $network ;
2018-01-28 06:18:08 -05:00
/*
* There is an index " uid_network_received " but not " uid_network_created "
* This avoids the creation of another index just for one purpose .
* And it doesn ' t really matter wether to look at " received " or " created "
*/
2018-07-01 03:57:59 -04:00
$condition [ 0 ] .= " AND `received` < UTC_TIMESTAMP() - INTERVAL ? DAY " ;
$condition [] = $days ;
2018-01-28 06:18:08 -05:00
} else {
2018-07-01 03:57:59 -04:00
$condition [ 0 ] .= " AND `created` < UTC_TIMESTAMP() - INTERVAL ? DAY " ;
$condition [] = $days ;
2018-01-28 06:18:08 -05:00
}
2018-07-30 22:23:48 -04:00
$items = self :: select ([ 'file' , 'resource-id' , 'starred' , 'type' , 'id' , 'post-type' ], $condition );
2018-01-28 06:18:08 -05:00
2018-07-21 08:46:04 -04:00
if ( ! DBA :: isResult ( $items )) {
2018-01-28 06:18:08 -05:00
return ;
}
2018-07-01 03:57:59 -04:00
$expire_items = PConfig :: get ( $uid , 'expire' , 'items' , true );
2018-01-28 06:18:08 -05:00
// Forcing expiring of items - but not notes and marked items
if ( $force ) {
$expire_items = true ;
}
2018-07-01 03:57:59 -04:00
$expire_notes = PConfig :: get ( $uid , 'expire' , 'notes' , true );
$expire_starred = PConfig :: get ( $uid , 'expire' , 'starred' , true );
$expire_photos = PConfig :: get ( $uid , 'expire' , 'photos' , false );
2018-01-28 06:18:08 -05:00
2018-07-01 05:08:58 -04:00
$expired = 0 ;
2018-01-28 06:18:08 -05:00
2018-07-01 03:57:59 -04:00
while ( $item = Item :: fetch ( $items )) {
2018-01-28 06:18:08 -05:00
// don't expire filed items
2018-07-01 03:57:59 -04:00
if ( strpos ( $item [ 'file' ], '[' ) !== false ) {
2018-01-28 06:18:08 -05:00
continue ;
}
// Only expire posts, not photos and photo comments
2018-07-01 03:57:59 -04:00
if ( ! $expire_photos && strlen ( $item [ 'resource-id' ])) {
2018-01-28 06:18:08 -05:00
continue ;
2018-07-01 03:57:59 -04:00
} elseif ( ! $expire_starred && intval ( $item [ 'starred' ])) {
2018-01-28 06:18:08 -05:00
continue ;
2018-07-30 22:23:48 -04:00
} elseif ( ! $expire_notes && (( $item [ 'type' ] == 'note' ) || ( $item [ 'post-type' ] == Item :: PT_PERSONAL_NOTE ))) {
2018-01-28 06:18:08 -05:00
continue ;
2018-07-30 22:23:48 -04:00
} elseif ( ! $expire_items && ( $item [ 'type' ] != 'note' ) && ( $item [ 'post-type' ] != Item :: PT_PERSONAL_NOTE )) {
2018-01-28 06:18:08 -05:00
continue ;
}
2018-02-06 11:32:00 -05:00
self :: deleteById ( $item [ 'id' ], PRIORITY_LOW );
2018-07-01 05:08:58 -04:00
++ $expired ;
2018-01-28 06:18:08 -05:00
}
2018-07-20 08:19:26 -04:00
DBA :: close ( $items );
2018-10-29 17:20:46 -04:00
Logger :: log ( 'User ' . $uid . " : expired $expired items; expire items: $expire_items , expire notes: $expire_notes , expire starred: $expire_starred , expire photos: $expire_photos " );
2018-01-28 06:18:08 -05:00
}
2018-01-28 11:28:59 -05:00
public static function firstPostDate ( $uid , $wall = false )
{
2018-02-22 01:52:58 -05:00
$condition = [ 'uid' => $uid , 'wall' => $wall , 'deleted' => false , 'visible' => true , 'moderated' => false ];
$params = [ 'order' => [ 'created' => false ]];
2018-07-20 08:19:26 -04:00
$thread = DBA :: selectFirst ( 'thread' , [ 'created' ], $condition , $params );
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $thread )) {
2018-02-22 01:52:58 -05:00
return substr ( DateTimeFormat :: local ( $thread [ 'created' ]), 0 , 10 );
2018-01-28 06:18:08 -05:00
}
return false ;
}
2018-02-01 14:14:11 -05:00
/**
* @ brief add / remove activity to an item
*
* Toggle activities as like , dislike , attend of an item
*
* @ param string $item_id
* @ param string $verb
* Activity verb . One of
* like , unlike , dislike , undislike , attendyes , unattendyes ,
* attendno , unattendno , attendmaybe , unattendmaybe
* @ hook 'post_local_end'
* array $arr
* 'post_id' => ID of posted item
*/
public static function performLike ( $item_id , $verb )
{
if ( ! local_user () && ! remote_user ()) {
return false ;
}
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 ;
default :
2018-10-29 17:20:46 -04:00
Logger :: log ( 'like: unknown verb ' . $verb . ' for item ' . $item_id );
2018-02-01 14:14:11 -05:00
return false ;
}
// Enable activity toggling instead of on/off
$event_verb_flag = $activity === ACTIVITY_ATTEND || $activity === ACTIVITY_ATTENDNO || $activity === ACTIVITY_ATTENDMAYBE ;
2018-10-29 17:20:46 -04:00
Logger :: log ( 'like: verb ' . $verb . ' item ' . $item_id );
2018-02-01 14:14:11 -05:00
2018-06-30 17:15:24 -04:00
$item = self :: selectFirst ( self :: ITEM_FIELDLIST , [ '`id` = ? OR `uri` = ?' , $item_id , $item_id ]);
2018-07-21 08:46:04 -04:00
if ( ! DBA :: isResult ( $item )) {
2018-10-29 17:20:46 -04:00
Logger :: log ( 'like: unknown item ' . $item_id );
2018-02-01 14:14:11 -05:00
return false ;
}
2018-07-07 06:43:43 -04:00
$item_uri = $item [ 'uri' ];
2018-02-01 14:14:11 -05:00
$uid = $item [ 'uid' ];
if (( $uid == 0 ) && local_user ()) {
$uid = local_user ();
}
2018-10-17 16:35:49 -04:00
if ( ! Security :: canWriteToUserWall ( $uid )) {
2018-10-29 17:20:46 -04:00
Logger :: log ( 'like: unable to write on wall ' . $uid );
2018-02-01 14:14:11 -05:00
return false ;
}
// Retrieves the local post owner
2018-07-20 08:19:26 -04:00
$owner_self_contact = DBA :: selectFirst ( 'contact' , [], [ 'uid' => $uid , 'self' => true ]);
2018-07-21 08:46:04 -04:00
if ( ! DBA :: isResult ( $owner_self_contact )) {
2018-10-29 17:20:46 -04:00
Logger :: log ( 'like: unknown owner ' . $uid );
2018-02-01 14:14:11 -05:00
return false ;
}
// Retrieve the current logged in user's public contact
$author_id = public_contact ();
2018-07-20 08:19:26 -04:00
$author_contact = DBA :: selectFirst ( 'contact' , [ 'url' ], [ 'id' => $author_id ]);
2018-07-21 08:46:04 -04:00
if ( ! DBA :: isResult ( $author_contact )) {
2018-10-29 17:20:46 -04:00
Logger :: log ( 'like: unknown author ' . $author_id );
2018-02-01 14:14:11 -05:00
return false ;
}
// Contact-id is the uid-dependant author contact
if ( local_user () == $uid ) {
$item_contact_id = $owner_self_contact [ 'id' ];
$item_contact = $owner_self_contact ;
} else {
2018-03-01 19:54:45 -05:00
$item_contact_id = Contact :: getIdForURL ( $author_contact [ 'url' ], $uid , true );
2018-07-20 08:19:26 -04:00
$item_contact = DBA :: selectFirst ( 'contact' , [], [ 'id' => $item_contact_id ]);
2018-07-21 08:46:04 -04:00
if ( ! DBA :: isResult ( $item_contact )) {
2018-10-29 17:20:46 -04:00
Logger :: log ( 'like: unknown item contact ' . $item_contact_id );
2018-02-01 14:14:11 -05:00
return false ;
}
}
// Look for an existing verb row
// event participation are essentially radio toggles. If you make a subsequent choice,
// we need to eradicate your first choice.
if ( $event_verb_flag ) {
2018-06-27 15:37:13 -04:00
$verbs = [ ACTIVITY_ATTEND , ACTIVITY_ATTENDNO , ACTIVITY_ATTENDMAYBE ];
2018-07-07 06:43:43 -04:00
// Translate to the index based activity index
$activities = [];
foreach ( $verbs as $verb ) {
$activities [] = self :: activityToIndex ( $verb );
}
2018-02-01 14:14:11 -05:00
} else {
2018-07-07 06:43:43 -04:00
$activities = self :: activityToIndex ( $activity );
2018-06-27 15:37:13 -04:00
}
2018-02-01 14:14:11 -05:00
2018-07-07 06:43:43 -04:00
$condition = [ 'activity' => $activities , 'deleted' => false , 'gravity' => GRAVITY_ACTIVITY ,
'author-id' => $author_id , 'uid' => $item [ 'uid' ], 'thr-parent' => $item_uri ];
2018-06-27 15:37:13 -04:00
$like_item = self :: selectFirst ([ 'id' , 'guid' , 'verb' ], $condition );
2018-02-01 14:14:11 -05:00
2018-06-27 15:37:13 -04:00
// If it exists, mark it as deleted
2018-07-21 08:46:04 -04:00
if ( DBA :: isResult ( $like_item )) {
2018-08-05 00:35:11 -04:00
self :: deleteById ( $like_item [ 'id' ]);
2018-02-01 14:14:11 -05:00
if ( ! $event_verb_flag || $like_item [ 'verb' ] == $activity ) {
return true ;
}
}
// Verb is "un-something", just trying to delete existing entries
if ( strpos ( $verb , 'un' ) === 0 ) {
return true ;
}
2018-10-06 04:51:52 -04:00
$objtype = $item [ 'resource-id' ] ? ACTIVITY_OBJ_IMAGE : ACTIVITY_OBJ_NOTE ;
2018-02-01 14:14:11 -05:00
$new_item = [
2018-09-27 07:52:15 -04:00
'guid' => System :: createUUID (),
2018-06-16 02:44:19 -04:00
'uri' => self :: newURI ( $item [ 'uid' ]),
2018-02-01 14:14:11 -05:00
'uid' => $item [ 'uid' ],
'contact-id' => $item_contact_id ,
'wall' => $item [ 'wall' ],
'origin' => 1 ,
2018-08-11 16:40:44 -04:00
'network' => Protocol :: DFRN ,
2018-06-27 14:09:33 -04:00
'gravity' => GRAVITY_ACTIVITY ,
2018-02-01 14:14:11 -05:00
'parent' => $item [ 'id' ],
'parent-uri' => $item [ 'uri' ],
'thr-parent' => $item [ 'uri' ],
2018-10-06 04:51:52 -04:00
'owner-id' => $author_id ,
2018-07-07 06:43:43 -04:00
'author-id' => $author_id ,
'body' => $activity ,
2018-02-01 14:14:11 -05:00
'verb' => $activity ,
'object-type' => $objtype ,
'allow_cid' => $item [ 'allow_cid' ],
'allow_gid' => $item [ 'allow_gid' ],
'deny_cid' => $item [ 'deny_cid' ],
'deny_gid' => $item [ 'deny_gid' ],
'visible' => 1 ,
'unseen' => 1 ,
];
2018-10-29 17:15:37 -04:00
$signed = Diaspora :: createLikeSignature ( $uid , $new_item );
2018-10-27 07:09:23 -04:00
if ( ! empty ( $signed )) {
2018-10-27 10:35:22 -04:00
$new_item [ 'diaspora_signed_text' ] = json_encode ( $signed );
2018-10-27 07:09:23 -04:00
}
2018-02-06 07:40:22 -05:00
$new_item_id = self :: insert ( $new_item );
2018-02-01 14:14:11 -05:00
2018-02-06 07:40:22 -05: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 14:14:11 -05:00
}
$new_item [ 'id' ] = $new_item_id ;
Addon :: callHooks ( 'post_local_end' , $new_item );
return true ;
}
2018-02-05 07:37:32 -05:00
2018-02-05 08:09:39 -05:00
private static function addThread ( $itemid , $onlyshadow = false )
{
2018-02-22 01:52:58 -05:00
$fields = [ 'uid' , 'created' , 'edited' , 'commented' , 'received' , 'changed' , 'wall' , 'private' , 'pubmail' ,
2018-07-19 09:52:05 -04:00
'moderated' , 'visible' , 'starred' , 'contact-id' , 'post-type' ,
2018-02-22 01:52:58 -05:00
'deleted' , 'origin' , 'forum_mode' , 'mention' , 'network' , 'author-id' , 'owner-id' ];
$condition = [ " `id` = ? AND (`parent` = ? OR `parent` = 0) " , $itemid , $itemid ];
2018-06-30 17:15:24 -04:00
$item = self :: selectFirst ( $fields , $condition );
2018-02-06 07:40:22 -05:00
2018-07-21 08:46:04 -04:00
if ( ! DBA :: isResult ( $item )) {
2018-02-05 07:37:32 -05:00
return ;
2018-02-05 08:09:39 -05:00
}
2018-02-06 07:40:22 -05:00
2018-02-05 07:37:32 -05:00
$item [ 'iid' ] = $itemid ;
2018-02-06 07:40:22 -05:00
2018-02-05 07:37:32 -05:00
if ( ! $onlyshadow ) {
2018-07-20 08:19:26 -04:00
$result = DBA :: insert ( 'thread' , $item );
2018-02-05 07:37:32 -05:00
2018-10-30 09:58:45 -04:00
Logger :: log ( " Add thread for item " . $itemid . " - " . print_r ( $result , true ), Logger :: DEBUG );
2018-02-05 07:37:32 -05:00
}
}
2018-02-06 07:40:22 -05:00
private static function updateThread ( $itemid , $setmention = false )
2018-02-05 08:09:39 -05:00
{
2018-07-19 09:52:05 -04:00
$fields = [ 'uid' , 'guid' , 'created' , 'edited' , 'commented' , 'received' , 'changed' , 'post-type' ,
'wall' , 'private' , 'pubmail' , 'moderated' , 'visible' , 'starred' , 'contact-id' ,
2018-06-25 02:33:12 -04:00
'deleted' , 'origin' , 'forum_mode' , 'network' , 'author-id' , 'owner-id' ];
2018-02-22 01:52:58 -05:00
$condition = [ " `id` = ? AND (`parent` = ? OR `parent` = 0) " , $itemid , $itemid ];
2018-02-06 07:40:22 -05:00
2018-06-30 17:15:24 -04:00
$item = self :: selectFirst ( $fields , $condition );
2018-07-21 08:46:04 -04:00
if ( ! DBA :: isResult ( $item )) {
2018-02-05 07:37:32 -05:00
return ;
}
2018-02-06 07:40:22 -05:00
2018-02-05 07:37:32 -05:00
if ( $setmention ) {
$item [ " mention " ] = 1 ;
}
2018-02-06 07:40:22 -05:00
2018-02-05 07:37:32 -05:00
$sql = " " ;
2018-02-06 07:40:22 -05:00
2018-02-21 16:08:37 -05:00
$fields = [];
2018-02-06 07:40:22 -05:00
2018-02-21 16:08:37 -05:00
foreach ( $item as $field => $data ) {
2018-06-25 02:33:12 -04:00
if ( ! in_array ( $field , [ " guid " ])) {
2018-02-21 16:08:37 -05:00
$fields [ $field ] = $data ;
2018-02-05 07:37:32 -05:00
}
2018-02-21 16:08:37 -05:00
}
2018-02-06 07:40:22 -05:00
2018-07-20 08:19:26 -04:00
$result = DBA :: update ( 'thread' , $fields , [ 'iid' => $itemid ]);
2018-02-06 07:40:22 -05:00
2018-10-30 09:58:45 -04:00
Logger :: log ( " Update thread for item " . $itemid . " - guid " . $item [ " guid " ] . " - " . ( int ) $result , Logger :: DEBUG );
2018-02-05 07:37:32 -05:00
}
2018-02-06 07:40:22 -05:00
private static function deleteThread ( $itemid , $itemuri = " " )
2018-02-05 08:09:39 -05:00
{
2018-07-20 08:19:26 -04:00
$item = DBA :: selectFirst ( 'thread' , [ 'uid' ], [ 'iid' => $itemid ]);
2018-07-21 08:46:04 -04:00
if ( ! DBA :: isResult ( $item )) {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'No thread found for id ' . $itemid , Logger :: DEBUG );
2018-02-05 07:37:32 -05:00
return ;
}
2018-02-06 07:40:22 -05:00
2018-08-19 08:46:11 -04:00
$result = DBA :: delete ( 'thread' , [ 'iid' => $itemid ], [ 'cascade' => false ]);
2018-02-06 07:40:22 -05:00
2018-10-30 09:58:45 -04:00
Logger :: log ( " deleteThread: Deleted thread for item " . $itemid . " - " . print_r ( $result , true ), Logger :: DEBUG );
2018-02-06 07:40:22 -05:00
2018-02-05 07:37:32 -05:00
if ( $itemuri != " " ) {
2018-02-22 01:52:58 -05:00
$condition = [ " `uri` = ? AND NOT `deleted` AND NOT (`uid` IN (?, 0)) " , $itemuri , $item [ " uid " ]];
2018-06-27 15:37:13 -04:00
if ( ! self :: exists ( $condition )) {
2018-07-20 08:19:26 -04:00
DBA :: delete ( 'item' , [ 'uri' => $itemuri , 'uid' => 0 ]);
2018-10-30 09:58:45 -04:00
Logger :: log ( " deleteThread: Deleted shadow for item " . $itemuri , Logger :: DEBUG );
2018-02-05 07:37:32 -05:00
}
}
}
2018-10-17 15:30:41 -04:00
public static function getPermissionsSQLByUserId ( $owner_id , $remote_verified = false , $groups = null )
{
$local_user = local_user ();
$remote_user = remote_user ();
/*
* Construct permissions
*
* default permissions - anonymous user
*/
$sql = " AND NOT `item`.`private` " ;
// 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 .
*/
$set = PermissionSet :: get ( $owner_id , $remote_user , $groups );
if ( ! empty ( $set )) {
$sql_set = " OR (`item`.`private` IN (1,2) AND `item`.`wall` AND `item`.`psid` IN ( " . implode ( ',' , $set ) . " )) " ;
} else {
$sql_set = '' ;
}
$sql = " AND (NOT `item`.`private` " . $sql_set . " ) " ;
}
return $sql ;
}
2018-11-06 21:12:41 -05:00
/**
* get translated item type
*
* @ param array $itme
* @ return string
*/
public static function postType ( $item )
{
if ( ! empty ( $item [ 'event-id' ])) {
return L10n :: t ( 'event' );
} elseif ( ! empty ( $item [ 'resource-id' ])) {
return L10n :: t ( 'photo' );
} elseif ( ! empty ( $item [ 'verb' ]) && $item [ 'verb' ] !== ACTIVITY_POST ) {
return L10n :: t ( 'activity' );
} elseif ( $item [ 'id' ] != $item [ 'parent' ]) {
return L10n :: t ( 'comment' );
}
return L10n :: t ( 'post' );
}
/**
* 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
* @ param bool $update
*
* @ todo Remove reference , simply return " rendered-html " and " rendered-hash "
*/
public static function putInCache ( & $item , $update = false )
{
$body = $item [ " body " ];
$rendered_hash = defaults ( $item , 'rendered-hash' , '' );
$rendered_html = defaults ( $item , 'rendered-html' , '' );
if ( $rendered_hash == ''
|| $rendered_html == " "
|| $rendered_hash != hash ( " md5 " , $item [ " body " ])
|| Config :: get ( " system " , " ignore_cache " )
) {
2018-11-07 07:19:39 -05:00
$a = self :: getApp ();
2018-11-06 21:12:41 -05:00
redir_private_images ( $a , $item );
$item [ " rendered-html " ] = prepare_text ( $item [ " body " ]);
$item [ " rendered-hash " ] = hash ( " md5 " , $item [ " body " ]);
$hook_data = [ 'item' => $item , 'rendered-html' => $item [ 'rendered-html' ], 'rendered-hash' => $item [ 'rendered-hash' ]];
Addon :: callHooks ( 'put_item_in_cache' , $hook_data );
$item [ 'rendered-html' ] = $hook_data [ 'rendered-html' ];
$item [ 'rendered-hash' ] = $hook_data [ 'rendered-hash' ];
unset ( $hook_data );
// Force an update if the generated values differ from the existing ones
if ( $rendered_hash != $item [ " rendered-hash " ]) {
$update = true ;
}
// Only compare the HTML when we forcefully ignore the cache
if ( Config :: get ( " system " , " ignore_cache " ) && ( $rendered_html != $item [ " rendered-html " ])) {
$update = true ;
}
if ( $update && ! empty ( $item [ " id " ])) {
self :: update (
[
'rendered-html' => $item [ " rendered-html " ],
'rendered-hash' => $item [ " rendered-hash " ]
],
[ 'id' => $item [ " id " ]]
);
}
}
$item [ " body " ] = $body ;
}
/**
* @ brief Given an item array , convert the body element from bbcode to html and add smilie icons .
* 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
* @ 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 )
*/
public static function prepareBody ( array & $item , $attach = false , $is_preview = false )
{
2018-11-07 07:19:39 -05:00
$a = self :: getApp ();
2018-11-06 21:12:41 -05:00
Addon :: callHooks ( 'prepare_body_init' , $item );
// In order to provide theme developers more possibilities, event items
// are treated differently.
if ( $item [ 'object-type' ] === ACTIVITY_OBJ_EVENT && isset ( $item [ 'event-id' ])) {
$ev = Event :: getItemHTML ( $item );
return $ev ;
}
$tags = Term :: populateTagsFromItem ( $item );
$item [ 'tags' ] = $tags [ 'tags' ];
$item [ 'hashtags' ] = $tags [ 'hashtags' ];
$item [ 'mentions' ] = $tags [ 'mentions' ];
// Compile eventual content filter reasons
$filter_reasons = [];
if ( ! $is_preview && public_contact () != $item [ 'author-id' ]) {
if ( ! empty ( $item [ 'content-warning' ]) && ( ! local_user () || ! PConfig :: get ( local_user (), 'system' , 'disable_cw' , false ))) {
$filter_reasons [] = L10n :: t ( 'Content warning: %s' , $item [ 'content-warning' ]);
}
$hook_data = [
'item' => $item ,
'filter_reasons' => $filter_reasons
];
Addon :: callHooks ( 'prepare_body_content_filter' , $hook_data );
$filter_reasons = $hook_data [ 'filter_reasons' ];
unset ( $hook_data );
}
// Update the cached values if there is no "zrl=..." on the links.
$update = ( ! local_user () && ! remote_user () && ( $item [ " uid " ] == 0 ));
// Or update it if the current viewer is the intented viewer.
if (( $item [ " uid " ] == local_user ()) && ( $item [ " uid " ] != 0 )) {
$update = true ;
}
self :: putInCache ( $item , $update );
$s = $item [ " rendered-html " ];
$hook_data = [
'item' => $item ,
'html' => $s ,
'preview' => $is_preview ,
'filter_reasons' => $filter_reasons
];
Addon :: callHooks ( 'prepare_body' , $hook_data );
$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 ;
}
$as = '' ;
$vhead = false ;
$matches = [];
preg_match_all ( '|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\"(?: title=\"(.*?)\")?|' , $item [ 'attach' ], $matches , PREG_SET_ORDER );
foreach ( $matches as $mtch ) {
$mime = $mtch [ 3 ];
$the_url = Contact :: magicLinkById ( $item [ 'author-id' ], $mtch [ 1 ]);
if ( strpos ( $mime , 'video' ) !== false ) {
if ( ! $vhead ) {
$vhead = true ;
$a -> page [ 'htmlhead' ] .= Renderer :: replaceMacros ( Renderer :: getMarkupTemplate ( 'videos_head.tpl' ), [
'$baseurl' => System :: baseUrl (),
]);
}
$url_parts = explode ( '/' , $the_url );
$id = end ( $url_parts );
$as .= Renderer :: replaceMacros ( Renderer :: getMarkupTemplate ( 'video_top.tpl' ), [
'$video' => [
'id' => $id ,
'title' => L10n :: t ( 'View Video' ),
'src' => $the_url ,
'mime' => $mime ,
],
]);
}
$filetype = strtolower ( substr ( $mime , 0 , strpos ( $mime , '/' )));
if ( $filetype ) {
$filesubtype = strtolower ( substr ( $mime , strpos ( $mime , '/' ) + 1 ));
$filesubtype = str_replace ( '.' , '-' , $filesubtype );
} else {
$filetype = 'unkn' ;
$filesubtype = 'unkn' ;
}
$title = escape_tags ( trim ( ! empty ( $mtch [ 4 ]) ? $mtch [ 4 ] : $mtch [ 1 ]));
$title .= ' ' . $mtch [ 2 ] . ' ' . L10n :: t ( 'bytes' );
$icon = '<div class="attachtype icon s22 type-' . $filetype . ' subtype-' . $filesubtype . '"></div>' ;
$as .= '<a href="' . strip_tags ( $the_url ) . '" title="' . $title . '" class="attachlink" target="_blank" >' . $icon . '</a>' ;
}
if ( $as != '' ) {
$s .= '<div class="body-attach">' . $as . '<div class="clear"></div></div>' ;
}
// Map.
if ( strpos ( $s , '<div class="map">' ) !== false && x ( $item , 'coord' )) {
$x = Map :: byCoordinates ( trim ( $item [ 'coord' ]));
if ( $x ) {
$s = preg_replace ( '/\<div class\=\"map\"\>/' , '$0' . $x , $s );
}
}
// Look for spoiler.
$spoilersearch = '<blockquote class="spoiler">' ;
// Remove line breaks before the spoiler.
while (( strpos ( $s , " \n " . $spoilersearch ) !== false )) {
$s = str_replace ( " \n " . $spoilersearch , $spoilersearch , $s );
}
while (( strpos ( $s , " <br /> " . $spoilersearch ) !== false )) {
$s = str_replace ( " <br /> " . $spoilersearch , $spoilersearch , $s );
}
while (( strpos ( $s , $spoilersearch ) !== false )) {
$pos = strpos ( $s , $spoilersearch );
$rnd = random_string ( 8 );
$spoilerreplace = '<br /> <span id="spoiler-wrap-' . $rnd . '" class="spoiler-wrap fakelink" onclick="openClose(\'spoiler-' . $rnd . '\');">' . L10n :: t ( 'Click to open/close' ) . '</span>' .
'<blockquote class="spoiler" id="spoiler-' . $rnd . '" style="display: none;">' ;
$s = substr ( $s , 0 , $pos ) . $spoilerreplace . substr ( $s , $pos + strlen ( $spoilersearch ));
}
// Look for quote with author.
$authorsearch = '<blockquote class="author">' ;
while (( strpos ( $s , $authorsearch ) !== false )) {
$pos = strpos ( $s , $authorsearch );
$rnd = random_string ( 8 );
$authorreplace = '<br /> <span id="author-wrap-' . $rnd . '" class="author-wrap fakelink" onclick="openClose(\'author-' . $rnd . '\');">' . L10n :: t ( 'Click to open/close' ) . '</span>' .
'<blockquote class="author" id="author-' . $rnd . '" style="display: block;">' ;
$s = substr ( $s , 0 , $pos ) . $authorreplace . substr ( $s , $pos + strlen ( $authorsearch ));
}
// Replace friendica image url size with theme preference.
if ( x ( $a -> theme_info , 'item_image_size' )) {
$ps = $a -> theme_info [ 'item_image_size' ];
$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 ];
Addon :: callHooks ( 'prepare_body_final' , $hook_data );
return $hook_data [ 'html' ];
}
2018-11-07 07:19:39 -05:00
/**
* get private link for item
* @ param array $item
* @ return boolean | array False if item has not plink , otherwise array ( 'href' => plink url , 'title' => translated title )
*/
public static function getPlink ( $item )
{
$a = self :: getApp ();
if ( $a -> user [ 'nickname' ] != " " ) {
$ret = [
'href' => " display/ " . $item [ 'guid' ],
'orig' => " display/ " . $item [ 'guid' ],
'title' => L10n :: t ( 'View on separate page' ),
'orig_title' => L10n :: t ( 'view on separate page' ),
];
if ( x ( $item , 'plink' )) {
$ret [ " href " ] = $a -> removeBaseURL ( $item [ 'plink' ]);
$ret [ " title " ] = L10n :: t ( 'link to source' );
}
} elseif ( x ( $item , 'plink' ) && ( $item [ 'private' ] != 1 )) {
$ret = [
'href' => $item [ 'plink' ],
'orig' => $item [ 'plink' ],
'title' => L10n :: t ( 'link to source' ),
];
} else {
$ret = [];
}
return $ret ;
}
2018-01-09 16:13:45 -05:00
}