Bigchange for contact image avatar support #115

This commit is contained in:
the-djmaze 2022-11-22 23:54:32 +01:00
parent 113a421d0f
commit 5069bc2950
32 changed files with 134 additions and 31 deletions

View file

@ -22,10 +22,12 @@
view.viewUserPicVisible = ko.observable(false);
view.message.subscribe(msg => {
view.viewUserPicVisible(false);
if (msg) {
let from = msg.from[0],
bimi = 'pass' == from.dkimStatus ? 1 : 0;
// view.viewUserPic(`?Avatar/${bimi}/${encodeURIComponent(from.email)}`);
// view.viewUserPicVisible(true);
rl.pluginRemoteRequest((iError, data) => {
if (!iError && data?.Result.type) {
view.viewUserPic(`data:${data.Result.type};base64,${data.Result.data}`);
@ -38,6 +40,23 @@
}
});
}
/*
if ('MailMessageList' === e.detail.viewModelTemplateID) {
const
template = document.getElementById('MailMessageList' ),
messageCheckbox = template.content.querySelector('.messageCheckbox');
messageCheckbox.dataset.bind = 'attr:{style:$root.viewUserPic($data)}';
e.detail.viewUserPic = msg => {
let from = msg.from[0],
bimi = 'pass' == from.dkimStatus ? 1 : 0;
return `background:no-repeat url("?Avatar/${bimi}/${encodeURIComponent(from.email)}") center / contain`;
return `background:no-repeat url("?Avatar/${bimi}/${encodeURIComponent(from.email)}") right / 32px;width:68px`;
};
.checkboxMessage {
background: #000;
}
}
*/
});
})(window.rl);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 995 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 382 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

View file

@ -8,7 +8,7 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
URL = 'https://snappymail.eu/',
VERSION = '1.0',
RELEASE = '2022-11-11',
REQUIRED = '2.21.0',
REQUIRED = '2.22.0',
CATEGORY = 'Contacts',
LICENSE = 'MIT',
DESCRIPTION = '';
@ -53,48 +53,82 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
return null;
}
// $this->verifyCacheByKey($sEmail);
$oActions = \RainLoop\Api::Actions();
$oActions->verifyCacheByKey($sEmail);
// DATA_IMAGE_USER_DOT_PIC
$sDomain = \explode('@', $sEmail);
$sDomain = \array_pop($sDomain);
$BIMI = $bBimi ? \SnappyMail\DNS::BIMI($sDomain) : null;
// TODO: process $BIMI value
$aResult = null;
// TODO: lookup contacts vCard
// TODO: make this optional
$aResult = static::Gravatar($sEmail);
if (!$aResult && \file_exists(__DIR__ . '/images/services/'.$sDomain.'.png')) {
$aResult = [
'image/png',
\file_get_contents(__DIR__ . '/images/services/'.$sDomain.'.png')
];
$oAccount = $oActions->getAccountFromToken();
if ($oAccount) {
$oAddressBookProvider = $oActions->AddressBookProvider($oAccount);
if ($oAddressBookProvider) {
$oContact = $oAddressBookProvider->GetContactByEmail($sEmail);
if ($oContact && $oContact->vCard && $oContact->vCard['PHOTO']) {
$aResult = [
'text/vcard',
$oContact->vCard
];
}
}
}
if (!$aResult) {
$aResult = [
'image/png',
\file_get_contents(__DIR__.'/images/empty-contact.png')
];
$sDomain = \explode('@', $sEmail);
$sDomain = \array_pop($sDomain);
$aUrls = [];
$BIMI = $bBimi ? \SnappyMail\DNS::BIMI($sDomain) : null;
if ($BIMI) {
$aUrls[] = $BIMI;
// $aResult = ['text/uri-list', $BIMI];
\SnappyMail\Log::debug('Avatar', "BIMI {$sDomain} for {$sUrl}");
} else {
\SnappyMail\Log::notice('Avatar', "BIMI 404 for {$sDomain}");
}
// TODO: make Gravatar optional
$sAsciiEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true);
$aUrls[] = 'http://gravatar.com/avatar/'.\md5(\strtolower($sAsciiEmail)).'?s=80&d=404';
foreach ($aUrls as $sUrl) {
if ($aResult = static::getUrl($sUrl)) {
break;
}
}
}
// $this->cacheByKey($sEmail);
if (!$aResult) {
$aServices = [
"services/{$sDomain}",
'services/' . \preg_replace('/^.+\\.([^.]+\\.[^.]+)$/D', '$1', $sDomain),
'empty-contact' // DATA_IMAGE_USER_DOT_PIC
];
foreach ($aServices as $service) {
if (\file_exists(__DIR__ . "/images/{$service}.png")) {
$aResult = [
'image/png',
\file_get_contents(__DIR__ . "/images/{$service}.png")
];
break;
}
}
}
$oActions->cacheByKey($sEmail);
return $aResult;
}
private static function Gravatar(string $sEmail) : ?array
private static function getUrl(string $sUrl) : ?array
{
$sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true);
$sGravatarUrl = 'http://gravatar.com/avatar/'.\md5(\strtolower($sEmail)).'?s=80&d=404';
$oHTTP = \SnappyMail\HTTP\Request::factory(/*'socket' or 'curl'*/);
$oHTTP->proxy = \RainLoop\Api::Config()->Get('labs', 'curl_proxy', '');
$oHTTP->proxy_auth = \RainLoop\Api::Config()->Get('labs', 'curl_proxy_auth', '');
$oHTTP->max_response_kb = 0;
$oHTTP->timeout = 15; // timeout in seconds.
$oResponse = $oHTTP->doRequest('GET', $sGravatarUrl);
$oResponse = $oHTTP->doRequest('GET', $sUrl);
if ($oResponse) {
if (200 === $oResponse->status && \str_starts_with($oResponse->getHeader('content-type'), 'image/')) {
return [
@ -102,11 +136,10 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
$oResponse->body
];
}
\SnappyMail\Log::notice('Gravatar', "error {$oResponse->status} for {$sGravatarUrl}");
\SnappyMail\Log::notice('Avatar', "error {$oResponse->status} for {$sUrl}");
} else {
\SnappyMail\Log::warning('Gravatar', "failed for {$sGravatarUrl}");
\SnappyMail\Log::warning('Avatar', "failed for {$sUrl}");
}
return null;
}
}

View file

@ -313,6 +313,12 @@ class KolabAddressBook implements \RainLoop\Providers\AddressBook\AddressBookInt
return $aResult;
}
public function GetContactByEmail(string $sEmail) : ?Contact;
{
// TODO
return null;
}
public function GetContactByID($mID, bool $bIsStrID = false) : ?Contact
{
if ($bIsStrID) {

View file

@ -8,7 +8,7 @@ class KolabPlugin extends \RainLoop\Plugins\AbstractPlugin
RELEASE = '2022-09-06',
CATEGORY = 'Contacts',
DESCRIPTION = 'Use an Address Book of Kolab.',
REQUIRED = '2.18.0';
REQUIRED = '2.22.0';
public function Init() : void
{

View file

@ -60,6 +60,11 @@ class AddressBook extends AbstractProvider
) : array();
}
public function GetContactByEmail(string $sEmail) : ?AddressBook\Classes\Contact
{
return $this->IsActive() ? $this->oDriver->GetContactByEmail($sEmail) : null;
}
public function GetContactByID($mID, bool $bIsStrID = false) : ?AddressBook\Classes\Contact
{
return $this->IsActive() ? $this->oDriver->GetContactByID($mID, $bIsStrID) : null;

View file

@ -20,6 +20,8 @@ interface AddressBookInterface
public function GetContacts(int $iOffset = 0, int $iLimit = 20, string $sSearch = '', int &$iResultCount = 0) : array;
public function GetContactByEmail(string $sEmail) : ?Classes\Contact;
public function GetContactByID($mID, bool $bIsStrID = false) : ?Classes\Contact;
public function GetSuggestions(string $sSearch, int $iLimit = 20) : array;

View file

@ -694,6 +694,40 @@ class PdoAddressBook
return [];
}
/**
* @param mixed $mID
*/
public function GetContactByEmail(string $sEmail) : ?Contact
{
$sLowerSearch = $this->specialConvertSearchValueLower($sEmail);
$sSql = 'SELECT
DISTINCT id_contact
FROM rainloop_ab_properties
WHERE id_user = :id_user
AND prop_type = '.PropertyType::JCARD.'
AND ('.
'prop_value LIKE :search ESCAPE \'=\''
. (\strlen($sLowerSearch) ? ' OR (prop_value_lower <> \'\' AND prop_value_lower LIKE :search_lower ESCAPE \'=\')' : '').
')';
$aParams = array(
':id_user' => array($this->iUserID, \PDO::PARAM_INT),
':search' => array($this->specialConvertSearchValue($sEmail, '='), \PDO::PARAM_STR)
);
if (\strlen($sLowerSearch)) {
$aParams[':search_lower'] = array($sLowerSearch, \PDO::PARAM_STR);
}
$oContact = null;
$iIdContact = 0;
$aContacts = $this->getContactsFromPDO(
$this->prepareAndExecute($sSql, $aParams)
);
return $aContacts ? $aContacts[0] : null;
}
/**
* @param mixed $mID
*/

View file

@ -4,6 +4,10 @@ namespace SnappyMail;
abstract class DNS
{
/**
* $domain = 'bimigroup.org'
* Then a TXT lookup is done on 'default._bimi.bimigroup.org'
*/
public static function BIMI(string $domain) : string
{
$oCache = \RainLoop\Api::Actions()->Cacher();
@ -18,7 +22,7 @@ abstract class DNS
}
if (null === $BIMI) {
$BIMI = '';
$values = \dns_get_record($domain, \DNS_TXT);
$values = \dns_get_record("default._bimi.{$domain}", \DNS_TXT);
if ($values) {
foreach ($values as $value) {
if (\str_starts_with($value['txt'], 'v=BIMI1')) {