Skip to content

Commit

Permalink
Check for support of 4Byte in database and existence of IntlBreakIter…
Browse files Browse the repository at this point in the history
…ator class

Signed-off-by: Georg Ehrke <[email protected]>
  • Loading branch information
georgehrke committed Jun 8, 2020
1 parent e6d8b70 commit 32dd658
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 73 deletions.
21 changes: 20 additions & 1 deletion apps/user_status/lib/Capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,37 @@
*/
namespace OCA\UserStatus;

use OCA\UserStatus\Service\EmojiService;
use OCP\Capabilities\ICapability;

/**
* Class Capabilities
*
* @package OCA\UserStatus
*/
class Capabilities implements ICapability {

/** @var EmojiService */
private $emojiService;

/**
* Capabilities constructor.
*
* @param EmojiService $emojiService
*/
public function __construct(EmojiService $emojiService) {
$this->emojiService = $emojiService;
}

/**
* @inheritDoc
*/
public function getCapabilities() {
return [
'user_status' => [
'enabled' => true,
]
'supports_emoji' => $this->emojiService->doesPlatformSupportEmoji(),
],
];
}
}
100 changes: 100 additions & 0 deletions apps/user_status/lib/Service/EmojiService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <[email protected]>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/

namespace OCA\UserStatus\Service;

use OCP\IDBConnection;

/**
* Class EmojiService
*
* @package OCA\UserStatus\Service
*/
class EmojiService {

/** @var IDBConnection */
private $db;

/**
* EmojiService constructor.
*
* @param IDBConnection $db
*/
public function __construct(IDBConnection $db) {
$this->db = $db;
}

/**
* @return bool
*/
public function doesPlatformSupportEmoji(): bool {
return $this->db->supports4ByteText() &&
\class_exists(\IntlBreakIterator::class);
}

/**
* @param string $emoji
* @return bool
*/
public function isValidEmoji(string $emoji): bool {
$intlBreakIterator = \IntlBreakIterator::createCharacterInstance();
$intlBreakIterator->setText($emoji);

$characterCount = 0;
while ($intlBreakIterator->next() !== \IntlBreakIterator::DONE) {
$characterCount++;
}

if ($characterCount !== 1) {
return false;
}

$codePointIterator = \IntlBreakIterator::createCodePointInstance();
$codePointIterator->setText($emoji);

foreach ($codePointIterator->getPartsIterator() as $codePoint) {
$codePointType = \IntlChar::charType($codePoint);

// If the current code-point is an emoji or a modifier (like a skin-tone)
// just continue and check the next character
if ($codePointType === \IntlChar::CHAR_CATEGORY_MODIFIER_SYMBOL ||
$codePointType === \IntlChar::CHAR_CATEGORY_MODIFIER_LETTER ||
$codePointType === \IntlChar::CHAR_CATEGORY_OTHER_SYMBOL) {
continue;
}

// If it's neither a modifier nor an emoji, we only allow
// a zero-width-joiner or a variation selector 16
$codePointValue = \IntlChar::ord($codePoint);
if ($codePointValue === 8205 || $codePointValue === 65039) {
continue;
}

return false;
}

return true;
}
}
57 changes: 11 additions & 46 deletions apps/user_status/lib/Service/StatusService.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ class StatusService {
/** @var ITimeFactory */
private $timeFactory;

/** @var EmojiService */
private $emojiService;

/**
* @var string[]
*/
Expand All @@ -64,11 +67,14 @@ class StatusService {
*
* @param UserStatusMapper $mapper
* @param ITimeFactory $timeFactory
* @param EmojiService $emojiService
*/
public function __construct(UserStatusMapper $mapper,
ITimeFactory $timeFactory) {
ITimeFactory $timeFactory,
EmojiService $emojiService) {
$this->mapper = $mapper;
$this->timeFactory = $timeFactory;
$this->emojiService = $emojiService;
}

/**
Expand Down Expand Up @@ -117,8 +123,11 @@ public function setStatus(string $userId,
if (!\in_array($statusType, $this->allowedStatusTypes, true)) {
throw new InvalidStatusTypeException('Status-type "' . $statusType . '" is not supported');
}
if ($statusIcon !== null && !$this->emojiService->doesPlatformSupportEmoji()) {
throw new InvalidStatusIconException('Platform does not support status-icon.');
}
// Check if statusIcon contains only one character
if ($statusIcon !== null && !$this->isValidEmoji($statusIcon)) {
if ($statusIcon !== null && !$this->emojiService->isValidEmoji($statusIcon)) {
throw new InvalidStatusIconException('Status-Icon is longer than one character');
}
// Check for maximum length of custom message
Expand Down Expand Up @@ -158,48 +167,4 @@ public function removeUserStatus(string $userId): bool {
$this->mapper->delete($userStatus);
return true;
}

/**
* @param string $emoji
* @return bool
*/
private function isValidEmoji(string $emoji): bool {
$intlBreakIterator = \IntlBreakIterator::createCharacterInstance();
$intlBreakIterator->setText($emoji);

$characterCount = 0;
while ($intlBreakIterator->next() !== \IntlBreakIterator::DONE) {
$characterCount++;
}

if ($characterCount !== 1) {
return false;
}

$codePointIterator = \IntlBreakIterator::createCodePointInstance();
$codePointIterator->setText($emoji);

foreach ($codePointIterator->getPartsIterator() as $codePoint) {
$codePointType = \IntlChar::charType($codePoint);

// If the current code-point is an emoji or a modifier (like a skin-tone)
// just continue and check the next character
if ($codePointType === \IntlChar::CHAR_CATEGORY_MODIFIER_SYMBOL ||
$codePointType === \IntlChar::CHAR_CATEGORY_MODIFIER_LETTER ||
$codePointType === \IntlChar::CHAR_CATEGORY_OTHER_SYMBOL) {
continue;
}

// If it's neither a modifier nor an emoji, we only allow
// a zero-width-joiner or a variation selector 16
$codePointValue = \IntlChar::ord($codePoint);
if ($codePointValue === 8205 || $codePointValue === 65039) {
continue;
}

return false;
}

return true;
}
}
26 changes: 24 additions & 2 deletions apps/user_status/tests/Unit/CapabilitiesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,46 @@
namespace OCA\UserStatus\Tests;

use OCA\UserStatus\Capabilities;
use OCA\UserStatus\Service\EmojiService;
use Test\TestCase;

class CapabilitiesTest extends TestCase {

/** @var EmojiService|\PHPUnit\Framework\MockObject\MockObject */
private $emojiService;

/** @var Capabilities */
private $capabilities;

protected function setUp(): void {
parent::setUp();

$this->capabilities = new Capabilities();
$this->emojiService = $this->createMock(EmojiService::class);
$this->capabilities = new Capabilities($this->emojiService);
}

public function testGetCapabilities(): void {
/**
* @param bool $supportsEmojis
*
* @dataProvider getCapabilitiesDataProvider
*/
public function testGetCapabilities(bool $supportsEmojis): void {
$this->emojiService->expects($this->once())
->method('doesPlatformSupportEmoji')
->willReturn($supportsEmojis);

$this->assertEquals([
'user_status' => [
'enabled' => true,
'supports_emoji' => $supportsEmojis,
]
], $this->capabilities->getCapabilities());
}

public function getCapabilitiesDataProvider(): array {
return [
[true],
[false],
];
}
}
100 changes: 100 additions & 0 deletions apps/user_status/tests/Unit/Service/EmojiServiceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <[email protected]>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/

namespace OCA\UserStatus\Tests\Service;

use OCA\UserStatus\Service\EmojiService;
use OCP\IDBConnection;
use Test\TestCase;

class EmojiServiceTest extends TestCase {

/** @var IDBConnection|\PHPUnit\Framework\MockObject\MockObject */
private $db;

/** @var EmojiService */
private $service;

protected function setUp(): void {
parent::setUp();

$this->db = $this->createMock(IDBConnection::class);
$this->service = new EmojiService($this->db);
}

/**
* @param bool $supports4ByteText
* @param bool $expected
*
* @dataProvider doesPlatformSupportEmojiDataProvider
*/
public function testDoesPlatformSupportEmoji(bool $supports4ByteText, bool $expected): void {
$this->db->expects($this->once())
->method('supports4ByteText')
->willReturn($supports4ByteText);

$this->assertEquals($expected, $this->service->doesPlatformSupportEmoji());
}

/**
* @return array
*/
public function doesPlatformSupportEmojiDataProvider(): array {
return [
[true, true],
[false, false],
];
}

/**
* @param string $emoji
* @param bool $expected
*
* @dataProvider isValidEmojiDataProvider
*/
public function testIsValidEmoji(string $emoji, bool $expected): void {
$actual = $this->service->isValidEmoji($emoji);

$this->assertEquals($expected, $actual);
}

public function isValidEmojiDataProvider(): array {
return [
['🏝', true],
['📱', true],
['🏢', true],
['📱📠', false],
['a', false],
['0', false],
['$', false],
// Test some more complex emojis with modifiers and zero-width-joiner
['👩🏿‍💻', true],
['🤷🏼‍♀️', true],
['🏳️‍🌈', true],
['👨‍👨‍👦‍👦', true],
['👩‍❤️‍👩', true]
];
}
}
Loading

0 comments on commit 32dd658

Please sign in to comment.