Browse Source

Allow setting Avatars on Communities by combining XEP-0084 (metadata + url) and XEP-0060

pull/808/head
Timothée Jaussoin 7 years ago
parent
commit
233a05a68a
  1. 1
      CHANGELOG.md
  2. 9
      app/Info.php
  3. 5
      app/widgets/CommunitiesServer/_communitiesserver.tpl
  4. 1
      app/widgets/CommunityAffiliations/CommunityAffiliations.php
  5. 7
      app/widgets/CommunityAffiliations/_communityaffiliations.tpl
  6. 40
      app/widgets/CommunityConfig/CommunityConfig.php
  7. 30
      app/widgets/CommunityConfig/_communityconfig_avatar.tpl
  8. 16
      app/widgets/CommunityData/CommunityData.php
  9. 5
      app/widgets/CommunityData/_communitydata.tpl
  10. 6
      app/widgets/CommunityData/communitydata.css
  11. 6
      app/widgets/CommunityData/communitydata.js
  12. 16
      app/widgets/CommunitySubscriptions/_communitysubscriptions.tpl
  13. 21
      database/migrations/20190124220622_add_avatar_hash_infos_table.php
  14. 21
      lib/moxl/src/Moxl/Stanza/Avatar.php
  15. 3
      lib/moxl/src/Moxl/Xec/Action/Avatar/Get.php
  16. 21
      lib/moxl/src/Moxl/Xec/Action/Avatar/Set.php
  17. 27
      lib/moxl/src/Moxl/Xec/Action/Pubsub/GetItem.php
  18. 18
      lib/moxl/src/Moxl/Xec/Action/Pubsub/GetItems.php
  19. 7
      lib/moxl/src/Moxl/Xec/Action/Pubsub/GetItemsId.php
  20. 23
      src/Movim/Picture.php

1
CHANGELOG.md

@ -23,6 +23,7 @@ v0.14.1 (trunk)
* Chat bubbles a bit more compact
* Display single emojis as small stickers
* Display chat states in MUC, handle the chat states with a new ChatStates class
* Allow setting Avatars on Communities by combining XEP-0084 (metadata + url) and XEP-0060
v0.14
---------------------------

9
app/Info.php

@ -8,7 +8,7 @@ class Info extends Model
{
protected $primaryKey = ['server', 'node'];
public $incrementing = false;
protected $fillable = ['server', 'node'];
protected $fillable = ['server', 'node', 'avatarhash'];
public function setAdminaddressesAttribute(array $arr)
{
@ -121,6 +121,13 @@ class Info extends Model
->first();
}
public function getPhoto($size = 'm')
{
return isset($this->attributes['avatarhash'])
? getPhoto($this->attributes['avatarhash'], $size)
: null;
}
public function set($query)
{
$from = (string)$query->attributes()->from;

5
app/widgets/CommunitiesServer/_communitiesserver.tpl

@ -45,9 +45,10 @@
</span>
{/if}
{if="$value->logo"}
{$url = $value->getPhoto('m')}
{if="$url"}
<span class="primary icon bubble">
<img src="{$value->getLogo(50)}">
<img src="{$url}"/>
</span>
{else}
<span class="primary icon bubble color {$value->node|stringToColor}">

1
app/widgets/CommunityAffiliations/CommunityAffiliations.php

@ -18,7 +18,6 @@ class CommunityAffiliations extends Base
$this->registerEvent('pubsub_setaffiliations_handle', 'onAffiliationsSet');
$this->registerEvent('pubsub_delete_handle', 'onDelete');
$this->registerEvent('pubsub_delete_error', 'onDeleteError');
$this->registerEvent('pubsub_getsubscriptions_handle', 'onSubscriptions');
$this->addjs('communityaffiliations.js');

7
app/widgets/CommunityAffiliations/_communityaffiliations.tpl

@ -1,5 +1,11 @@
{if="$role == 'owner'"}
<ul class="list active">
<li onclick="CommunityConfig_ajaxGetAvatar('{$info->server|echapJS}', '{$info->node|echapJS}')">
<span class="primary icon gray">
<i class="material-icons">image</i>
</span>
<p class="normal">{$c->__('page.avatar')}</p>
</li>
<li onclick="CommunityConfig_ajaxGetConfig('{$info->server|echapJS}', '{$info->node|echapJS}')">
<span class="primary icon gray">
<i class="material-icons">settings</i>
@ -109,4 +115,3 @@
{/loop}
</ul>
{/if}

40
app/widgets/CommunityConfig/CommunityConfig.php

@ -4,15 +4,20 @@ use Movim\Widget\Base;
use Moxl\Xec\Action\Pubsub\GetConfig;
use Moxl\Xec\Action\Pubsub\SetConfig;
use Moxl\Xec\Action\Avatar\Get as AvatarGet;
use Moxl\Xec\Action\Avatar\Set as AvatarSet;
use Respect\Validation\Validator;
use Movim\Picture;
class CommunityConfig extends Base
{
public function load()
{
$this->registerEvent('pubsub_getconfig_handle', 'onConfig');
$this->registerEvent('pubsub_setconfig_handle', 'onConfigSaved');
$this->registerEvent('avatar_set_pubsub', 'onAvatarSet');
}
public function onConfig($packet)
@ -33,11 +38,46 @@ class CommunityConfig extends Base
Dialog::fill($view->draw('_communityconfig'), true);
}
public function onAvatarSet($packet)
{
$this->rpc('Dialog_ajaxClear');
Notification::append(null, $this->__('avatar.updated'));
}
public function onConfigSaved()
{
Notification::append(false, $this->__('communityaffiliation.config_saved'));
}
public function ajaxGetAvatar($origin, $node)
{
if (!$this->validateServerNode($origin, $node)) return;
$view = $this->tpl();
$view->assign('info', \App\Info::where('server', $origin)
->where('node', $node)
->first());
Dialog::fill($view->draw('_communityconfig_avatar'));
}
public function ajaxSetAvatar($origin, $node, $form)
{
if (!$this->validateServerNode($origin, $node)) return;
$key = $origin.$node.'avatar';
$p = new Picture;
$p->fromBase($form->photobin->value);
$p->set($key, 'jpeg', 60);
$r = new AvatarSet;
$r->setTo($origin)
->setNode($node)
->setUrl($p->getOriginal($key))
->setData($p->toBase())->request();
}
public function ajaxGetConfig($origin, $node, $advanced = false)
{
if (!$this->validateServerNode($origin, $node)) return;

30
app/widgets/CommunityConfig/_communityconfig_avatar.tpl

@ -0,0 +1,30 @@
<section class="scroll">
<form name="avatarcommunity">
<img src="{$info->getPhoto('o')}">
<input type="hidden" name="photobin"/>
</form>
<ul class="list thick divided">
<li>
<span class="primary icon bubble color green">
<i class="material-icons">attach_file</i>
</span>
<p>{$c->__('avatar.file')}</p>
<p><input type="file" onchange="MovimAvatar.file(this.files, 'avatarcommunity');"></p>
</li>
</ul>
</section>
<div>
<button onclick="Dialog_ajaxClear()" class="button flat">
{$c->__('button.close')}
</button>
<button
type="button"
onclick="
CommunityConfig_ajaxSetAvatar('{$info->server}', '{$info->node}', MovimUtils.formToJson('avatarcommunity'));
this.value = '{$c->__('button.submitting')}';
this.className='button flat inactive';"
class="button flat"
>
{$c->__('button.submit')}
</button>
</div>

16
app/widgets/CommunityData/CommunityData.php

@ -1,5 +1,7 @@
<?php
use Moxl\Xec\Action\Pubsub\GetItem;
use Movim\Widget\Base;
use Respect\Validation\Validator;
@ -8,18 +10,30 @@ class CommunityData extends Base
{
public function load()
{
$this->addcss('communitydata.css');
$this->addjs('communitydata.js');
$this->registerEvent('disco_request_handle', 'onDiscoRequest', 'community');
$this->registerEvent('pubsub_getitem_avatar', 'onDiscoRequest', 'community');
}
public function onDiscoRequest($packet)
{
list($origin, $node) = $packet->content;
list($origin, $node) = array_values($packet->content);
if ((substr($node, 0, 30) != 'urn:xmpp:microblog:0:comments/')) {
$this->rpc('MovimTpl.fill', '#community_data', $this->prepareData($origin, $node));
}
}
public function ajaxGetAvatar($origin, $node)
{
$g = new GetItem;
$g->setTo($origin)
->setNode($node)
->setId('urn:xmpp:avatar:metadata')
->request();
}
public function prepareData($origin, $node)
{
$view = $this->tpl();

5
app/widgets/CommunityData/_communitydata.tpl

@ -1,10 +1,11 @@
<br />
{if="$info"}
<ul class="list block middle flex">
{if="$info->logo"}
{$url = $info->getPhoto('l')}
{if="$url"}
<li class="block large">
<p class="center">
<img src="{$info->getLogo()}" style="max-width: 100%"/>
<img src="{$url}" style="max-width: 100%"/>
</p>
</li>
{/if}

6
app/widgets/CommunityData/communitydata.css

@ -0,0 +1,6 @@
#community_data li:first-child img {
height: 25rem;
border-radius: 0.25rem;
margin-top: 1rem;
margin-bottom: -2rem;
}

6
app/widgets/CommunityData/communitydata.js

@ -0,0 +1,6 @@
MovimWebsocket.attach(function() {
var parts = MovimUtils.urlParts();
if (parts.params.length > 0) {
CommunityData_ajaxGetAvatar(parts.params[0], parts.params[1]);
}
});

16
app/widgets/CommunitySubscriptions/_communitysubscriptions.tpl

@ -25,9 +25,19 @@
onclick="MovimUtils.redirect('{$c->route('community', [$value->server, $value->node])}')"
title="{$value->server} - {$value->node}"
>
<span class="primary icon bubble color {$value->node|stringToColor}">
{$value->node|firstLetterCapitalize}
</span>
{if="$value->info"}
{$url = $value->info->getPhoto('m')}
{/if}
{if="$url"}
<span class="primary icon bubble">
<img src="{$url}"/>
</span>
{else}
<span class="primary icon bubble color {$value->node|stringToColor}">
{$value->node|firstLetterCapitalize}
</span>
{/if}
<span class="control icon gray">
<i class="material-icons">chevron_right</i>
</span>

21
database/migrations/20190124220622_add_avatar_hash_infos_table.php

@ -0,0 +1,21 @@
<?php
use Movim\Migration;
use Illuminate\Database\Schema\Blueprint;
class AddAvatarHashInfosTable extends Migration
{
public function up()
{
$this->schema->table('infos', function(Blueprint $table) {
$table->string('avatarhash', 128)->nullable();
});
}
public function down()
{
$this->schema->table('infos', function(Blueprint $table) {
$table->dropColumn('avatarhash');
});
}
}

21
lib/moxl/src/Moxl/Stanza/Avatar.php

@ -17,29 +17,29 @@ class Avatar
\Moxl\API::request($xml);
}
static function set($data)
static function set($data, $to = false, $node = false)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$pubsub = $dom->createElement('pubsub');
$pubsub->setAttribute('xmlns', 'http://jabber.org/protocol/pubsub');
$publish = $dom->createElement('publish');
$publish->setAttribute('node', 'urn:xmpp:avatar:data');
$publish->setAttribute('node', $node ? $node : 'urn:xmpp:avatar:data');
$pubsub->appendChild($publish);
$item = $dom->createElement('item');
$item->setAttribute('id', sha1(base64_decode($data)));
$item->setAttribute('id', $node ? 'urn:xmpp:avatar:data' : sha1(base64_decode($data)));
$publish->appendChild($item);
$data = $dom->createElement('data', $data);
$data->setAttribute('xmlns', 'urn:xmpp:avatar:data');
$item->appendChild($data);
$xml = \Moxl\API::iqWrapper($pubsub, false, 'set');
$xml = \Moxl\API::iqWrapper($pubsub, $to, 'set');
\Moxl\API::request($xml);
}
static function setMetadata($data)
static function setMetadata($data, $url = false, $to = false, $node = false)
{
$decoded = base64_decode($data);
@ -48,11 +48,11 @@ class Avatar
$pubsub->setAttribute('xmlns', 'http://jabber.org/protocol/pubsub');
$publish = $dom->createElement('publish');
$publish->setAttribute('node', 'urn:xmpp:avatar:metadata');
$publish->setAttribute('node', $node ? $node : 'urn:xmpp:avatar:metadata');
$pubsub->appendChild($publish);
$item = $dom->createElement('item');
$item->setAttribute('id', sha1($decoded));
$item->setAttribute('id', $node ? 'urn:xmpp:avatar:metadata' : sha1($decoded));
$publish->appendChild($item);
$metadata = $dom->createElement('metadata');
@ -60,6 +60,11 @@ class Avatar
$item->appendChild($metadata);
$info = $dom->createElement('info');
if ($url) {
$info->setAttribute('url', $url);
}
$info->setAttribute('height', '410');
$info->setAttribute('width', '410');
$info->setAttribute('type', 'image/jpeg');
@ -67,7 +72,7 @@ class Avatar
$info->setAttribute('bytes', strlen($decoded));
$metadata->appendChild($info);
$xml = \Moxl\API::iqWrapper($pubsub, false, 'set');
$xml = \Moxl\API::iqWrapper($pubsub, $to, 'set');
\Moxl\API::request($xml);
}
}

3
lib/moxl/src/Moxl/Xec/Action/Avatar/Get.php

@ -8,11 +8,12 @@ use Moxl\Stanza\Avatar;
class Get extends Action
{
protected $_to;
protected $_node = false;
public function request()
{
$this->store();
Avatar::get($this->_to);
Avatar::get($this->_to, $this->_node);
}
public function handle($stanza, $parent = false)

21
lib/moxl/src/Moxl/Xec/Action/Avatar/Set.php

@ -8,18 +8,31 @@ use Moxl\Stanza\Avatar;
class Set extends Action
{
protected $_data;
protected $_to = false;
protected $_node = false;
protected $_url = false;
public function request()
{
$this->store();
Avatar::set($this->_data);
Avatar::setMetadata($this->_data);
if ($this->_url === false) {
Avatar::set($this->_data, $this->_to, $this->_node);
}
Avatar::setMetadata($this->_data, $this->_url, $this->_to, $this->_node);
}
public function handle($stanza, $parent = false)
{
$this->pack(\App\User::me()->contact);
$this->deliver();
if ($this->_to == false && $this->_node == false) {
$this->pack(\App\User::me()->contact);
$this->deliver();
} else {
$this->method('pubsub');
$this->pack(['to' => $this->_to, 'node' => $this->_node]);
$this->deliver();
}
}
public function errorFeatureNotImplemented($stanza)

27
lib/moxl/src/Moxl/Xec/Action/Pubsub/GetItem.php

@ -6,6 +6,8 @@ use Moxl\Xec\Action;
use Moxl\Stanza\Pubsub;
use Moxl\Xec\Action\Pubsub\Errors;
use Movim\Picture;
class GetItem extends Errors
{
protected $_to;
@ -26,7 +28,7 @@ class GetItem extends Errors
if ($stanza->pubsub->items->item) {
foreach ($stanza->pubsub->items->item as $item) {
if (isset($item->entry)
&&(string)$item->entry->attributes()->xmlns == 'http://www.w3.org/2005/Atom') {
&& (string)$item->entry->attributes()->xmlns == 'http://www.w3.org/2005/Atom') {
$p = \App\Post::firstOrNew([
'server' => $this->_to,
'node' => $this->_node,
@ -52,6 +54,29 @@ class GetItem extends Errors
$this->pack($p);
$this->deliver();
} elseif (isset($item->metadata)
&& (string)$item->metadata->attributes()->xmlns == 'urn:xmpp:avatar:metadata'
&& isset($item->metadata->info->attributes()->url)) {
$i = \App\Info::firstOrNew([
'server' => $this->_to,
'node' => $this->_node
]);
if ($i->avatarhash !== (string)$item->metadata->info->attributes()->id) {
$p = new Picture;
$p->fromURL((string)$item->metadata->info->attributes()->url);
$p->set((string)$item->metadata->info->attributes()->id);
$i->avatarhash = (string)$item->metadata->info->attributes()->id;
$i->save();
$this->method('avatar');
$this->pack([
'server' => $this->_to,
'node' => $this->_node
]);
$this->deliver();
}
}
}
} else {

18
lib/moxl/src/Moxl/Xec/Action/Pubsub/GetItems.php

@ -6,6 +6,8 @@ use Moxl\Xec\Action;
use Moxl\Stanza\Pubsub;
use Moxl\Xec\Action\Pubsub\Errors;
use Movim\Picture;
class GetItems extends Errors
{
protected $_to;
@ -64,6 +66,22 @@ class GetItems extends Errors
array_push($ids, $p->nodeid);
}
} elseif (isset($item->metadata)
&& (string)$item->metadata->attributes()->xmlns == 'urn:xmpp:avatar:metadata'
&& isset($item->metadata->info->attributes()->url)) {
$i = \App\Info::firstOrNew([
'server' => $this->_to,
'node' => $this->_node
]);
if ($i->avatarhash !== (string)$item->metadata->info->attributes()->id) {
$p = new Picture;
$p->fromURL((string)$item->metadata->info->attributes()->url);
$p->set((string)$item->metadata->info->attributes()->id);
$i->avatarhash = (string)$item->metadata->info->attributes()->id;
$i->save();
}
}
}

7
lib/moxl/src/Moxl/Xec/Action/Pubsub/GetItemsId.php

@ -11,6 +11,10 @@ class GetItemsId extends Errors
{
protected $_to;
protected $_node;
private $_forbidenIds = [
'urn:xmpp:avatar:data',
'urn:xmpp:avatar:metadata'
];
public function request()
{
@ -28,7 +32,8 @@ class GetItemsId extends Errors
->where('node', $this->_node)
->where('nodeid', $id)
->count() > 0
&& !empty($id)) {
&& !empty($id)
&& !in_array($id, $this->_forbidenIds)) {
$gi = new GetItem;
$gi->setTo($this->_to)
->setNode($this->_node)

23
src/Movim/Picture.php

@ -82,6 +82,15 @@ class Picture
&& filemtime($original) < time() - 3600 * DEFAULT_PICTURE_EXPIRATION_HOURS));
}
/**
* @desc Get the original picture URL, without timestamp
* @param $key The key of the picture
*/
public function getOriginal($key)
{
return $this->get($key, false, false, DEFAULT_PICTURE_FORMAT, true);
}
/**
* @desc Get a picture of the current size
* @param $key The key of the picture
@ -89,7 +98,7 @@ class Picture
* @param $height The height requested
* @return The url of the picture
*/
public function get($key, $width = false, $height = false, $format = DEFAULT_PICTURE_FORMAT)
public function get($key, $width = false, $height = false, $format = DEFAULT_PICTURE_FORMAT, bool $noTime = false)
{
if (!in_array($format, array_keys($this->_formats))) {
$format = DEFAULT_PICTURE_FORMAT;
@ -104,7 +113,8 @@ class Picture
if (file_exists($original)) {
$this->fromPath($original);
return urilize(
$this->_folder . md5($this->_key) . $this->_formats[$format]
$this->_folder . md5($this->_key) . $this->_formats[$format],
$noTime
);
}
}
@ -113,7 +123,7 @@ class Picture
if (file_exists(
$this->_path.md5($this->_key) .
'_' . $width.$this->_formats[$format]
)
)
) {
$this->fromPath(
$this->_path.md5($this->_key) .
@ -122,16 +132,19 @@ class Picture
return urilize(
$this->_folder.md5($this->_key) .
'_' . $width.$this->_formats[$format]
'_' . $width.$this->_formats[$format],
$noTime
);
}
if (file_exists($original)) {
$this->fromPath($original);
$this->_createThumbnail($width, $height);
return urilize(
$this->_folder.md5($this->_key) .
'_' . $width . $this->_formats[$format]
'_' . $width . $this->_formats[$format],
$noTime
);
}
}

Loading…
Cancel
Save