mirror of https://github.com/movim/movim
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
857 lines
30 KiB
857 lines
30 KiB
<?php
|
|
|
|
namespace App;
|
|
|
|
use DOMDocument;
|
|
use DOMXPath;
|
|
use Movim\Model;
|
|
use Movim\Image;
|
|
use Movim\Session;
|
|
|
|
use Illuminate\Database\QueryException;
|
|
use Illuminate\Database\Capsule\Manager as DB;
|
|
use Illuminate\Support\Collection;
|
|
use Movim\XMPPUri;
|
|
use Moxl\Xec\Action\BOB\Request;
|
|
|
|
class Message extends Model
|
|
{
|
|
protected $primaryKey = ['user_id', 'jidfrom', 'id'];
|
|
public $incrementing = false;
|
|
public $mucpm; // Only used in Message Payloads to detect composer/paused PM messages
|
|
|
|
protected $guarded = [];
|
|
|
|
protected $with = ['reactions', 'parent.from', 'resolvedUrl', 'replace', 'file'];
|
|
|
|
protected $attributes = [
|
|
'type' => 'chat'
|
|
];
|
|
|
|
protected $casts = [
|
|
'quoted' => 'boolean',
|
|
'markable' => 'boolean'
|
|
];
|
|
|
|
private ?Collection $messageFiles = null;
|
|
|
|
public static $inlinePlaceholder = 'inline-img:';
|
|
|
|
public const MESSAGE_TYPE = [
|
|
'chat',
|
|
'headline',
|
|
'invitation',
|
|
'jingle_incoming',
|
|
'jingle_outgoing',
|
|
'jingle_end',
|
|
'jingle_retract',
|
|
'jingle_reject'
|
|
];
|
|
public const MESSAGE_TYPE_MUC = [
|
|
'groupchat',
|
|
'muji_propose',
|
|
'muji_retract',
|
|
'muc_owner',
|
|
'muc_admin',
|
|
'muc_outcast',
|
|
'muc_member'
|
|
];
|
|
|
|
public static function boot()
|
|
{
|
|
parent::boot();
|
|
|
|
static::saved(function (Message $message) {
|
|
if ($message->messageFiles != null && $message->messageFiles->isNotEmpty()) {
|
|
$mid = Message::where('id', $message->id)
|
|
->where('user_id', me()->id)
|
|
->where('jidfrom', $message->jidfrom)
|
|
->first()
|
|
->mid;
|
|
|
|
MessageFile::where('message_mid', $mid)->delete();
|
|
|
|
$message->messageFiles->each(function ($file) use ($mid) {
|
|
$file->message_mid = $mid;
|
|
$file->save();
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
public function parent()
|
|
{
|
|
return $this->belongsTo('App\Message', 'parentmid', 'mid');
|
|
}
|
|
|
|
public function replace()
|
|
{
|
|
return $this->belongsTo('App\Message', 'replaceid', 'originid')->without('replace');
|
|
}
|
|
|
|
public function resolvedUrl()
|
|
{
|
|
return $this->belongsTo('App\Url', 'urlid', 'id');
|
|
}
|
|
|
|
public function from()
|
|
{
|
|
return $this->belongsTo('App\Contact', 'jidfrom', 'id');
|
|
}
|
|
|
|
public function user()
|
|
{
|
|
return $this->belongsTo('App\User');
|
|
}
|
|
|
|
public function post()
|
|
{
|
|
return $this->belongsTo('App\Post', 'postid', 'id');
|
|
}
|
|
|
|
public function scopeJid($query, string $jid)
|
|
{
|
|
$jidFromToMessages = DB::table('messages')
|
|
->where('user_id', me()->id)
|
|
->where('jidfrom', $jid)
|
|
->unionAll(
|
|
DB::table('messages')
|
|
->where('user_id', me()->id)
|
|
->where('jidto', $jid)
|
|
);
|
|
|
|
return $query->select('*')->from(
|
|
$jidFromToMessages,
|
|
'messages'
|
|
)->where('user_id', me()->id);
|
|
}
|
|
|
|
public function reactions()
|
|
{
|
|
return $this->hasMany('App\Reaction', 'message_mid', 'mid');
|
|
}
|
|
|
|
public function file()
|
|
{
|
|
return $this->hasOne('App\MessageFile', 'message_mid', 'mid');
|
|
}
|
|
|
|
public function files()
|
|
{
|
|
return $this->hasMany('App\MessageFile', 'message_mid', 'mid');
|
|
}
|
|
|
|
public function getStickerImageAttribute(): ?Image
|
|
{
|
|
$image = new Image;
|
|
$image->setKey($this->sticker_cid_hash);
|
|
|
|
if ($image->load()) {
|
|
return $image;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function getInlinesAttribute(): ?array
|
|
{
|
|
return array_key_exists('inlines', $this->attributes) && $this->attributes['inlines'] !== null
|
|
? unserialize($this->attributes['inlines'])
|
|
: null;
|
|
}
|
|
|
|
public function getOmemoheaderAttribute()
|
|
{
|
|
return array_key_exists('omemoheader', $this->attributes) && $this->attributes['omemoheader'] !== null
|
|
? unserialize($this->attributes['omemoheader'])
|
|
: null;
|
|
}
|
|
|
|
public function getJidfromAttribute()
|
|
{
|
|
return \unechap($this->attributes['jidfrom']);
|
|
}
|
|
|
|
public function getJidAttribute()
|
|
{
|
|
return $this->attributes['jidfrom'] == me()->id
|
|
? \unechap($this->attributes['jidto'])
|
|
: \unechap($this->attributes['jidfrom']);
|
|
}
|
|
|
|
public static function findByStanza(?\SimpleXMLElement $stanza = null, ?\SimpleXMLElement $parent = null): Message
|
|
{
|
|
$jidfrom = baseJid((string)$stanza->attributes()->from);
|
|
|
|
if (
|
|
$stanza->attributes()->xmlns
|
|
&& $stanza->attributes()->xmlns == 'urn:xmpp:mam:2'
|
|
) {
|
|
return self::firstOrNew([
|
|
'user_id' => me()->id,
|
|
'stanzaid' => (string)$stanza->attributes()->id,
|
|
'jidfrom' => baseJid((string)$stanza->forwarded->message->attributes()->from)
|
|
]);
|
|
} elseif (
|
|
$stanza->{'stanza-id'} && $stanza->{'stanza-id'}->attributes()->id
|
|
&& ($stanza->{'stanza-id'}->attributes()->by == $jidfrom
|
|
|| $stanza->{'stanza-id'}->attributes()->by == me()->id
|
|
)
|
|
) {
|
|
return self::firstOrNew([
|
|
'user_id' => me()->id,
|
|
'stanzaid' => (string)$stanza->{'stanza-id'}->attributes()->id,
|
|
'jidfrom' => $jidfrom
|
|
]);
|
|
} else {
|
|
$message = new Message;
|
|
$message->user_id = me()->id;
|
|
$message->id = 'm_' . generateUUID();
|
|
$message->jidfrom = $jidfrom;
|
|
return $message;
|
|
}
|
|
}
|
|
|
|
public static function getLast(string $to, bool $muc = false): ?Message
|
|
{
|
|
$m = null;
|
|
|
|
if ($muc) {
|
|
// Resolve the current presence
|
|
$presence = me()->session->presences()
|
|
->where('jid', $to)
|
|
->where('muc', true)
|
|
->where('mucjid', me()->id)
|
|
->first();
|
|
|
|
if ($presence) {
|
|
$m = me()->messages()
|
|
->where('type', 'groupchat')
|
|
->where('jidfrom', $to)
|
|
->where('jidto', me()->id)
|
|
->where('resource', $presence->resource)
|
|
->orderBy('published', 'desc')
|
|
->first();
|
|
}
|
|
} else {
|
|
$m = me()->messages()
|
|
->where('jidto', $to)
|
|
->orderBy('published', 'desc')
|
|
->first();
|
|
}
|
|
|
|
return $m;
|
|
}
|
|
|
|
public function isLast(): bool
|
|
{
|
|
$last = Message::getLast($this->isMuc() ? $this->jidfrom : $this->jidto, $this->isMuc());
|
|
|
|
return ($last && $this->mid == $last->mid);
|
|
}
|
|
|
|
public static function eventMessageFactory(string $type, string $from, string $thread): Message
|
|
{
|
|
$userid = me()->id;
|
|
$message = new \App\Message;
|
|
$message->user_id = $userid;
|
|
$message->id = 'm_' . generateUUID();
|
|
$message->jidto = $userid;
|
|
$message->jidfrom = $from;
|
|
$message->published = gmdate('Y-m-d H:i:s');
|
|
$message->thread = $thread;
|
|
$message->type = $type;
|
|
|
|
return $message;
|
|
}
|
|
|
|
public function clearUnreads()
|
|
{
|
|
if ($this->jidfrom == $this->user_id) {
|
|
$this->user->messages()
|
|
->where('jidfrom', $this->jidto)
|
|
->where('seen', false)
|
|
->update(['seen' => true]);
|
|
}
|
|
}
|
|
|
|
public function set($stanza, $parent = false)
|
|
{
|
|
$this->messageFiles = collect();
|
|
|
|
// We reset the URL resolution to refresh it once the message is displayed
|
|
$this->resolved = false;
|
|
|
|
$jidTo = explodeJid((string)$stanza->attributes()->to);
|
|
$jidFrom = explodeJid((string)$stanza->attributes()->from);
|
|
|
|
$this->user_id = me()->id;
|
|
|
|
if (!$this->id) {
|
|
$this->id = 'm_' . generateUUID();
|
|
}
|
|
|
|
if ($stanza->attributes()->id) {
|
|
$this->messageid = (string)$stanza->attributes()->id;
|
|
}
|
|
|
|
if (!$this->jidto) {
|
|
$this->jidto = $jidTo['jid'];
|
|
}
|
|
|
|
if (!$this->jidfrom) {
|
|
$this->jidfrom = $jidFrom['jid'];
|
|
}
|
|
|
|
// If the message is from me
|
|
if ($this->jidfrom == $this->user_id) {
|
|
$this->seen = true;
|
|
}
|
|
|
|
if (isset($jidFrom['resource'])) {
|
|
$this->resource = $jidFrom['resource'];
|
|
}
|
|
|
|
if ($stanza->delay) {
|
|
$this->published = gmdate('Y-m-d H:i:s', strtotime($stanza->delay->attributes()->stamp));
|
|
} elseif ($parent && $parent->delay) {
|
|
$this->published = gmdate('Y-m-d H:i:s', strtotime($parent->delay->attributes()->stamp));
|
|
} elseif (!isset($stanza->replace) || $this->published === null) {
|
|
$this->published = gmdate('Y-m-d H:i:s');
|
|
}
|
|
|
|
$this->type = 'chat';
|
|
if ($stanza->attributes()->type) {
|
|
$this->type = (string)$stanza->attributes()->type;
|
|
}
|
|
|
|
// https://xmpp.org/extensions/xep-0359.html#stanza-id
|
|
if (
|
|
$stanza->{'origin-id'}
|
|
&& (string)$stanza->{'origin-id'}->attributes()->xmlns == 'urn:xmpp:sid:0'
|
|
) {
|
|
$this->originid = (string)$stanza->{'origin-id'}->attributes()->id;
|
|
}
|
|
|
|
// https://xmpp.org/extensions/xep-0359.html#origin-id for groupchat only
|
|
if (
|
|
$this->isMuc()
|
|
&& $stanza->{'stanza-id'}
|
|
&& $stanza->{'stanza-id'}->attributes()->id
|
|
&& (string)$stanza->{'stanza-id'}->attributes()->xmlns == 'urn:xmpp:sid:0'
|
|
&& ($stanza->{'stanza-id'}->attributes()->by == $this->jidfrom
|
|
|| $stanza->{'stanza-id'}->attributes()->by == me()->id
|
|
)
|
|
) {
|
|
if ($this->isMuc()) {
|
|
$session = Session::instance();
|
|
|
|
// Cache the state in Session for performances purpose
|
|
$sessionKey = $this->jidfrom . '_stanza_id';
|
|
$conferenceStanzaIdEnabled = $session->get($sessionKey, null);
|
|
|
|
if ($conferenceStanzaIdEnabled == null) {
|
|
$conference = $this->user->session->conferences()
|
|
->where('conference', $this->jidfrom)
|
|
->first();
|
|
|
|
$session->set($sessionKey, $conference && $conference->info && $conference->info->hasStanzaId());
|
|
}
|
|
|
|
if ($session->get($sessionKey, false)) {
|
|
$this->stanzaid = (string)$stanza->{'stanza-id'}->attributes()->id;
|
|
}
|
|
} else {
|
|
$this->stanzaid = (string)$stanza->{'stanza-id'}->attributes()->id;
|
|
}
|
|
}
|
|
|
|
// If it's a MUC message, we assume that the server already handled it
|
|
if ($this->isMuc()) {
|
|
$this->delivered = gmdate('Y-m-d H:i:s');
|
|
}
|
|
|
|
if (
|
|
$this->type !== 'groupchat'
|
|
&& $stanza->x
|
|
&& (string)$stanza->x->attributes()->xmlns == 'http://jabber.org/protocol/muc#user'
|
|
) {
|
|
$this->mucpm = true;
|
|
if ($parent && (string)$parent->attributes()->xmlns == 'urn:xmpp:forward:0') {
|
|
$this->jidto = (string)$stanza->attributes()->to;
|
|
} elseif (isset($jidFrom['resource'])) {
|
|
$this->jidfrom = $jidFrom['jid'] . '/' . $jidFrom['resource'];
|
|
}
|
|
}
|
|
|
|
# XEP-0444: Message Reactions
|
|
if (
|
|
isset($stanza->reactions)
|
|
&& $stanza->reactions->attributes()->xmlns == 'urn:xmpp:reactions:0'
|
|
) {
|
|
$parentMessage = $this->resolveParentMessage($this->jidfrom, (string)$stanza->reactions->attributes()->id);
|
|
|
|
if ($parentMessage) {
|
|
$resource = $this->isMuc()
|
|
? $this->resource
|
|
: $this->jidfrom;
|
|
|
|
$parentMessage
|
|
->reactions()
|
|
->where('jidfrom', $resource)
|
|
->delete();
|
|
|
|
$emojis = [];
|
|
$now = \Carbon\Carbon::now();
|
|
$emoji = \Movim\Emoji::getInstance();
|
|
|
|
foreach ($stanza->reactions->reaction as $children) {
|
|
$emoji->replace((string)$children);
|
|
if ($emoji->isSingleEmoji()) {
|
|
$reaction = new Reaction;
|
|
$reaction->message_mid = $parentMessage->mid;
|
|
$reaction->emoji = (string)$children;
|
|
$reaction->jidfrom = $resource;
|
|
$reaction->created_at = $now;
|
|
$reaction->updated_at = $now;
|
|
|
|
\array_push($emojis, $reaction->toArray());
|
|
}
|
|
}
|
|
|
|
try {
|
|
Reaction::insert($emojis);
|
|
} catch (QueryException $e) {
|
|
// Duplicate ?
|
|
logError($e);
|
|
}
|
|
|
|
return $parentMessage;
|
|
}
|
|
|
|
return null;
|
|
} elseif ($stanza->body || $stanza->subject) {
|
|
if ($stanza->body) {
|
|
$this->body = (string)$stanza->body;
|
|
}
|
|
|
|
if ($stanza->subject) {
|
|
$this->subject = (string)$stanza->subject;
|
|
}
|
|
|
|
if ($stanza->thread) {
|
|
$this->thread = (string)$stanza->thread;
|
|
}
|
|
|
|
// XEP-0333: Chat Markers
|
|
$this->markable = (bool)($stanza->markable && $stanza->markable->attributes()->xmlns == 'urn:xmpp:chat-markers:0');
|
|
|
|
// Reply can be handled by XEP-0461: Message Replies or by the threadid Jabber mechanism
|
|
if ($stanza->reply && $stanza->reply->attributes()->xmlns == 'urn:xmpp:reply:0') {
|
|
$parentMessage = $this->resolveParentMessage($this->jidfrom, (string)$stanza->reply->attributes()->id);
|
|
|
|
if (
|
|
$parentMessage && $parentMessage->mid != $this->mid
|
|
&& $parentMessage->originid != $this->originid
|
|
) {
|
|
$this->parentmid = $parentMessage->mid;
|
|
}
|
|
|
|
if (
|
|
$stanza->fallback && $stanza->fallback->attributes()->xmlns == 'urn:xmpp:fallback:0'
|
|
&& $stanza->fallback->attributes()->for == 'urn:xmpp:reply:0'
|
|
) {
|
|
$this->body = mb_substr(
|
|
htmlspecialchars_decode($this->body, ENT_XML1),
|
|
(int)$stanza->fallback->body->attributes()->end
|
|
);
|
|
}
|
|
}
|
|
|
|
if ($this->isMuc()) {
|
|
$presence = $this->user->session?->presences()
|
|
->where('jid', $this->jidfrom)
|
|
->where('mucjid', $this->user->id)
|
|
->first();
|
|
|
|
if (
|
|
$presence
|
|
&& $this->body != null
|
|
&& strpos($this->body, $presence->resource) !== false
|
|
&& $this->resource != $presence->resource
|
|
) {
|
|
$this->quoted = true;
|
|
}
|
|
}
|
|
|
|
if (
|
|
$stanza->html
|
|
&& (string)$stanza->html->attributes()->xmlns = 'http://jabber.org/protocol/xhtml-im'
|
|
) {
|
|
$head = '<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head>';
|
|
$dom = new DOMDocument('1.0', 'UTF-8');
|
|
$dom->loadHTML($head . (string)$stanza->html->body);
|
|
$xpath = new DOMXPath($dom);
|
|
$imgs = $xpath->query("//img[starts-with(@src,'cid:')]");
|
|
|
|
if ($imgs && $imgs->count() >= 1) {
|
|
$texts = $xpath->query('//p/text()');
|
|
|
|
// Message with inline images
|
|
if (($imgs->count() > 1) || ($texts && $texts->count() > 0)) {
|
|
$inlines = [];
|
|
|
|
foreach ($imgs as $img) {
|
|
$key = generateKey(12);
|
|
$cid = getCid($img->getAttribute('src'));
|
|
|
|
$inlines[$key] = [
|
|
'hash' => $cid['hash'],
|
|
'algorythm' => $cid['algorythm'],
|
|
'alt' => $img->getAttribute('alt'),
|
|
];
|
|
$img->replaceWith(self::$inlinePlaceholder . $key);
|
|
}
|
|
|
|
$this->attributes['inlines'] = serialize($inlines);
|
|
$this->body = (string)$dom->textContent;
|
|
}
|
|
|
|
// One sticker only
|
|
elseif ($imgs->count() == 1) {
|
|
$cid = getCid($imgs->item(0)->getAttribute('src'));
|
|
|
|
if ($cid) {
|
|
$this->sticker_cid_hash = $cid['hash'];
|
|
$this->sticker_cid_algorythm = $cid['algorythm'];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// XEP-0385: Stateless Inline Media Sharing (SIMS)
|
|
if (
|
|
$stanza->reference
|
|
&& (string)$stanza->reference->attributes()->xmlns == 'urn:xmpp:reference:0'
|
|
) {
|
|
$messageFile = new MessageFile;
|
|
|
|
if (
|
|
$stanza->reference->{'media-sharing'}
|
|
&& (string)$stanza->reference->{'media-sharing'}->attributes()->xmlns == 'urn:xmpp:sims:1'
|
|
) {
|
|
$file = $stanza->reference->{'media-sharing'}->file;
|
|
|
|
if (isset($file)) {
|
|
if (preg_match('/\w+\/[-+.\w]+/', $file->{'media-type'}) == 1) {
|
|
$messageFile->type = (string)$file->{'media-type'};
|
|
}
|
|
$messageFile->size = (int)$file->size;
|
|
$messageFile->name = (string)$file->name;
|
|
}
|
|
|
|
if ($stanza->reference->{'media-sharing'}->sources) {
|
|
$source = $stanza->reference->{'media-sharing'}->sources->reference;
|
|
|
|
if (!filter_var((string)$source->attributes()->uri, FILTER_VALIDATE_URL) === false) {
|
|
$messageFile->url = (string)$source->attributes()->uri;
|
|
}
|
|
}
|
|
|
|
if (
|
|
$stanza->reference->{'media-sharing'}->file->thumbnail
|
|
&& (string)$stanza->reference->{'media-sharing'}->file->thumbnail->attributes()->xmlns == 'urn:xmpp:thumbs:1'
|
|
) {
|
|
$thumbnailAttributes = $stanza->reference->{'media-sharing'}->file->thumbnail->attributes();
|
|
|
|
if (!filter_var((string)$thumbnailAttributes->uri, FILTER_VALIDATE_URL) === false) {
|
|
$messageFile->thumbnail_width = (int)$thumbnailAttributes->width;
|
|
$messageFile->thumbnail_height = (int)$thumbnailAttributes->height;
|
|
$messageFile->thumbnail_type = (string)$thumbnailAttributes->{'media-type'};
|
|
$messageFile->thumbnail_url = (string)$thumbnailAttributes->uri;
|
|
}
|
|
|
|
if (substr((string)$thumbnailAttributes->uri, 0, 21) == 'data:image/thumbhash,') {
|
|
$messageFile->thumbnail_width = (int)$thumbnailAttributes->width;
|
|
$messageFile->thumbnail_height = (int)$thumbnailAttributes->height;
|
|
$messageFile->thumbnail_type = (string)$thumbnailAttributes->{'media-type'};
|
|
$messageFile->thumbnail_url = substr((string)$thumbnailAttributes->uri, 21);
|
|
}
|
|
}
|
|
|
|
if (
|
|
$messageFile->url
|
|
&& $messageFile->type
|
|
&& $messageFile->size
|
|
&& $messageFile->name
|
|
) {
|
|
if (empty($messageFile->name)) {
|
|
$messageFile->name =
|
|
pathinfo(parse_url($messageFile->uri, PHP_URL_PATH), PATHINFO_BASENAME)
|
|
. ' (' . parse_url($messageFile->uri, PHP_URL_HOST) . ')';
|
|
}
|
|
|
|
$this->picture = $messageFile->isPicture;
|
|
$this->messageFiles->push($messageFile);
|
|
}
|
|
} else {
|
|
$xmppUri = new XMPPUri((string)$stanza->reference->attributes()->uri);
|
|
|
|
if ($post = $xmppUri->getPost()) {
|
|
$this->postid = $post->id;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (
|
|
$stanza->encryption
|
|
&& (string)$stanza->encryption->attributes()->xmlns == 'urn:xmpp:eme:0'
|
|
) {
|
|
$this->encrypted = true;
|
|
}
|
|
|
|
if (
|
|
$stanza->replace
|
|
&& (string)$stanza->replace->attributes()->xmlns == 'urn:xmpp:message-correct:0'
|
|
) {
|
|
// Here the replaceid could be a bad one, we will handle it later
|
|
$this->replaceid = (string)$stanza->replace->attributes()->id;
|
|
}
|
|
|
|
if (isset($stanza->x->invite)) {
|
|
$this->type = 'invitation';
|
|
$this->subject = $this->jidfrom;
|
|
$this->jidfrom = baseJid((string)$stanza->x->invite->attributes()->from);
|
|
}
|
|
} elseif (
|
|
isset($stanza->x)
|
|
&& $stanza->x->attributes()->xmlns == 'jabber:x:conference'
|
|
) {
|
|
$this->type = 'invitation';
|
|
$this->body = (string)$stanza->x->attributes()->reason;
|
|
$this->subject = (string)$stanza->x->attributes()->jid;
|
|
}
|
|
|
|
# XEP-0384 OMEMO Encryption
|
|
if (
|
|
isset($stanza->encrypted)
|
|
&& $stanza->encrypted->attributes()->xmlns == 'eu.siacs.conversations.axolotl'
|
|
) {
|
|
$omemoHeader = new MessageOmemoHeader;
|
|
$omemoHeader->set($stanza);
|
|
$this->attributes['omemoheader'] = (string)$omemoHeader;
|
|
$this->attributes['bundleid'] = (int)$omemoHeader->sid;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @desc Prepare and return the body with inline images, and request them if missing
|
|
*/
|
|
public function getInlinedBodyAttribute(?bool $alt = false, bool $triggerRequest = false): ?string
|
|
{
|
|
if (!array_key_exists('body', $this->attributes)) return null;
|
|
|
|
$body = $this->attributes['body'];
|
|
|
|
if (is_array($this->getInlinesAttribute())) {
|
|
foreach ($this->getInlinesAttribute() as $key => $inline) {
|
|
if ($alt == true) {
|
|
$body = str_replace(
|
|
Message::$inlinePlaceholder . $key,
|
|
$inline['alt'],
|
|
$body
|
|
);
|
|
|
|
continue;
|
|
}
|
|
|
|
$url = Image::getOrCreate($inline['hash']);
|
|
|
|
if ($url) {
|
|
$dom = new \DOMDocument('1.0', 'UTF-8');
|
|
$img = $dom->createElement('img');
|
|
$img->setAttribute('class', 'inline');
|
|
$img->setAttribute('src', $url);
|
|
$img->setAttribute('alt', $inline['alt']);
|
|
$img->setAttribute('title', $inline['alt']);
|
|
$dom->append($img);
|
|
|
|
$body = str_replace(
|
|
Message::$inlinePlaceholder . $key,
|
|
$dom->saveHTML($dom->documentElement),
|
|
$body
|
|
);
|
|
} else {
|
|
$body = str_replace(
|
|
Message::$inlinePlaceholder . $key,
|
|
$inline['alt'],
|
|
$body
|
|
);
|
|
|
|
if ($triggerRequest) {
|
|
$r = new Request;
|
|
$r->setTo($this->attributes['jidfrom'])
|
|
->setResource($this->attributes['resource'])
|
|
->setHash($inline['hash'])
|
|
->setAlgorythm($inline['algorythm'])
|
|
->request();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $body;
|
|
}
|
|
|
|
public function isEmpty(): bool
|
|
{
|
|
return (empty($this->body)
|
|
&& empty($this->sticker)
|
|
&& !$this->file
|
|
);
|
|
}
|
|
|
|
public function isMuc(): bool
|
|
{
|
|
return ($this->type == 'groupchat');
|
|
}
|
|
|
|
public function isSubject(): bool
|
|
{
|
|
return !empty($this->subject);
|
|
}
|
|
|
|
public function isMine(): bool
|
|
{
|
|
if ($this->isMuc()) {
|
|
return $this->user->session->presences()
|
|
->where('jid', $this->jidfrom)
|
|
->where('resource', $this->resource)
|
|
->where('mucjid', $this->user_id)
|
|
->where('muc', true)
|
|
->count() > 0;
|
|
}
|
|
|
|
return ($this->user_id == $this->jidfrom);
|
|
}
|
|
|
|
public function isClassic(): bool
|
|
{
|
|
return in_array($this->type, ['chat', 'groupchat']);
|
|
}
|
|
|
|
public function retract()
|
|
{
|
|
$this->retracted = true;
|
|
$this->oldid = null;
|
|
$this->body = $this->html = 'retracted';
|
|
}
|
|
|
|
public function addUrls()
|
|
{
|
|
if (is_string($this->body)) {
|
|
$this->body = addUrls($this->body);
|
|
}
|
|
}
|
|
|
|
public function resolveColor(): string
|
|
{
|
|
$this->color = stringToColor($this->resource);
|
|
|
|
return $this->color;
|
|
}
|
|
|
|
public function valid()
|
|
{
|
|
return
|
|
strlen($this->attributes['jidto']) < 256
|
|
&& strlen($this->attributes['jidfrom']) < 256
|
|
&& (!isset($this->attributes['resource']) || strlen($this->attributes['resource']) < 256)
|
|
&& (!isset($this->attributes['thread']) || strlen($this->attributes['thread']) < 128)
|
|
&& (!isset($this->attributes['replaceid']) || strlen($this->attributes['replaceid']) < 64)
|
|
&& (!isset($this->attributes['originid']) || strlen($this->attributes['originid']) < 255)
|
|
&& (!isset($this->attributes['id']) || strlen($this->attributes['id']) < 64)
|
|
&& (!isset($this->attributes['oldid']) || strlen($this->attributes['oldid']) < 64);
|
|
}
|
|
|
|
// toArray is already used
|
|
public function toRawArray()
|
|
{
|
|
$array = [
|
|
'user_id' => $this->attributes['user_id'] ?? null,
|
|
'id' => $this->attributes['id'] ?? null,
|
|
'oldid' => $this->attributes['oldid'] ?? null,
|
|
'jidto' => $this->attributes['jidto'] ?? null,
|
|
'jidfrom' => $this->attributes['jidfrom'] ?? null,
|
|
'resource' => $this->attributes['resource'] ?? null,
|
|
'type' => $this->attributes['type'] ?? null,
|
|
'subject' => $this->attributes['subject'] ?? null,
|
|
'thread' => $this->attributes['thread'] ?? null,
|
|
'body' => $this->attributes['body'] ?? null,
|
|
'html' => $this->attributes['html'] ?? null,
|
|
'published' => $this->attributes['published'] ?? null,
|
|
'delivered' => $this->attributes['deliver'] ?? null,
|
|
'displayed' => $this->attributes['displayed'] ?? null,
|
|
'quoted' => $this->attributes['quoted'] ?? false,
|
|
'markable' => $this->attributes['markable'] ?? false,
|
|
'sticker' => $this->attributes['sticker'] ?? null,
|
|
'created_at' => $this->attributes['created_at'] ?? null,
|
|
'updated_at' => $this->attributes['updated_at'] ?? null,
|
|
'replaceid' => $this->attributes['replaceid'] ?? null,
|
|
'seen' => $this->attributes['seen'] ?? false,
|
|
'encrypted' => $this->attributes['encrypted'] ?? false,
|
|
'originid' => $this->attributes['originid'] ?? null,
|
|
'retracted' => $this->attributes['retracted'] ?? false,
|
|
'resolved' => $this->attributes['resolved'] ?? false,
|
|
'picture' => $this->attributes['picture'] ?? false,
|
|
'parentmid' => $this->attributes['parentmid'] ?? null,
|
|
'inline' => $this->attributes['inlines'] ?? null,
|
|
];
|
|
|
|
// Generate a proper mid
|
|
if (
|
|
empty($this->attributes['mid'])
|
|
|| ($this->attributes['mid'] && $this->attributes['mid'] == 1)
|
|
) {
|
|
$array['mid'] = $this->getNextStatementId();
|
|
} else {
|
|
$array['mid'] = $this->attributes['mid'];
|
|
}
|
|
|
|
return $array;
|
|
}
|
|
|
|
public function getNextStatementId()
|
|
{
|
|
$next_id = DB::select("select nextval('messages_mid_seq'::regclass)");
|
|
return intval($next_id['0']->nextval);
|
|
}
|
|
|
|
// https://xmpp.org/extensions/xep-0444.html#business-id
|
|
// https://xmpp.org/extensions/xep-0461.html#business-id
|
|
private function resolveParentMessage(string $from, string $id): ?Message
|
|
{
|
|
$parentMessage = null;
|
|
|
|
if ($this->isMuc()) {
|
|
$parentMessage = $this->user->messages()->jid($from)
|
|
->where('stanzaid', $id)
|
|
->first();
|
|
} else {
|
|
$parentMessage = $this->user->messages()->jid($from)
|
|
->where('messageid', $id)
|
|
->first();
|
|
|
|
// Rare case, origin-id
|
|
if (!$parentMessage) {
|
|
$parentMessage = $this->user->messages()->jid($from)
|
|
->where('originid', $id)
|
|
->first();
|
|
}
|
|
}
|
|
|
|
return $parentMessage;
|
|
}
|
|
}
|