Browse Source

MUC Members handling for private chats

pull/978/head
Timothée Jaussoin 5 years ago
parent
commit
ba1da95ae6
  1. 36
      app/Conference.php
  2. 36
      app/Member.php
  3. 2
      app/User.php
  4. 1
      app/views/about.tpl
  5. 1
      app/views/admin.tpl
  6. 4
      app/views/chat.tpl
  7. 1
      app/views/community.tpl
  8. 1
      app/views/conf.tpl
  9. 1
      app/views/help.tpl
  10. 56
      app/widgets/Chat/_chat_header.tpl
  11. 3
      app/widgets/Chats/chats.tpl
  12. 9
      app/widgets/Rooms/Rooms.php
  13. 23
      app/widgets/Rooms/_rooms_room.tpl
  14. 17
      app/widgets/Rooms/locales.ini
  15. 4
      app/widgets/Rooms/rooms.css
  16. 16
      app/widgets/Rooms/rooms.tpl
  17. 14
      app/widgets/RoomsExplore/_roomsexplore.tpl
  18. 20
      app/widgets/RoomsUtils/RoomsUtils.php
  19. 97
      app/widgets/RoomsUtils/_rooms_add.tpl
  20. 331
      app/widgets/RoomsUtils/_rooms_drawer.tpl
  21. 14
      app/widgets/RoomsUtils/_rooms_invite.tpl
  22. 3
      app/widgets/Search/Search.php
  23. 31
      app/widgets/Search/_search.tpl
  24. 11
      app/widgets/Search/_search_roster.tpl
  25. 5
      app/widgets/Tabs/tabs.js
  26. 1
      app/widgets/Tabs/tabs.tpl
  27. 31
      database/migrations/20201005202613_create_members_table.php
  28. 137
      lib/moxl/src/Stanza/Muc.php
  29. 24
      lib/moxl/src/Xec/Action/Muc/CreateChannel.php
  30. 24
      lib/moxl/src/Xec/Action/Muc/CreateGroupChat.php
  31. 59
      lib/moxl/src/Xec/Action/Muc/GetMembers.php
  32. 2
      lib/moxl/src/Xec/Handler.php
  33. 36
      lib/moxl/src/Xec/Payload/MucUser.php
  34. 6
      public/theme/css/form.css

36
app/Conference.php

@ -66,6 +66,13 @@ class Conference extends Model
->where('mucjid', \App\User::me()->id);
}
public function members()
{
return $this->hasMany('App\Member', 'conference', 'conference')
->orderBy('role')
->orderBy('affiliation', 'desc');
}
public function pictures()
{
return $this->hasMany('App\Message', 'jidfrom', 'conference')
@ -124,6 +131,25 @@ class Conference extends Model
return self::$notifications[$this->notify];
}
public function getTitleAttribute()
{
if ($this->isGroupChat() && $this->members()->count() > 0) {
$title = '';
$i = 0;
foreach ($this->members()->take(3)->get() as $member) {
$title .= $member->truename;
if ($i < 2) $title .= ', ';
$i++;
}
if ($this->members()->count() > 3) $title .= '…';
return $title;
}
return $this->name;
}
public function getSubjectAttribute()
{
$subject = \App\User::me()
@ -145,4 +171,14 @@ class Conference extends Model
return false;
}
// https://docs.modernxmpp.org/client/groupchat/#types-of-chat
public function isGroupChat()
{
if ($this->info) {
return $this->info->mucmembersonly && !$this->info->mucsemianonymous;
}
return false;
}
}

36
app/Member.php

@ -0,0 +1,36 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Member extends Model
{
public function save(array $options = [])
{
try {
parent::save($options);
} catch (\Exception $e) {
/*
* When a member is received by two accounts simultaenously
* in different processes they can be saved using the insert state
* in the DB causing an error
*/
}
}
public function contact()
{
return $this->hasOne('App\Contact', 'id', 'jid');
}
public function getTruenameAttribute()
{
if ($this->contact && $this->contact->truename) {
return $this->contact->truename;
}
return explodeJid($this->jid)['username'] ?? $this->jid;
}
}

2
app/User.php

@ -53,7 +53,7 @@ class User extends Model
->where('seen', false)
->where('jidfrom', '!=', $this->id)
->where(function ($query) use ($quoted) {
$query->whereIn('type', ['chat', 'headline'])
$query->whereIn('type', ['chat', 'headline', 'invitation'])
->orWhere(function ($query) use ($quoted) {
$query->where('type', 'groupchat')
->whereNull('subject');

1
app/views/about.tpl

@ -15,6 +15,7 @@
</ul>
</header>
<?php $this->widget('Tabs');?>
<ul class="tabs" id="navtabs"></ul>
<?php $this->widget('About');?>
<?php $this->widget('Help');?>

1
app/views/admin.tpl

@ -15,6 +15,7 @@
</ul>
</header>
<?php $this->widget('Tabs');?>
<ul class="tabs" id="navtabs"></ul>
<?php $this->widget('AdminTest');?>
<?php $this->widget('AdminMain');?>
<?php $this->widget('AdminSessions');?>

4
app/views/chat.tpl

@ -18,6 +18,9 @@
<?php $this->widget('Chat');?>
<?php $this->widget('ChatActions');?>
<div id="scroll_block">
<a class="button action color" onclick="Search_ajaxRequest(true)">
<i class="material-icons">add</i>
</a>
<?php $this->widget('Chats');?>
<?php $this->widget('Rooms');?>
<?php $this->widget('RoomsUtils');?>
@ -25,5 +28,6 @@
</main>
<?php $this->widget('Snap');?>
<?php $this->widget('Tabs');?>
<?php $this->widget('Draw');?>
<?php $this->widget('RoomsExplore');?>

1
app/views/community.tpl

@ -29,6 +29,7 @@
</ul>
</header>
<?php $this->widget('Tabs');?>
<ul class="tabs" id="navtabs"></ul>
<?php $this->widget('Communities'); ?>
<?php if (\App\User::me()->hasPubsub()) { ?>
<?php $this->widget('CommunitySubscriptions'); ?>

1
app/views/conf.tpl

@ -28,6 +28,7 @@
</header>
<?php $this->widget('Tabs');?>
<ul class="tabs" id="navtabs"></ul>
<?php $this->widget('Vcard4');?>
<?php if (\App\User::me()->hasPubsub()) { ?>
<?php $this->widget('Avatar');?>

1
app/views/help.tpl

@ -25,6 +25,7 @@
</ul>
</header>
<?php $this->widget('Tabs');?>
<ul class="tabs" id="navtabs"></ul>
<?php $this->widget('Help');?>
<?php $this->widget('About');?>
</div>

56
app/widgets/Chat/_chat_header.tpl

@ -30,7 +30,12 @@
{$conference->name|firstLetterCapitalize|addEmojis}
{/autoescape}
{/if}
{if="$conference->connected"}
{if="$conference->isGroupChat()"}
{$count = $conference->members()->count()}
<span class="counter alt">
{if="$count > 99"}99+{else}{$count}{/if}
</span>
{elseif="$conference->connected"}
{$count = $conference->presences()->count()}
<span class="counter alt">
{if="$count > 99"}99+{else}{$count}{/if}
@ -53,9 +58,9 @@
</span>
<div>
{if="$conference && $conference->name"}
{if="$conference && $conference->title"}
<p class="line active" title="{$jid|echapJS}" onclick="RoomsUtils_ajaxShowSubject('{$jid|echapJS}')">
{$conference->name}
{$conference->title}
{if="$conference->notify == 0"}
<span class="second" title="{$c->__('room.notify_never')}">
<i class="material-icons">notifications_off</i>
@ -77,15 +82,14 @@
<p>{$c->__('button.connecting')}…</p>
{elseif="$conference && $subject = $conference->subject"}
<p class="line active" title="{$subject}" onclick="RoomsUtils_ajaxShowSubject('{$jid|echapJS}')">
{if="$conference->info && $conference->info->mucpublic"}
<span title="{$c->__('room.public_muc_text')}">
{$c->__('room.public_muc')} <i class="material-icons">wifi_tethering</i>
{if="$conference->isGroupChat()"}
<span title="{$c->__('room.group_chat_text')}">
<i class="material-icons">people_alt</i> {$c->__('room.group_chat')}
</span>
·
{/if}
{if="$conference->info && !$conference->info->mucsemianonymous"}
<span title="{$c->__('room.nonanonymous_muc_text')}">
{$c->__('room.nonanonymous_muc')} <i class="material-icons">face</i>
{else}
<span title="{$c->__('room.channel_text')}">
<i class="material-icons">wifi_tethering</i> {$c->__('room.channel')}
</span>
·
{/if}
@ -93,15 +97,14 @@
</p>
{else}
<p class="line active" id="{$jid|cleanupId}-state" onclick="RoomsUtils_ajaxShowSubject('{$jid|echapJS}')">
{if="$conference->info && $conference->info->mucpublic"}
<span title="{$c->__('room.public_muc_text')}">
{$c->__('room.public_muc')} <i class="material-icons">wifi_tethering</i>
{if="$conference->isGroupChat()"}
<span title="{$c->__('room.group_chat_text')}">
<i class="material-icons">people_alt</i> {$c->__('room.group_chat')}
</span>
·
{/if}
{if="$conference->info && !$conference->info->mucsemianonymous"}
<span title="{$c->__('room.nonanonymous_muc_text')}">
{$c->__('room.nonanonymous_muc')} <i class="material-icons">face</i>
{else}
<span title="{$c->__('room.channel_text')}">
<i class="material-icons">wifi_tethering</i> {$c->__('room.channel')}
</span>
·
{/if}
@ -167,25 +170,6 @@
</li>
{/if}
<li class="divided" onclick="RoomsUtils_ajaxAskInvite('{$jid|echapJS}');">
<div>
<p class="normal">{$c->__('room.invite')}</p>
</div>
</li>
<li onclick="RoomsUtils_ajaxAdd('{$jid|echapJS}');">
<div>
<p class="normal">{$c->__('chatroom.config')}</p>
</div>
</li>
{if="!$anon"}
<li onclick="RoomsUtils_ajaxRemove('{$jid|echapJS}')">
<div>
<p class="normal">{$c->__('button.delete')}</p>
</div>
</li>
{/if}
<li onclick="Rooms_ajaxExit('{$jid|echapJS}'); {if="$anon"}Presence_ajaxLogout(){/if}">
<div>
<p class="normal">{$c->__('status.disconnect')}</p>

3
app/widgets/Chats/chats.tpl

@ -5,9 +5,6 @@
{$c->__('page.chats')}
</p>
</div>
<span class="control icon active gray" onclick="Search_ajaxRequest()">
<i class="material-icons">person_search</i>
</span>
</li>
</ul>
<ul id="chats_widget_list" class="list middle active divided spaced">

9
app/widgets/Rooms/Rooms.php

@ -3,6 +3,7 @@
use Moxl\Xec\Action\Disco\Request;
use Moxl\Xec\Action\Presence\Muc;
use Moxl\Xec\Action\Presence\Unavailable;
use Moxl\Xec\Action\Muc\GetMembers;
use Movim\Session;
use Movim\ChatStates;
@ -146,7 +147,7 @@ class Rooms extends Base
$conference = $this->user->session->conferences()
->where('conference', $room)
->with('info', 'contact', 'presence')
->withCount('unreads', 'quoted')
->withCount('unreads', 'quoted', 'presences')
->first();
if ($conference) {
@ -158,7 +159,7 @@ class Rooms extends Base
{
$conferences = $this->user->session->conferences()
->with('info', 'contact', 'presence')
->withCount('unreads', 'quoted')
->withCount('unreads', 'quoted', 'presences')
->get();
$this->rpc('Rooms.clearRooms');
@ -212,6 +213,10 @@ class Rooms extends Base
->request();
}
$m = new GetMembers;
$m->setTo($room)
->request();
if ($noNotify) {
$p->noNotify();
}

23
app/widgets/Rooms/_rooms_room.tpl

@ -1,7 +1,10 @@
<li id="{$conference->conference|cleanupId}"
data-jid="{$conference->conference}"
{if="$conference->nick != null"} data-nick="{$conference->nick}" {/if}
class="room {if="$conference->connected"}connected{/if}">
class="room
{if="$conference->connected"}connected{/if}
{if="$conference->isGroupChat()"}groupchat{/if}
">
{$url = $conference->getPhoto()}
{if="$url"}
<span class="primary icon bubble color small
@ -26,7 +29,7 @@
<div>
<p class="normal line">
{if="$conference->connected"}
{if="!$conference->isGroupChat() && $conference->connected"}
{$count = $conference->presences()->count()}
<span title="{$c->__('communitydata.sub', $count)}"
class="info
@ -34,32 +37,20 @@
moderator
{/if}
">
{if="$conference->info && $conference->info->mucpublic"}
<i class="material-icons" title="{$c->__('room.public_muc_text')}">wifi_tethering</i>
{/if}
{if="$conference->info && !$conference->info->mucsemianonymous"}
<i class="material-icons" title="{$c->__('room.nonanonymous_muc_text')}">face</i>
{/if}
{$count} <i class="material-icons">people</i>
</span>
{elseif="isset($info) && $info->occupants > 0"}
{elseif="!$conference->isGroupChat() && isset($info) && $info->occupants > 0"}
<span title="{$c->__('communitydata.sub', $info->occupants)}"
class="info
{if="$conference->connected && $conference->presence->mucrole == 'moderator'"}
moderator
{/if}
">
{if="$conference->info && $conference->info->mucpublic"}
<i class="material-icons" title="{$c->__('room.public_muc_text')}">wifi_tethering</i>
{/if}
{if="$conference->info && !$conference->info->mucsemianonymous"}
<i class="material-icons" title="{$c->__('room.nonanonymous_muc_text')}">face</i>
{/if}
{$info->occupants} <i class="material-icons">people</i>
</span>
{/if}
<span title="{$conference->conference}">{$conference->name}</span>
<span title="{$conference->conference}">{$conference->title}</span>
<span class="second">
{if="$conference->notify == 0"}
<i class="material-icons" title="{$c->__('room.notify_never')}">notifications_off</i>

17
app/widgets/Rooms/locales.ini

@ -1,7 +1,8 @@
[rooms]
create_or_join = Create or join a chatroom
create = Create a chatroom
edit = Edit a chatroom
join = Join a chatroom
join_custom = Join a custom chatroom
empty_text1 = You don’t have any chatroom yet.
empty_text2 = Add one by clicking on the add button.
empty_synchronize_title = You had some rooms previously listed?
@ -32,7 +33,8 @@ remove_text = You are going to remove the following chatroom. Please confirm y
connected = Connected to the chatroom
disconnected = Disconnected from the chatroom
synchronized = "%s rooms synchronized"
users = Users in the room
users = Users
members = Members
bad_nickname = Please enter a correct nickname (2 to 40 characters)
conflict = Username already taken
autojoin = Join this chatroom on connect
@ -47,11 +49,9 @@ updated = Bookmarks updated
[room]
anonymous_title = Public chatroom
no_room = Please provide a room address
anonymous_text1 = You are currently logged as an anonymous user.
anonymous_text2 = You can join using your own account or create one on the login page by loging out using the cross in the top-right corner.
anonymous_login = Login on %s
nick = Your nickname
invite = Invite a contact
invited = Invitation sent
invite_code = Send this link to your contacts
@ -59,6 +59,15 @@ public_muc = Public
public_muc_text = This chatroom is publicly discoverable
nonanonymous_muc = Public profiles
nonanonymous_muc_text = "The participants of this chatroom can see each other's profile"
group_chat = Group Chat
group_chat_text = Private group chat with invited members
group_chat_add = Add a new member
group_chat_members = Members
channel = Channel
channel_users = Users
channel_text = Public channel that can be publicly discoverable and joineable
notify_title = Notifications
notify_never = Never notify
notify_quoted = Notify when quoted

4
app/widgets/Rooms/rooms.css

@ -35,6 +35,10 @@
order: 1;
}
#rooms_widget ul.list.rooms li.connected.groupchat {
order: 0;
}
#rooms_widget ul.list.rooms:empty ~ ul.toggle_show,
#rooms_widget ul.list.rooms:not(:empty):not(.all) ~ ul.toggle_show li span.primary i:first-child,
#rooms_widget ul.list.rooms:not(:empty).all ~ ul.toggle_show li span.primary i:last-child,

16
app/widgets/Rooms/rooms.tpl

@ -1,6 +1,6 @@
<div id="rooms_widget">
<ul class="list divided spaced thin">
<li class="subheader" title="{$c->__('page.configuration')}">
<ul class="list thin">
<li class="subheader block large" title="{$c->__('page.configuration')}">
<div>
<p>
{$c->__('chatrooms.title')}
@ -10,18 +10,8 @@
<i class="material-icons flip-hor">view_list</i>
</span>
</li>
<li class="divided spaced active">
<span class="primary small icon small gray">
<i class="material-icons">explore</i>
</span>
<span class="control icon gray active divided" onclick="RoomsUtils_ajaxAdd();">
<i class="material-icons">group_add</i>
</span>
<div>
<p class="normal line" onclick="RoomsExplore_ajaxSearch();">{$c->__('rooms.create_or_join')}</p>
</div>
</li>
</ul>
<ul class="list rooms divided spaced thin active"></ul>
<ul class="list thick active spaced toggle_show">

14
app/widgets/RoomsExplore/_roomsexplore.tpl

@ -1,4 +1,18 @@
<section>
<ul class="list active">
<li onclick="RoomsUtils_ajaxAdd(); Drawer.clear();">
<span class="primary icon gray">
<i class="material-icons">input</i>
</span>
<span class="control icon gray">
<i class="material-icons">chevron_right</i>
</span>
<div>
<p class="normal line">{$c->__('rooms.join_custom')}</p>
</div>
</li>
</ul>
<hr />
<ul class="list divided spaced middle" id="roomsexplore_local"></ul>
<ul class="list divided spaced middle" id="roomsexplore_global"></ul>
</section>

20
app/widgets/RoomsUtils/RoomsUtils.php

@ -4,6 +4,8 @@ use Moxl\Xec\Action\Vcard\Set as VcardSet;
use Moxl\Xec\Action\Message\Invite;
use Moxl\Xec\Action\Muc\SetSubject;
use Moxl\Xec\Action\Muc\Destroy;
use Moxl\Xec\Action\Muc\CreateChannel;
use Moxl\Xec\Action\Muc\CreateGroupChat;
use Moxl\Xec\Action\Disco\Items;
use Moxl\Xec\Action\Bookmark2\Set;
use Moxl\Xec\Action\Bookmark2\Delete;
@ -46,12 +48,21 @@ class RoomsUtils extends Base
$view = $this->tpl();
$view->assign('conference', $conference);
$view->assign('room', $room);
$view->assign('list', $conference->presences()
->with('capability')
->get());
$view->assign('presences', $conference->presences()
->with('capability')
->get());
if ($conference->isGroupChat()) {
$view->assign('members', $conference->members()
->with('contact')
->get());
}
$view->assign('me', $this->user->id);
Drawer::fill($view->draw('_rooms_drawer'));
$this->rpc('Tabs.create');
}
/**
@ -155,7 +166,7 @@ class RoomsUtils extends Base
/**
* @brief Display the add room form
*/
public function ajaxAdd($room = false, $name = null)
public function ajaxAdd($room = false, $name = null, $create = false)
{
$view = $this->tpl();
@ -168,6 +179,7 @@ class RoomsUtils extends Base
->whereType('text')
->first());
$view->assign('id', $room);
$view->assign('create', $create);
$view->assign(
'conference',
$this->user->session->conferences()

97
app/widgets/RoomsUtils/_rooms_add.tpl

@ -1,11 +1,13 @@
<section>
<form name="bookmarkmucadd">
{if="isset($conference)"}
<h3>{$c->__('rooms.edit')}</h3>
{elseif="isset($id)"}
<h3>{$c->__('rooms.join')}</h3>
{if="$create"}
<h3>{$c->__('rooms.create')}</h3>
{else}
<h3>{$c->__('rooms.create_or_join')}</h3>
{if="isset($conference)"}
<h3>{$c->__('rooms.edit')}</h3>
{else}
<h3>{$c->__('rooms.join')}</h3>
{/if}
{/if}
{if="isset($id)"}
@ -36,25 +38,56 @@
<datalist id="suggestions">
</datalist>
<div>
<input
{if="isset($conference)"}
value="{$conference->name}"
{elseif="isset($info)"}
value="{$info->name}"
{elseif="isset($name)"}
value="{$name}"
{/if}
name="name"
placeholder="{$c->__('chatrooms.name_placeholder')}"
{if="!isset($id)"}
{if="$create"}
<div>
<ul class="list middle">
<li class="wide">
<span class="control">
<div class="radio">
<input name="type" value="groupchat"
id="type_groupchat" type="radio"
checked>
<label for="type_groupchat"></label>
</div>
</span>
<div>
<p>{$c->__('room.group_chat')}</p>
<p>{$c->__('room.group_chat_text')}</p>
</div>
</li>
<li class="wide">
<span class="control">
<div class="radio">
<input name="type" value="channel"
id="type_channel" type="radio">
<label for="type_channel"></label>
</div>
</span>
<div>
<p>{$c->__('room.channel')}</p>
<p>{$c->__('room.channel_text')}</p>
</div>
</li>
</ul>
</div>
<div>
<input
{if="isset($conference)"}
value="{$conference->name}"
{elseif="isset($info)"}
value="{$info->name}"
{elseif="isset($name)"}
value="{$name}"
{/if}
name="name"
placeholder="{$c->__('chatrooms.name_placeholder')}"
onblur="RoomsUtils_ajaxResolveSlug(this.value)"
{/if}
required />
<label>{$c->__('chatrooms.name')}</label>
</div>
required />
<label>{$c->__('chatrooms.name')}</label>
</div>
{/if}
{if="isset($id)"}
{if="isset($id) && !$create"}
<input type="hidden" value="{$id}" name="jid"/>
{else}
<div>
@ -73,6 +106,23 @@
</div>
{/if}
{if="!$create"}
<div>
<input
{if="isset($conference)"}
value="{$conference->name}"
{elseif="isset($info)"}
value="{$info->name}"
{elseif="isset($name)"}
value="{$name}"
{/if}
name="name"
placeholder="{$c->__('chatrooms.name_placeholder')}"
required />
<label>{$c->__('chatrooms.name')}</label>
</div>
{/if}
<div>
<input
{if="isset($conference) && !empty($conference->nick)"}
@ -90,7 +140,8 @@
<option value="never" {if="isset($conference) && $conference->notify == 0"}selected{/if}>
{$c->__('room.notify_never')}
</option>
<option value="quoted" {if="isset($conference) && $conference->notify == 1"}selected{/if}>
<option value="quoted" {if="isset($conference) && $conference->notify == 1"}selected{/if}
{if="!isset($conference)"}selected{/if}>
{$c->__('room.notify_quoted')}
</option>
<option value="always" {if="isset($conference) && $conference->notify == 2"}selected{/if}>

331
app/widgets/RoomsUtils/_rooms_drawer.tpl

@ -21,10 +21,21 @@
{/autoescape}
</span>
{/if}
<span title="{$c->__('chatroom.config')}"
class="control icon active"
onclick="RoomsUtils_ajaxAdd('{$room|echapJS}'); Drawer.clear()">
<i class="material-icons">edit</i>
</span>
<span title="{$c->__('button.delete')}"
class="control icon active"
onclick="RoomsUtils_ajaxRemove('{$room|echapJS}'); Drawer.clear()">
<i class="material-icons">delete</i>
</span>
<div>
{if="$conference && $conference->name"}
{if="$conference && $conference->title"}
<p class="line" title="{$room}">
{$conference->name}
{$conference->title}
</p>
<p class="line">{$room}</p>
{else}
@ -36,27 +47,55 @@
</li>
</ul>
</header>
<ul class="list middle">
{if="$conference->isGroupChat()"}
<li>
<span class="primary icon gray">
<i class="material-icons">people_alt</i>
</span>
<div>
<a class="button flat oppose" >
</a>
<p class="line">{$c->__('room.group_chat')}</p>
<p class="all">{$c->__('room.group_chat_text')}</p>
</div>
</li>
{else}
<li>
<span class="primary icon gray">
<i class="material-icons">wifi_tethering</i>
</span>
<div>
<p class="line">{$c->__('room.channel')}</p>
<p class="all">{$c->__('room.channel_text')}</p>
</div>
</li>
{/if}
</ul>
<ul class="list">
<p class="all">
{if="$conference->subject"}
<li>
<span class="primary icon gray">
<i class="material-icons">short_text</i>
</span>
<div>
<p class="all normal">
{autoescape="off"}
{$conference->subject|addUrls}
{/autoescape}
</p>
</div>
</li>
{/if}
</p>
{if="$conference->subject"}
<li>
<span class="primary icon gray">
<i class="material-icons">short_text</i>
</span>
<div>
<p class="line">{$c->__('page.about')}</p>
<p class="all">
{autoescape="off"}
{$conference->subject|addUrls}
{/autoescape}
</p>
</div>
</li>
{/if}
{if="$conference->info && $conference->info->mucpublic"}
<li>
<span class="primary icon gray">
<i class="material-icons">wifi_tethering</i>
<i class="material-icons">explore</i>
</span>
<div>
<p class="line">{$c->__('room.public_muc')}</p>
@ -64,7 +103,7 @@
</div>
</li>
{/if}
{if="$conference->info && !$conference->info->mucsemianonymous"}
{if="!$conference->isGroupChat() && $conference->info && !$conference->info->mucsemianonymous"}
<li>
<span class="primary icon gray">
<i class="material-icons">face</i>
@ -76,11 +115,21 @@
</li>
{/if}
<li class="subheader">
<div>
<p>{$c->__('room.notify_title')}</p>
</div>
</li>
{if="$conference->notify == 0"}
<li>
<span class="primary icon gray">
<i class="material-icons">notifications_off</i>
</span>
<span class="control icon gray active"
onclick="RoomsUtils_ajaxAdd('{$room|echapJS}'); Drawer.clear()">
<i class="material-icons">settings</i>
</span>
<div>
<p class="line">{$c->__('room.notify_title')}</p>
<p class="line">{$c->__('room.notify_never')}</p>
@ -91,6 +140,10 @@
<span class="primary icon gray">
<i class="material-icons">notifications_active</i>
</span>
<span class="control icon gray active"
onclick="RoomsUtils_ajaxAdd('{$room|echapJS}'); Drawer.clear()">
<i class="material-icons">settings</i>
</span>
<div>
<p class="line">{$c->__('room.notify_title')}</p>
<p class="line">{$c->__('room.notify_always')}</p>
@ -101,6 +154,10 @@
<span class="primary icon gray">
<i class="material-icons">notifications</i>
</span>
<span class="control icon gray active"
onclick="RoomsUtils_ajaxAdd('{$room|echapJS}'); Drawer.clear()">
<i class="material-icons">settings</i>
</span>
<div>
<p class="line">{$c->__('room.notify_title')}</p>
<p class="line">{$c->__('room.notify_quoted')}</p>
@ -109,96 +166,164 @@
{/if}
</ul>
{if="$conference->pictures()->count() > 0"}
<ul class="list">
<li class="subheader">
<div>
<p>
{$c->__('general.pictures')}
</p>
</div>
</li>
</ul>
<ul class="grid active">
{loop="$conference->pictures()->take(8)->get()"}
<li style="background-image: url('{$value->file['uri']|protectPicture}')"
onclick="Preview_ajaxShow('{$value->file['uri']}')">
<i class="material-icons">visibility</i>
</li>
{/loop}
</ul>
{/if}
<br />
<ul class="tabs" id="navtabs"></ul>
<ul class="list thin">
<li class="subheader">
<div>
<p>
<span class="info">{$list|count}</span>
{$c->__('chatrooms.users')}
</p>
</div>
</li>
{loop="$list"}
<li class="{if="$value->last > 60"} inactive{/if}"
title="{$value->resource}">
{if="$url = $value->conferencePicture"}
<span class="primary icon bubble small status {$value->presencekey}">
<img src="{$url}">
</span>
{else}
<span class="primary icon bubble small color {$value->resource|stringToColor} status {$value->presencekey}">
<i class="material-icons">people</i>
</span>
{/if}
{if="$value->mucaffiliation == 'owner'"}
<span class="control icon yellow" title="{$c->__('rooms.owner')}">
<i class="material-icons">star</i>
{if="$conference->isGroupChat()"}
<div class="tabelem" title="{$c->__('room.group_chat_members')}" id="room_members">
<ul class="list">
<li class="active" onclick="RoomsUtils_ajaxAskInvite('{$conference->conference|echapJS}'); Drawer.clear();">
<span class="primary icon gray">
<i class="material-icons">person_add</i>
</span>
{elseif="$value->mucaffiliation == 'admin'"}
<span class="control icon gray" title="{$c->__('rooms.admin')}">
<i class="material-icons">star</i>
<span class="control icon gray">
<i class="material-icons">chevron_right</i>
</span>
{/if}
{if="$value->mucrole == 'visitor'"}
<span class="control icon gray" title="{$c->__('rooms.visitor')}">
<i class="material-icons">speaker_notes_off</i>
<div>
<p class="line normal">
{$c->__('room.group_chat_add')}
</p>
</div>
</li>
</ul>
<ul class="list thin">
{loop="$members"}
{$presence = $presences->where('mucjid', $value->jid)->first()}
<li title="{$value->truename}">
{if="$value->contact && $url = $value->contact->getPhoto('s')"}
<span class="primary icon bubble small status {if="$presence"}{$presence->presencekey}{/if}">
<img src="{$url}">
</span>
{else}
<span class="primary icon bubble small color {$value->jid|stringToColor} status {if="$presence"}{$presence->presencekey}{/if}">
<i class="material-icons">people</i>
</span>
{/if}
{if="$value->affiliation == 'owner'"}
<span class="control icon yellow" title="{$c->__('rooms.owner')}">
<i class="material-icons">star</i>
</span>
{elseif="$value->affiliation == 'admin'"}
<span class="control icon gray" title="{$c->__('rooms.admin')}">
<i class="material-icons">star</i>
</span>
{/if}
{if="$value->jid != $me"}
<span class="control icon active gray divided" onclick="
Chats_ajaxOpen('{$value->jid|echapJS}');
Chat_ajaxGet('{$value->jid|echapJS}');
Drawer_ajaxClear();">
<i class="material-icons">comment</i>
</span>
{/if}
<div>
<p class="line normal">
{if="$value->jid == $me"}
{$value->truename}
{else}
<a href="{$c->route('contact', $value->jid)}">{$value->truename}</a>
{/if}
</p>
</div>
</li>
{/loop}
</ul>
</div>
{else}
<div class="tabelem" title="{$c->__('room.channel_users')}" id="room_users">
<ul class="list">
<li class="active" onclick="RoomsUtils_ajaxAskInvite('{$conference->conference|echapJS}'); Drawer.clear();">
<span class="primary icon gray">
<i class="material-icons">person_add</i>
</span>
{/if}
{if="$value->mucjid != $me"}
<span class="control icon active gray divided" onclick="
Chats_ajaxOpen('{$value->mucjid|echapJS}');
Chat_ajaxGet('{$value->mucjid|echapJS}');
Drawer_ajaxClear();">
<i class="material-icons">comment</i>
<span class="control icon gray">
<i class="material-icons">chevron_right</i>
</span>
{/if}
<div>
<p class="line normal">
{if="$value->mucjid && strpos($value->mucjid, '/') == false"}
{if="$value->mucjid == $me"}
{$value->resource}
{else}
<a href="{$c->route('contact', $value->mucjid)}">{$value->resource}</a>
{/if}
<div>
<p class="line normal">
{$c->__('room.invite')}
</p>
</div>
</li>
</ul>
<ul class="list thin">
{loop="$presences"}
<li class="{if="$value->last > 60"} inactive{/if}"
title="{$value->resource}">
{if="$url = $value->conferencePicture"}
<span class="primary icon bubble small status {$value->presencekey}">
<img src="{$url}">
</span>
{else}
{$value->resource}
<span class="primary icon bubble small color {$value->resource|stringToColor} status {$value->presencekey}">
<i class="material-icons">people</i>
</span>
{/if}
{if="$value->capability"}
<span class="second" title="{$value->capability->name}">
<i class="material-icons">{$value->capability->getDeviceIcon()}</i>
{if="$value->mucaffiliation == 'owner'"}
<span class="control icon yellow" title="{$c->__('rooms.owner')}">
<i class="material-icons">star</i>
</span>
{elseif="$value->mucaffiliation == 'admin'"}
<span class="control icon gray" title="{$c->__('rooms.admin')}">
<i class="material-icons">star</i>
</span>
{/if}
</p>
{if="$value->seen"}
<p class="line">
{$c->__('last.title')} {$value->seen|strtotime|prepareDate:true,true}
</p>
{elseif="$value->status"}
<p class="line" title="{$value->status}">{$value->status}</p>
{/if}
</div>
</li>
{/loop}
</ul>
{if="$value->mucrole == 'visitor'"}
<span class="control icon gray" title="{$c->__('rooms.visitor')}">
<i class="material-icons">speaker_notes_off</i>
</span>
{/if}
{if="$value->mucjid != $me"}
<span class="control icon active gray divided" onclick="
Chats_ajaxOpen('{$value->mucjid|echapJS}');
Chat_ajaxGet('{$value->mucjid|echapJS}');
Drawer_ajaxClear();">
<i class="material-icons">comment</i>
</span>
{/if}
<div>
<p class="line normal">
{if="$value->mucjid && strpos($value->mucjid, '/') == false"}
{if="$value->mucjid == $me"}
{$value->resource}
{else}
<a href="{$c->route('contact', $value->mucjid)}">{$value->resource}</a>
{/if}
{else}
{$value->resource}
{/if}
{if="$value->capability"}
<span class="second" title="{$value->capability->name}">
<i class="material-icons">{$value->capability->getDeviceIcon()}</i>
</span>
{/if}
</p>
{if="$value->seen"}
<p class="line">
{$c->__('last.title')} {$value->seen|strtotime|prepareDate:true,true}
</p>
{elseif="$value->status"}
<p class="line" title="{$value->status}">{$value->status}</p>
{/if}
</div>
</li>
{/loop}
</ul>
</div>
{/if}
{if="$conference->pictures()->count() > 0"}
<div class="tabelem" title="{$c->__('general.pictures')}" id="room_medias">
<ul class="grid active">
{loop="$conference->pictures()->take(16)->get()"}
<li style="background-image: url('{$value->file['uri']|protectPicture}')"
onclick="Preview_ajaxShow('{$value->file['uri']}')">
<i class="material-icons">visibility</i>
</li>
{/loop}
</ul>
</div>
{/if}
</section>

14
app/widgets/RoomsUtils/_rooms_invite.tpl

@ -2,13 +2,6 @@
<form name="invite">
<h3>{$c->__('room.invite')}</h3>
<h4>{$room}</h4>
<div>
<input
readonly
value="{$c->route('login', $invite->code)}">
<label>{$c->__('room.invite_code')}</label>
</div>
<h2 style="text-align: center;">{$c->__('global.or')}</h2>
<div>
<input type="hidden" value="{$room}" name="to" id="to"/>
<datalist id="contact_list">
@ -25,6 +18,13 @@
placeholder="user@server.tld"/>
<label>{$c->__('roster.add_contact_info1')}</label>
</div>
<h2 style="text-align: center;">{$c->__('global.or')}</h2>
<div>
<input
readonly
value="{$c->route('login', $invite->code)}">
<label>{$c->__('room.invite_code')}</label>
</div>
</section>
<div class="no_bar">
<button class="button flat" onclick="Dialog_ajaxClear()">

3
app/widgets/Search/Search.php

@ -21,11 +21,12 @@ class Search extends Base
$this->addcss('search.css');
}
public function ajaxRequest()
public function ajaxRequest($chatroomActions = false)
{
$view = $this->tpl();
$view->assign('empty', $this->prepareSearch(''));
$view->assign('chatroomactions', $chatroomActions);
Drawer::fill($view->draw('_search'), true);
$this->rpc('Search.init');

31
app/widgets/Search/_search.tpl

@ -1,4 +1,35 @@
<section id="search">
{if="$chatroomactions"}
<ul class="list active">
<li onclick="RoomsExplore_ajaxSearch();">
<span class="primary icon gray">
<i class="material-icons">explore</i>
</span>
<span class="control icon gray">
<i class="material-icons">chevron_right</i>
</span>
<div>
<p class="line normal">
{$c->__('rooms.join')}
</p>
</div>
</li>
<li onclick="RoomsUtils_ajaxAdd(false, null, true); Drawer.clear()">
<span class="primary icon gray">
<i class="material-icons">group_add</i>
</span>
<span class="control icon gray">
<i class="material-icons">chevron_right</i>
</span>
<div>
<p class="line normal">
{$c->__('rooms.create')}
</p>
</div>
</li>
</ul>
<hr />
{/if}
<ul id="roster" class="list spin"></ul>
<div id="results">

11
app/widgets/Search/_search_roster.tpl

@ -13,21 +13,23 @@
>
{$url = $value->getPhoto('m')}
{if="$url"}
<span class="primary icon bubble
<span class="primary icon bubble active
{if="!$value->presence || $value->presence->value > 4"}
disabled
{else}
status {$value->presence->presencekey}
{/if}"
style="background-image: url({$url});">
style="background-image: url({$url});"
onclick="MovimUtils.reload('{$c->route('contact', $value->jid)}')">
</span>
{else}
<span class="primary icon bubble color {$value->jid|stringToColor}
<span class="primary icon bubble color active {$value->jid|stringToColor}
{if="!$value->presence || $value->presence->value > 4"}
disabled
{else}
status {$value->presence->presencekey}
{/if}"
onclick="MovimUtils.reload('{$c->route('contact', $value->jid)}')"
>
<i class="material-icons">person</i>
</span>
@ -52,9 +54,6 @@
<span class="control icon active gray" onclick="Search.chat('{$value->jid|echapJS}')">
<i class="material-icons">comment</i>
</span>
<span class="control icon active gray" onclick="MovimUtils.reload('{$c->route('contact', $value->jid)}')">
<i class="material-icons">person</i>
</span>
<div>
<p class="normal line">
{$value->truename}

5
app/widgets/Tabs/tabs.js

@ -2,8 +2,13 @@ var Tabs = {
create : function() {
// We search all the div with "tab" class
var tabs = document.querySelectorAll('.tabelem');
if (tabs.length == 0) return;
var current = null;
document.querySelector('#navtabs').innerHTML = '';
// We create the list
for (var i=0; i<tabs.length; i++) {
if (window.location.hash == '#' + tabs[i].id + '_tab') {

1
app/widgets/Tabs/tabs.tpl

@ -1 +0,0 @@
<ul class="tabs" id="navtabs"></ul>

31
database/migrations/20201005202613_create_members_table.php

@ -0,0 +1,31 @@
<?php
use Movim\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateMembersTable extends Migration
{
public function up()
{
$this->schema->create('members', function (Blueprint $table) {
$table->increments('id');
$table->string('conference', 128);
$table->string('jid', 128);
$table->string('nick', 128)->nullable();
$table->string('role', 32)->nullable();
$table->string('affiliation', 32);
$table->timestamps();
$table->unique(['conference', 'jid']);
$table->index('conference');
$table->foreign('jid')
->references('id')->on('contacts');
});
}
public function down()
{
$this->schema->drop('members');
}
}

137
lib/moxl/src/Stanza/Muc.php

@ -102,4 +102,141 @@ class Muc
$xml = \Moxl\API::iqWrapper($query, $to, 'set');
\Moxl\API::request($xml);
}
public static function createGroupChat($to, $name)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$query = $dom->createElementNS('http://jabber.org/protocol/muc#owner', 'query');
$dom->appendChild($query);
$x = $dom->createElementNS('jabber:x:data', 'x');
$x->setAttribute('type', 'submit');
$query->appendChild($x);
$field = $dom->createElement('field');
$field->setAttribute('var', 'FORM_TYPE');
$x->appendChild($field);
$value = $dom->createElement('value', 'http://jabber.org/protocol/muc#roomconfig');
$field->appendChild($value);
$field = $dom->createElement('field');
$field->setAttribute('var', 'muc#roomconfig_roomname');
$x->appendChild($field);
$value = $dom->createElement('value', $name);
$field->appendChild($value);
$field = $dom->createElement('field');
$field->setAttribute('var', 'muc#roomconfig_persistentroom');
$x->appendChild($field);
$value = $dom->createElement('value', 'true');
$field->appendChild($value);
$field = $dom->createElement('field');
$field->setAttribute('var', 'muc#roomconfig_changesubject');
$x->appendChild($field);
$value = $dom->createElement('value', 'false');
$field->appendChild($value);
$field = $dom->createElement('field');
$field->setAttribute('var', 'muc#roomconfig_membersonly');
$x->appendChild($field);
$value = $dom->createElement('value', 'true');
$field->appendChild($value);
$field = $dom->createElement('field');
$field->setAttribute('var', 'muc#roomconfig_whois');
$x->appendChild($field);
$value = $dom->createElement('value', 'anyone');
$field->appendChild($value);
$field = $dom->createElement('field');
$field->setAttribute('var', 'muc#roomconfig_publicroom');
$x->appendChild($field);
$value = $dom->createElement('value', 'false');
$field->appendChild($value);
$field = $dom->createElement('field');
$field->setAttribute('var', 'muc#muc#roomconfig_allowpm');
$x->appendChild($field);
$value = $dom->createElement('value', 'none');
$field->appendChild($value);
$xml = \Moxl\API::iqWrapper($query, $to, 'set');
\Moxl\API::request($xml);
}
public static function createChannel($to, $name)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$query = $dom->createElementNS('http://jabber.org/protocol/muc#owner', 'query');
$dom->appendChild($query);
$x = $dom->createElementNS('jabber:x:data', 'x');
$x->setAttribute('type', 'submit');
$query->appendChild($x);
$field = $dom->createElement('field');
$field->setAttribute('var', 'FORM_TYPE');
$x->appendChild($field);
$value = $dom->createElement('value', 'http://jabber.org/protocol/muc#roomconfig');
$field->appendChild($value);
$field = $dom->createElement('field');
$field->setAttribute('var', 'muc#roomconfig_roomname');
$x->appendChild($field);
$value = $dom->createElement('value', $name);
$field->appendChild($value);
$field = $dom->createElement('field');
$field->setAttribute('var', 'muc#roomconfig_persistentroom');
$x->appendChild($field);
$value = $dom->createElement('value', 'true');
$field->appendChild($value);
$field = $dom->createElement('field');
$field->setAttribute('var', 'muc#roomconfig_changesubject');
$x->appendChild($field);
$value = $dom->createElement('value', 'false');
$field->appendChild($value);
$field = $dom->createElement('field');
$field->setAttribute('var', 'muc#roomconfig_membersonly');
$x->appendChild($field);
$value = $dom->createElement('value', 'false');
$field->appendChild($value);
$field = $dom->createElement('field');
$field->setAttribute('var', 'muc#roomconfig_whois');
$x->appendChild($field);
$value = $dom->createElement('value', 'moderators');
$field->appendChild($value);
$field = $dom->createElement('field');
$field->setAttribute('var', 'muc#roomconfig_publicroom');
$x->appendChild($field);
$value = $dom->createElement('value', 'true');
$field->appendChild($value);
$field = $dom->createElement('field');
$field->setAttribute('var', 'muc#muc#roomconfig_allowpm');
$x->appendChild($field);
$value = $dom->createElement('value', 'anyone');
$field->appendChild($value);
$xml = \Moxl\API::iqWrapper($query, $to, 'set');
\Moxl\API::request($xml);
}
public static function getMembers($to, $affiliation)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$query = $dom->createElementNS('http://jabber.org/protocol/muc#admin', 'query');
$item = $dom->createElement('item');
$item->setAttribute('affiliation', $affiliation);
$query->appendChild($item);
$xml = \Moxl\API::iqWrapper($query, $to, 'get');
\Moxl\API::request($xml);
}
}

24
lib/moxl/src/Xec/Action/Muc/CreateChannel.php

@ -0,0 +1,24 @@
<?php
namespace Moxl\Xec\Action\Muc;
use Moxl\Xec\Action;
use Moxl\Stanza\Muc;
class CreateChannel extends Action
{
protected $_to;
protected $_name;
public function request()
{
$this->store();
Muc::createChannel($this->_to, $this->_name);
}
public function handle($stanza, $parent = false)
{
$this->pack($this->_to);
$this->deliver();
}
}

24
lib/moxl/src/Xec/Action/Muc/CreateGroupChat.php

@ -0,0 +1,24 @@
<?php
namespace Moxl\Xec\Action\Muc;
use Moxl\Xec\Action;
use Moxl\Stanza\Muc;
class CreateGroupChat extends Action
{
protected $_to;
protected $_name;
public function request()
{
$this->store();
Muc::createGroupChat($this->_to, $this->_name);
}
public function handle($stanza, $parent = false)
{
$this->pack($this->_to);
$this->deliver();
}
}

59
lib/moxl/src/Xec/Action/Muc/GetMembers.php

@ -0,0 +1,59 @@
<?php
namespace Moxl\Xec\Action\Muc;
use Moxl\Xec\Action;
use Moxl\Stanza\Muc;
use App\Member;
class GetMembers extends Action
{
protected $_to;
private $lastStanzaId;
public function request()
{
$this->lastStanzaId = \generateKey(6);
$this->store();
Muc::getMembers($this->_to, 'member');
$this->store();
Muc::getMembers($this->_to, 'owner');
$this->store($this->lastStanzaId);
Muc::getMembers($this->_to, 'admin');
}
public function handle($stanza, $parent = false)
{
$i = 0;
foreach ($stanza->query->item as $item) {
if ($i == 0) {
Member::where('conference', $this->_to)
->where('affiliation', (string)$item->attributes()->affiliation)
->delete();
}
$member = new Member;
$member->conference = $this->_to;
$member->jid = (string)$item->attributes()->jid;
$member->affiliation = (string)$item->attributes()->affiliation;
if ($item->attributes()->role) {
$member->role = (string)$item->attributes()->role;
}
if ($item->attributes()->nick) {
$member->nick = (string)$item->attributes()->nick;
}
$member->save();
$i++;
}
// Only fire the request for the last one
if ($stanza->attributes()->id == $this->lastStanzaId) {
$this->deliver();
}
}
}

2
lib/moxl/src/Xec/Handler.php

@ -119,6 +119,8 @@ class Handler
'9a534a8b4d6324e23f4187123e406729' => 'Message',
'78e731027d8fd50ed642340b7c9a63b3' => 'Message',// TLS
'89d8bb4741fd3a62e8b20e0d52a85a36' => 'MucUser',
'f9e18585fd0e0873c52e880c800f267a' => 'ReceiptAck',
'004a75eb0a92fca2b868732b56863e66' => 'ReceiptRequest',
'3ca6c24643a9389b91323ddd1aaa84d0' => 'Displayed',

36
lib/moxl/src/Xec/Payload/MucUser.php

@ -0,0 +1,36 @@
<?php
namespace Moxl\Xec\Payload;
use App\Member;
class MucUser extends Payload
{
public function handle($stanza, $parent = false)
{
if (isset($stanza->item)) {
$from = current(explode('/', (string)$parent->attributes()->from));
$jid = current(explode('/', (string)$stanza->item->attributes()->jid));
if (empty($jid)) return;
$member = Member::where('conference', $from)
->where('jid', $jid)
->first();
if (!$member) {
$member = new Member;
$member->conference = $from;
$member->jid = $jid;
}
if ($member && in_array((string)$stanza->item->attributes()->affiliation, ['outcast', 'none'])) {
$member->delete();
} else {
$member->affiliation = (string)$stanza->item->attributes()->affiliation;
$member->save();
}
}
}
}

6
public/theme/css/form.css

@ -396,6 +396,12 @@ input[type=button].flat:focus {
line-height: 5rem;
}
body > nav.active ~ main .button.action,
body > div.dialog:not(:empty) ~ main .button.action,
body > div.drawer:not(:empty):not(.empty) ~ main .button.action {
transform: scale(0);
}
.button.action,
ul.list.actions > li > p {
box-shadow: 0 0.5rem 1.25rem rgba(0, 0, 0, 0.23), 0 0.5rem 1.25rem rgba(0, 0, 0, 0.16);

Loading…
Cancel
Save