Skip to content

Commit a6b64d7

Browse files
committed
Allow using Emoji ZWJ Sequences
Signed-off-by: Georg Ehrke <developer@georgehrke.com>
1 parent fb878f5 commit a6b64d7

File tree

3 files changed

+52
-1
lines changed

3 files changed

+52
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
!/apps/updatenotification
3939
!/apps/theming
4040
!/apps/twofactor_backupcodes
41+
!/apps/user_status
4142
!/apps/workflowengine
4243
/apps/files_external/3rdparty/irodsphp/PHPUnitTest
4344
/apps/files_external/3rdparty/irodsphp/web

apps/user_status/lib/Service/StatusService.php

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ public function setStatus(string $userId,
118118
throw new InvalidStatusTypeException('Status-type "' . $statusType . '" is not supported');
119119
}
120120
// Check if statusIcon contains only one character
121-
if (\mb_strlen($statusIcon) > 1) {
121+
if ($statusIcon !== null && !$this->isValidEmoji($statusIcon)) {
122122
throw new InvalidStatusIconException('Status-Icon is longer than one character');
123123
}
124124
// Check for maximum length of custom message
@@ -158,4 +158,48 @@ public function removeUserStatus(string $userId): bool {
158158
$this->mapper->delete($userStatus);
159159
return true;
160160
}
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+
}
161205
}

apps/user_status/tests/Unit/Service/StatusServiceTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,12 @@ public function setStatusDataProvider(): array {
199199
// Clear at is in the past
200200
['john.doe', 'busy', '📱', 'In a phone call', 10, true, false, true, InvalidClearAtException::class, 'ClearAt is in the past'],
201201
['john.doe', 'busy', '📱', 'In a phone call', 10, false, false, true, InvalidClearAtException::class, 'ClearAt is in the past'],
202+
// Test some more complex emojis with modifiers and zero-width-joiner
203+
['john.doe', 'busy', '👩🏿‍💻', 'In a phone call', 42, true, true, false, null, null],
204+
['john.doe', 'busy', '🤷🏼‍♀️', 'In a phone call', 42, true, true, false, null, null],
205+
['john.doe', 'busy', '🏳️‍🌈', 'In a phone call', 42, true, true, false, null, null],
206+
['john.doe', 'busy', '👨‍👨‍👦‍👦', 'In a phone call', 42, true, true, false, null, null],
207+
['john.doe', 'busy', '👩‍❤️‍👩', 'In a phone call', 42, true, true, false, null, null],
202208
];
203209
}
204210

0 commit comments

Comments
 (0)