Skip to content

Commit 845afe9

Browse files
committed
Check for support of 4Byte in database and existence of IntlBreakIterator class
Signed-off-by: Georg Ehrke <developer@georgehrke.com>
1 parent e6d8b70 commit 845afe9

File tree

6 files changed

+300
-73
lines changed

6 files changed

+300
-73
lines changed

apps/user_status/lib/Capabilities.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,37 @@
2424
*/
2525
namespace OCA\UserStatus;
2626

27+
use OCA\UserStatus\Service\EmojiService;
2728
use OCP\Capabilities\ICapability;
2829

30+
/**
31+
* Class Capabilities
32+
*
33+
* @package OCA\UserStatus
34+
*/
2935
class Capabilities implements ICapability {
3036

37+
/** @var EmojiService */
38+
private $emojiService;
39+
40+
/**
41+
* Capabilities constructor.
42+
*
43+
* @param EmojiService $emojiService
44+
*/
45+
public function __construct(EmojiService $emojiService) {
46+
$this->emojiService = $emojiService;
47+
}
48+
3149
/**
3250
* @inheritDoc
3351
*/
3452
public function getCapabilities() {
3553
return [
3654
'user_status' => [
3755
'enabled' => true,
38-
]
56+
'supports_emoji' => $this->emojiService->doesPlatformSupportEmoji(),
57+
],
3958
];
4059
}
4160
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @copyright Copyright (c) 2020, Georg Ehrke
7+
*
8+
* @author Georg Ehrke <oc.list@georgehrke.com>
9+
*
10+
* @license AGPL-3.0
11+
*
12+
* This code is free software: you can redistribute it and/or modify
13+
* it under the terms of the GNU Affero General Public License, version 3,
14+
* as published by the Free Software Foundation.
15+
*
16+
* This program is distributed in the hope that it will be useful,
17+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+
* GNU Affero General Public License for more details.
20+
*
21+
* You should have received a copy of the GNU Affero General Public License, version 3,
22+
* along with this program. If not, see <http://www.gnu.org/licenses/>
23+
*
24+
*/
25+
26+
namespace OCA\UserStatus\Service;
27+
28+
use OCP\IDBConnection;
29+
30+
/**
31+
* Class EmojiService
32+
*
33+
* @package OCA\UserStatus\Service
34+
*/
35+
class EmojiService {
36+
37+
/** @var IDBConnection */
38+
private $db;
39+
40+
/**
41+
* EmojiService constructor.
42+
*
43+
* @param IDBConnection $db
44+
*/
45+
public function __construct(IDBConnection $db) {
46+
$this->db = $db;
47+
}
48+
49+
/**
50+
* @return bool
51+
*/
52+
public function doesPlatformSupportEmoji(): bool {
53+
return $this->db->supports4ByteText() &&
54+
\class_exists(\IntlBreakIterator::class);
55+
}
56+
57+
/**
58+
* @param string $emoji
59+
* @return bool
60+
*/
61+
public function isValidEmoji(string $emoji): bool {
62+
$intlBreakIterator = \IntlBreakIterator::createCharacterInstance();
63+
$intlBreakIterator->setText($emoji);
64+
65+
$characterCount = 0;
66+
while ($intlBreakIterator->next() !== \IntlBreakIterator::DONE) {
67+
$characterCount++;
68+
}
69+
70+
if ($characterCount !== 1) {
71+
return false;
72+
}
73+
74+
$codePointIterator = \IntlBreakIterator::createCodePointInstance();
75+
$codePointIterator->setText($emoji);
76+
77+
foreach ($codePointIterator->getPartsIterator() as $codePoint) {
78+
$codePointType = \IntlChar::charType($codePoint);
79+
80+
// If the current code-point is an emoji or a modifier (like a skin-tone)
81+
// just continue and check the next character
82+
if ($codePointType === \IntlChar::CHAR_CATEGORY_MODIFIER_SYMBOL ||
83+
$codePointType === \IntlChar::CHAR_CATEGORY_MODIFIER_LETTER ||
84+
$codePointType === \IntlChar::CHAR_CATEGORY_OTHER_SYMBOL) {
85+
continue;
86+
}
87+
88+
// If it's neither a modifier nor an emoji, we only allow
89+
// a zero-width-joiner or a variation selector 16
90+
$codePointValue = \IntlChar::ord($codePoint);
91+
if ($codePointValue === 8205 || $codePointValue === 65039) {
92+
continue;
93+
}
94+
95+
return false;
96+
}
97+
98+
return true;
99+
}
100+
}

apps/user_status/lib/Service/StatusService.php

Lines changed: 11 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ class StatusService {
4747
/** @var ITimeFactory */
4848
private $timeFactory;
4949

50+
/** @var EmojiService */
51+
private $emojiService;
52+
5053
/**
5154
* @var string[]
5255
*/
@@ -64,11 +67,14 @@ class StatusService {
6467
*
6568
* @param UserStatusMapper $mapper
6669
* @param ITimeFactory $timeFactory
70+
* @param EmojiService $emojiService
6771
*/
6872
public function __construct(UserStatusMapper $mapper,
69-
ITimeFactory $timeFactory) {
73+
ITimeFactory $timeFactory,
74+
EmojiService $emojiService) {
7075
$this->mapper = $mapper;
7176
$this->timeFactory = $timeFactory;
77+
$this->emojiService = $emojiService;
7278
}
7379

7480
/**
@@ -117,8 +123,11 @@ public function setStatus(string $userId,
117123
if (!\in_array($statusType, $this->allowedStatusTypes, true)) {
118124
throw new InvalidStatusTypeException('Status-type "' . $statusType . '" is not supported');
119125
}
126+
if ($statusIcon !== null && !$this->emojiService->doesPlatformSupportEmoji()) {
127+
throw new InvalidStatusIconException('Platform does not support status-icon.');
128+
}
120129
// Check if statusIcon contains only one character
121-
if ($statusIcon !== null && !$this->isValidEmoji($statusIcon)) {
130+
if ($statusIcon !== null && !$this->emojiService->isValidEmoji($statusIcon)) {
122131
throw new InvalidStatusIconException('Status-Icon is longer than one character');
123132
}
124133
// Check for maximum length of custom message
@@ -158,48 +167,4 @@ public function removeUserStatus(string $userId): bool {
158167
$this->mapper->delete($userStatus);
159168
return true;
160169
}
161-
162-
/**
163-
* @param string $emoji
164-
* @return bool
165-
*/
166-
private function isValidEmoji(string $emoji): bool {
167-
$intlBreakIterator = \IntlBreakIterator::createCharacterInstance();
168-
$intlBreakIterator->setText($emoji);
169-
170-
$characterCount = 0;
171-
while ($intlBreakIterator->next() !== \IntlBreakIterator::DONE) {
172-
$characterCount++;
173-
}
174-
175-
if ($characterCount !== 1) {
176-
return false;
177-
}
178-
179-
$codePointIterator = \IntlBreakIterator::createCodePointInstance();
180-
$codePointIterator->setText($emoji);
181-
182-
foreach ($codePointIterator->getPartsIterator() as $codePoint) {
183-
$codePointType = \IntlChar::charType($codePoint);
184-
185-
// If the current code-point is an emoji or a modifier (like a skin-tone)
186-
// just continue and check the next character
187-
if ($codePointType === \IntlChar::CHAR_CATEGORY_MODIFIER_SYMBOL ||
188-
$codePointType === \IntlChar::CHAR_CATEGORY_MODIFIER_LETTER ||
189-
$codePointType === \IntlChar::CHAR_CATEGORY_OTHER_SYMBOL) {
190-
continue;
191-
}
192-
193-
// If it's neither a modifier nor an emoji, we only allow
194-
// a zero-width-joiner or a variation selector 16
195-
$codePointValue = \IntlChar::ord($codePoint);
196-
if ($codePointValue === 8205 || $codePointValue === 65039) {
197-
continue;
198-
}
199-
200-
return false;
201-
}
202-
203-
return true;
204-
}
205170
}

apps/user_status/tests/Unit/CapabilitiesTest.php

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,46 @@
2626
namespace OCA\UserStatus\Tests;
2727

2828
use OCA\UserStatus\Capabilities;
29+
use OCA\UserStatus\Service\EmojiService;
2930
use Test\TestCase;
3031

3132
class CapabilitiesTest extends TestCase {
3233

34+
/** @var EmojiService|\PHPUnit\Framework\MockObject\MockObject */
35+
private $emojiService;
36+
3337
/** @var Capabilities */
3438
private $capabilities;
3539

3640
protected function setUp(): void {
3741
parent::setUp();
3842

39-
$this->capabilities = new Capabilities();
43+
$this->emojiService = $this->createMock(EmojiService::class);
44+
$this->capabilities = new Capabilities($this->emojiService);
4045
}
4146

42-
public function testGetCapabilities(): void {
47+
/**
48+
* @param bool $supportsEmojis
49+
*
50+
* @dataProvider getCapabilitiesDataProvider
51+
*/
52+
public function testGetCapabilities(bool $supportsEmojis): void {
53+
$this->emojiService->expects($this->once())
54+
->method('doesPlatformSupportEmoji')
55+
->willReturn($supportsEmojis);
56+
4357
$this->assertEquals([
4458
'user_status' => [
4559
'enabled' => true,
60+
'supports_emoji' => $supportsEmojis,
4661
]
4762
], $this->capabilities->getCapabilities());
4863
}
64+
65+
public function getCapabilitiesDataProvider(): array {
66+
return [
67+
[true],
68+
[false],
69+
];
70+
}
4971
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @copyright Copyright (c) 2020, Georg Ehrke
7+
*
8+
* @author Georg Ehrke <oc.list@georgehrke.com>
9+
*
10+
* @license AGPL-3.0
11+
*
12+
* This code is free software: you can redistribute it and/or modify
13+
* it under the terms of the GNU Affero General Public License, version 3,
14+
* as published by the Free Software Foundation.
15+
*
16+
* This program is distributed in the hope that it will be useful,
17+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+
* GNU Affero General Public License for more details.
20+
*
21+
* You should have received a copy of the GNU Affero General Public License, version 3,
22+
* along with this program. If not, see <http://www.gnu.org/licenses/>
23+
*
24+
*/
25+
26+
namespace OCA\UserStatus\Tests\Service;
27+
28+
use OCA\UserStatus\Service\EmojiService;
29+
use OCP\IDBConnection;
30+
use Test\TestCase;
31+
32+
class EmojiServiceTest extends TestCase {
33+
34+
/** @var IDBConnection|\PHPUnit\Framework\MockObject\MockObject */
35+
private $db;
36+
37+
/** @var EmojiService */
38+
private $service;
39+
40+
protected function setUp(): void {
41+
parent::setUp();
42+
43+
$this->db = $this->createMock(IDBConnection::class);
44+
$this->service = new EmojiService($this->db);
45+
}
46+
47+
/**
48+
* @param bool $supports4ByteText
49+
* @param bool $expected
50+
*
51+
* @dataProvider doesPlatformSupportEmojiDataProvider
52+
*/
53+
public function testDoesPlatformSupportEmoji(bool $supports4ByteText, bool $expected): void {
54+
$this->db->expects($this->once())
55+
->method('supports4ByteText')
56+
->willReturn($supports4ByteText);
57+
58+
$this->assertEquals($expected, $this->service->doesPlatformSupportEmoji());
59+
}
60+
61+
/**
62+
* @return array
63+
*/
64+
public function doesPlatformSupportEmojiDataProvider(): array {
65+
return [
66+
[true, true],
67+
[false, false],
68+
];
69+
}
70+
71+
/**
72+
* @param string $emoji
73+
* @param bool $expected
74+
*
75+
* @dataProvider isValidEmojiDataProvider
76+
*/
77+
public function testIsValidEmoji(string $emoji, bool $expected): void {
78+
$actual = $this->service->isValidEmoji($emoji);
79+
80+
$this->assertEquals($expected, $actual);
81+
}
82+
83+
public function isValidEmojiDataProvider(): array {
84+
return [
85+
['🏝', true],
86+
['📱', true],
87+
['🏢', true],
88+
['📱📠', false],
89+
['a', false],
90+
['0', false],
91+
['$', false],
92+
// Test some more complex emojis with modifiers and zero-width-joiner
93+
['👩🏿‍💻', true],
94+
['🤷🏼‍♀️', true],
95+
['🏳️‍🌈', true],
96+
['👨‍👨‍👦‍👦', true],
97+
['👩‍❤️‍👩', true]
98+
];
99+
}
100+
}
101+

0 commit comments

Comments
 (0)