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.
 
 
 
 

505 lines
18 KiB

<?php
namespace App;
use Movim\Model;
use Movim\Picture;
use Movim\Route;
use Illuminate\Database\QueryException;
use Illuminate\Database\Capsule\Manager as DB;
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'];
protected $attributes = [
'type' => 'chat'
];
protected $casts = [
'quoted' => 'boolean',
'markable' => 'boolean'
];
public function save(array $options = [])
{
try {
parent::save($options);
} catch (\Exception $e) {
\Utils::error($e->getMessage());
}
}
public function user()
{
return $this->belongsTo('App\User');
}
public function scopeJid($query, string $jid)
{
$jidFromToMessages = DB::table('messages')
->where('user_id', \App\User::me()->id)
->where('jidfrom', $jid)
->unionAll(DB::table('messages')
->where('user_id', \App\User::me()->id)
->where('jidto', $jid)
);
return $query->select('*')->from(
$jidFromToMessages,
'messages'
)->where('user_id', \App\User::me()->id);
}
public function reactions()
{
return $this->hasMany('App\Reaction', 'message_mid', 'mid');
}
public function setFileAttribute(array $file)
{
$this->resolved = true;
$this->picture = typeIsPicture($file['type']);
$this->attributes['file'] = serialize($file);
}
public function getFileAttribute()
{
if (isset($this->attributes['file'])) {
$file = unserialize($this->attributes['file']);
if (\array_key_exists('size', $file)) {
$file['cleansize'] = sizeToCleanSize($file['size']);
}
return $file;
}
return null;
}
public function getJidfromAttribute()
{
return \unechap($this->attributes['jidfrom']);
}
public static function findByStanza($stanza)
{
$jidfrom = current(explode('/', (string)$stanza->attributes()->from));
/**
* If this stanza replaces another one, we load the original message
*/
if ($stanza->replace) {
return self::firstOrNew([
'user_id' => \App\User::me()->id,
'replaceid' => (string)$stanza->replace->attributes()->id,
'jidfrom' => $jidfrom
]);
}
/**
* If not we just create or load a message
*/
$id = ($stanza->{'stanza-id'} && $stanza->{'stanza-id'}->attributes()->id)
? (string)$stanza->{'stanza-id'}->attributes()->id
: 'm_' . generateUUID();
return self::firstOrNew([
'user_id' => \App\User::me()->id,
'id' => $id,
'jidfrom' => $jidfrom
]);
}
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->id = ($stanza->{'stanza-id'} && $stanza->{'stanza-id'}->attributes()->id)
? (string)$stanza->{'stanza-id'}->attributes()->id
: 'm_' . generateUUID();
if ($stanza->attributes()->id) {
$this->replaceid = $stanza->attributes()->id;
}
$jid = explode('/', (string)$stanza->attributes()->from);
$to = current(explode('/', (string)$stanza->attributes()->to));
$this->user_id = \App\User::me()->id;
if (!$this->jidto) {
$this->jidto = $to;
}
if (!$this->jidfrom) {
$this->jidfrom = $jid[0];
}
// If the message is from me
if ($this->jidfrom == $this->user_id) {
$this->seen = true;
}
if (isset($jid[1])) {
$this->resource = $jid[1];
}
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;
}
if (isset($jid[1])
&& $this->type !== 'groupchat'
&& $stanza->x
&& (string)$stanza->x->attributes()->xmlns == 'http://jabber.org/protocol/muc#user') {
$this->mucpm = true;
$this->jidfrom = $jid[0].'/'.$jid[1];
}
if ($stanza->body || $stanza->subject) {
/*if (isset($stanza->attributes()->id)) {
$this->id = (string)$stanza->attributes()->id;
}*/
if ($stanza->body) {
$this->body = (string)$stanza->body;
}
# HipChat MUC specific cards
if (in_array(
explodeJid($this->jidfrom)['server'],
['conf.hipchat.com', 'conf.btf.hipchat.com']
)
&& $this->type == 'groupchat'
&& $stanza->x
&& $stanza->x->attributes()->xmlns == 'http://hipchat.com/protocol/muc#room'
&& $stanza->x->card) {
$this->body = trim(html_entity_decode($this->body));
}
$this->markable = (bool)($stanza->markable);
if ($stanza->subject) {
$this->subject = (string)$stanza->subject;
}
if ($stanza->thread) {
$this->thread = (string)$stanza->thread;
}
if ($this->type == 'groupchat') {
$presence = $this->user->session->presences()
->where('jid', $this->jidfrom)
->where('mucjid', $this->user->id)
->first();
if ($presence
&& strpos($this->body, $presence->resource) !== false
&& $this->resource != $presence->resource) {
$this->quoted = true;
}
}
if ($stanza->html) {
$results = [];
$xml = \simplexml_load_string((string)$stanza->html);
if (!$xml) {
$xml = \simplexml_load_string((string)$stanza->html->body);
if ($xml) {
$results = $xml->xpath('//img/@src');
}
} else {
$xml->registerXPathNamespace('xhtml', 'http://www.w3.org/1999/xhtml');
$results = $xml->xpath('//xhtml:img/@src');
}
if (!empty($results)) {
if (substr((string)$results[0], 0, 10) == 'data:image') {
$str = explode('base64,', $results[0]);
if (isset($str[1])) {
$p = new Picture;
$p->fromBase(urldecode($str[1]));
$key = sha1(urldecode($str[1]));
$p->set($key, 'png');
$this->sticker = $key;
}
} else {
$this->sticker = getCid((string)$results[0]);
}
}
}
if ($stanza->reference
&& (string)$stanza->reference->attributes()->xmlns == 'urn:xmpp:reference:0') {
$filetmp = [];
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) {
$filetmp['type'] = (string)$file->{'media-type'};
}
$filetmp['size'] = (int)$file->size;
$filetmp['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) {
$filetmp['uri'] = (string)$source->attributes()->uri;
}
}
if (array_key_exists('uri', $filetmp)
&& array_key_exists('type', $filetmp)
&& array_key_exists('size', $filetmp)
&& array_key_exists('name', $filetmp)) {
if (empty($filetmp['name'])) {
$filetmp['name'] =
pathinfo(parse_url($filetmp['uri'], PHP_URL_PATH), PATHINFO_BASENAME)
. ' ('.parse_url($filetmp['uri'], PHP_URL_HOST).')';
}
$this->file = $filetmp;
}
} elseif (\in_array($stanza->reference->attributes()->type, ['mention', 'data'])
&& $stanza->reference->attributes()->uri) {
$uri = parse_url($stanza->reference->attributes()->uri);
if ($uri['scheme'] === 'xmpp') {
$begin = '<a href="' . Route::urlize('share', $stanza->reference->attributes()->uri) . '">';
if ($stanza->reference->attributes()->begin && $stanza->reference->attributes()->end) {
$this->html = substr_replace(
$this->body,
$begin,
(int)$stanza->reference->attributes()->begin,
0
);
$this->html = substr_replace(
$this->html,
'</a>',
(int)$stanza->reference->attributes()->end + strlen($begin),
0
);
} else {
$this->html = $begin . $this->body . '</a>';
}
$this->file = [
'type' => 'xmpp',
'uri' => (string)$stanza->reference->attributes()->uri,
];
}
}
}
if ($stanza->encryption
&& (string)$stanza->encryption->attributes()->xmlns == 'urn:xmpp:eme:0') {
$this->encrypted = true;
}
if ($stanza->{'origin-id'}
&& (string)$stanza->{'origin-id'}->attributes()->xmlns == 'urn:xmpp:sid:0') {
$this->originid = (string)$stanza->{'origin-id'}->attributes()->id;
}
if ($stanza->replace
&& $this->user->messages()
->where('jidfrom', $this->jidfrom)
->where('replaceid', $this->replaceid)
->count() == 0
) {
$message = $this->user->messages()
->where('jidfrom', $this->jidfrom)
->where('replaceid', (string)$stanza->replace->attributes()->id)
->first();
if ($message) {
$this->oldid = $message->id;
}
/**
* We prepare the existing message to be edited in the DB
*/
try {
Message::where('replaceid', (string)$stanza->replace->attributes()->id)
->where('user_id', $this->user_id)
->where('jidfrom', $this->jidfrom)
->update(['id' => $this->id]);
} catch (\Exception $e) {
\Utils::error($e->getMessage());
}
}
if (isset($stanza->x->invite)) {
$this->type = 'invitation';
$this->subject = $this->jidfrom;
$this->jidfrom = current(explode('/', (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-xxxx: Message Reactions
elseif (isset($stanza->reactions)
&& $stanza->reactions->attributes()->xmlns == 'urn:xmpp:reactions:0') {
$parentMessage = \App\Message::jid($this->jidfrom)
->where('replaceid', (string)$stanza->reactions->attributes()->to)
->first();
if ($parentMessage) {
$resource = ($this->type == 'groupchat')
? $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 $exception) {
// Duplicate ?
}
return $parentMessage;
}
}
return $this;
}
public function isEmpty()
{
return (empty($this->body)
&& empty($this->file)
&& empty($this->sticker)
);
}
public function isSubject()
{
return !empty($this->subject);
}
public function retract()
{
$this->retracted = true;
$this->oldid = null;
$this->body = $this->html = 'retracted';
}
public function addUrls()
{
if (is_string($this->body)) {
$old = $this->body;
$this->body = addUrls($this->body);
// TODO fix addUrls, see https://github.com/movim/movim/issues/877
if (strlen($this->body) < strlen($old)) {
$this->body = $old;
}
}
}
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);
}
public function toRawArray()
{
return [
'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,
'picture' => $this->attributes['picture'] ?? null,
'sticker' => $this->attributes['sticker'] ?? null,
'file' => isset($this->attributes['file']) ? $this->attributes['file'] : 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,
];
}
}