Browse Source
Add user-status app
Add user-status app
Signed-off-by: Georg Ehrke <developer@georgehrke.com>pull/21186/head
No known key found for this signature in database
GPG Key ID: 9D98FD9380A1CB43
83 changed files with 6681 additions and 6 deletions
-
1.gitignore
-
1COPYING-README
-
2Makefile
-
31apps/user_status/appinfo/info.xml
-
43apps/user_status/appinfo/routes.php
-
7apps/user_status/composer/autoload.php
-
13apps/user_status/composer/composer.json
-
445apps/user_status/composer/composer/ClassLoader.php
-
21apps/user_status/composer/composer/LICENSE
-
31apps/user_status/composer/composer/autoload_classmap.php
-
9apps/user_status/composer/composer/autoload_namespaces.php
-
10apps/user_status/composer/composer/autoload_psr4.php
-
46apps/user_status/composer/composer/autoload_real.php
-
57apps/user_status/composer/composer/autoload_static.php
-
37apps/user_status/css/user-status-menu.scss
-
1apps/user_status/img/app.svg
-
1apps/user_status/img/user-status-away.svg
-
1apps/user_status/img/user-status-dnd.svg
-
1apps/user_status/img/user-status-invisible.svg
-
1apps/user_status/img/user-status-online.svg
-
2apps/user_status/js/user-status-menu.js
-
1apps/user_status/js/user-status-menu.js.map
-
74apps/user_status/lib/AppInfo/Application.php
-
63apps/user_status/lib/BackgroundJob/ClearOldStatusesBackgroundJob.php
-
60apps/user_status/lib/Capabilities.php
-
92apps/user_status/lib/Controller/HeartbeatController.php
-
65apps/user_status/lib/Controller/PredefinedStatusController.php
-
107apps/user_status/lib/Controller/StatusesController.php
-
191apps/user_status/lib/Controller/UserStatusController.php
-
90apps/user_status/lib/Db/UserStatus.php
-
104apps/user_status/lib/Db/UserStatusMapper.php
-
29apps/user_status/lib/Exception/InvalidClearAtException.php
-
29apps/user_status/lib/Exception/InvalidMessageIdException.php
-
29apps/user_status/lib/Exception/InvalidStatusIconException.php
-
29apps/user_status/lib/Exception/InvalidStatusTypeException.php
-
29apps/user_status/lib/Exception/StatusMessageTooLongException.php
-
75apps/user_status/lib/Listener/BeforeTemplateRenderedListener.php
-
65apps/user_status/lib/Listener/UserDeletedListener.php
-
133apps/user_status/lib/Listener/UserLiveStatusListener.php
-
97apps/user_status/lib/Migration/Version0001Date20200602134824.php
-
100apps/user_status/lib/Service/EmojiService.php
-
84apps/user_status/lib/Service/JSDataService.php
-
187apps/user_status/lib/Service/PredefinedStatusService.php
-
335apps/user_status/lib/Service/StatusService.php
-
271apps/user_status/src/App.vue
-
102apps/user_status/src/components/ClearAtSelect.vue
-
65apps/user_status/src/components/CustomMessageInput.vue
-
111apps/user_status/src/components/PredefinedStatus.vue
-
90apps/user_status/src/components/PredefinedStatusesList.vue
-
236apps/user_status/src/components/SetStatusModal.vue
-
68apps/user_status/src/filters/clearAtFilter.js
-
23apps/user_status/src/main-user-status-menu.js
-
68apps/user_status/src/services/clearAtOptionsService.js
-
63apps/user_status/src/services/clearAtService.js
-
34apps/user_status/src/services/dateService.js
-
40apps/user_status/src/services/heartbeatService.js
-
39apps/user_status/src/services/predefinedStatusService.js
-
52apps/user_status/src/services/statusOptionsService.js
-
98apps/user_status/src/services/statusService.js
-
35apps/user_status/src/store/index.js
-
64apps/user_status/src/store/predefinedStatuses.js
-
232apps/user_status/src/store/userStatus.js
-
63apps/user_status/tests/Unit/BackgroundJob/ClearOldStatusesBackgroundJobTest.php
-
71apps/user_status/tests/Unit/CapabilitiesTest.php
-
74apps/user_status/tests/Unit/Controller/PredefinedStatusControllerTest.php
-
114apps/user_status/tests/Unit/Controller/StatusesControllerTest.php
-
340apps/user_status/tests/Unit/Controller/UserStatusControllerTest.php
-
168apps/user_status/tests/Unit/Db/UserStatusMapperTest.php
-
71apps/user_status/tests/Unit/Listener/UserDeletedListenerTest.php
-
162apps/user_status/tests/Unit/Listener/UserLiveStatusListenerTest.php
-
100apps/user_status/tests/Unit/Service/EmojiServiceTest.php
-
184apps/user_status/tests/Unit/Service/PredefinedStatusServiceTest.php
-
592apps/user_status/tests/Unit/Service/StatusServiceTest.php
-
36apps/user_status/tests/bootstrap.php
-
18apps/user_status/webpack.js
-
1lib/composer/composer/autoload_classmap.php
-
1lib/composer/composer/autoload_static.php
-
8lib/private/NavigationManager.php
-
101lib/public/User/Events/UserLiveStatusEvent.php
-
56package-lock.json
-
1package.json
-
4tests/lib/NavigationManagerTest.php
-
2webpack.common.js
@ -0,0 +1,31 @@ |
|||
<?xml version="1.0"?> |
|||
<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" |
|||
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd"> |
|||
<id>user_status</id> |
|||
<name>User status</name> |
|||
<summary>User status</summary> |
|||
<description><![CDATA[User status]]></description> |
|||
<version>0.0.2</version> |
|||
<licence>agpl</licence> |
|||
<author mail="oc.list@georgehrke.com" >Georg Ehrke</author> |
|||
<namespace>UserStatus</namespace> |
|||
<default_enable/> |
|||
<category>social</category> |
|||
<bugs>https://github.com/nextcloud/server</bugs> |
|||
<navigations> |
|||
<navigation> |
|||
<id>user_status-menuitem</id> |
|||
<name>User status</name> |
|||
<route /> |
|||
<order>1</order> |
|||
<icon>info.svg</icon> |
|||
<type>settings</type> |
|||
</navigation> |
|||
</navigations> |
|||
<dependencies> |
|||
<nextcloud min-version="20" max-version="20"/> |
|||
</dependencies> |
|||
<background-jobs> |
|||
<job>OCA\UserStatus\BackgroundJob\ClearOldStatusesBackgroundJob</job> |
|||
</background-jobs> |
|||
</info> |
|||
@ -0,0 +1,43 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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/> |
|||
* |
|||
*/ |
|||
|
|||
return [ |
|||
'ocs' => [ |
|||
// Routes for querying statuses
|
|||
['name' => 'Statuses#findAll', 'url' => '/api/v1/statuses', 'verb' => 'GET'], |
|||
['name' => 'Statuses#find', 'url' => '/api/v1/statuses/{userId}', 'verb' => 'GET'], |
|||
// Routes for manipulating your own status
|
|||
['name' => 'UserStatus#getStatus', 'url' => '/api/v1/user_status', 'verb' => 'GET'], |
|||
['name' => 'UserStatus#setStatus', 'url' => '/api/v1/user_status/status', 'verb' => 'PUT'], |
|||
['name' => 'UserStatus#setPredefinedMessage', 'url' => '/api/v1/user_status/message/predefined', 'verb' => 'PUT'], |
|||
['name' => 'UserStatus#setCustomMessage', 'url' => '/api/v1/user_status/message/custom', 'verb' => 'PUT'], |
|||
['name' => 'UserStatus#clearMessage', 'url' => '/api/v1/user_status/message', 'verb' => 'DELETE'], |
|||
// Routes for listing default routes
|
|||
['name' => 'PredefinedStatus#findAll', 'url' => '/api/v1/predefined_statuses/', 'verb' => 'GET'] |
|||
], |
|||
'routes' => [ |
|||
['name' => 'Heartbeat#heartbeat', 'url' => '/heartbeat', 'verb' => 'PUT'], |
|||
], |
|||
]; |
|||
@ -0,0 +1,7 @@ |
|||
<?php |
|||
|
|||
// autoload.php @generated by Composer
|
|||
|
|||
require_once __DIR__ . '/composer/autoload_real.php'; |
|||
|
|||
return ComposerAutoloaderInitUserStatus::getLoader(); |
|||
@ -0,0 +1,13 @@ |
|||
{ |
|||
"config" : { |
|||
"vendor-dir": ".", |
|||
"optimize-autoloader": true, |
|||
"classmap-authoritative": true, |
|||
"autoloader-suffix": "UserStatus" |
|||
}, |
|||
"autoload" : { |
|||
"psr-4": { |
|||
"OCA\\UserStatus\\": "../lib/" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,445 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Composer. |
|||
* |
|||
* (c) Nils Adermann <naderman@naderman.de> |
|||
* Jordi Boggiano <j.boggiano@seld.be> |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
namespace Composer\Autoload; |
|||
|
|||
/** |
|||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader. |
|||
* |
|||
* $loader = new \Composer\Autoload\ClassLoader(); |
|||
* |
|||
* // register classes with namespaces
|
|||
* $loader->add('Symfony\Component', __DIR__.'/component'); |
|||
* $loader->add('Symfony', __DIR__.'/framework'); |
|||
* |
|||
* // activate the autoloader
|
|||
* $loader->register(); |
|||
* |
|||
* // to enable searching the include path (eg. for PEAR packages)
|
|||
* $loader->setUseIncludePath(true); |
|||
* |
|||
* In this example, if you try to use a class in the Symfony\Component |
|||
* namespace or one of its children (Symfony\Component\Console for instance), |
|||
* the autoloader will first look for the class under the component/ |
|||
* directory, and it will then fallback to the framework/ directory if not |
|||
* found before giving up. |
|||
* |
|||
* This class is loosely based on the Symfony UniversalClassLoader. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
* @author Jordi Boggiano <j.boggiano@seld.be> |
|||
* @see http://www.php-fig.org/psr/psr-0/ |
|||
* @see http://www.php-fig.org/psr/psr-4/ |
|||
*/ |
|||
class ClassLoader |
|||
{ |
|||
// PSR-4
|
|||
private $prefixLengthsPsr4 = array(); |
|||
private $prefixDirsPsr4 = array(); |
|||
private $fallbackDirsPsr4 = array(); |
|||
|
|||
// PSR-0
|
|||
private $prefixesPsr0 = array(); |
|||
private $fallbackDirsPsr0 = array(); |
|||
|
|||
private $useIncludePath = false; |
|||
private $classMap = array(); |
|||
private $classMapAuthoritative = false; |
|||
private $missingClasses = array(); |
|||
private $apcuPrefix; |
|||
|
|||
public function getPrefixes() |
|||
{ |
|||
if (!empty($this->prefixesPsr0)) { |
|||
return call_user_func_array('array_merge', $this->prefixesPsr0); |
|||
} |
|||
|
|||
return array(); |
|||
} |
|||
|
|||
public function getPrefixesPsr4() |
|||
{ |
|||
return $this->prefixDirsPsr4; |
|||
} |
|||
|
|||
public function getFallbackDirs() |
|||
{ |
|||
return $this->fallbackDirsPsr0; |
|||
} |
|||
|
|||
public function getFallbackDirsPsr4() |
|||
{ |
|||
return $this->fallbackDirsPsr4; |
|||
} |
|||
|
|||
public function getClassMap() |
|||
{ |
|||
return $this->classMap; |
|||
} |
|||
|
|||
/** |
|||
* @param array $classMap Class to filename map |
|||
*/ |
|||
public function addClassMap(array $classMap) |
|||
{ |
|||
if ($this->classMap) { |
|||
$this->classMap = array_merge($this->classMap, $classMap); |
|||
} else { |
|||
$this->classMap = $classMap; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Registers a set of PSR-0 directories for a given prefix, either |
|||
* appending or prepending to the ones previously set for this prefix. |
|||
* |
|||
* @param string $prefix The prefix |
|||
* @param array|string $paths The PSR-0 root directories |
|||
* @param bool $prepend Whether to prepend the directories |
|||
*/ |
|||
public function add($prefix, $paths, $prepend = false) |
|||
{ |
|||
if (!$prefix) { |
|||
if ($prepend) { |
|||
$this->fallbackDirsPsr0 = array_merge( |
|||
(array) $paths, |
|||
$this->fallbackDirsPsr0 |
|||
); |
|||
} else { |
|||
$this->fallbackDirsPsr0 = array_merge( |
|||
$this->fallbackDirsPsr0, |
|||
(array) $paths |
|||
); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
$first = $prefix[0]; |
|||
if (!isset($this->prefixesPsr0[$first][$prefix])) { |
|||
$this->prefixesPsr0[$first][$prefix] = (array) $paths; |
|||
|
|||
return; |
|||
} |
|||
if ($prepend) { |
|||
$this->prefixesPsr0[$first][$prefix] = array_merge( |
|||
(array) $paths, |
|||
$this->prefixesPsr0[$first][$prefix] |
|||
); |
|||
} else { |
|||
$this->prefixesPsr0[$first][$prefix] = array_merge( |
|||
$this->prefixesPsr0[$first][$prefix], |
|||
(array) $paths |
|||
); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Registers a set of PSR-4 directories for a given namespace, either |
|||
* appending or prepending to the ones previously set for this namespace. |
|||
* |
|||
* @param string $prefix The prefix/namespace, with trailing '\\' |
|||
* @param array|string $paths The PSR-4 base directories |
|||
* @param bool $prepend Whether to prepend the directories |
|||
* |
|||
* @throws \InvalidArgumentException |
|||
*/ |
|||
public function addPsr4($prefix, $paths, $prepend = false) |
|||
{ |
|||
if (!$prefix) { |
|||
// Register directories for the root namespace.
|
|||
if ($prepend) { |
|||
$this->fallbackDirsPsr4 = array_merge( |
|||
(array) $paths, |
|||
$this->fallbackDirsPsr4 |
|||
); |
|||
} else { |
|||
$this->fallbackDirsPsr4 = array_merge( |
|||
$this->fallbackDirsPsr4, |
|||
(array) $paths |
|||
); |
|||
} |
|||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) { |
|||
// Register directories for a new namespace.
|
|||
$length = strlen($prefix); |
|||
if ('\\' !== $prefix[$length - 1]) { |
|||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); |
|||
} |
|||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; |
|||
$this->prefixDirsPsr4[$prefix] = (array) $paths; |
|||
} elseif ($prepend) { |
|||
// Prepend directories for an already registered namespace.
|
|||
$this->prefixDirsPsr4[$prefix] = array_merge( |
|||
(array) $paths, |
|||
$this->prefixDirsPsr4[$prefix] |
|||
); |
|||
} else { |
|||
// Append directories for an already registered namespace.
|
|||
$this->prefixDirsPsr4[$prefix] = array_merge( |
|||
$this->prefixDirsPsr4[$prefix], |
|||
(array) $paths |
|||
); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Registers a set of PSR-0 directories for a given prefix, |
|||
* replacing any others previously set for this prefix. |
|||
* |
|||
* @param string $prefix The prefix |
|||
* @param array|string $paths The PSR-0 base directories |
|||
*/ |
|||
public function set($prefix, $paths) |
|||
{ |
|||
if (!$prefix) { |
|||
$this->fallbackDirsPsr0 = (array) $paths; |
|||
} else { |
|||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Registers a set of PSR-4 directories for a given namespace, |
|||
* replacing any others previously set for this namespace. |
|||
* |
|||
* @param string $prefix The prefix/namespace, with trailing '\\' |
|||
* @param array|string $paths The PSR-4 base directories |
|||
* |
|||
* @throws \InvalidArgumentException |
|||
*/ |
|||
public function setPsr4($prefix, $paths) |
|||
{ |
|||
if (!$prefix) { |
|||
$this->fallbackDirsPsr4 = (array) $paths; |
|||
} else { |
|||
$length = strlen($prefix); |
|||
if ('\\' !== $prefix[$length - 1]) { |
|||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); |
|||
} |
|||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; |
|||
$this->prefixDirsPsr4[$prefix] = (array) $paths; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Turns on searching the include path for class files. |
|||
* |
|||
* @param bool $useIncludePath |
|||
*/ |
|||
public function setUseIncludePath($useIncludePath) |
|||
{ |
|||
$this->useIncludePath = $useIncludePath; |
|||
} |
|||
|
|||
/** |
|||
* Can be used to check if the autoloader uses the include path to check |
|||
* for classes. |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function getUseIncludePath() |
|||
{ |
|||
return $this->useIncludePath; |
|||
} |
|||
|
|||
/** |
|||
* Turns off searching the prefix and fallback directories for classes |
|||
* that have not been registered with the class map. |
|||
* |
|||
* @param bool $classMapAuthoritative |
|||
*/ |
|||
public function setClassMapAuthoritative($classMapAuthoritative) |
|||
{ |
|||
$this->classMapAuthoritative = $classMapAuthoritative; |
|||
} |
|||
|
|||
/** |
|||
* Should class lookup fail if not found in the current class map? |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function isClassMapAuthoritative() |
|||
{ |
|||
return $this->classMapAuthoritative; |
|||
} |
|||
|
|||
/** |
|||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled. |
|||
* |
|||
* @param string|null $apcuPrefix |
|||
*/ |
|||
public function setApcuPrefix($apcuPrefix) |
|||
{ |
|||
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; |
|||
} |
|||
|
|||
/** |
|||
* The APCu prefix in use, or null if APCu caching is not enabled. |
|||
* |
|||
* @return string|null |
|||
*/ |
|||
public function getApcuPrefix() |
|||
{ |
|||
return $this->apcuPrefix; |
|||
} |
|||
|
|||
/** |
|||
* Registers this instance as an autoloader. |
|||
* |
|||
* @param bool $prepend Whether to prepend the autoloader or not |
|||
*/ |
|||
public function register($prepend = false) |
|||
{ |
|||
spl_autoload_register(array($this, 'loadClass'), true, $prepend); |
|||
} |
|||
|
|||
/** |
|||
* Unregisters this instance as an autoloader. |
|||
*/ |
|||
public function unregister() |
|||
{ |
|||
spl_autoload_unregister(array($this, 'loadClass')); |
|||
} |
|||
|
|||
/** |
|||
* Loads the given class or interface. |
|||
* |
|||
* @param string $class The name of the class |
|||
* @return bool|null True if loaded, null otherwise |
|||
*/ |
|||
public function loadClass($class) |
|||
{ |
|||
if ($file = $this->findFile($class)) { |
|||
includeFile($file); |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Finds the path to the file where the class is defined. |
|||
* |
|||
* @param string $class The name of the class |
|||
* |
|||
* @return string|false The path if found, false otherwise |
|||
*/ |
|||
public function findFile($class) |
|||
{ |
|||
// class map lookup
|
|||
if (isset($this->classMap[$class])) { |
|||
return $this->classMap[$class]; |
|||
} |
|||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { |
|||
return false; |
|||
} |
|||
if (null !== $this->apcuPrefix) { |
|||
$file = apcu_fetch($this->apcuPrefix.$class, $hit); |
|||
if ($hit) { |
|||
return $file; |
|||
} |
|||
} |
|||
|
|||
$file = $this->findFileWithExtension($class, '.php'); |
|||
|
|||
// Search for Hack files if we are running on HHVM
|
|||
if (false === $file && defined('HHVM_VERSION')) { |
|||
$file = $this->findFileWithExtension($class, '.hh'); |
|||
} |
|||
|
|||
if (null !== $this->apcuPrefix) { |
|||
apcu_add($this->apcuPrefix.$class, $file); |
|||
} |
|||
|
|||
if (false === $file) { |
|||
// Remember that this class does not exist.
|
|||
$this->missingClasses[$class] = true; |
|||
} |
|||
|
|||
return $file; |
|||
} |
|||
|
|||
private function findFileWithExtension($class, $ext) |
|||
{ |
|||
// PSR-4 lookup
|
|||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; |
|||
|
|||
$first = $class[0]; |
|||
if (isset($this->prefixLengthsPsr4[$first])) { |
|||
$subPath = $class; |
|||
while (false !== $lastPos = strrpos($subPath, '\\')) { |
|||
$subPath = substr($subPath, 0, $lastPos); |
|||
$search = $subPath . '\\'; |
|||
if (isset($this->prefixDirsPsr4[$search])) { |
|||
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); |
|||
foreach ($this->prefixDirsPsr4[$search] as $dir) { |
|||
if (file_exists($file = $dir . $pathEnd)) { |
|||
return $file; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// PSR-4 fallback dirs
|
|||
foreach ($this->fallbackDirsPsr4 as $dir) { |
|||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { |
|||
return $file; |
|||
} |
|||
} |
|||
|
|||
// PSR-0 lookup
|
|||
if (false !== $pos = strrpos($class, '\\')) { |
|||
// namespaced class name
|
|||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) |
|||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); |
|||
} else { |
|||
// PEAR-like class name
|
|||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; |
|||
} |
|||
|
|||
if (isset($this->prefixesPsr0[$first])) { |
|||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { |
|||
if (0 === strpos($class, $prefix)) { |
|||
foreach ($dirs as $dir) { |
|||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { |
|||
return $file; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// PSR-0 fallback dirs
|
|||
foreach ($this->fallbackDirsPsr0 as $dir) { |
|||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { |
|||
return $file; |
|||
} |
|||
} |
|||
|
|||
// PSR-0 include paths.
|
|||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { |
|||
return $file; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Scope isolated include. |
|||
* |
|||
* Prevents access to $this/self from included files. |
|||
*/ |
|||
function includeFile($file) |
|||
{ |
|||
include $file; |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
|
|||
Copyright (c) Nils Adermann, Jordi Boggiano |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is furnished |
|||
to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|||
THE SOFTWARE. |
|||
|
|||
@ -0,0 +1,31 @@ |
|||
<?php |
|||
|
|||
// autoload_classmap.php @generated by Composer
|
|||
|
|||
$vendorDir = dirname(dirname(__FILE__)); |
|||
$baseDir = $vendorDir; |
|||
|
|||
return array( |
|||
'OCA\\UserStatus\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php', |
|||
'OCA\\UserStatus\\BackgroundJob\\ClearOldStatusesBackgroundJob' => $baseDir . '/../lib/BackgroundJob/ClearOldStatusesBackgroundJob.php', |
|||
'OCA\\UserStatus\\Capabilities' => $baseDir . '/../lib/Capabilities.php', |
|||
'OCA\\UserStatus\\Controller\\HeartbeatController' => $baseDir . '/../lib/Controller/HeartbeatController.php', |
|||
'OCA\\UserStatus\\Controller\\PredefinedStatusController' => $baseDir . '/../lib/Controller/PredefinedStatusController.php', |
|||
'OCA\\UserStatus\\Controller\\StatusesController' => $baseDir . '/../lib/Controller/StatusesController.php', |
|||
'OCA\\UserStatus\\Controller\\UserStatusController' => $baseDir . '/../lib/Controller/UserStatusController.php', |
|||
'OCA\\UserStatus\\Db\\UserStatus' => $baseDir . '/../lib/Db/UserStatus.php', |
|||
'OCA\\UserStatus\\Db\\UserStatusMapper' => $baseDir . '/../lib/Db/UserStatusMapper.php', |
|||
'OCA\\UserStatus\\Exception\\InvalidClearAtException' => $baseDir . '/../lib/Exception/InvalidClearAtException.php', |
|||
'OCA\\UserStatus\\Exception\\InvalidMessageIdException' => $baseDir . '/../lib/Exception/InvalidMessageIdException.php', |
|||
'OCA\\UserStatus\\Exception\\InvalidStatusIconException' => $baseDir . '/../lib/Exception/InvalidStatusIconException.php', |
|||
'OCA\\UserStatus\\Exception\\InvalidStatusTypeException' => $baseDir . '/../lib/Exception/InvalidStatusTypeException.php', |
|||
'OCA\\UserStatus\\Exception\\StatusMessageTooLongException' => $baseDir . '/../lib/Exception/StatusMessageTooLongException.php', |
|||
'OCA\\UserStatus\\Listener\\BeforeTemplateRenderedListener' => $baseDir . '/../lib/Listener/BeforeTemplateRenderedListener.php', |
|||
'OCA\\UserStatus\\Listener\\UserDeletedListener' => $baseDir . '/../lib/Listener/UserDeletedListener.php', |
|||
'OCA\\UserStatus\\Listener\\UserLiveStatusListener' => $baseDir . '/../lib/Listener/UserLiveStatusListener.php', |
|||
'OCA\\UserStatus\\Migration\\Version0001Date20200602134824' => $baseDir . '/../lib/Migration/Version0001Date20200602134824.php', |
|||
'OCA\\UserStatus\\Service\\EmojiService' => $baseDir . '/../lib/Service/EmojiService.php', |
|||
'OCA\\UserStatus\\Service\\JSDataService' => $baseDir . '/../lib/Service/JSDataService.php', |
|||
'OCA\\UserStatus\\Service\\PredefinedStatusService' => $baseDir . '/../lib/Service/PredefinedStatusService.php', |
|||
'OCA\\UserStatus\\Service\\StatusService' => $baseDir . '/../lib/Service/StatusService.php', |
|||
); |
|||
@ -0,0 +1,9 @@ |
|||
<?php |
|||
|
|||
// autoload_namespaces.php @generated by Composer
|
|||
|
|||
$vendorDir = dirname(dirname(__FILE__)); |
|||
$baseDir = $vendorDir; |
|||
|
|||
return array( |
|||
); |
|||
@ -0,0 +1,10 @@ |
|||
<?php |
|||
|
|||
// autoload_psr4.php @generated by Composer
|
|||
|
|||
$vendorDir = dirname(dirname(__FILE__)); |
|||
$baseDir = $vendorDir; |
|||
|
|||
return array( |
|||
'OCA\\UserStatus\\' => array($baseDir . '/../lib'), |
|||
); |
|||
@ -0,0 +1,46 @@ |
|||
<?php |
|||
|
|||
// autoload_real.php @generated by Composer
|
|||
|
|||
class ComposerAutoloaderInitUserStatus |
|||
{ |
|||
private static $loader; |
|||
|
|||
public static function loadClassLoader($class) |
|||
{ |
|||
if ('Composer\Autoload\ClassLoader' === $class) { |
|||
require __DIR__ . '/ClassLoader.php'; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @return \Composer\Autoload\ClassLoader |
|||
*/ |
|||
public static function getLoader() |
|||
{ |
|||
if (null !== self::$loader) { |
|||
return self::$loader; |
|||
} |
|||
|
|||
spl_autoload_register(array('ComposerAutoloaderInitUserStatus', 'loadClassLoader'), true, true); |
|||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(); |
|||
spl_autoload_unregister(array('ComposerAutoloaderInitUserStatus', 'loadClassLoader')); |
|||
|
|||
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); |
|||
if ($useStaticLoader) { |
|||
require_once __DIR__ . '/autoload_static.php'; |
|||
|
|||
call_user_func(\Composer\Autoload\ComposerStaticInitUserStatus::getInitializer($loader)); |
|||
} else { |
|||
$classMap = require __DIR__ . '/autoload_classmap.php'; |
|||
if ($classMap) { |
|||
$loader->addClassMap($classMap); |
|||
} |
|||
} |
|||
|
|||
$loader->setClassMapAuthoritative(true); |
|||
$loader->register(true); |
|||
|
|||
return $loader; |
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
<?php |
|||
|
|||
// autoload_static.php @generated by Composer
|
|||
|
|||
namespace Composer\Autoload; |
|||
|
|||
class ComposerStaticInitUserStatus |
|||
{ |
|||
public static $prefixLengthsPsr4 = array ( |
|||
'O' => |
|||
array ( |
|||
'OCA\\UserStatus\\' => 15, |
|||
), |
|||
); |
|||
|
|||
public static $prefixDirsPsr4 = array ( |
|||
'OCA\\UserStatus\\' => |
|||
array ( |
|||
0 => __DIR__ . '/..' . '/../lib', |
|||
), |
|||
); |
|||
|
|||
public static $classMap = array ( |
|||
'OCA\\UserStatus\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php', |
|||
'OCA\\UserStatus\\BackgroundJob\\ClearOldStatusesBackgroundJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/ClearOldStatusesBackgroundJob.php', |
|||
'OCA\\UserStatus\\Capabilities' => __DIR__ . '/..' . '/../lib/Capabilities.php', |
|||
'OCA\\UserStatus\\Controller\\HeartbeatController' => __DIR__ . '/..' . '/../lib/Controller/HeartbeatController.php', |
|||
'OCA\\UserStatus\\Controller\\PredefinedStatusController' => __DIR__ . '/..' . '/../lib/Controller/PredefinedStatusController.php', |
|||
'OCA\\UserStatus\\Controller\\StatusesController' => __DIR__ . '/..' . '/../lib/Controller/StatusesController.php', |
|||
'OCA\\UserStatus\\Controller\\UserStatusController' => __DIR__ . '/..' . '/../lib/Controller/UserStatusController.php', |
|||
'OCA\\UserStatus\\Db\\UserStatus' => __DIR__ . '/..' . '/../lib/Db/UserStatus.php', |
|||
'OCA\\UserStatus\\Db\\UserStatusMapper' => __DIR__ . '/..' . '/../lib/Db/UserStatusMapper.php', |
|||
'OCA\\UserStatus\\Exception\\InvalidClearAtException' => __DIR__ . '/..' . '/../lib/Exception/InvalidClearAtException.php', |
|||
'OCA\\UserStatus\\Exception\\InvalidMessageIdException' => __DIR__ . '/..' . '/../lib/Exception/InvalidMessageIdException.php', |
|||
'OCA\\UserStatus\\Exception\\InvalidStatusIconException' => __DIR__ . '/..' . '/../lib/Exception/InvalidStatusIconException.php', |
|||
'OCA\\UserStatus\\Exception\\InvalidStatusTypeException' => __DIR__ . '/..' . '/../lib/Exception/InvalidStatusTypeException.php', |
|||
'OCA\\UserStatus\\Exception\\StatusMessageTooLongException' => __DIR__ . '/..' . '/../lib/Exception/StatusMessageTooLongException.php', |
|||
'OCA\\UserStatus\\Listener\\BeforeTemplateRenderedListener' => __DIR__ . '/..' . '/../lib/Listener/BeforeTemplateRenderedListener.php', |
|||
'OCA\\UserStatus\\Listener\\UserDeletedListener' => __DIR__ . '/..' . '/../lib/Listener/UserDeletedListener.php', |
|||
'OCA\\UserStatus\\Listener\\UserLiveStatusListener' => __DIR__ . '/..' . '/../lib/Listener/UserLiveStatusListener.php', |
|||
'OCA\\UserStatus\\Migration\\Version0001Date20200602134824' => __DIR__ . '/..' . '/../lib/Migration/Version0001Date20200602134824.php', |
|||
'OCA\\UserStatus\\Service\\EmojiService' => __DIR__ . '/..' . '/../lib/Service/EmojiService.php', |
|||
'OCA\\UserStatus\\Service\\JSDataService' => __DIR__ . '/..' . '/../lib/Service/JSDataService.php', |
|||
'OCA\\UserStatus\\Service\\PredefinedStatusService' => __DIR__ . '/..' . '/../lib/Service/PredefinedStatusService.php', |
|||
'OCA\\UserStatus\\Service\\StatusService' => __DIR__ . '/..' . '/../lib/Service/StatusService.php', |
|||
); |
|||
|
|||
public static function getInitializer(ClassLoader $loader) |
|||
{ |
|||
return \Closure::bind(function () use ($loader) { |
|||
$loader->prefixLengthsPsr4 = ComposerStaticInitUserStatus::$prefixLengthsPsr4; |
|||
$loader->prefixDirsPsr4 = ComposerStaticInitUserStatus::$prefixDirsPsr4; |
|||
$loader->classMap = ComposerStaticInitUserStatus::$classMap; |
|||
|
|||
}, null, ClassLoader::class); |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
/** |
|||
* @copyright Copyright (c) 2020 Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* 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 |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
* |
|||
*/ |
|||
|
|||
.icon-user-status-away { |
|||
@include icon-color('user-status-away', 'user_status', '#F4A331', 1); |
|||
} |
|||
|
|||
.icon-user-status-dnd { |
|||
@include icon-color('user-status-dnd', 'user_status', '#ED484C', 1); |
|||
} |
|||
|
|||
.icon-user-status-invisible { |
|||
@include icon-color('user-status-invisible', 'user_status', '#000000', 1); |
|||
} |
|||
|
|||
.icon-user-status-online { |
|||
@include icon-color('user-status-online', 'user_status', '#49B382', 2); |
|||
} |
|||
@ -0,0 +1 @@ |
|||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24" viewBox="0 0 24 24" width="24"><g><rect fill="none" height="24" width="24"/></g><g><g><g><path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10S17.5,2,12,2z M16.2,16.2L11,13V7h1.5v5.2l4.5,2.7L16.2,16.2z"/></g></g></g></svg> |
|||
@ -0,0 +1 @@ |
|||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24" viewBox="0 0 24 24" width="24"><g><rect fill="none" height="24" width="24"/></g><g><g><g><path fill="#F4A331" d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10S17.5,2,12,2z M16.2,16.2L11,13V7h1.5v5.2l4.5,2.7L16.2,16.2z"/></g></g></g></svg> |
|||
@ -0,0 +1 @@ |
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path fill="#ED484C" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11H7v-2h10v2z"/></svg> |
|||
@ -0,0 +1 @@ |
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/></svg> |
|||
@ -0,0 +1 @@ |
|||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24" viewBox="0 0 24 24" width="24"><g><path fill="#49B382" d="M8,16h8V8H8V16z M12,2C6.48,2,2,6.48,2,12s4.48,10,10,10s10-4.48,10-10 S17.52,2,12,2L12,2z"/></g></svg> |
|||
2
apps/user_status/js/user-status-menu.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
1
apps/user_status/js/user-status-menu.js.map
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,74 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\AppInfo; |
|||
|
|||
use OCA\UserStatus\Capabilities; |
|||
use OCA\UserStatus\Listener\BeforeTemplateRenderedListener; |
|||
use OCA\UserStatus\Listener\UserDeletedListener; |
|||
use OCA\UserStatus\Listener\UserLiveStatusListener; |
|||
use OCP\AppFramework\App; |
|||
use OCP\AppFramework\Bootstrap\IBootContext; |
|||
use OCP\AppFramework\Bootstrap\IBootstrap; |
|||
use OCP\AppFramework\Bootstrap\IRegistrationContext; |
|||
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent; |
|||
use OCP\User\Events\UserDeletedEvent; |
|||
use OCP\User\Events\UserLiveStatusEvent; |
|||
|
|||
/** |
|||
* Class Application |
|||
* |
|||
* @package OCA\UserStatus\AppInfo |
|||
*/ |
|||
class Application extends App implements IBootstrap { |
|||
|
|||
/** @var string */ |
|||
public const APP_ID = 'user_status'; |
|||
|
|||
/** |
|||
* Application constructor. |
|||
* |
|||
* @param array $urlParams |
|||
*/ |
|||
public function __construct(array $urlParams = []) { |
|||
parent::__construct(self::APP_ID, $urlParams); |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
*/ |
|||
public function register(IRegistrationContext $context): void { |
|||
// Register OCS Capabilities
|
|||
$context->registerCapability(Capabilities::class); |
|||
|
|||
// Register Event Listeners
|
|||
$context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class); |
|||
$context->registerEventListener(UserLiveStatusEvent::class, UserLiveStatusListener::class); |
|||
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class); |
|||
} |
|||
|
|||
public function boot(IBootContext $context): void { |
|||
} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\BackgroundJob; |
|||
|
|||
use OCA\UserStatus\Db\UserStatusMapper; |
|||
use OCP\AppFramework\Utility\ITimeFactory; |
|||
use OCP\BackgroundJob\TimedJob; |
|||
|
|||
/** |
|||
* Class ClearOldStatusesBackgroundJob |
|||
* |
|||
* @package OCA\UserStatus\BackgroundJob |
|||
*/ |
|||
class ClearOldStatusesBackgroundJob extends TimedJob { |
|||
|
|||
/** @var UserStatusMapper */ |
|||
private $mapper; |
|||
|
|||
/** |
|||
* ClearOldStatusesBackgroundJob constructor. |
|||
* |
|||
* @param ITimeFactory $time |
|||
* @param UserStatusMapper $mapper |
|||
*/ |
|||
public function __construct(ITimeFactory $time, |
|||
UserStatusMapper $mapper) { |
|||
parent::__construct($time); |
|||
$this->mapper = $mapper; |
|||
|
|||
// Run every time the cron is run
|
|||
$this->setInterval(60); |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
*/ |
|||
protected function run($argument) { |
|||
$this->mapper->clearOlderThan($this->time->getTime()); |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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; |
|||
|
|||
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(), |
|||
], |
|||
]; |
|||
} |
|||
} |
|||
@ -0,0 +1,92 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Controller; |
|||
|
|||
use OCP\AppFramework\Controller; |
|||
use OCP\AppFramework\Http; |
|||
use OCP\AppFramework\Http\JSONResponse; |
|||
use OCP\AppFramework\Utility\ITimeFactory; |
|||
use OCP\EventDispatcher\IEventDispatcher; |
|||
use OCP\IRequest; |
|||
use OCP\IUserSession; |
|||
use OCP\User\Events\UserLiveStatusEvent; |
|||
|
|||
class HeartbeatController extends Controller { |
|||
|
|||
/** @var IEventDispatcher */ |
|||
private $eventDispatcher; |
|||
|
|||
/** @var IUserSession */ |
|||
private $userSession; |
|||
|
|||
/** @var ITimeFactory */ |
|||
private $timeFactory; |
|||
|
|||
/** |
|||
* HeartbeatController constructor. |
|||
* |
|||
* @param string $appName |
|||
* @param IRequest $request |
|||
* @param IEventDispatcher $eventDispatcher |
|||
*/ |
|||
public function __construct(string $appName, |
|||
IRequest $request, |
|||
IEventDispatcher $eventDispatcher, |
|||
IUserSession $userSession, |
|||
ITimeFactory $timeFactory) { |
|||
parent::__construct($appName, $request); |
|||
$this->eventDispatcher = $eventDispatcher; |
|||
$this->userSession = $userSession; |
|||
$this->timeFactory = $timeFactory; |
|||
} |
|||
|
|||
/** |
|||
* @NoAdminRequired |
|||
* |
|||
* @param string $status |
|||
* @return JSONResponse |
|||
*/ |
|||
public function heartbeat(string $status): JSONResponse { |
|||
if (!\in_array($status, ['online', 'away'])) { |
|||
return new JSONResponse([], Http::STATUS_BAD_REQUEST); |
|||
} |
|||
|
|||
$user = $this->userSession->getUser(); |
|||
if ($user === null) { |
|||
return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR); |
|||
} |
|||
|
|||
$this->eventDispatcher->dispatchTyped( |
|||
new UserLiveStatusEvent( |
|||
$user, |
|||
$status, |
|||
$this->timeFactory->getTime() |
|||
) |
|||
); |
|||
|
|||
return new JSONResponse([], Http::STATUS_NO_CONTENT); |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Controller; |
|||
|
|||
use OCA\UserStatus\Service\PredefinedStatusService; |
|||
use OCP\AppFramework\Http\DataResponse; |
|||
use OCP\AppFramework\OCSController; |
|||
use OCP\IRequest; |
|||
|
|||
/** |
|||
* Class DefaultStatusController |
|||
* |
|||
* @package OCA\UserStatus\Controller |
|||
*/ |
|||
class PredefinedStatusController extends OCSController { |
|||
|
|||
/** @var PredefinedStatusService */ |
|||
private $predefinedStatusService; |
|||
|
|||
/** |
|||
* AStatusController constructor. |
|||
* |
|||
* @param string $appName |
|||
* @param IRequest $request |
|||
* @param PredefinedStatusService $predefinedStatusService |
|||
*/ |
|||
public function __construct(string $appName, |
|||
IRequest $request, |
|||
PredefinedStatusService $predefinedStatusService) { |
|||
parent::__construct($appName, $request); |
|||
$this->predefinedStatusService = $predefinedStatusService; |
|||
} |
|||
|
|||
/** |
|||
* @NoAdminRequired |
|||
* |
|||
* @return DataResponse |
|||
*/ |
|||
public function findAll():DataResponse { |
|||
return new DataResponse($this->predefinedStatusService->getDefaultStatuses()); |
|||
} |
|||
} |
|||
@ -0,0 +1,107 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Controller; |
|||
|
|||
use OCA\UserStatus\Db\UserStatus; |
|||
use OCA\UserStatus\Service\StatusService; |
|||
use OCP\AppFramework\Db\DoesNotExistException; |
|||
use OCP\AppFramework\Http\DataResponse; |
|||
use OCP\AppFramework\OCS\OCSNotFoundException; |
|||
use OCP\AppFramework\OCSController; |
|||
use OCP\IRequest; |
|||
|
|||
class StatusesController extends OCSController { |
|||
|
|||
/** @var StatusService */ |
|||
private $service; |
|||
|
|||
/** |
|||
* StatusesController constructor. |
|||
* |
|||
* @param string $appName |
|||
* @param IRequest $request |
|||
* @param StatusService $service |
|||
*/ |
|||
public function __construct(string $appName, |
|||
IRequest $request, |
|||
StatusService $service) { |
|||
parent::__construct($appName, $request); |
|||
$this->service = $service; |
|||
} |
|||
|
|||
/** |
|||
* @NoAdminRequired |
|||
* |
|||
* @param int|null $limit |
|||
* @param int|null $offset |
|||
* @return DataResponse |
|||
*/ |
|||
public function findAll(?int $limit=null, ?int $offset=null): DataResponse { |
|||
$allStatuses = $this->service->findAll($limit, $offset); |
|||
|
|||
return new DataResponse(array_map(function ($userStatus) { |
|||
return $this->formatStatus($userStatus); |
|||
}, $allStatuses)); |
|||
} |
|||
|
|||
/** |
|||
* @NoAdminRequired |
|||
* |
|||
* @param string $userId |
|||
* @return DataResponse |
|||
* @throws OCSNotFoundException |
|||
*/ |
|||
public function find(string $userId): DataResponse { |
|||
try { |
|||
$userStatus = $this->service->findByUserId($userId); |
|||
} catch (DoesNotExistException $ex) { |
|||
throw new OCSNotFoundException('No status for the requested userId'); |
|||
} |
|||
|
|||
return new DataResponse($this->formatStatus($userStatus)); |
|||
} |
|||
|
|||
/** |
|||
* @NoAdminRequired |
|||
* |
|||
* @param UserStatus $status |
|||
* @return array |
|||
*/ |
|||
private function formatStatus(UserStatus $status): array { |
|||
$visibleStatus = $status->getStatus(); |
|||
if ($visibleStatus === 'invisible') { |
|||
$visibleStatus = 'offline'; |
|||
} |
|||
|
|||
return [ |
|||
'userId' => $status->getUserId(), |
|||
'message' => $status->getCustomMessage(), |
|||
'icon' => $status->getCustomIcon(), |
|||
'clearAt' => $status->getClearAt(), |
|||
'status' => $visibleStatus, |
|||
]; |
|||
} |
|||
} |
|||
@ -0,0 +1,191 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Controller; |
|||
|
|||
use OCA\UserStatus\Db\UserStatus; |
|||
use OCA\UserStatus\Exception\InvalidClearAtException; |
|||
use OCA\UserStatus\Exception\InvalidMessageIdException; |
|||
use OCA\UserStatus\Exception\InvalidStatusIconException; |
|||
use OCA\UserStatus\Exception\InvalidStatusTypeException; |
|||
use OCA\UserStatus\Exception\StatusMessageTooLongException; |
|||
use OCA\UserStatus\Service\StatusService; |
|||
use OCP\AppFramework\Db\DoesNotExistException; |
|||
use OCP\AppFramework\Http\DataResponse; |
|||
use OCP\AppFramework\OCS\OCSBadRequestException; |
|||
use OCP\AppFramework\OCS\OCSNotFoundException; |
|||
use OCP\AppFramework\OCSController; |
|||
use OCP\ILogger; |
|||
use OCP\IRequest; |
|||
|
|||
class UserStatusController extends OCSController { |
|||
|
|||
/** @var string */ |
|||
private $userId; |
|||
|
|||
/** @var ILogger */ |
|||
private $logger; |
|||
|
|||
/** @var StatusService */ |
|||
private $service; |
|||
|
|||
/** |
|||
* StatusesController constructor. |
|||
* |
|||
* @param string $appName |
|||
* @param IRequest $request |
|||
* @param string $userId |
|||
* @param ILogger $logger; |
|||
* @param StatusService $service |
|||
*/ |
|||
public function __construct(string $appName, |
|||
IRequest $request, |
|||
string $userId, |
|||
ILogger $logger, |
|||
StatusService $service) { |
|||
parent::__construct($appName, $request); |
|||
$this->userId = $userId; |
|||
$this->logger = $logger; |
|||
$this->service = $service; |
|||
} |
|||
|
|||
/** |
|||
* @NoAdminRequired |
|||
* |
|||
* @return DataResponse |
|||
* @throws OCSNotFoundException |
|||
*/ |
|||
public function getStatus(): DataResponse { |
|||
try { |
|||
$userStatus = $this->service->findByUserId($this->userId); |
|||
} catch (DoesNotExistException $ex) { |
|||
throw new OCSNotFoundException('No status for the current user'); |
|||
} |
|||
|
|||
return new DataResponse($this->formatStatus($userStatus)); |
|||
} |
|||
|
|||
/** |
|||
* @NoAdminRequired |
|||
* |
|||
* @param string $statusType |
|||
* @return DataResponse |
|||
* @throws OCSBadRequestException |
|||
*/ |
|||
public function setStatus(string $statusType): DataResponse { |
|||
try { |
|||
$status = $this->service->setStatus($this->userId, $statusType, null, true); |
|||
return new DataResponse($this->formatStatus($status)); |
|||
} catch (InvalidStatusTypeException $ex) { |
|||
$this->logger->debug('New user-status for "' . $this->userId . '" was rejected due to an invalid status type "' . $statusType . '"'); |
|||
throw new OCSBadRequestException($ex->getMessage(), $ex); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @NoAdminRequired |
|||
* |
|||
* @param string $messageId |
|||
* @param int|null $clearAt |
|||
* @return DataResponse |
|||
* @throws OCSBadRequestException |
|||
*/ |
|||
public function setPredefinedMessage(string $messageId, |
|||
?int $clearAt): DataResponse { |
|||
try { |
|||
$status = $this->service->setPredefinedMessage($this->userId, $messageId, $clearAt); |
|||
return new DataResponse($this->formatStatus($status)); |
|||
} catch (InvalidClearAtException $ex) { |
|||
$this->logger->debug('New user-status for "' . $this->userId . '" was rejected due to an invalid clearAt value "' . $clearAt . '"'); |
|||
throw new OCSBadRequestException($ex->getMessage(), $ex); |
|||
} catch (InvalidMessageIdException $ex) { |
|||
$this->logger->debug('New user-status for "' . $this->userId . '" was rejected due to an invalid message-id "' . $messageId . '"'); |
|||
throw new OCSBadRequestException($ex->getMessage(), $ex); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @NoAdminRequired |
|||
* |
|||
* @param string|null $statusIcon |
|||
* @param string $message |
|||
* @param int|null $clearAt |
|||
* @return DataResponse |
|||
* @throws OCSBadRequestException |
|||
*/ |
|||
public function setCustomMessage(?string $statusIcon, |
|||
string $message, |
|||
?int $clearAt): DataResponse { |
|||
try { |
|||
$status = $this->service->setCustomMessage($this->userId, $statusIcon, $message, $clearAt); |
|||
return new DataResponse($this->formatStatus($status)); |
|||
} catch (InvalidClearAtException $ex) { |
|||
$this->logger->debug('New user-status for "' . $this->userId . '" was rejected due to an invalid clearAt value "' . $clearAt . '"'); |
|||
throw new OCSBadRequestException($ex->getMessage(), $ex); |
|||
} catch (InvalidStatusIconException $ex) { |
|||
$this->logger->debug('New user-status for "' . $this->userId . '" was rejected due to an invalid icon value "' . $statusIcon . '"'); |
|||
throw new OCSBadRequestException($ex->getMessage(), $ex); |
|||
} catch (StatusMessageTooLongException $ex) { |
|||
$this->logger->debug('New user-status for "' . $this->userId . '" was rejected due to a too long status message.'); |
|||
throw new OCSBadRequestException($ex->getMessage(), $ex); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @NoAdminRequired |
|||
* |
|||
* @return DataResponse |
|||
*/ |
|||
public function clearStatus(): DataResponse { |
|||
$this->service->clearStatus($this->userId); |
|||
return new DataResponse([]); |
|||
} |
|||
|
|||
/** |
|||
* @NoAdminRequired |
|||
* |
|||
* @return DataResponse |
|||
*/ |
|||
public function clearMessage(): DataResponse { |
|||
$this->service->clearMessage($this->userId); |
|||
return new DataResponse([]); |
|||
} |
|||
|
|||
/** |
|||
* @param UserStatus $status |
|||
* @return array |
|||
*/ |
|||
private function formatStatus(UserStatus $status): array { |
|||
return [ |
|||
'userId' => $status->getUserId(), |
|||
'message' => $status->getCustomMessage(), |
|||
'messageId' => $status->getMessageId(), |
|||
'messageIsPredefined' => $status->getMessageId() !== null, |
|||
'icon' => $status->getCustomIcon(), |
|||
'clearAt' => $status->getClearAt(), |
|||
'status' => $status->getStatus(), |
|||
'statusIsUserDefined' => $status->getIsUserDefined(), |
|||
]; |
|||
} |
|||
} |
|||
@ -0,0 +1,90 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Db; |
|||
|
|||
use OCP\AppFramework\Db\Entity; |
|||
|
|||
/** |
|||
* Class UserStatus |
|||
* |
|||
* @package OCA\UserStatus\Db |
|||
* |
|||
* @method int getId() |
|||
* @method void setId(int $id) |
|||
* @method string getUserId() |
|||
* @method void setUserId(string $userId) |
|||
* @method string getStatus() |
|||
* @method void setStatus(string $status) |
|||
* @method int getStatusTimestamp() |
|||
* @method void setStatusTimestamp(int $statusTimestamp) |
|||
* @method bool getIsUserDefined() |
|||
* @method void setIsUserDefined(bool $isUserDefined) |
|||
* @method string getMessageId() |
|||
* @method void setMessageId(string|null $messageId) |
|||
* @method string getCustomIcon() |
|||
* @method void setCustomIcon(string|null $customIcon) |
|||
* @method string getCustomMessage() |
|||
* @method void setCustomMessage(string|null $customMessage) |
|||
* @method int getClearAt() |
|||
* @method void setClearAt(int|null $clearAt) |
|||
*/ |
|||
class UserStatus extends Entity { |
|||
|
|||
/** @var string */ |
|||
public $userId; |
|||
|
|||
/** @var string */ |
|||
public $status; |
|||
|
|||
/** @var int */ |
|||
public $statusTimestamp; |
|||
|
|||
/** @var boolean */ |
|||
public $isUserDefined; |
|||
|
|||
/** @var string|null */ |
|||
public $messageId; |
|||
|
|||
/** @var string|null */ |
|||
public $customIcon; |
|||
|
|||
/** @var string|null */ |
|||
public $customMessage; |
|||
|
|||
/** @var int|null */ |
|||
public $clearAt; |
|||
|
|||
public function __construct() { |
|||
$this->addType('userId', 'string'); |
|||
$this->addType('status', 'string'); |
|||
$this->addType('statusTimestamp', 'int'); |
|||
$this->addType('isUserDefined', 'boolean'); |
|||
$this->addType('messageId', 'string'); |
|||
$this->addType('customIcon', 'string'); |
|||
$this->addType('customMessage', 'string'); |
|||
$this->addType('clearAt', 'int'); |
|||
} |
|||
} |
|||
@ -0,0 +1,104 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Db; |
|||
|
|||
use OCP\AppFramework\Db\QBMapper; |
|||
use OCP\DB\QueryBuilder\IQueryBuilder; |
|||
use OCP\IDBConnection; |
|||
|
|||
/** |
|||
* Class UserStatusMapper |
|||
* |
|||
* @package OCA\UserStatus\Db |
|||
* |
|||
* @method UserStatus insert(UserStatus $entity) |
|||
* @method UserStatus update(UserStatus $entity) |
|||
* @method UserStatus insertOrUpdate(UserStatus $entity) |
|||
* @method UserStatus delete(UserStatus $entity) |
|||
*/ |
|||
class UserStatusMapper extends QBMapper { |
|||
|
|||
/** |
|||
* @param IDBConnection $db |
|||
*/ |
|||
public function __construct(IDBConnection $db) { |
|||
parent::__construct($db, 'user_status'); |
|||
} |
|||
|
|||
/** |
|||
* @param int|null $limit |
|||
* @param int|null $offset |
|||
* @return UserStatus[] |
|||
*/ |
|||
public function findAll(?int $limit = null, ?int $offset = null):array { |
|||
$qb = $this->db->getQueryBuilder(); |
|||
$qb |
|||
->select('*') |
|||
->from($this->tableName); |
|||
|
|||
if ($limit !== null) { |
|||
$qb->setMaxResults($limit); |
|||
} |
|||
if ($offset !== null) { |
|||
$qb->setFirstResult($offset); |
|||
} |
|||
|
|||
return $this->findEntities($qb); |
|||
} |
|||
|
|||
/** |
|||
* @param string $userId |
|||
* @return UserStatus |
|||
* @throws \OCP\AppFramework\Db\DoesNotExistException |
|||
*/ |
|||
public function findByUserId(string $userId):UserStatus { |
|||
$qb = $this->db->getQueryBuilder(); |
|||
$qb |
|||
->select('*') |
|||
->from($this->tableName) |
|||
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR))); |
|||
|
|||
return $this->findEntity($qb); |
|||
} |
|||
|
|||
/** |
|||
* Clear all statuses older than a given timestamp |
|||
* |
|||
* @param int $timestamp |
|||
*/ |
|||
public function clearOlderThan(int $timestamp): void { |
|||
$qb = $this->db->getQueryBuilder(); |
|||
$qb->update($this->tableName) |
|||
->set('message_id', $qb->createNamedParameter(null)) |
|||
->set('custom_icon', $qb->createNamedParameter(null)) |
|||
->set('custom_message', $qb->createNamedParameter(null)) |
|||
->set('clear_at', $qb->createNamedParameter(null)) |
|||
->where($qb->expr()->isNotNull('clear_at')) |
|||
->andWhere($qb->expr()->lte('clear_at', $qb->createNamedParameter($timestamp, IQueryBuilder::PARAM_INT))); |
|||
|
|||
$qb->execute(); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Exception; |
|||
|
|||
class InvalidClearAtException extends \Exception { |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Exception; |
|||
|
|||
class InvalidMessageIdException extends \Exception { |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Exception; |
|||
|
|||
class InvalidStatusIconException extends \Exception { |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Exception; |
|||
|
|||
class InvalidStatusTypeException extends \Exception { |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Exception; |
|||
|
|||
class StatusMessageTooLongException extends \Exception { |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Listener; |
|||
|
|||
use OCA\UserStatus\AppInfo\Application; |
|||
use OCA\UserStatus\Service\JSDataService; |
|||
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent; |
|||
use OCP\EventDispatcher\Event; |
|||
use OCP\EventDispatcher\IEventListener; |
|||
use OCP\IInitialStateService; |
|||
|
|||
class BeforeTemplateRenderedListener implements IEventListener { |
|||
|
|||
/** @var IInitialStateService */ |
|||
private $initialState; |
|||
|
|||
/** @var JSDataService */ |
|||
private $jsDataService; |
|||
|
|||
/** |
|||
* BeforeTemplateRenderedListener constructor. |
|||
* |
|||
* @param IInitialStateService $initialState |
|||
* @param JSDataService $jsDataService |
|||
*/ |
|||
public function __construct(IInitialStateService $initialState, |
|||
JSDataService $jsDataService) { |
|||
$this->initialState = $initialState; |
|||
$this->jsDataService = $jsDataService; |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
*/ |
|||
public function handle(Event $event): void { |
|||
if (!($event instanceof BeforeTemplateRenderedEvent)) { |
|||
// Unrelated
|
|||
return; |
|||
} |
|||
|
|||
if (!$event->isLoggedIn()) { |
|||
return; |
|||
} |
|||
|
|||
$this->initialState->provideLazyInitialState(Application::APP_ID, 'status', function () { |
|||
return $this->jsDataService; |
|||
}); |
|||
|
|||
\OCP\Util::addScript('user_status', 'user-status-menu'); |
|||
\OCP\Util::addStyle('user_status', 'user-status-menu'); |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Listener; |
|||
|
|||
use OCA\UserStatus\Service\StatusService; |
|||
use OCP\EventDispatcher\IEventListener; |
|||
use OCP\EventDispatcher\Event; |
|||
use OCP\User\Events\UserDeletedEvent; |
|||
|
|||
/** |
|||
* Class UserDeletedListener |
|||
* |
|||
* @package OCA\UserStatus\Listener |
|||
*/ |
|||
class UserDeletedListener implements IEventListener { |
|||
|
|||
/** @var StatusService */ |
|||
private $service; |
|||
|
|||
/** |
|||
* UserDeletedListener constructor. |
|||
* |
|||
* @param StatusService $service |
|||
*/ |
|||
public function __construct(StatusService $service) { |
|||
$this->service = $service; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* @inheritDoc |
|||
*/ |
|||
public function handle(Event $event): void { |
|||
if (!($event instanceof UserDeletedEvent)) { |
|||
// Unrelated
|
|||
return; |
|||
} |
|||
|
|||
$user = $event->getUser(); |
|||
$this->service->removeUserStatus($user->getUID()); |
|||
} |
|||
} |
|||
@ -0,0 +1,133 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Listener; |
|||
|
|||
use OCA\UserStatus\Db\UserStatus; |
|||
use OCA\UserStatus\Db\UserStatusMapper; |
|||
use OCP\AppFramework\Db\DoesNotExistException; |
|||
use OCP\AppFramework\Utility\ITimeFactory; |
|||
use OCP\EventDispatcher\IEventListener; |
|||
use OCP\EventDispatcher\Event; |
|||
use OCP\User\Events\UserLiveStatusEvent; |
|||
|
|||
/** |
|||
* Class UserDeletedListener |
|||
* |
|||
* @package OCA\UserStatus\Listener |
|||
*/ |
|||
class UserLiveStatusListener implements IEventListener { |
|||
|
|||
/** @var UserStatusMapper */ |
|||
private $mapper; |
|||
|
|||
/** @var ITimeFactory */ |
|||
private $timeFactory; |
|||
|
|||
/** @var string[] */ |
|||
private $priorityOrderedStatuses = [ |
|||
'online', |
|||
'away', |
|||
'dnd', |
|||
'invisible', |
|||
'offline' |
|||
]; |
|||
|
|||
/** @var string[] */ |
|||
private $persistentUserStatuses = [ |
|||
'away', |
|||
'dnd', |
|||
'invisible', |
|||
]; |
|||
|
|||
/** @var int */ |
|||
private $offlineThreshold = 300; |
|||
|
|||
/** |
|||
* UserLiveStatusListener constructor. |
|||
* |
|||
* @param UserStatusMapper $mapper |
|||
* @param ITimeFactory $timeFactory |
|||
*/ |
|||
public function __construct(UserStatusMapper $mapper, |
|||
ITimeFactory $timeFactory) { |
|||
$this->mapper = $mapper; |
|||
$this->timeFactory = $timeFactory; |
|||
} |
|||
|
|||
/** |
|||
* @inheritDoc |
|||
*/ |
|||
public function handle(Event $event): void { |
|||
if (!($event instanceof UserLiveStatusEvent)) { |
|||
// Unrelated
|
|||
return; |
|||
} |
|||
|
|||
$user = $event->getUser(); |
|||
try { |
|||
$userStatus = $this->mapper->findByUserId($user->getUID()); |
|||
} catch (DoesNotExistException $ex) { |
|||
$userStatus = new UserStatus(); |
|||
$userStatus->setUserId($user->getUID()); |
|||
$userStatus->setStatus('offline'); |
|||
$userStatus->setStatusTimestamp(0); |
|||
$userStatus->setIsUserDefined(false); |
|||
} |
|||
|
|||
// If the status is user-defined and one of the persistent statuses, we
|
|||
// will not override it.
|
|||
if ($userStatus->getIsUserDefined() && |
|||
\in_array($userStatus->getStatus(), $this->persistentUserStatuses, true)) { |
|||
return; |
|||
} |
|||
|
|||
$needsUpdate = false; |
|||
|
|||
// If the current status is older than 5 minutes,
|
|||
// treat it as outdated and update
|
|||
if ($userStatus->getStatusTimestamp() < ($this->timeFactory->getTime() - $this->offlineThreshold)) { |
|||
$needsUpdate = true; |
|||
} |
|||
|
|||
// If the emitted status is more important than the current status
|
|||
// treat it as outdated and update
|
|||
if (array_search($event->getStatus(), $this->priorityOrderedStatuses) < array_search($userStatus->getStatus(), $this->priorityOrderedStatuses)) { |
|||
$needsUpdate = true; |
|||
} |
|||
|
|||
if ($needsUpdate) { |
|||
$userStatus->setStatus($event->getStatus()); |
|||
$userStatus->setStatusTimestamp($event->getTimestamp()); |
|||
$userStatus->setIsUserDefined(false); |
|||
|
|||
if ($userStatus->getId() === null) { |
|||
$this->mapper->insert($userStatus); |
|||
} else { |
|||
$this->mapper->update($userStatus); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,97 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Migration; |
|||
|
|||
use Doctrine\DBAL\Types\Types; |
|||
use OCP\DB\ISchemaWrapper; |
|||
use OCP\Migration\IOutput; |
|||
use OCP\Migration\SimpleMigrationStep; |
|||
|
|||
/** |
|||
* Class Version0001Date20200602134824 |
|||
* |
|||
* @package OCA\UserStatus\Migration |
|||
*/ |
|||
class Version0001Date20200602134824 extends SimpleMigrationStep { |
|||
|
|||
/** |
|||
* @param IOutput $output |
|||
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` |
|||
* @param array $options |
|||
* @return null|ISchemaWrapper |
|||
* @since 20.0.0 |
|||
*/ |
|||
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) { |
|||
/** @var ISchemaWrapper $schema */ |
|||
$schema = $schemaClosure(); |
|||
|
|||
$statusTable = $schema->createTable('user_status'); |
|||
$statusTable->addColumn('id', Types::BIGINT, [ |
|||
'autoincrement' => true, |
|||
'notnull' => true, |
|||
'length' => 20, |
|||
'unsigned' => true, |
|||
]); |
|||
$statusTable->addColumn('user_id', Types::STRING, [ |
|||
'notnull' => true, |
|||
'length' => 255, |
|||
]); |
|||
$statusTable->addColumn('status', Types::STRING, [ |
|||
'notnull' => true, |
|||
'length' => 255, |
|||
]); |
|||
$statusTable->addColumn('status_timestamp', Types::INTEGER, [ |
|||
'notnull' => true, |
|||
'length' => 11, |
|||
'unsigned' => true, |
|||
]); |
|||
$statusTable->addColumn('is_user_defined', Types::BOOLEAN, [ |
|||
'notnull' => true, |
|||
]); |
|||
$statusTable->addColumn('message_id', Types::STRING, [ |
|||
'notnull' => false, |
|||
'length' => 255, |
|||
]); |
|||
$statusTable->addColumn('custom_icon', Types::STRING, [ |
|||
'notnull' => false, |
|||
'length' => 255, |
|||
]); |
|||
$statusTable->addColumn('custom_message', Types::TEXT, [ |
|||
'notnull' => false, |
|||
]); |
|||
$statusTable->addColumn('clear_at', Types::INTEGER, [ |
|||
'notnull' => false, |
|||
'length' => 11, |
|||
'unsigned' => true, |
|||
]); |
|||
|
|||
$statusTable->setPrimaryKey(['id']); |
|||
$statusTable->addUniqueIndex(['user_id'], 'user_status_uid_ix'); |
|||
$statusTable->addIndex(['clear_at'], 'user_status_clr_ix'); |
|||
|
|||
return $schema; |
|||
} |
|||
} |
|||
@ -0,0 +1,100 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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; |
|||
} |
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\AppFramework\Db\DoesNotExistException; |
|||
use OCP\IUserSession; |
|||
|
|||
class JSDataService implements \JsonSerializable { |
|||
|
|||
/** @var IUserSession */ |
|||
private $userSession; |
|||
|
|||
/** @var StatusService */ |
|||
private $statusService; |
|||
|
|||
/** |
|||
* JSDataService constructor. |
|||
* |
|||
* @param IUserSession $userSession |
|||
* @param StatusService $statusService |
|||
*/ |
|||
public function __construct(IUserSession $userSession, |
|||
StatusService $statusService) { |
|||
$this->userSession = $userSession; |
|||
$this->statusService = $statusService; |
|||
} |
|||
|
|||
public function jsonSerialize() { |
|||
$user = $this->userSession->getUser(); |
|||
|
|||
if ($user === null) { |
|||
return []; |
|||
} |
|||
|
|||
try { |
|||
$status = $this->statusService->findByUserId($user->getUID()); |
|||
} catch (DoesNotExistException $ex) { |
|||
return [ |
|||
'userId' => $user->getUID(), |
|||
'message' => null, |
|||
'messageId' => null, |
|||
'messageIsPredefined' => false, |
|||
'icon' => null, |
|||
'clearAt' => null, |
|||
'status' => 'offline', |
|||
'statusIsUserDefined' => false, |
|||
]; |
|||
} |
|||
|
|||
return [ |
|||
'userId' => $status->getUserId(), |
|||
'message' => $status->getCustomMessage(), |
|||
'messageId' => $status->getMessageId(), |
|||
'messageIsPredefined' => $status->getMessageId() !== null, |
|||
'icon' => $status->getCustomIcon(), |
|||
'clearAt' => $status->getClearAt(), |
|||
'status' => $status->getStatus(), |
|||
'statusIsUserDefined' => $status->getIsUserDefined(), |
|||
]; |
|||
} |
|||
} |
|||
@ -0,0 +1,187 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\IL10N; |
|||
|
|||
/** |
|||
* Class DefaultStatusService |
|||
* |
|||
* We are offering a set of default statuses, so we can |
|||
* translate them into different languages. |
|||
* |
|||
* @package OCA\UserStatus\Service |
|||
*/ |
|||
class PredefinedStatusService { |
|||
private const MEETING = 'meeting'; |
|||
private const COMMUTING = 'commuting'; |
|||
private const SICK_LEAVE = 'sick-leave'; |
|||
private const VACATIONING = 'vacationing'; |
|||
private const REMOTE_WORK = 'remote-work'; |
|||
|
|||
/** @var IL10N */ |
|||
private $l10n; |
|||
|
|||
/** |
|||
* DefaultStatusService constructor. |
|||
* |
|||
* @param IL10N $l10n |
|||
*/ |
|||
public function __construct(IL10N $l10n) { |
|||
$this->l10n = $l10n; |
|||
} |
|||
|
|||
/** |
|||
* @return array |
|||
*/ |
|||
public function getDefaultStatuses(): array { |
|||
return [ |
|||
[ |
|||
'id' => self::MEETING, |
|||
'icon' => '📅', |
|||
'message' => $this->getTranslatedStatusForId(self::MEETING), |
|||
'clearAt' => [ |
|||
'type' => 'period', |
|||
'time' => 3600, |
|||
], |
|||
], |
|||
[ |
|||
'id' => self::COMMUTING, |
|||
'icon' => '🚌', |
|||
'message' => $this->getTranslatedStatusForId(self::COMMUTING), |
|||
'clearAt' => [ |
|||
'type' => 'period', |
|||
'time' => 1800, |
|||
], |
|||
], |
|||
[ |
|||
'id' => self::REMOTE_WORK, |
|||
'icon' => '🏡', |
|||
'message' => $this->getTranslatedStatusForId(self::REMOTE_WORK), |
|||
'clearAt' => [ |
|||
'type' => 'end-of', |
|||
'time' => 'day', |
|||
], |
|||
], |
|||
[ |
|||
'id' => self::SICK_LEAVE, |
|||
'icon' => '🤒', |
|||
'message' => $this->getTranslatedStatusForId(self::SICK_LEAVE), |
|||
'clearAt' => [ |
|||
'type' => 'end-of', |
|||
'time' => 'day', |
|||
], |
|||
], |
|||
[ |
|||
'id' => self::VACATIONING, |
|||
'icon' => '🌴', |
|||
'message' => $this->getTranslatedStatusForId(self::VACATIONING), |
|||
'clearAt' => null, |
|||
], |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* @param string $id |
|||
* @return array|null |
|||
*/ |
|||
public function getDefaultStatusById(string $id): ?array { |
|||
foreach ($this->getDefaultStatuses() as $status) { |
|||
if ($status['id'] === $id) { |
|||
return $status; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* @param string $id |
|||
* @return string|null |
|||
*/ |
|||
public function getIconForId(string $id): ?string { |
|||
switch ($id) { |
|||
case self::MEETING: |
|||
return '📅'; |
|||
|
|||
case self::COMMUTING: |
|||
return '🚌'; |
|||
|
|||
case self::SICK_LEAVE: |
|||
return '🤒'; |
|||
|
|||
case self::VACATIONING: |
|||
return '🌴'; |
|||
|
|||
case self::REMOTE_WORK: |
|||
return '🏡'; |
|||
|
|||
default: |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @param string $lang |
|||
* @param string $id |
|||
* @return string|null |
|||
*/ |
|||
public function getTranslatedStatusForId(string $id): ?string { |
|||
switch ($id) { |
|||
case self::MEETING: |
|||
return $this->l10n->t('In a meeting'); |
|||
|
|||
case self::COMMUTING: |
|||
return $this->l10n->t('Commuting'); |
|||
|
|||
case self::SICK_LEAVE: |
|||
return $this->l10n->t('Out sick'); |
|||
|
|||
case self::VACATIONING: |
|||
return $this->l10n->t('Vacationing'); |
|||
|
|||
case self::REMOTE_WORK: |
|||
return $this->l10n->t('Working remotely'); |
|||
|
|||
default: |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @param string $id |
|||
* @return bool |
|||
*/ |
|||
public function isValidId(string $id): bool { |
|||
return \in_array($id, [ |
|||
self::MEETING, |
|||
self::COMMUTING, |
|||
self::SICK_LEAVE, |
|||
self::VACATIONING, |
|||
self::REMOTE_WORK, |
|||
], true); |
|||
} |
|||
} |
|||
@ -0,0 +1,335 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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 OCA\UserStatus\Db\UserStatus; |
|||
use OCA\UserStatus\Db\UserStatusMapper; |
|||
use OCA\UserStatus\Exception\InvalidClearAtException; |
|||
use OCA\UserStatus\Exception\InvalidMessageIdException; |
|||
use OCA\UserStatus\Exception\InvalidStatusIconException; |
|||
use OCA\UserStatus\Exception\InvalidStatusTypeException; |
|||
use OCA\UserStatus\Exception\StatusMessageTooLongException; |
|||
use OCP\AppFramework\Db\DoesNotExistException; |
|||
use OCP\AppFramework\Utility\ITimeFactory; |
|||
|
|||
/** |
|||
* Class StatusService |
|||
* |
|||
* @package OCA\UserStatus\Service |
|||
*/ |
|||
class StatusService { |
|||
|
|||
/** @var UserStatusMapper */ |
|||
private $mapper; |
|||
|
|||
/** @var ITimeFactory */ |
|||
private $timeFactory; |
|||
|
|||
/** @var PredefinedStatusService */ |
|||
private $predefinedStatusService; |
|||
|
|||
/** @var EmojiService */ |
|||
private $emojiService; |
|||
|
|||
/** @var string[] */ |
|||
private $allowedStatusTypes = [ |
|||
'online', |
|||
'away', |
|||
'dnd', |
|||
'invisible', |
|||
'offline' |
|||
]; |
|||
|
|||
/** @var int */ |
|||
private $maximumMessageLength = 80; |
|||
|
|||
/** |
|||
* StatusService constructor. |
|||
* |
|||
* @param UserStatusMapper $mapper |
|||
* @param ITimeFactory $timeFactory |
|||
* @param PredefinedStatusService $defaultStatusService, |
|||
* @param EmojiService $emojiService |
|||
*/ |
|||
public function __construct(UserStatusMapper $mapper, |
|||
ITimeFactory $timeFactory, |
|||
PredefinedStatusService $defaultStatusService, |
|||
EmojiService $emojiService) { |
|||
$this->mapper = $mapper; |
|||
$this->timeFactory = $timeFactory; |
|||
$this->predefinedStatusService = $defaultStatusService; |
|||
$this->emojiService = $emojiService; |
|||
} |
|||
|
|||
/** |
|||
* @param int|null $limit |
|||
* @param int|null $offset |
|||
* @return UserStatus[] |
|||
*/ |
|||
public function findAll(?int $limit = null, ?int $offset = null): array { |
|||
return array_map(function ($status) { |
|||
return $this->processStatus($status); |
|||
}, $this->mapper->findAll($limit, $offset)); |
|||
} |
|||
|
|||
/** |
|||
* @param string $userId |
|||
* @return UserStatus |
|||
* @throws DoesNotExistException |
|||
*/ |
|||
public function findByUserId(string $userId):UserStatus { |
|||
return $this->processStatus($this->mapper->findByUserId($userId)); |
|||
} |
|||
|
|||
/** |
|||
* @param string $userId |
|||
* @param string $status |
|||
* @param int|null $statusTimestamp |
|||
* @param bool $isUserDefined |
|||
* @return UserStatus |
|||
* @throws InvalidStatusTypeException |
|||
*/ |
|||
public function setStatus(string $userId, |
|||
string $status, |
|||
?int $statusTimestamp, |
|||
bool $isUserDefined): UserStatus { |
|||
try { |
|||
$userStatus = $this->mapper->findByUserId($userId); |
|||
} catch (DoesNotExistException $ex) { |
|||
$userStatus = new UserStatus(); |
|||
$userStatus->setUserId($userId); |
|||
} |
|||
|
|||
// Check if status-type is valid
|
|||
if (!\in_array($status, $this->allowedStatusTypes, true)) { |
|||
throw new InvalidStatusTypeException('Status-type "' . $status . '" is not supported'); |
|||
} |
|||
if ($statusTimestamp === null) { |
|||
$statusTimestamp = $this->timeFactory->getTime(); |
|||
} |
|||
|
|||
$userStatus->setStatus($status); |
|||
$userStatus->setStatusTimestamp($statusTimestamp); |
|||
$userStatus->setIsUserDefined($isUserDefined); |
|||
|
|||
if ($userStatus->getId() === null) { |
|||
return $this->mapper->insert($userStatus); |
|||
} |
|||
|
|||
return $this->mapper->update($userStatus); |
|||
} |
|||
|
|||
/** |
|||
* @param string $userId |
|||
* @param string $messageId |
|||
* @param int|null $clearAt |
|||
* @return UserStatus |
|||
* @throws InvalidMessageIdException |
|||
* @throws InvalidClearAtException |
|||
*/ |
|||
public function setPredefinedMessage(string $userId, |
|||
string $messageId, |
|||
?int $clearAt): UserStatus { |
|||
try { |
|||
$userStatus = $this->mapper->findByUserId($userId); |
|||
} catch (DoesNotExistException $ex) { |
|||
$userStatus = new UserStatus(); |
|||
$userStatus->setUserId($userId); |
|||
$userStatus->setStatus('offline'); |
|||
$userStatus->setStatusTimestamp(0); |
|||
$userStatus->setIsUserDefined(false); |
|||
} |
|||
|
|||
if (!$this->predefinedStatusService->isValidId($messageId)) { |
|||
throw new InvalidMessageIdException('Message-Id "' . $messageId . '" is not supported'); |
|||
} |
|||
|
|||
// Check that clearAt is in the future
|
|||
if ($clearAt !== null && $clearAt < $this->timeFactory->getTime()) { |
|||
throw new InvalidClearAtException('ClearAt is in the past'); |
|||
} |
|||
|
|||
$userStatus->setMessageId($messageId); |
|||
$userStatus->setCustomIcon(null); |
|||
$userStatus->setCustomMessage(null); |
|||
$userStatus->setClearAt($clearAt); |
|||
|
|||
if ($userStatus->getId() === null) { |
|||
return $this->mapper->insert($userStatus); |
|||
} |
|||
|
|||
return $this->mapper->update($userStatus); |
|||
} |
|||
|
|||
/** |
|||
* @param string $userId |
|||
* @param string|null $statusIcon |
|||
* @param string|null $message |
|||
* @param int|null $clearAt |
|||
* @return UserStatus |
|||
* @throws InvalidClearAtException |
|||
* @throws InvalidStatusIconException |
|||
* @throws StatusMessageTooLongException |
|||
*/ |
|||
public function setCustomMessage(string $userId, |
|||
?string $statusIcon, |
|||
string $message, |
|||
?int $clearAt): UserStatus { |
|||
try { |
|||
$userStatus = $this->mapper->findByUserId($userId); |
|||
} catch (DoesNotExistException $ex) { |
|||
$userStatus = new UserStatus(); |
|||
$userStatus->setUserId($userId); |
|||
$userStatus->setStatus('offline'); |
|||
$userStatus->setStatusTimestamp(0); |
|||
$userStatus->setIsUserDefined(false); |
|||
} |
|||
|
|||
// Check if statusIcon contains only one character
|
|||
if ($statusIcon !== null && !$this->emojiService->isValidEmoji($statusIcon)) { |
|||
throw new InvalidStatusIconException('Status-Icon is longer than one character'); |
|||
} |
|||
// Check for maximum length of custom message
|
|||
if (\mb_strlen($message) > $this->maximumMessageLength) { |
|||
throw new StatusMessageTooLongException('Message is longer than supported length of ' . $this->maximumMessageLength . ' characters'); |
|||
} |
|||
// Check that clearAt is in the future
|
|||
if ($clearAt !== null && $clearAt < $this->timeFactory->getTime()) { |
|||
throw new InvalidClearAtException('ClearAt is in the past'); |
|||
} |
|||
|
|||
$userStatus->setMessageId(null); |
|||
$userStatus->setCustomIcon($statusIcon); |
|||
$userStatus->setCustomMessage($message); |
|||
$userStatus->setClearAt($clearAt); |
|||
|
|||
if ($userStatus->getId() === null) { |
|||
return $this->mapper->insert($userStatus); |
|||
} |
|||
|
|||
return $this->mapper->update($userStatus); |
|||
} |
|||
|
|||
/** |
|||
* @param string $userId |
|||
* @return bool |
|||
*/ |
|||
public function clearStatus(string $userId): bool { |
|||
try { |
|||
$userStatus = $this->mapper->findByUserId($userId); |
|||
} catch (DoesNotExistException $ex) { |
|||
// if there is no status to remove, just return
|
|||
return false; |
|||
} |
|||
|
|||
$userStatus->setStatus('offline'); |
|||
$userStatus->setStatusTimestamp(0); |
|||
$userStatus->setIsUserDefined(false); |
|||
|
|||
$this->mapper->update($userStatus); |
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* @param string $userId |
|||
* @return bool |
|||
*/ |
|||
public function clearMessage(string $userId): bool { |
|||
try { |
|||
$userStatus = $this->mapper->findByUserId($userId); |
|||
} catch (DoesNotExistException $ex) { |
|||
// if there is no status to remove, just return
|
|||
return false; |
|||
} |
|||
|
|||
$userStatus->setMessageId(null); |
|||
$userStatus->setCustomMessage(null); |
|||
$userStatus->setCustomIcon(null); |
|||
$userStatus->setClearAt(null); |
|||
|
|||
$this->mapper->update($userStatus); |
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* @param string $userId |
|||
* @return bool |
|||
*/ |
|||
public function removeUserStatus(string $userId): bool { |
|||
try { |
|||
$userStatus = $this->mapper->findByUserId($userId); |
|||
} catch (DoesNotExistException $ex) { |
|||
// if there is no status to remove, just return
|
|||
return false; |
|||
} |
|||
|
|||
$this->mapper->delete($userStatus); |
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* Processes a status to check if custom message is still |
|||
* up to date and provides translated default status if needed |
|||
* |
|||
* @param UserStatus $status |
|||
* @returns UserStatus |
|||
*/ |
|||
private function processStatus(UserStatus $status): UserStatus { |
|||
$clearAt = $status->getClearAt(); |
|||
if ($clearAt !== null && $clearAt < $this->timeFactory->getTime()) { |
|||
$this->cleanStatus($status); |
|||
} |
|||
if ($status->getMessageId() !== null) { |
|||
$this->addDefaultMessage($status); |
|||
} |
|||
|
|||
return $status; |
|||
} |
|||
|
|||
/** |
|||
* @param UserStatus $status |
|||
*/ |
|||
private function cleanStatus(UserStatus $status): void { |
|||
$status->setMessageId(null); |
|||
$status->setCustomIcon(null); |
|||
$status->setCustomMessage(null); |
|||
$status->setClearAt(null); |
|||
|
|||
$this->mapper->update($status); |
|||
} |
|||
|
|||
/** |
|||
* @param UserStatus $status |
|||
*/ |
|||
private function addDefaultMessage(UserStatus $status): void { |
|||
// If the message is predefined, insert the translated message and icon
|
|||
$predefinedMessage = $this->predefinedStatusService->getDefaultStatusById($status->getMessageId()); |
|||
if ($predefinedMessage !== null) { |
|||
$status->setCustomMessage($predefinedMessage['message']); |
|||
$status->setCustomIcon($predefinedMessage['icon']); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,271 @@ |
|||
<!-- |
|||
- @copyright Copyright (c) 2020 Georg Ehrke <oc.list@georgehrke.com> |
|||
- @author Georg Ehrke <oc.list@georgehrke.com> |
|||
- |
|||
- @license GNU AGPL version 3 or any later version |
|||
- |
|||
- This program is free software: you can redistribute it and/or modify |
|||
- it under the terms of the GNU Affero General Public License as |
|||
- published by the Free Software Foundation, either version 3 of the |
|||
- License, or (at your option) any later version. |
|||
- |
|||
- 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 |
|||
- along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
- |
|||
--> |
|||
|
|||
<template> |
|||
<li> |
|||
<div id="user-status-menu-item"> |
|||
<span id="user-status-menu-item__header">{{ displayName }}</span> |
|||
<Actions |
|||
id="user-status-menu-item__subheader" |
|||
:default-icon="statusIcon" |
|||
:menu-title="visibleMessage"> |
|||
<ActionButton |
|||
v-for="status in statuses" |
|||
:key="status.type" |
|||
:icon="status.icon" |
|||
:close-after-click="true" |
|||
@click.prevent.stop="changeStatus(status.type)"> |
|||
{{ status.label }} |
|||
</ActionButton> |
|||
<ActionButton |
|||
icon="icon-rename" |
|||
:close-after-click="true" |
|||
@click.prevent.stop="openModal"> |
|||
{{ $t('user_status', 'Set custom status') }} |
|||
</ActionButton> |
|||
</Actions> |
|||
<SetStatusModal |
|||
v-if="isModalOpen" |
|||
@close="closeModal" /> |
|||
</div> |
|||
</li> |
|||
</template> |
|||
|
|||
<script> |
|||
import { getCurrentUser } from '@nextcloud/auth' |
|||
import SetStatusModal from './components/SetStatusModal' |
|||
import Actions from '@nextcloud/vue/dist/Components/Actions' |
|||
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton' |
|||
import { mapState } from 'vuex' |
|||
import { showError } from '@nextcloud/dialogs' |
|||
import { getAllStatusOptions } from './services/statusOptionsService' |
|||
import { sendHeartbeat } from './services/heartbeatService' |
|||
import debounce from 'debounce' |
|||
|
|||
export default { |
|||
name: 'App', |
|||
components: { |
|||
Actions, |
|||
ActionButton, |
|||
SetStatusModal, |
|||
}, |
|||
data() { |
|||
return { |
|||
isModalOpen: false, |
|||
statuses: getAllStatusOptions(), |
|||
heartbeatInterval: null, |
|||
setAwayTimeout: null, |
|||
mouseMoveListener: null, |
|||
isAway: false, |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapState({ |
|||
statusType: state => state.userStatus.status, |
|||
statusIsUserDefined: state => state.userStatus.statusIsUserDefined, |
|||
customIcon: state => state.userStatus.icon, |
|||
customMessage: state => state.userStatus.message, |
|||
}), |
|||
/** |
|||
* The display-name of the current user |
|||
* |
|||
* @returns {String} |
|||
*/ |
|||
displayName() { |
|||
return getCurrentUser().displayName |
|||
}, |
|||
/** |
|||
* The message displayed in the top right corner |
|||
* |
|||
* @returns {String} |
|||
*/ |
|||
visibleMessage() { |
|||
if (this.customIcon && this.customMessage) { |
|||
return `${this.customIcon} ${this.customMessage}` |
|||
} |
|||
if (this.customMessage) { |
|||
return this.customMessage |
|||
} |
|||
|
|||
if (this.statusIsUserDefined) { |
|||
switch (this.statusType) { |
|||
case 'online': |
|||
return this.$t('user_status', 'Online') |
|||
|
|||
case 'away': |
|||
return this.$t('user_status', 'Away') |
|||
|
|||
case 'dnd': |
|||
return this.$t('user_status', 'Do not disturb') |
|||
|
|||
case 'invisible': |
|||
return this.$t('user_status', 'Invisible') |
|||
|
|||
case 'offline': |
|||
return this.$t('user_status', 'Offline') |
|||
} |
|||
} |
|||
|
|||
return this.$t('user_status', 'Set status') |
|||
}, |
|||
/** |
|||
* The status indicator icon |
|||
* |
|||
* @returns {String|null} |
|||
*/ |
|||
statusIcon() { |
|||
switch (this.statusType) { |
|||
case 'online': |
|||
return 'icon-user-status-online' |
|||
|
|||
case 'away': |
|||
return 'icon-user-status-away' |
|||
|
|||
case 'dnd': |
|||
return 'icon-user-status-dnd' |
|||
|
|||
case 'invisible': |
|||
case 'offline': |
|||
return 'icon-user-status-invisible' |
|||
} |
|||
|
|||
return '' |
|||
}, |
|||
}, |
|||
/** |
|||
* Loads the current user's status from initial state |
|||
* and stores it in Vuex |
|||
*/ |
|||
mounted() { |
|||
this.$store.dispatch('loadStatusFromInitialState') |
|||
|
|||
if (OC.config.session_keepalive) { |
|||
// Send the latest status to the server every 5 minutes |
|||
this.heartbeatInterval = setInterval(this._backgroundHeartbeat.bind(this), 1000 * 60 * 5) |
|||
this.setAwayTimeout = () => { |
|||
this.isAway = true |
|||
} |
|||
// Catch mouse movements, but debounce to once every 30 seconds |
|||
this.mouseMoveListener = debounce(() => { |
|||
const wasAway = this.isAway |
|||
this.isAway = false |
|||
// Reset the two minute counter |
|||
clearTimeout(this.setAwayTimeout) |
|||
// If the user did not move the mouse within two minutes, |
|||
// mark them as away |
|||
setTimeout(this.setAwayTimeout, 1000 * 60 * 2) |
|||
|
|||
if (wasAway) { |
|||
this._backgroundHeartbeat() |
|||
} |
|||
}, 1000 * 2, true) |
|||
window.addEventListener('mousemove', this.mouseMoveListener, { |
|||
capture: true, |
|||
passive: true, |
|||
}) |
|||
|
|||
this._backgroundHeartbeat() |
|||
} |
|||
}, |
|||
/** |
|||
* Some housekeeping before destroying the component |
|||
*/ |
|||
beforeDestroy() { |
|||
window.removeEventListener('mouseMove', this.mouseMoveListener) |
|||
clearInterval(this.heartbeatInterval) |
|||
}, |
|||
methods: { |
|||
/** |
|||
* Opens the modal to set a custom status |
|||
*/ |
|||
openModal() { |
|||
this.isModalOpen = true |
|||
}, |
|||
/** |
|||
* Closes the modal |
|||
*/ |
|||
closeModal() { |
|||
this.isModalOpen = false |
|||
}, |
|||
/** |
|||
* Changes the user-status |
|||
* |
|||
* @param {String} statusType (online / away / dnd / invisible) |
|||
*/ |
|||
async changeStatus(statusType) { |
|||
try { |
|||
await this.$store.dispatch('setStatus', { statusType }) |
|||
} catch (err) { |
|||
showError(this.$t('user_status', 'There was an error saving the new status')) |
|||
console.debug(err) |
|||
} |
|||
}, |
|||
/** |
|||
* Sends the status heartbeat to the server |
|||
* |
|||
* @returns {Promise<void>} |
|||
* @private |
|||
*/ |
|||
async _backgroundHeartbeat() { |
|||
await sendHeartbeat(this.isAway) |
|||
await this.$store.dispatch('reFetchStatusFromServer') |
|||
}, |
|||
}, |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
#user-status-menu-item { |
|||
&__header { |
|||
display: block; |
|||
align-items: center; |
|||
color: var(--color-main-text); |
|||
padding: 10px 12px 5px 12px; |
|||
box-sizing: border-box; |
|||
opacity: 1; |
|||
white-space: nowrap; |
|||
width: 100%; |
|||
text-align: center; |
|||
max-width: 250px; |
|||
text-overflow: ellipsis; |
|||
min-width: 175px; |
|||
} |
|||
|
|||
&__subheader { |
|||
width: 100%; |
|||
|
|||
> button { |
|||
background-color: var(--color-main-background); |
|||
background-size: 16px; |
|||
border: 0; |
|||
border-radius: 0; |
|||
font-weight: normal; |
|||
font-size: 0.875em; |
|||
padding-left: 40px; |
|||
|
|||
&:hover, |
|||
&:focus { |
|||
box-shadow: inset 4px 0 var(--color-primary-element); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,102 @@ |
|||
<!-- |
|||
- @copyright Copyright (c) 2020 Georg Ehrke <oc.list@georgehrke.com> |
|||
- @author Georg Ehrke <oc.list@georgehrke.com> |
|||
- |
|||
- @license GNU AGPL version 3 or any later version |
|||
- |
|||
- This program is free software: you can redistribute it and/or modify |
|||
- it under the terms of the GNU Affero General Public License as |
|||
- published by the Free Software Foundation, either version 3 of the |
|||
- License, or (at your option) any later version. |
|||
- |
|||
- 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 |
|||
- along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
- |
|||
--> |
|||
|
|||
<template> |
|||
<div class="clear-at-select"> |
|||
<span |
|||
class="clear-at-select__label"> |
|||
{{ $t('user_select', 'Clear status after') }} |
|||
</span> |
|||
<Multiselect |
|||
label="label" |
|||
:value="option" |
|||
:options="options" |
|||
open-direction="top" |
|||
@select="select" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect' |
|||
import { getAllClearAtOptions } from '../services/clearAtOptionsService' |
|||
import { clearAtFilter } from '../filters/clearAtFilter' |
|||
|
|||
export default { |
|||
name: 'ClearAtSelect', |
|||
components: { |
|||
Multiselect, |
|||
}, |
|||
props: { |
|||
clearAt: { |
|||
type: Object, |
|||
default: null, |
|||
}, |
|||
}, |
|||
data() { |
|||
return { |
|||
options: getAllClearAtOptions(), |
|||
} |
|||
}, |
|||
computed: { |
|||
/** |
|||
* Returns an object of the currently selected option |
|||
* |
|||
* @returns {Object} |
|||
*/ |
|||
option() { |
|||
return { |
|||
clearAt: this.clearAt, |
|||
label: clearAtFilter(this.clearAt), |
|||
} |
|||
}, |
|||
}, |
|||
methods: { |
|||
/** |
|||
* Triggered when the user selects a new option. |
|||
* |
|||
* @param {Object=} option The new selected option |
|||
*/ |
|||
select(option) { |
|||
if (!option) { |
|||
return |
|||
} |
|||
|
|||
this.$emit('selectClearAt', option.clearAt) |
|||
}, |
|||
}, |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.clear-at-select { |
|||
display: flex; |
|||
margin-bottom: 10px; |
|||
align-items: center; |
|||
|
|||
&__label { |
|||
margin-right: 10px; |
|||
} |
|||
|
|||
.multiselect { |
|||
flex-grow: 1; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,65 @@ |
|||
<!-- |
|||
- @copyright Copyright (c) 2020 Georg Ehrke <oc.list@georgehrke.com> |
|||
- @author Georg Ehrke <oc.list@georgehrke.com> |
|||
- |
|||
- @license GNU AGPL version 3 or any later version |
|||
- |
|||
- This program is free software: you can redistribute it and/or modify |
|||
- it under the terms of the GNU Affero General Public License as |
|||
- published by the Free Software Foundation, either version 3 of the |
|||
- License, or (at your option) any later version. |
|||
- |
|||
- 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 |
|||
- along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
- |
|||
--> |
|||
<template> |
|||
<form |
|||
class="custom-input__form" |
|||
@submit.prevent> |
|||
<input |
|||
:placeholder="$t('user_status', 'What\'s your status?')" |
|||
type="text" |
|||
:value="message" |
|||
@change="change"> |
|||
</form> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'CustomMessageInput', |
|||
props: { |
|||
message: { |
|||
type: String, |
|||
required: true, |
|||
default: () => '', |
|||
}, |
|||
}, |
|||
methods: { |
|||
/** |
|||
* Notifies the parent component about a changed input |
|||
* |
|||
* @param {Event} event The Change Event |
|||
*/ |
|||
change(event) { |
|||
this.$emit('change', event.target.value) |
|||
}, |
|||
}, |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.custom-input__form { |
|||
flex-grow: 1; |
|||
|
|||
input { |
|||
width: 100%; |
|||
border-radius: 0 var(--border-radius) var(--border-radius) 0; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,111 @@ |
|||
<!-- |
|||
- @copyright Copyright (c) 2020 Georg Ehrke <oc.list@georgehrke.com> |
|||
- @author Georg Ehrke <oc.list@georgehrke.com> |
|||
- |
|||
- @license GNU AGPL version 3 or any later version |
|||
- |
|||
- This program is free software: you can redistribute it and/or modify |
|||
- it under the terms of the GNU Affero General Public License as |
|||
- published by the Free Software Foundation, either version 3 of the |
|||
- License, or (at your option) any later version. |
|||
- |
|||
- 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 |
|||
- along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
- |
|||
--> |
|||
<template> |
|||
<div |
|||
class="predefined-status" |
|||
tabindex="0" |
|||
@keyup.enter="select" |
|||
@keyup.space="select" |
|||
@click="select"> |
|||
<span class="predefined-status__icon"> |
|||
{{ icon }} |
|||
</span> |
|||
<span class="predefined-status__message"> |
|||
{{ message }} |
|||
</span> |
|||
<span class="predefined-status__clear-at"> |
|||
{{ clearAt | clearAtFilter }} |
|||
</span> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { clearAtFilter } from '../filters/clearAtFilter' |
|||
|
|||
export default { |
|||
name: 'PredefinedStatus', |
|||
filters: { |
|||
clearAtFilter, |
|||
}, |
|||
props: { |
|||
messageId: { |
|||
type: String, |
|||
required: true, |
|||
}, |
|||
icon: { |
|||
type: String, |
|||
required: true, |
|||
}, |
|||
message: { |
|||
type: String, |
|||
required: true, |
|||
}, |
|||
clearAt: { |
|||
type: Object, |
|||
required: false, |
|||
default: null, |
|||
}, |
|||
}, |
|||
methods: { |
|||
/** |
|||
* Emits an event when the user clicks the row |
|||
*/ |
|||
select() { |
|||
this.$emit('select') |
|||
}, |
|||
}, |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.predefined-status { |
|||
display: flex; |
|||
flex-wrap: nowrap; |
|||
justify-content: flex-start; |
|||
flex-basis: 100%; |
|||
border-radius: var(--border-radius); |
|||
align-items: center; |
|||
min-height: 44px; |
|||
|
|||
&:hover, |
|||
&:focus { |
|||
background-color: var(--color-background-hover); |
|||
} |
|||
|
|||
&__icon { |
|||
flex-basis: 40px; |
|||
text-align: center; |
|||
} |
|||
|
|||
&__message { |
|||
font-weight: bold; |
|||
padding: 0 6px; |
|||
} |
|||
|
|||
&__clear-at { |
|||
opacity: .7; |
|||
|
|||
&::before { |
|||
content: ' - '; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,90 @@ |
|||
<!-- |
|||
- @copyright Copyright (c) 2020 Georg Ehrke <oc.list@georgehrke.com> |
|||
- @author Georg Ehrke <oc.list@georgehrke.com> |
|||
- |
|||
- @license GNU AGPL version 3 or any later version |
|||
- |
|||
- This program is free software: you can redistribute it and/or modify |
|||
- it under the terms of the GNU Affero General Public License as |
|||
- published by the Free Software Foundation, either version 3 of the |
|||
- License, or (at your option) any later version. |
|||
- |
|||
- 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 |
|||
- along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
- |
|||
--> |
|||
|
|||
<template> |
|||
<div |
|||
v-if="hasLoaded" |
|||
class="predefined-statuses-list"> |
|||
<PredefinedStatus |
|||
v-for="status in predefinedStatuses" |
|||
:key="status.id" |
|||
:message-id="status.id" |
|||
:icon="status.icon" |
|||
:message="status.message" |
|||
:clear-at="status.clearAt" |
|||
@select="selectStatus(status)" /> |
|||
</div> |
|||
<div |
|||
v-else |
|||
class="predefined-statuses-list"> |
|||
<div class="icon icon-loading-small" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import PredefinedStatus from './PredefinedStatus' |
|||
import { mapState } from 'vuex' |
|||
|
|||
export default { |
|||
name: 'PredefinedStatusesList', |
|||
components: { |
|||
PredefinedStatus, |
|||
}, |
|||
computed: { |
|||
...mapState({ |
|||
predefinedStatuses: state => state.predefinedStatuses.predefinedStatuses, |
|||
}), |
|||
/** |
|||
* Indicator whether the predefined statuses have already been loaded |
|||
* |
|||
* @returns {boolean} |
|||
*/ |
|||
hasLoaded() { |
|||
return this.predefinedStatuses.length > 0 |
|||
}, |
|||
}, |
|||
/** |
|||
* Loads all predefined statuses from the server |
|||
* when this component is mounted |
|||
*/ |
|||
mounted() { |
|||
this.$store.dispatch('loadAllPredefinedStatuses') |
|||
}, |
|||
methods: { |
|||
/** |
|||
* Emits an event when the user selects a status |
|||
* |
|||
* @param {Object} status The selected status |
|||
*/ |
|||
selectStatus(status) { |
|||
this.$emit('selectStatus', status) |
|||
}, |
|||
}, |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.predefined-statuses-list { |
|||
display: flex; |
|||
flex-direction: column; |
|||
margin-bottom: 10px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,236 @@ |
|||
<!-- |
|||
- @copyright Copyright (c) 2020 Georg Ehrke <oc.list@georgehrke.com> |
|||
- @author Georg Ehrke <oc.list@georgehrke.com> |
|||
- |
|||
- @license GNU AGPL version 3 or any later version |
|||
- |
|||
- This program is free software: you can redistribute it and/or modify |
|||
- it under the terms of the GNU Affero General Public License as |
|||
- published by the Free Software Foundation, either version 3 of the |
|||
- License, or (at your option) any later version. |
|||
- |
|||
- 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 |
|||
- along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
- |
|||
--> |
|||
|
|||
<template> |
|||
<Modal |
|||
size="normal" |
|||
:title="$t('user_status', 'Set a custom status')" |
|||
@close="closeModal"> |
|||
<div class="set-status-modal"> |
|||
<div class="set-status-modal__header"> |
|||
<h3>{{ $t('user_status', 'Set a custom status') }}</h3> |
|||
</div> |
|||
<div class="set-status-modal__custom-input"> |
|||
<EmojiPicker @select="setIcon"> |
|||
<button |
|||
class="custom-input__emoji-button"> |
|||
{{ visibleIcon }} |
|||
</button> |
|||
</EmojiPicker> |
|||
<CustomMessageInput |
|||
:message="message" |
|||
@change="setMessage" /> |
|||
</div> |
|||
<PredefinedStatusesList |
|||
@selectStatus="selectPredefinedMessage" /> |
|||
<ClearAtSelect |
|||
:clear-at="clearAt" |
|||
@selectClearAt="setClearAt" /> |
|||
<div class="status-buttons"> |
|||
<button class="status-buttons__select" @click="clearStatus"> |
|||
{{ $t('user_status', 'Clear custom status') }} |
|||
</button> |
|||
<button class="status-buttons__primary primary" @click="saveStatus"> |
|||
{{ $t('user_status', 'Set status') }} |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</Modal> |
|||
</template> |
|||
|
|||
<script> |
|||
import EmojiPicker from '@nextcloud/vue/dist/Components/EmojiPicker' |
|||
import Modal from '@nextcloud/vue/dist/Components/Modal' |
|||
import PredefinedStatusesList from './PredefinedStatusesList' |
|||
import CustomMessageInput from './CustomMessageInput' |
|||
import ClearAtSelect from './ClearAtSelect' |
|||
import { showError } from '@nextcloud/dialogs' |
|||
|
|||
export default { |
|||
name: 'SetStatusModal', |
|||
components: { |
|||
EmojiPicker, |
|||
Modal, |
|||
CustomMessageInput, |
|||
PredefinedStatusesList, |
|||
ClearAtSelect, |
|||
}, |
|||
data() { |
|||
return { |
|||
icon: null, |
|||
message: null, |
|||
clearAt: null, |
|||
} |
|||
}, |
|||
computed: { |
|||
/** |
|||
* Returns the user-set icon or a smiley in case no icon is set |
|||
* |
|||
* @returns {String} |
|||
*/ |
|||
visibleIcon() { |
|||
return this.icon || '😀' |
|||
}, |
|||
}, |
|||
/** |
|||
* Loads the current status when a user opens dialog |
|||
*/ |
|||
mounted() { |
|||
this.messageId = this.$store.state.userStatus.messageId |
|||
this.icon = this.$store.state.userStatus.icon |
|||
this.message = this.$store.state.userStatus.message |
|||
|
|||
if (this.$store.state.userStatus.clearAt !== null) { |
|||
this.clearAt = { |
|||
type: '_time', |
|||
time: this.$store.state.userStatus.clearAt, |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
/** |
|||
* Closes the Set Status modal |
|||
*/ |
|||
closeModal() { |
|||
this.$emit('close') |
|||
}, |
|||
/** |
|||
* Sets a new icon |
|||
* |
|||
* @param {String} icon The new icon |
|||
*/ |
|||
setIcon(icon) { |
|||
this.messageId = null |
|||
this.icon = icon |
|||
}, |
|||
/** |
|||
* Sets a new message |
|||
* |
|||
* @param {String} message The new message |
|||
*/ |
|||
setMessage(message) { |
|||
this.messageId = null |
|||
this.message = message |
|||
}, |
|||
/** |
|||
* Sets a new clearAt value |
|||
* |
|||
* @param {Object} clearAt The new clearAt object |
|||
*/ |
|||
setClearAt(clearAt) { |
|||
this.clearAt = clearAt |
|||
}, |
|||
/** |
|||
* Sets new icon/message/clearAt based on a predefined message |
|||
* |
|||
* @param {Object} status The predefined status object |
|||
*/ |
|||
selectPredefinedMessage(status) { |
|||
this.messageId = status.id |
|||
this.clearAt = status.clearAt |
|||
this.icon = status.icon |
|||
this.message = status.message |
|||
}, |
|||
/** |
|||
* Saves the status and closes the |
|||
* |
|||
* @returns {Promise<void>} |
|||
*/ |
|||
async saveStatus() { |
|||
try { |
|||
this.isSavingStatus = true |
|||
|
|||
if (this.messageId !== null) { |
|||
await this.$store.dispatch('setPredefinedMessage', { |
|||
messageId: this.messageId, |
|||
clearAt: this.clearAt, |
|||
}) |
|||
} else { |
|||
await this.$store.dispatch('setCustomMessage', { |
|||
message: this.message, |
|||
icon: this.icon, |
|||
clearAt: this.clearAt, |
|||
}) |
|||
} |
|||
} catch (err) { |
|||
showError(this.$t('user_status', 'There was an error saving the status')) |
|||
console.debug(err) |
|||
this.isSavingStatus = false |
|||
return |
|||
} |
|||
|
|||
this.isSavingStatus = false |
|||
this.closeModal() |
|||
}, |
|||
/** |
|||
* |
|||
* @returns {Promise<void>} |
|||
*/ |
|||
async clearStatus() { |
|||
try { |
|||
this.isSavingStatus = true |
|||
|
|||
await this.$store.dispatch('clearMessage') |
|||
} catch (err) { |
|||
showError(this.$t('user_status', 'There was an error clearing the status')) |
|||
console.debug(err) |
|||
this.isSavingStatus = false |
|||
return |
|||
} |
|||
|
|||
this.isSavingStatus = false |
|||
this.closeModal() |
|||
}, |
|||
}, |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.set-status-modal { |
|||
min-width: 500px; |
|||
min-height: 200px; |
|||
padding: 8px 20px 20px 20px; |
|||
|
|||
&__custom-input { |
|||
display: flex; |
|||
width: 100%; |
|||
margin-bottom: 10px; |
|||
|
|||
.custom-input__emoji-button { |
|||
flex-basis: 40px; |
|||
width: 40px; |
|||
flex-grow: 0; |
|||
border-radius: var(--border-radius) 0 0 var(--border-radius); |
|||
height: 34px; |
|||
margin-right: 0; |
|||
border-right: none; |
|||
} |
|||
} |
|||
|
|||
.status-buttons { |
|||
display: flex; |
|||
|
|||
button { |
|||
flex-basis: 50%; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,68 @@ |
|||
/** |
|||
* @copyright Copyright (c) 2020 Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* 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 |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
* |
|||
*/ |
|||
import { translate as t } from '@nextcloud/l10n' |
|||
import moment from '@nextcloud/moment' |
|||
import { dateFactory } from '../services/dateService' |
|||
|
|||
/** |
|||
* Formats a clearAt object to be human readable |
|||
* |
|||
* @param {Object} clearAt The clearAt object |
|||
* @returns {string|null} |
|||
*/ |
|||
const clearAtFilter = (clearAt) => { |
|||
if (clearAt === null) { |
|||
return t('user_status', 'Don\'t clear') |
|||
} |
|||
|
|||
if (clearAt.type === 'end-of') { |
|||
switch (clearAt.time) { |
|||
case 'day': |
|||
return t('user_status', 'Today') |
|||
case 'week': |
|||
return t('user_status', 'This week') |
|||
|
|||
default: |
|||
return null |
|||
} |
|||
} |
|||
|
|||
if (clearAt.type === 'period') { |
|||
return moment.duration(clearAt.time * 1000).humanize() |
|||
} |
|||
|
|||
// This is not an officially supported type
|
|||
// but only used internally to show the remaining time
|
|||
// in the Set Status Modal
|
|||
if (clearAt.type === '_time') { |
|||
const momentNow = moment(dateFactory()) |
|||
const momentClearAt = moment(clearAt.time, 'X') |
|||
|
|||
return moment.duration(momentNow.diff(momentClearAt)).humanize() |
|||
} |
|||
|
|||
return null |
|||
} |
|||
|
|||
export { |
|||
clearAtFilter, |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
import Vue from 'vue' |
|||
import { getRequestToken } from '@nextcloud/auth' |
|||
import App from './App' |
|||
import store from './store' |
|||
|
|||
// eslint-disable-next-line camelcase
|
|||
__webpack_nonce__ = btoa(getRequestToken()) |
|||
|
|||
// Correct the root of the app for chunk loading
|
|||
// OC.linkTo matches the apps folders
|
|||
// OC.generateUrl ensure the index.php (or not)
|
|||
// eslint-disable-next-line
|
|||
__webpack_public_path__ = OC.linkTo('user_status', 'js/') |
|||
|
|||
Vue.prototype.t = t |
|||
Vue.prototype.$t = t |
|||
|
|||
const app = new Vue({ |
|||
render: h => h(App), |
|||
store, |
|||
}).$mount('li[data-id="user_status-menuitem"]') |
|||
|
|||
export { app } |
|||
@ -0,0 +1,68 @@ |
|||
/** |
|||
* @copyright Copyright (c) 2020 Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* 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 |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
* |
|||
*/ |
|||
import { translate as t } from '@nextcloud/l10n' |
|||
|
|||
/** |
|||
* Returns an array |
|||
* |
|||
* @returns {Object[]} |
|||
*/ |
|||
const getAllClearAtOptions = () => { |
|||
return [{ |
|||
label: t('user_status', 'Don\'t clear'), |
|||
clearAt: null, |
|||
}, { |
|||
label: t('user_status', '30 minutes'), |
|||
clearAt: { |
|||
type: 'period', |
|||
time: 1800, |
|||
}, |
|||
}, { |
|||
label: t('user_status', '1 hour'), |
|||
clearAt: { |
|||
type: 'period', |
|||
time: 3600, |
|||
}, |
|||
}, { |
|||
label: t('user_status', '4 hours'), |
|||
clearAt: { |
|||
type: 'period', |
|||
time: 14400, |
|||
}, |
|||
}, { |
|||
label: t('user_status', 'Today'), |
|||
clearAt: { |
|||
type: 'end-of', |
|||
time: 'day', |
|||
}, |
|||
}, { |
|||
label: t('user_status', 'This week'), |
|||
clearAt: { |
|||
type: 'end-of', |
|||
time: 'week', |
|||
}, |
|||
}] |
|||
} |
|||
|
|||
export { |
|||
getAllClearAtOptions, |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
/** |
|||
* @copyright Copyright (c) 2020 Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* 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 |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
* |
|||
*/ |
|||
import { |
|||
dateFactory, |
|||
} from './dateService' |
|||
import moment from '@nextcloud/moment' |
|||
|
|||
/** |
|||
* Calculates the actual clearAt timestamp |
|||
* |
|||
* @param {Object|null} clearAt The clear-at config |
|||
* @returns {Number|null} |
|||
*/ |
|||
const getTimestampForClearAt = (clearAt) => { |
|||
if (clearAt === null) { |
|||
return null |
|||
} |
|||
|
|||
const date = dateFactory() |
|||
|
|||
if (clearAt.type === 'period') { |
|||
date.setSeconds(date.getSeconds() + clearAt.time) |
|||
return Math.floor(date.getTime() / 1000) |
|||
} |
|||
if (clearAt.type === 'end-of') { |
|||
switch (clearAt.time) { |
|||
case 'day': |
|||
case 'week': |
|||
return Number(moment(date).endOf(clearAt.time).format('X')) |
|||
} |
|||
} |
|||
// This is not an officially supported type
|
|||
// but only used internally to show the remaining time
|
|||
// in the Set Status Modal
|
|||
if (clearAt.type === '_time') { |
|||
return clearAt.time |
|||
} |
|||
|
|||
return null |
|||
} |
|||
|
|||
export { |
|||
getTimestampForClearAt, |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
/** |
|||
* @copyright Copyright (c) 2020 Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* 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 |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
* |
|||
*/ |
|||
|
|||
/** |
|||
* Returns a new Date object |
|||
* |
|||
* @returns {Date} |
|||
*/ |
|||
const dateFactory = () => { |
|||
return new Date() |
|||
} |
|||
|
|||
export { |
|||
dateFactory, |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
/** |
|||
* @copyright Copyright (c) 2020 Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* 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 |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
* |
|||
*/ |
|||
import HttpClient from '@nextcloud/axios' |
|||
import { generateUrl } from '@nextcloud/router' |
|||
|
|||
/** |
|||
* Sends a heartbeat |
|||
* |
|||
* @param {Boolean} isAway Whether or not the user is active |
|||
* @returns {Promise<void>} |
|||
*/ |
|||
const sendHeartbeat = async(isAway) => { |
|||
const url = generateUrl('/apps/user_status/heartbeat') |
|||
await HttpClient.put(url, { |
|||
status: isAway ? 'away' : 'online', |
|||
}) |
|||
} |
|||
|
|||
export { |
|||
sendHeartbeat, |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
/** |
|||
* @copyright Copyright (c) 2020 Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* 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 |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
* |
|||
*/ |
|||
import HttpClient from '@nextcloud/axios' |
|||
import { generateOcsUrl } from '@nextcloud/router' |
|||
|
|||
/** |
|||
* Fetches all predefined statuses from the server |
|||
* |
|||
* @returns {Promise<void>} |
|||
*/ |
|||
const fetchAllPredefinedStatuses = async() => { |
|||
const url = generateOcsUrl('apps/user_status/api/v1', 2) + '/predefined_statuses?format=json' |
|||
const response = await HttpClient.get(url) |
|||
|
|||
return response.data.ocs.data |
|||
} |
|||
|
|||
export { |
|||
fetchAllPredefinedStatuses, |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
/** |
|||
* @copyright Copyright (c) 2020 Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* 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 |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
* |
|||
*/ |
|||
import { translate as t } from '@nextcloud/l10n' |
|||
|
|||
/** |
|||
* Returns a list of all user-definable statuses |
|||
* |
|||
* @returns {Object[]} |
|||
*/ |
|||
const getAllStatusOptions = () => { |
|||
return [{ |
|||
type: 'online', |
|||
label: t('user_status', 'Online'), |
|||
icon: 'icon-user-status-online', |
|||
}, { |
|||
type: 'away', |
|||
label: t('user_status', 'Away'), |
|||
icon: 'icon-user-status-away', |
|||
}, { |
|||
type: 'dnd', |
|||
label: t('user_status', 'Do not disturb'), |
|||
icon: 'icon-user-status-dnd', |
|||
|
|||
}, { |
|||
type: 'invisible', |
|||
label: t('user_status', 'Invisible'), |
|||
icon: 'icon-user-status-invisible', |
|||
}] |
|||
} |
|||
|
|||
export { |
|||
getAllStatusOptions, |
|||
} |
|||
@ -0,0 +1,98 @@ |
|||
/** |
|||
* @copyright Copyright (c) 2020 Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* 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 |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
* |
|||
*/ |
|||
import HttpClient from '@nextcloud/axios' |
|||
import { generateOcsUrl } from '@nextcloud/router' |
|||
|
|||
/** |
|||
* Fetches the current user-status |
|||
* |
|||
* @returns {Promise<Object>} |
|||
*/ |
|||
const fetchCurrentStatus = async() => { |
|||
const url = generateOcsUrl('apps/user_status/api/v1', 2) + 'user_status' |
|||
const response = await HttpClient.get(url) |
|||
|
|||
return response.data.ocs.data |
|||
} |
|||
|
|||
/** |
|||
* Sets the status |
|||
* |
|||
* @param {String} statusType The status (online / away / dnd / invisible) |
|||
* @returns {Promise<void>} |
|||
*/ |
|||
const setStatus = async(statusType) => { |
|||
const url = generateOcsUrl('apps/user_status/api/v1', 2) + 'user_status/status' |
|||
await HttpClient.put(url, { |
|||
statusType, |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* Sets a message based on our predefined statuses |
|||
* |
|||
* @param {String} messageId The id of the message, taken from predefined status service |
|||
* @param {Number|null} clearAt When to automatically clean the status |
|||
* @returns {Promise<void>} |
|||
*/ |
|||
const setPredefinedMessage = async(messageId, clearAt = null) => { |
|||
const url = generateOcsUrl('apps/user_status/api/v1', 2) + 'user_status/message/predefined?format=json' |
|||
await HttpClient.put(url, { |
|||
messageId, |
|||
clearAt, |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* Sets a custom message |
|||
* |
|||
* @param {String} message The user-defined message |
|||
* @param {String|null} statusIcon The user-defined icon |
|||
* @param {Number|null} clearAt When to automatically clean the status |
|||
* @returns {Promise<void>} |
|||
*/ |
|||
const setCustomMessage = async(message, statusIcon = null, clearAt = null) => { |
|||
const url = generateOcsUrl('apps/user_status/api/v1', 2) + 'user_status/message/custom?format=json' |
|||
await HttpClient.put(url, { |
|||
message, |
|||
statusIcon, |
|||
clearAt, |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* Clears the current status of the user |
|||
* |
|||
* @returns {Promise<void>} |
|||
*/ |
|||
const clearMessage = async() => { |
|||
const url = generateOcsUrl('apps/user_status/api/v1', 2) + 'user_status/message?format=json' |
|||
await HttpClient.delete(url) |
|||
} |
|||
|
|||
export { |
|||
fetchCurrentStatus, |
|||
setStatus, |
|||
setCustomMessage, |
|||
setPredefinedMessage, |
|||
clearMessage, |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
/** |
|||
* @copyright Copyright (c) 2020 Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* 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 |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
* |
|||
*/ |
|||
import Vue from 'vue' |
|||
import Vuex from 'vuex' |
|||
import predefinedStatuses from './predefinedStatuses' |
|||
import userStatus from './userStatus' |
|||
|
|||
Vue.use(Vuex) |
|||
|
|||
export default new Vuex.Store({ |
|||
modules: { |
|||
predefinedStatuses, |
|||
userStatus, |
|||
}, |
|||
strict: true, |
|||
}) |
|||
@ -0,0 +1,64 @@ |
|||
/** |
|||
* @copyright Copyright (c) 2020 Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* 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 |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
* |
|||
*/ |
|||
import { fetchAllPredefinedStatuses } from '../services/predefinedStatusService' |
|||
|
|||
const state = { |
|||
predefinedStatuses: [], |
|||
} |
|||
|
|||
const mutations = { |
|||
|
|||
/** |
|||
* Adds a predefined status to the state |
|||
* |
|||
* @param {Object} state The Vuex state |
|||
* @param {Object} status The status to add |
|||
*/ |
|||
addPredefinedStatus(state, status) { |
|||
state.predefinedStatuses.push(status) |
|||
}, |
|||
} |
|||
|
|||
const getters = {} |
|||
|
|||
const actions = { |
|||
|
|||
/** |
|||
* Loads all predefined statuses from the server |
|||
* |
|||
* @param {Object} vuex The Vuex components |
|||
* @param {Function} vuex.commit The Vuex commit function |
|||
*/ |
|||
async loadAllPredefinedStatuses({ state, commit }) { |
|||
if (state.predefinedStatuses.length > 0) { |
|||
return |
|||
} |
|||
|
|||
const statuses = await fetchAllPredefinedStatuses() |
|||
for (const status of statuses) { |
|||
commit('addPredefinedStatus', status) |
|||
} |
|||
}, |
|||
|
|||
} |
|||
|
|||
export default { state, mutations, getters, actions } |
|||
@ -0,0 +1,232 @@ |
|||
/** |
|||
* @copyright Copyright (c) 2020 Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @license GNU AGPL version 3 or any later version |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License as |
|||
* published by the Free Software Foundation, either version 3 of the |
|||
* License, or (at your option) any later version. |
|||
* |
|||
* 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 |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
* |
|||
*/ |
|||
import { |
|||
fetchCurrentStatus, |
|||
setStatus, |
|||
setPredefinedMessage, |
|||
setCustomMessage, |
|||
clearMessage, |
|||
} from '../services/statusService' |
|||
import { loadState } from '@nextcloud/initial-state' |
|||
import { getTimestampForClearAt } from '../services/clearAtService' |
|||
|
|||
const state = { |
|||
// Status (online / away / dnd / invisible / offline)
|
|||
status: null, |
|||
// Whether or not the status is user-defined
|
|||
statusIsUserDefined: null, |
|||
// A custom message set by the user
|
|||
message: null, |
|||
// The icon selected by the user
|
|||
icon: null, |
|||
// When to automatically clean the status
|
|||
clearAt: null, |
|||
// Whether or not the message is predefined
|
|||
// (and can automatically be translated by Nextcloud)
|
|||
messageIsPredefined: null, |
|||
// The id of the message in case it's predefined
|
|||
messageId: null, |
|||
} |
|||
|
|||
const mutations = { |
|||
|
|||
/** |
|||
* Sets a new status |
|||
* |
|||
* @param {Object} state The Vuex state |
|||
* @param {Object} data The destructuring object |
|||
* @param {String} data.statusType The new status type |
|||
*/ |
|||
setStatus(state, { statusType }) { |
|||
state.status = statusType |
|||
state.statusIsUserDefined = true |
|||
}, |
|||
|
|||
/** |
|||
* Sets a message using a predefined message |
|||
* |
|||
* @param {Object} state The Vuex state |
|||
* @param {Object} data The destructuring object |
|||
* @param {String} data.messageId The messageId |
|||
* @param {Number|null} data.clearAt When to automatically clear the status |
|||
* @param {String} data.message The message |
|||
* @param {String} data.icon The icon |
|||
*/ |
|||
setPredefinedMessage(state, { messageId, clearAt, message, icon }) { |
|||
state.messageId = messageId |
|||
state.messageIsPredefined = true |
|||
|
|||
state.message = message |
|||
state.icon = icon |
|||
state.clearAt = clearAt |
|||
}, |
|||
|
|||
/** |
|||
* Sets a custom message |
|||
* |
|||
* @param {Object} state The Vuex state |
|||
* @param {Object} data The destructuring object |
|||
* @param {String} data.message The message |
|||
* @param {String} data.icon The icon |
|||
* @param {Number} data.clearAt When to automatically clear the status |
|||
*/ |
|||
setCustomMessage(state, { message, icon, clearAt }) { |
|||
state.messageId = null |
|||
state.messageIsPredefined = false |
|||
|
|||
state.message = message |
|||
state.icon = icon |
|||
state.clearAt = clearAt |
|||
}, |
|||
|
|||
/** |
|||
* Clears the status |
|||
* |
|||
* @param {Object} state The Vuex state |
|||
*/ |
|||
clearMessage(state) { |
|||
state.messageId = null |
|||
state.messageIsPredefined = false |
|||
|
|||
state.message = null |
|||
state.icon = null |
|||
state.clearAt = null |
|||
}, |
|||
|
|||
/** |
|||
* Loads the status from initial state |
|||
* |
|||
* @param {Object} state The Vuex state |
|||
* @param {Object} data The destructuring object |
|||
* @param {String} data.status The status type |
|||
* @param {Boolean} data.statusIsUserDefined Whether or not this status is user-defined |
|||
* @param {String} data.message The message |
|||
* @param {String} data.icon The icon |
|||
* @param {Number} data.clearAt When to automatically clear the status |
|||
* @param {Boolean} data.messageIsPredefined Whether or not the message is predefined |
|||
* @param {string} data.messageId The id of the predefined message |
|||
*/ |
|||
loadStatusFromServer(state, { status, statusIsUserDefined, message, icon, clearAt, messageIsPredefined, messageId }) { |
|||
state.status = status |
|||
state.statusIsUserDefined = statusIsUserDefined |
|||
state.message = message |
|||
state.icon = icon |
|||
state.clearAt = clearAt |
|||
state.messageIsPredefined = messageIsPredefined |
|||
state.messageId = messageId |
|||
}, |
|||
} |
|||
|
|||
const getters = {} |
|||
|
|||
const actions = { |
|||
|
|||
/** |
|||
* Sets a new status |
|||
* |
|||
* @param {Object} vuex The Vuex destructuring object |
|||
* @param {Function} vuex.commit The Vuex commit function |
|||
* @param {Object} data The data destructuring object |
|||
* @param {String} data.statusType The new status type |
|||
* @returns {Promise<void>} |
|||
*/ |
|||
async setStatus({ commit }, { statusType }) { |
|||
await setStatus(statusType) |
|||
commit('setStatus', { statusType }) |
|||
}, |
|||
|
|||
/** |
|||
* Sets a message using a predefined message |
|||
* |
|||
* @param {Object} vuex The Vuex destructuring object |
|||
* @param {Function} vuex.commit The Vuex commit function |
|||
* @param {Object} vuex.rootState The Vuex root state |
|||
* @param {Object} data The data destructuring object |
|||
* @param {String} data.messageId The messageId |
|||
* @param {Object|null} data.clearAt When to automatically clear the status |
|||
* @returns {Promise<void>} |
|||
*/ |
|||
async setPredefinedMessage({ commit, rootState }, { messageId, clearAt }) { |
|||
const resolvedClearAt = getTimestampForClearAt(clearAt) |
|||
|
|||
await setPredefinedMessage(messageId, resolvedClearAt) |
|||
const status = rootState.predefinedStatuses.predefinedStatuses.find((status) => status.id === messageId) |
|||
const { message, icon } = status |
|||
|
|||
commit('setPredefinedMessage', { messageId, clearAt: resolvedClearAt, message, icon }) |
|||
}, |
|||
|
|||
/** |
|||
* Sets a custom message |
|||
* |
|||
* @param {Object} vuex The Vuex destructuring object |
|||
* @param {Function} vuex.commit The Vuex commit function |
|||
* @param {Object} data The data destructuring object |
|||
* @param {String} data.message The message |
|||
* @param {String} data.icon The icon |
|||
* @param {Object|null} data.clearAt When to automatically clear the status |
|||
* @returns {Promise<void>} |
|||
*/ |
|||
async setCustomMessage({ commit }, { message, icon, clearAt }) { |
|||
const resolvedClearAt = getTimestampForClearAt(clearAt) |
|||
|
|||
await setCustomMessage(message, icon, resolvedClearAt) |
|||
commit('setCustomMessage', { message, icon, clearAt: resolvedClearAt }) |
|||
}, |
|||
|
|||
/** |
|||
* Clears the status |
|||
* |
|||
* @param {Object} vuex The Vuex destructuring object |
|||
* @param {Function} vuex.commit The Vuex commit function |
|||
* @returns {Promise<void>} |
|||
*/ |
|||
async clearMessage({ commit }) { |
|||
await clearMessage() |
|||
commit('clearMessage') |
|||
}, |
|||
|
|||
/** |
|||
* Re-fetches the status from the server |
|||
* |
|||
* @param {Object} vuex The Vuex destructuring object |
|||
* @param {Function} vuex.commit The Vuex commit function |
|||
* @returns {Promise<void>} |
|||
*/ |
|||
async reFetchStatusFromServer({ commit }) { |
|||
const status = await fetchCurrentStatus() |
|||
commit('loadStatusFromServer', status) |
|||
}, |
|||
|
|||
/** |
|||
* Loads the server from the initial state |
|||
* |
|||
* @param {Object} vuex The Vuex destructuring object |
|||
* @param {Function} vuex.commit The Vuex commit function |
|||
*/ |
|||
loadStatusFromInitialState({ commit }) { |
|||
const status = loadState('user_status', 'status') |
|||
commit('loadStatusFromServer', status) |
|||
}, |
|||
} |
|||
|
|||
export default { state, mutations, getters, actions } |
|||
@ -0,0 +1,63 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\BackgroundJob; |
|||
|
|||
use OCA\UserStatus\BackgroundJob\ClearOldStatusesBackgroundJob; |
|||
use OCA\UserStatus\Db\UserStatusMapper; |
|||
use OCP\AppFramework\Utility\ITimeFactory; |
|||
use Test\TestCase; |
|||
|
|||
class ClearOldStatusesBackgroundJobTest extends TestCase { |
|||
|
|||
/** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ |
|||
private $time; |
|||
|
|||
/** @var UserStatusMapper|\PHPUnit\Framework\MockObject\MockObject */ |
|||
private $mapper; |
|||
|
|||
/** @var ClearOldStatusesBackgroundJob */ |
|||
private $job; |
|||
|
|||
protected function setUp(): void { |
|||
parent::setUp(); |
|||
|
|||
$this->time = $this->createMock(ITimeFactory::class); |
|||
$this->mapper = $this->createMock(UserStatusMapper::class); |
|||
|
|||
$this->job = new ClearOldStatusesBackgroundJob($this->time, $this->mapper); |
|||
} |
|||
|
|||
public function testRun() { |
|||
$this->mapper->expects($this->once()) |
|||
->method('clearOlderThan') |
|||
->with(1337); |
|||
|
|||
$this->time->method('getTime') |
|||
->willReturn(1337); |
|||
|
|||
self::invokePrivate($this->job, 'run', [[]]); |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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; |
|||
|
|||
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->emojiService = $this->createMock(EmojiService::class); |
|||
$this->capabilities = new Capabilities($this->emojiService); |
|||
} |
|||
|
|||
/** |
|||
* @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], |
|||
]; |
|||
} |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Controller; |
|||
|
|||
use OCA\UserStatus\Controller\PredefinedStatusController; |
|||
use OCA\UserStatus\Service\PredefinedStatusService; |
|||
use OCP\IRequest; |
|||
use Test\TestCase; |
|||
|
|||
class PredefinedStatusControllerTest extends TestCase { |
|||
|
|||
/** @var PredefinedStatusService|\PHPUnit\Framework\MockObject\MockObject */ |
|||
private $service; |
|||
|
|||
/** @var PredefinedStatusController */ |
|||
private $controller; |
|||
|
|||
protected function setUp(): void { |
|||
parent::setUp(); |
|||
|
|||
$request = $this->createMock(IRequest::class); |
|||
$this->service = $this->createMock(PredefinedStatusService::class); |
|||
|
|||
$this->controller = new PredefinedStatusController('user_status', $request, |
|||
$this->service); |
|||
} |
|||
|
|||
public function testFindAll() { |
|||
$this->service->expects($this->once()) |
|||
->method('getDefaultStatuses') |
|||
->with() |
|||
->willReturn([ |
|||
[ |
|||
'id' => 'predefined-status-one', |
|||
], |
|||
[ |
|||
'id' => 'predefined-status-two', |
|||
], |
|||
]); |
|||
|
|||
$actual = $this->controller->findAll(); |
|||
$this->assertEquals([ |
|||
[ |
|||
'id' => 'predefined-status-one', |
|||
], |
|||
[ |
|||
'id' => 'predefined-status-two', |
|||
], |
|||
], $actual->getData()); |
|||
} |
|||
} |
|||
@ -0,0 +1,114 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Controller; |
|||
|
|||
use OCA\UserStatus\Controller\StatusesController; |
|||
use OCA\UserStatus\Db\UserStatus; |
|||
use OCA\UserStatus\Service\StatusService; |
|||
use OCP\AppFramework\Db\DoesNotExistException; |
|||
use OCP\AppFramework\OCS\OCSNotFoundException; |
|||
use OCP\IRequest; |
|||
use Test\TestCase; |
|||
|
|||
class StatusesControllerTest extends TestCase { |
|||
|
|||
/** @var StatusService|\PHPUnit\Framework\MockObject\MockObject */ |
|||
private $service; |
|||
|
|||
/** @var StatusesController */ |
|||
private $controller; |
|||
|
|||
protected function setUp(): void { |
|||
parent::setUp(); |
|||
|
|||
$request = $this->createMock(IRequest::class); |
|||
$this->service = $this->createMock(StatusService::class); |
|||
|
|||
$this->controller = new StatusesController('user_status', $request, $this->service); |
|||
} |
|||
|
|||
public function testFindAll(): void { |
|||
$userStatus = $this->getUserStatus(); |
|||
|
|||
$this->service->expects($this->once()) |
|||
->method('findAll') |
|||
->with(20, 40) |
|||
->willReturn([$userStatus]); |
|||
|
|||
$response = $this->controller->findAll(20, 40); |
|||
$this->assertEquals([[ |
|||
'userId' => 'john.doe', |
|||
'status' => 'offline', |
|||
'icon' => '🏝', |
|||
'message' => 'On vacation', |
|||
'clearAt' => 60000, |
|||
]], $response->getData()); |
|||
} |
|||
|
|||
public function testFind(): void { |
|||
$userStatus = $this->getUserStatus(); |
|||
|
|||
$this->service->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with('john.doe') |
|||
->willReturn($userStatus); |
|||
|
|||
$response = $this->controller->find('john.doe'); |
|||
$this->assertEquals([ |
|||
'userId' => 'john.doe', |
|||
'status' => 'offline', |
|||
'icon' => '🏝', |
|||
'message' => 'On vacation', |
|||
'clearAt' => 60000, |
|||
], $response->getData()); |
|||
} |
|||
|
|||
public function testFindDoesNotExist(): void { |
|||
$this->service->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with('john.doe') |
|||
->willThrowException(new DoesNotExistException('')); |
|||
|
|||
$this->expectException(OCSNotFoundException::class); |
|||
$this->expectExceptionMessage('No status for the requested userId'); |
|||
|
|||
$this->controller->find('john.doe'); |
|||
} |
|||
|
|||
private function getUserStatus(): UserStatus { |
|||
$userStatus = new UserStatus(); |
|||
$userStatus->setId(1337); |
|||
$userStatus->setUserId('john.doe'); |
|||
$userStatus->setStatus('invisible'); |
|||
$userStatus->setStatusTimestamp(5000); |
|||
$userStatus->setIsUserDefined(true); |
|||
$userStatus->setCustomIcon('🏝'); |
|||
$userStatus->setCustomMessage('On vacation'); |
|||
$userStatus->setClearAt(60000); |
|||
|
|||
return $userStatus; |
|||
} |
|||
} |
|||
@ -0,0 +1,340 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Controller; |
|||
|
|||
use OCA\UserStatus\Controller\UserStatusController; |
|||
use OCA\UserStatus\Db\UserStatus; |
|||
use OCA\UserStatus\Exception\InvalidClearAtException; |
|||
use OCA\UserStatus\Exception\InvalidMessageIdException; |
|||
use OCA\UserStatus\Exception\InvalidStatusIconException; |
|||
use OCA\UserStatus\Exception\InvalidStatusTypeException; |
|||
use OCA\UserStatus\Exception\StatusMessageTooLongException; |
|||
use OCA\UserStatus\Service\StatusService; |
|||
use OCP\AppFramework\Db\DoesNotExistException; |
|||
use OCP\AppFramework\OCS\OCSBadRequestException; |
|||
use OCP\AppFramework\OCS\OCSNotFoundException; |
|||
use OCP\ILogger; |
|||
use OCP\IRequest; |
|||
use Test\TestCase; |
|||
use Throwable; |
|||
|
|||
class UserStatusControllerTest extends TestCase { |
|||
|
|||
/** @var ILogger|\PHPUnit\Framework\MockObject\MockObject */ |
|||
private $logger; |
|||
|
|||
/** @var StatusService|\PHPUnit\Framework\MockObject\MockObject */ |
|||
private $service; |
|||
|
|||
/** @var UserStatusController */ |
|||
private $controller; |
|||
|
|||
protected function setUp(): void { |
|||
parent::setUp(); |
|||
|
|||
$request = $this->createMock(IRequest::class); |
|||
$userId = 'john.doe'; |
|||
$this->logger = $this->createMock(ILogger::class); |
|||
$this->service = $this->createMock(StatusService::class); |
|||
|
|||
$this->controller = new UserStatusController('user_status', $request, $userId, $this->logger, $this->service); |
|||
} |
|||
|
|||
public function testGetStatus(): void { |
|||
$userStatus = $this->getUserStatus(); |
|||
|
|||
$this->service->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with('john.doe') |
|||
->willReturn($userStatus); |
|||
|
|||
$response = $this->controller->getStatus(); |
|||
$this->assertEquals([ |
|||
'userId' => 'john.doe', |
|||
'status' => 'invisible', |
|||
'icon' => '🏝', |
|||
'message' => 'On vacation', |
|||
'clearAt' => 60000, |
|||
'statusIsUserDefined' => true, |
|||
'messageIsPredefined' => false, |
|||
'messageId' => null, |
|||
], $response->getData()); |
|||
} |
|||
|
|||
public function testGetStatusDoesNotExist(): void { |
|||
$this->service->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with('john.doe') |
|||
->willThrowException(new DoesNotExistException('')); |
|||
|
|||
$this->expectException(OCSNotFoundException::class); |
|||
$this->expectExceptionMessage('No status for the current user'); |
|||
|
|||
$this->controller->getStatus(); |
|||
} |
|||
|
|||
/** |
|||
* @param string $statusType |
|||
* @param string|null $statusIcon |
|||
* @param string|null $message |
|||
* @param int|null $clearAt |
|||
* @param bool $expectSuccess |
|||
* @param bool $expectException |
|||
* @param Throwable|null $exception |
|||
* @param bool $expectLogger |
|||
* @param string|null $expectedLogMessage |
|||
* |
|||
* @dataProvider setStatusDataProvider |
|||
*/ |
|||
public function testSetStatus(string $statusType, |
|||
?string $statusIcon, |
|||
?string $message, |
|||
?int $clearAt, |
|||
bool $expectSuccess, |
|||
bool $expectException, |
|||
?Throwable $exception, |
|||
bool $expectLogger, |
|||
?string $expectedLogMessage): void { |
|||
$userStatus = $this->getUserStatus(); |
|||
|
|||
if ($expectException) { |
|||
$this->service->expects($this->once()) |
|||
->method('setStatus') |
|||
->with('john.doe', $statusType, null, true) |
|||
->willThrowException($exception); |
|||
} else { |
|||
$this->service->expects($this->once()) |
|||
->method('setStatus') |
|||
->with('john.doe', $statusType, null, true) |
|||
->willReturn($userStatus); |
|||
} |
|||
|
|||
if ($expectLogger) { |
|||
$this->logger->expects($this->once()) |
|||
->method('debug') |
|||
->with($expectedLogMessage); |
|||
} |
|||
if ($expectException) { |
|||
$this->expectException(OCSBadRequestException::class); |
|||
$this->expectExceptionMessage('Original exception message'); |
|||
} |
|||
|
|||
$response = $this->controller->setStatus($statusType); |
|||
|
|||
if ($expectSuccess) { |
|||
$this->assertEquals([ |
|||
'userId' => 'john.doe', |
|||
'status' => 'invisible', |
|||
'icon' => '🏝', |
|||
'message' => 'On vacation', |
|||
'clearAt' => 60000, |
|||
'statusIsUserDefined' => true, |
|||
'messageIsPredefined' => false, |
|||
'messageId' => null, |
|||
], $response->getData()); |
|||
} |
|||
} |
|||
|
|||
public function setStatusDataProvider(): array { |
|||
return [ |
|||
['busy', '👨🏽💻', 'Busy developing the status feature', 500, true, false, null, false, null], |
|||
['busy', '👨🏽💻', 'Busy developing the status feature', 500, false, true, new InvalidStatusTypeException('Original exception message'), true, |
|||
'New user-status for "john.doe" was rejected due to an invalid status type "busy"'], |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* @param string $messageId |
|||
* @param int|null $clearAt |
|||
* @param bool $expectSuccess |
|||
* @param bool $expectException |
|||
* @param Throwable|null $exception |
|||
* @param bool $expectLogger |
|||
* @param string|null $expectedLogMessage |
|||
* |
|||
* @dataProvider setPredefinedMessageDataProvider |
|||
*/ |
|||
public function testSetPredefinedMessage(string $messageId, |
|||
?int $clearAt, |
|||
bool $expectSuccess, |
|||
bool $expectException, |
|||
?Throwable $exception, |
|||
bool $expectLogger, |
|||
?string $expectedLogMessage): void { |
|||
$userStatus = $this->getUserStatus(); |
|||
|
|||
if ($expectException) { |
|||
$this->service->expects($this->once()) |
|||
->method('setPredefinedMessage') |
|||
->with('john.doe', $messageId, $clearAt) |
|||
->willThrowException($exception); |
|||
} else { |
|||
$this->service->expects($this->once()) |
|||
->method('setPredefinedMessage') |
|||
->with('john.doe', $messageId, $clearAt) |
|||
->willReturn($userStatus); |
|||
} |
|||
|
|||
if ($expectLogger) { |
|||
$this->logger->expects($this->once()) |
|||
->method('debug') |
|||
->with($expectedLogMessage); |
|||
} |
|||
if ($expectException) { |
|||
$this->expectException(OCSBadRequestException::class); |
|||
$this->expectExceptionMessage('Original exception message'); |
|||
} |
|||
|
|||
$response = $this->controller->setPredefinedMessage($messageId, $clearAt); |
|||
|
|||
if ($expectSuccess) { |
|||
$this->assertEquals([ |
|||
'userId' => 'john.doe', |
|||
'status' => 'invisible', |
|||
'icon' => '🏝', |
|||
'message' => 'On vacation', |
|||
'clearAt' => 60000, |
|||
'statusIsUserDefined' => true, |
|||
'messageIsPredefined' => false, |
|||
'messageId' => null, |
|||
], $response->getData()); |
|||
} |
|||
} |
|||
|
|||
public function setPredefinedMessageDataProvider(): array { |
|||
return [ |
|||
['messageId-42', 500, true, false, null, false, null], |
|||
['messageId-42', 500, false, true, new InvalidClearAtException('Original exception message'), true, |
|||
'New user-status for "john.doe" was rejected due to an invalid clearAt value "500"'], |
|||
['messageId-42', 500, false, true, new InvalidMessageIdException('Original exception message'), true, |
|||
'New user-status for "john.doe" was rejected due to an invalid message-id "messageId-42"'], |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* @param string|null $statusIcon |
|||
* @param string $message |
|||
* @param int|null $clearAt |
|||
* @param bool $expectSuccess |
|||
* @param bool $expectException |
|||
* @param Throwable|null $exception |
|||
* @param bool $expectLogger |
|||
* @param string|null $expectedLogMessage |
|||
* |
|||
* @dataProvider setCustomMessageDataProvider |
|||
*/ |
|||
public function testSetCustomMessage(?string $statusIcon, |
|||
string $message, |
|||
?int $clearAt, |
|||
bool $expectSuccess, |
|||
bool $expectException, |
|||
?Throwable $exception, |
|||
bool $expectLogger, |
|||
?string $expectedLogMessage): void { |
|||
$userStatus = $this->getUserStatus(); |
|||
|
|||
if ($expectException) { |
|||
$this->service->expects($this->once()) |
|||
->method('setCustomMessage') |
|||
->with('john.doe', $statusIcon, $message, $clearAt) |
|||
->willThrowException($exception); |
|||
} else { |
|||
$this->service->expects($this->once()) |
|||
->method('setCustomMessage') |
|||
->with('john.doe', $statusIcon, $message, $clearAt) |
|||
->willReturn($userStatus); |
|||
} |
|||
|
|||
if ($expectLogger) { |
|||
$this->logger->expects($this->once()) |
|||
->method('debug') |
|||
->with($expectedLogMessage); |
|||
} |
|||
if ($expectException) { |
|||
$this->expectException(OCSBadRequestException::class); |
|||
$this->expectExceptionMessage('Original exception message'); |
|||
} |
|||
|
|||
$response = $this->controller->setCustomMessage($statusIcon, $message, $clearAt); |
|||
|
|||
if ($expectSuccess) { |
|||
$this->assertEquals([ |
|||
'userId' => 'john.doe', |
|||
'status' => 'invisible', |
|||
'icon' => '🏝', |
|||
'message' => 'On vacation', |
|||
'clearAt' => 60000, |
|||
'statusIsUserDefined' => true, |
|||
'messageIsPredefined' => false, |
|||
'messageId' => null, |
|||
], $response->getData()); |
|||
} |
|||
} |
|||
|
|||
public function setCustomMessageDataProvider(): array { |
|||
return [ |
|||
['👨🏽💻', 'Busy developing the status feature', 500, true, false, null, false, null], |
|||
['👨🏽💻', 'Busy developing the status feature', 500, false, true, new InvalidClearAtException('Original exception message'), true, |
|||
'New user-status for "john.doe" was rejected due to an invalid clearAt value "500"'], |
|||
['👨🏽💻', 'Busy developing the status feature', 500, false, true, new InvalidStatusIconException('Original exception message'), true, |
|||
'New user-status for "john.doe" was rejected due to an invalid icon value "👨🏽💻"'], |
|||
['👨🏽💻', 'Busy developing the status feature', 500, false, true, new StatusMessageTooLongException('Original exception message'), true, |
|||
'New user-status for "john.doe" was rejected due to a too long status message.'], |
|||
]; |
|||
} |
|||
|
|||
public function testClearStatus(): void { |
|||
$this->service->expects($this->once()) |
|||
->method('clearStatus') |
|||
->with('john.doe'); |
|||
|
|||
$response = $this->controller->clearStatus(); |
|||
$this->assertEquals([], $response->getData()); |
|||
} |
|||
|
|||
public function testClearMessage(): void { |
|||
$this->service->expects($this->once()) |
|||
->method('clearMessage') |
|||
->with('john.doe'); |
|||
|
|||
$response = $this->controller->clearMessage(); |
|||
$this->assertEquals([], $response->getData()); |
|||
} |
|||
|
|||
private function getUserStatus(): UserStatus { |
|||
$userStatus = new UserStatus(); |
|||
$userStatus->setId(1337); |
|||
$userStatus->setUserId('john.doe'); |
|||
$userStatus->setStatus('invisible'); |
|||
$userStatus->setStatusTimestamp(5000); |
|||
$userStatus->setIsUserDefined(true); |
|||
$userStatus->setCustomIcon('🏝'); |
|||
$userStatus->setCustomMessage('On vacation'); |
|||
$userStatus->setClearAt(60000); |
|||
|
|||
return $userStatus; |
|||
} |
|||
} |
|||
@ -0,0 +1,168 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Db; |
|||
|
|||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException; |
|||
use OCA\UserStatus\Db\UserStatus; |
|||
use OCA\UserStatus\Db\UserStatusMapper; |
|||
use Test\TestCase; |
|||
|
|||
class UserStatusMapperTest extends TestCase { |
|||
|
|||
/** @var UserStatusMapper */ |
|||
private $mapper; |
|||
|
|||
protected function setUp(): void { |
|||
parent::setUp(); |
|||
|
|||
// make sure that DB is empty
|
|||
$qb = self::$realDatabase->getQueryBuilder(); |
|||
$qb->delete('user_status')->execute(); |
|||
|
|||
$this->mapper = new UserStatusMapper(self::$realDatabase); |
|||
} |
|||
|
|||
public function testGetTableName(): void { |
|||
$this->assertEquals('user_status', $this->mapper->getTableName()); |
|||
} |
|||
|
|||
public function testGetFindAll(): void { |
|||
$this->insertSampleStatuses(); |
|||
|
|||
$allResults = $this->mapper->findAll(); |
|||
$this->assertCount(3, $allResults); |
|||
|
|||
$limitedResults = $this->mapper->findAll(2); |
|||
$this->assertCount(2, $limitedResults); |
|||
$this->assertEquals('admin', $limitedResults[0]->getUserId()); |
|||
$this->assertEquals('user1', $limitedResults[1]->getUserId()); |
|||
|
|||
$offsetResults = $this->mapper->findAll(null, 2); |
|||
$this->assertCount(1, $offsetResults); |
|||
$this->assertEquals('user2', $offsetResults[0]->getUserId()); |
|||
} |
|||
|
|||
public function testGetFind(): void { |
|||
$this->insertSampleStatuses(); |
|||
|
|||
$adminStatus = $this->mapper->findByUserId('admin'); |
|||
$this->assertEquals('admin', $adminStatus->getUserId()); |
|||
$this->assertEquals('offline', $adminStatus->getStatus()); |
|||
$this->assertEquals(0, $adminStatus->getStatusTimestamp()); |
|||
$this->assertEquals(false, $adminStatus->getIsUserDefined()); |
|||
$this->assertEquals(null, $adminStatus->getCustomIcon()); |
|||
$this->assertEquals(null, $adminStatus->getCustomMessage()); |
|||
$this->assertEquals(null, $adminStatus->getClearAt()); |
|||
|
|||
$user1Status = $this->mapper->findByUserId('user1'); |
|||
$this->assertEquals('user1', $user1Status->getUserId()); |
|||
$this->assertEquals('dnd', $user1Status->getStatus()); |
|||
$this->assertEquals(5000, $user1Status->getStatusTimestamp()); |
|||
$this->assertEquals(true, $user1Status->getIsUserDefined()); |
|||
$this->assertEquals('💩', $user1Status->getCustomIcon()); |
|||
$this->assertEquals('Do not disturb', $user1Status->getCustomMessage()); |
|||
$this->assertEquals(50000, $user1Status->getClearAt()); |
|||
|
|||
$user2Status = $this->mapper->findByUserId('user2'); |
|||
$this->assertEquals('user2', $user2Status->getUserId()); |
|||
$this->assertEquals('away', $user2Status->getStatus()); |
|||
$this->assertEquals(5000, $user2Status->getStatusTimestamp()); |
|||
$this->assertEquals(false, $user2Status->getIsUserDefined()); |
|||
$this->assertEquals('🏝', $user2Status->getCustomIcon()); |
|||
$this->assertEquals('On vacation', $user2Status->getCustomMessage()); |
|||
$this->assertEquals(60000, $user2Status->getClearAt()); |
|||
} |
|||
|
|||
public function testUserIdUnique(): void { |
|||
// Test that inserting a second status for a user is throwing an exception
|
|||
|
|||
$userStatus1 = new UserStatus(); |
|||
$userStatus1->setUserId('admin'); |
|||
$userStatus1->setStatus('dnd'); |
|||
$userStatus1->setStatusTimestamp(5000); |
|||
$userStatus1->setIsUserDefined(true); |
|||
|
|||
$this->mapper->insert($userStatus1); |
|||
|
|||
$userStatus2 = new UserStatus(); |
|||
$userStatus2->setUserId('admin'); |
|||
$userStatus2->setStatus('away'); |
|||
$userStatus2->setStatusTimestamp(6000); |
|||
$userStatus2->setIsUserDefined(false); |
|||
|
|||
$this->expectException(UniqueConstraintViolationException::class); |
|||
|
|||
$this->mapper->insert($userStatus2); |
|||
} |
|||
|
|||
public function testClearOlderThan(): void { |
|||
$this->insertSampleStatuses(); |
|||
|
|||
$this->mapper->clearOlderThan(55000); |
|||
|
|||
$allStatuses = $this->mapper->findAll(); |
|||
$this->assertCount(3, $allStatuses); |
|||
|
|||
$user1Status = $this->mapper->findByUserId('user1'); |
|||
$this->assertEquals('user1', $user1Status->getUserId()); |
|||
$this->assertEquals('dnd', $user1Status->getStatus()); |
|||
$this->assertEquals(5000, $user1Status->getStatusTimestamp()); |
|||
$this->assertEquals(true, $user1Status->getIsUserDefined()); |
|||
$this->assertEquals(null, $user1Status->getCustomIcon()); |
|||
$this->assertEquals(null, $user1Status->getCustomMessage()); |
|||
$this->assertEquals(null, $user1Status->getClearAt()); |
|||
} |
|||
|
|||
private function insertSampleStatuses(): void { |
|||
$userStatus1 = new UserStatus(); |
|||
$userStatus1->setUserId('admin'); |
|||
$userStatus1->setStatus('offline'); |
|||
$userStatus1->setStatusTimestamp(0); |
|||
$userStatus1->setIsUserDefined(false); |
|||
|
|||
$userStatus2 = new UserStatus(); |
|||
$userStatus2->setUserId('user1'); |
|||
$userStatus2->setStatus('dnd'); |
|||
$userStatus2->setStatusTimestamp(5000); |
|||
$userStatus2->setIsUserDefined(true); |
|||
$userStatus2->setCustomIcon('💩'); |
|||
$userStatus2->setCustomMessage('Do not disturb'); |
|||
$userStatus2->setClearAt(50000); |
|||
|
|||
$userStatus3 = new UserStatus(); |
|||
$userStatus3->setUserId('user2'); |
|||
$userStatus3->setStatus('away'); |
|||
$userStatus3->setStatusTimestamp(5000); |
|||
$userStatus3->setIsUserDefined(false); |
|||
$userStatus3->setCustomIcon('🏝'); |
|||
$userStatus3->setCustomMessage('On vacation'); |
|||
$userStatus3->setClearAt(60000); |
|||
|
|||
$this->mapper->insert($userStatus1); |
|||
$this->mapper->insert($userStatus2); |
|||
$this->mapper->insert($userStatus3); |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Listener; |
|||
|
|||
use OCA\UserStatus\Listener\UserDeletedListener; |
|||
use OCA\UserStatus\Service\StatusService; |
|||
use OCP\EventDispatcher\GenericEvent; |
|||
use OCP\IUser; |
|||
use OCP\User\Events\UserDeletedEvent; |
|||
use Test\TestCase; |
|||
|
|||
class UserDeletedListenerTest extends TestCase { |
|||
|
|||
/** @var StatusService|\PHPUnit\Framework\MockObject\MockObject */ |
|||
private $service; |
|||
|
|||
/** @var UserDeletedListener */ |
|||
private $listener; |
|||
|
|||
protected function setUp(): void { |
|||
parent::setUp(); |
|||
|
|||
$this->service = $this->createMock(StatusService::class); |
|||
$this->listener = new UserDeletedListener($this->service); |
|||
} |
|||
|
|||
public function testHandleWithCorrectEvent(): void { |
|||
$user = $this->createMock(IUser::class); |
|||
$user->expects($this->once()) |
|||
->method('getUID') |
|||
->willReturn('john.doe'); |
|||
|
|||
$this->service->expects($this->once()) |
|||
->method('removeUserStatus') |
|||
->with('john.doe'); |
|||
|
|||
$event = new UserDeletedEvent($user); |
|||
$this->listener->handle($event); |
|||
} |
|||
|
|||
public function testHandleWithWrongEvent(): void { |
|||
$this->service->expects($this->never()) |
|||
->method('removeUserStatus'); |
|||
|
|||
$event = new GenericEvent(); |
|||
$this->listener->handle($event); |
|||
} |
|||
} |
|||
@ -0,0 +1,162 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Listener; |
|||
|
|||
use OCA\UserStatus\Db\UserStatus; |
|||
use OCA\UserStatus\Db\UserStatusMapper; |
|||
use OCA\UserStatus\Listener\UserDeletedListener; |
|||
use OCA\UserStatus\Listener\UserLiveStatusListener; |
|||
use OCP\AppFramework\Db\DoesNotExistException; |
|||
use OCP\AppFramework\Utility\ITimeFactory; |
|||
use OCP\EventDispatcher\GenericEvent; |
|||
use OCP\IUser; |
|||
use OCP\User\Events\UserLiveStatusEvent; |
|||
use Test\TestCase; |
|||
|
|||
class UserLiveStatusListenerTest extends TestCase { |
|||
|
|||
/** @var UserStatusMapper|\PHPUnit\Framework\MockObject\MockObject */ |
|||
private $mapper; |
|||
|
|||
/** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ |
|||
private $timeFactory; |
|||
|
|||
/** @var UserDeletedListener */ |
|||
private $listener; |
|||
|
|||
protected function setUp(): void { |
|||
parent::setUp(); |
|||
|
|||
$this->mapper = $this->createMock(UserStatusMapper::class); |
|||
$this->timeFactory = $this->createMock(ITimeFactory::class); |
|||
$this->listener = new UserLiveStatusListener($this->mapper, $this->timeFactory); |
|||
} |
|||
|
|||
/** |
|||
* @param string $userId |
|||
* @param string $previousStatus |
|||
* @param int $previousTimestamp |
|||
* @param bool $previousIsUserDefined |
|||
* @param string $eventStatus |
|||
* @param int $eventTimestamp |
|||
* @param bool $expectExisting |
|||
* @param bool $expectUpdate |
|||
* |
|||
* @dataProvider handleEventWithCorrectEventDataProvider |
|||
*/ |
|||
public function testHandleWithCorrectEvent(string $userId, |
|||
string $previousStatus, |
|||
int $previousTimestamp, |
|||
bool $previousIsUserDefined, |
|||
string $eventStatus, |
|||
int $eventTimestamp, |
|||
bool $expectExisting, |
|||
bool $expectUpdate): void { |
|||
$userStatus = new UserStatus(); |
|||
|
|||
if ($expectExisting) { |
|||
$userStatus->setId(42); |
|||
$userStatus->setUserId($userId); |
|||
$userStatus->setStatus($previousStatus); |
|||
$userStatus->setStatusTimestamp($previousTimestamp); |
|||
$userStatus->setIsUserDefined($previousIsUserDefined); |
|||
|
|||
$this->mapper->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with($userId) |
|||
->willReturn($userStatus); |
|||
} else { |
|||
$this->mapper->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with($userId) |
|||
->willThrowException(new DoesNotExistException('')); |
|||
} |
|||
|
|||
$user = $this->createMock(IUser::class); |
|||
$user->method('getUID')->willReturn($userId); |
|||
$event = new UserLiveStatusEvent($user, $eventStatus, $eventTimestamp); |
|||
|
|||
$this->timeFactory->expects($this->once()) |
|||
->method('getTime') |
|||
->willReturn(5000); |
|||
|
|||
if ($expectUpdate) { |
|||
if ($expectExisting) { |
|||
$this->mapper->expects($this->never()) |
|||
->method('insert'); |
|||
$this->mapper->expects($this->once()) |
|||
->method('update') |
|||
->with($this->callback(function ($userStatus) use ($eventStatus, $eventTimestamp) { |
|||
$this->assertEquals($eventStatus, $userStatus->getStatus()); |
|||
$this->assertEquals($eventTimestamp, $userStatus->getStatusTimestamp()); |
|||
$this->assertFalse($userStatus->getIsUserDefined()); |
|||
|
|||
return true; |
|||
})); |
|||
} else { |
|||
$this->mapper->expects($this->once()) |
|||
->method('insert') |
|||
->with($this->callback(function ($userStatus) use ($eventStatus, $eventTimestamp) { |
|||
$this->assertEquals($eventStatus, $userStatus->getStatus()); |
|||
$this->assertEquals($eventTimestamp, $userStatus->getStatusTimestamp()); |
|||
$this->assertFalse($userStatus->getIsUserDefined()); |
|||
|
|||
return true; |
|||
})); |
|||
$this->mapper->expects($this->never()) |
|||
->method('update'); |
|||
} |
|||
|
|||
$this->listener->handle($event); |
|||
} else { |
|||
$this->mapper->expects($this->never()) |
|||
->method('insert'); |
|||
$this->mapper->expects($this->never()) |
|||
->method('update'); |
|||
|
|||
$this->listener->handle($event); |
|||
} |
|||
} |
|||
|
|||
public function handleEventWithCorrectEventDataProvider(): array { |
|||
return [ |
|||
['john.doe', 'offline', 0, false, 'online', 5000, true, true], |
|||
['john.doe', 'offline', 0, false, 'online', 5000, false, true], |
|||
['john.doe', 'online', 5000, false, 'online', 5000, true, false], |
|||
['john.doe', 'online', 5000, false, 'online', 5000, false, true], |
|||
['john.doe', 'away', 5000, false, 'online', 5000, true, true], |
|||
['john.doe', 'online', 5000, false, 'away', 5000, true, false], |
|||
]; |
|||
} |
|||
|
|||
public function testHandleWithWrongEvent(): void { |
|||
$this->mapper->expects($this->never()) |
|||
->method('insertOrUpdate'); |
|||
|
|||
$event = new GenericEvent(); |
|||
$this->listener->handle($event); |
|||
} |
|||
} |
|||
@ -0,0 +1,100 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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] |
|||
]; |
|||
} |
|||
} |
|||
@ -0,0 +1,184 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\PredefinedStatusService; |
|||
use OCP\IL10N; |
|||
use Test\TestCase; |
|||
|
|||
class PredefinedStatusServiceTest extends TestCase { |
|||
|
|||
/** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */ |
|||
protected $l10n; |
|||
|
|||
/** @var PredefinedStatusService */ |
|||
protected $service; |
|||
|
|||
protected function setUp(): void { |
|||
parent::setUp(); |
|||
|
|||
$this->l10n = $this->createMock(IL10N::class); |
|||
|
|||
$this->service = new PredefinedStatusService($this->l10n); |
|||
} |
|||
|
|||
public function testGetDefaultStatuses(): void { |
|||
$this->l10n->expects($this->exactly(5)) |
|||
->method('t') |
|||
->withConsecutive( |
|||
['In a meeting'], |
|||
['Commuting'], |
|||
['Working remotely'], |
|||
['Out sick'], |
|||
['Vacationing'] |
|||
) |
|||
->willReturnArgument(0); |
|||
|
|||
$actual = $this->service->getDefaultStatuses(); |
|||
$this->assertEquals([ |
|||
[ |
|||
'id' => 'meeting', |
|||
'icon' => '📅', |
|||
'message' => 'In a meeting', |
|||
'clearAt' => [ |
|||
'type' => 'period', |
|||
'time' => 3600, |
|||
], |
|||
], |
|||
[ |
|||
'id' => 'commuting', |
|||
'icon' => '🚌', |
|||
'message' => 'Commuting', |
|||
'clearAt' => [ |
|||
'type' => 'period', |
|||
'time' => 1800, |
|||
], |
|||
], |
|||
[ |
|||
'id' => 'remote-work', |
|||
'icon' => '🏡', |
|||
'message' => 'Working remotely', |
|||
'clearAt' => [ |
|||
'type' => 'end-of', |
|||
'time' => 'day', |
|||
], |
|||
], |
|||
[ |
|||
'id' => 'sick-leave', |
|||
'icon' => '🤒', |
|||
'message' => 'Out sick', |
|||
'clearAt' => [ |
|||
'type' => 'end-of', |
|||
'time' => 'day', |
|||
], |
|||
], |
|||
[ |
|||
'id' => 'vacationing', |
|||
'icon' => '🌴', |
|||
'message' => 'Vacationing', |
|||
'clearAt' => null, |
|||
], |
|||
], $actual); |
|||
} |
|||
|
|||
/** |
|||
* @param string $id |
|||
* @param string|null $expectedIcon |
|||
* |
|||
* @dataProvider getIconForIdDataProvider |
|||
*/ |
|||
public function testGetIconForId(string $id, ?string $expectedIcon): void { |
|||
$actual = $this->service->getIconForId($id); |
|||
$this->assertEquals($expectedIcon, $actual); |
|||
} |
|||
|
|||
/** |
|||
* @return array |
|||
*/ |
|||
public function getIconForIdDataProvider(): array { |
|||
return [ |
|||
['meeting', '📅'], |
|||
['commuting', '🚌'], |
|||
['sick-leave', '🤒'], |
|||
['vacationing', '🌴'], |
|||
['remote-work', '🏡'], |
|||
['unknown-id', null], |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* @param string $id |
|||
* @param string|null $expected |
|||
* |
|||
* @dataProvider getTranslatedStatusForIdDataProvider |
|||
*/ |
|||
public function testGetTranslatedStatusForId(string $id, ?string $expected): void { |
|||
$this->l10n->method('t') |
|||
->willReturnArgument(0); |
|||
|
|||
$actual = $this->service->getTranslatedStatusForId($id); |
|||
$this->assertEquals($expected, $actual); |
|||
} |
|||
|
|||
/** |
|||
* @return array |
|||
*/ |
|||
public function getTranslatedStatusForIdDataProvider(): array { |
|||
return [ |
|||
['meeting', 'In a meeting'], |
|||
['commuting', 'Commuting'], |
|||
['sick-leave', 'Out sick'], |
|||
['vacationing', 'Vacationing'], |
|||
['remote-work', 'Working remotely'], |
|||
['unknown-id', null], |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* @param string $id |
|||
* @param bool $expected |
|||
* |
|||
* @dataProvider isValidIdDataProvider |
|||
*/ |
|||
public function testIsValidId(string $id, bool $expected): void { |
|||
$actual = $this->service->isValidId($id); |
|||
$this->assertEquals($expected, $actual); |
|||
} |
|||
|
|||
/** |
|||
* @return array |
|||
*/ |
|||
public function isValidIdDataProvider(): array { |
|||
return [ |
|||
['meeting', true], |
|||
['commuting', true], |
|||
['sick-leave', true], |
|||
['vacationing', true], |
|||
['remote-work', true], |
|||
['unknown-id', false], |
|||
]; |
|||
} |
|||
} |
|||
@ -0,0 +1,592 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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\Db\UserStatus; |
|||
use OCA\UserStatus\Db\UserStatusMapper; |
|||
use OCA\UserStatus\Exception\InvalidClearAtException; |
|||
use OCA\UserStatus\Exception\InvalidMessageIdException; |
|||
use OCA\UserStatus\Exception\InvalidStatusIconException; |
|||
use OCA\UserStatus\Exception\InvalidStatusTypeException; |
|||
use OCA\UserStatus\Exception\StatusMessageTooLongException; |
|||
use OCA\UserStatus\Service\EmojiService; |
|||
use OCA\UserStatus\Service\PredefinedStatusService; |
|||
use OCA\UserStatus\Service\StatusService; |
|||
use OCP\AppFramework\Db\DoesNotExistException; |
|||
use OCP\AppFramework\Utility\ITimeFactory; |
|||
use Test\TestCase; |
|||
|
|||
class StatusServiceTest extends TestCase { |
|||
|
|||
/** @var UserStatusMapper|\PHPUnit\Framework\MockObject\MockObject */ |
|||
private $mapper; |
|||
|
|||
/** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ |
|||
private $timeFactory; |
|||
|
|||
/** @var PredefinedStatusService|\PHPUnit\Framework\MockObject\MockObject */ |
|||
private $predefinedStatusService; |
|||
|
|||
/** @var EmojiService|\PHPUnit\Framework\MockObject\MockObject */ |
|||
private $emojiService; |
|||
|
|||
/** @var StatusService */ |
|||
private $service; |
|||
|
|||
protected function setUp(): void { |
|||
parent::setUp(); |
|||
|
|||
$this->mapper = $this->createMock(UserStatusMapper::class); |
|||
$this->timeFactory = $this->createMock(ITimeFactory::class); |
|||
$this->predefinedStatusService = $this->createMock(PredefinedStatusService::class); |
|||
$this->emojiService = $this->createMock(EmojiService::class); |
|||
$this->service = new StatusService($this->mapper, |
|||
$this->timeFactory, |
|||
$this->predefinedStatusService, |
|||
$this->emojiService); |
|||
} |
|||
|
|||
public function testFindAll(): void { |
|||
$status1 = $this->createMock(UserStatus::class); |
|||
$status2 = $this->createMock(UserStatus::class); |
|||
|
|||
$this->mapper->expects($this->once()) |
|||
->method('findAll') |
|||
->with(20, 50) |
|||
->willReturn([$status1, $status2]); |
|||
|
|||
$this->assertEquals([ |
|||
$status1, |
|||
$status2, |
|||
], $this->service->findAll(20, 50)); |
|||
} |
|||
|
|||
public function testFindByUserId(): void { |
|||
$status = $this->createMock(UserStatus::class); |
|||
$this->mapper->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with('john.doe') |
|||
->willReturn($status); |
|||
|
|||
$this->assertEquals($status, $this->service->findByUserId('john.doe')); |
|||
} |
|||
|
|||
public function testFindByUserIdDoesNotExist(): void { |
|||
$this->mapper->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with('john.doe') |
|||
->willThrowException(new DoesNotExistException('')); |
|||
|
|||
$this->expectException(DoesNotExistException::class); |
|||
$this->service->findByUserId('john.doe'); |
|||
} |
|||
|
|||
public function testFindAllAddDefaultMessage(): void { |
|||
$status = new UserStatus(); |
|||
$status->setMessageId('commuting'); |
|||
|
|||
$this->predefinedStatusService->expects($this->once()) |
|||
->method('getDefaultStatusById') |
|||
->with('commuting') |
|||
->willReturn([ |
|||
'id' => 'commuting', |
|||
'icon' => '🚌', |
|||
'message' => 'Commuting', |
|||
'clearAt' => [ |
|||
'type' => 'period', |
|||
'time' => 1800, |
|||
], |
|||
]); |
|||
$this->mapper->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with('john.doe') |
|||
->willReturn($status); |
|||
|
|||
$this->assertEquals($status, $this->service->findByUserId('john.doe')); |
|||
$this->assertEquals('🚌', $status->getCustomIcon()); |
|||
$this->assertEquals('Commuting', $status->getCustomMessage()); |
|||
} |
|||
|
|||
public function testFindAllClearStatus(): void { |
|||
$status = new UserStatus(); |
|||
$status->setClearAt(50); |
|||
$status->setMessageId('commuting'); |
|||
|
|||
$this->timeFactory->expects($this->once()) |
|||
->method('getTime') |
|||
->willReturn(60); |
|||
$this->predefinedStatusService->expects($this->never()) |
|||
->method('getDefaultStatusById'); |
|||
$this->mapper->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with('john.doe') |
|||
->willReturn($status); |
|||
$this->assertEquals($status, $this->service->findByUserId('john.doe')); |
|||
$this->assertNull($status->getClearAt()); |
|||
$this->assertNull($status->getMessageId()); |
|||
} |
|||
|
|||
/** |
|||
* @param string $userId |
|||
* @param string $status |
|||
* @param int|null $statusTimestamp |
|||
* @param bool $isUserDefined |
|||
* @param bool $expectExisting |
|||
* @param bool $expectSuccess |
|||
* @param bool $expectTimeFactory |
|||
* @param bool $expectException |
|||
* @param string|null $expectedExceptionClass |
|||
* @param string|null $expectedExceptionMessage |
|||
* |
|||
* @dataProvider setStatusDataProvider |
|||
*/ |
|||
public function testSetStatus(string $userId, |
|||
string $status, |
|||
?int $statusTimestamp, |
|||
bool $isUserDefined, |
|||
bool $expectExisting, |
|||
bool $expectSuccess, |
|||
bool $expectTimeFactory, |
|||
bool $expectException, |
|||
?string $expectedExceptionClass, |
|||
?string $expectedExceptionMessage): void { |
|||
$userStatus = new UserStatus(); |
|||
|
|||
if ($expectExisting) { |
|||
$userStatus->setId(42); |
|||
$userStatus->setUserId($userId); |
|||
|
|||
$this->mapper->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with($userId) |
|||
->willReturn($userStatus); |
|||
} else { |
|||
$this->mapper->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with($userId) |
|||
->willThrowException(new DoesNotExistException('')); |
|||
} |
|||
|
|||
if ($expectTimeFactory) { |
|||
$this->timeFactory |
|||
->method('getTime') |
|||
->willReturn(40); |
|||
} |
|||
|
|||
if ($expectException) { |
|||
$this->expectException($expectedExceptionClass); |
|||
$this->expectExceptionMessage($expectedExceptionMessage); |
|||
|
|||
$this->service->setStatus($userId, $status, $statusTimestamp, $isUserDefined); |
|||
} |
|||
|
|||
if ($expectSuccess) { |
|||
if ($expectExisting) { |
|||
$this->mapper->expects($this->once()) |
|||
->method('update') |
|||
->willReturnArgument(0); |
|||
} else { |
|||
$this->mapper->expects($this->once()) |
|||
->method('insert') |
|||
->willReturnArgument(0); |
|||
} |
|||
|
|||
$actual = $this->service->setStatus($userId, $status, $statusTimestamp, $isUserDefined); |
|||
|
|||
$this->assertEquals('john.doe', $actual->getUserId()); |
|||
$this->assertEquals($status, $actual->getStatus()); |
|||
$this->assertEquals($statusTimestamp ?? 40, $actual->getStatusTimestamp()); |
|||
$this->assertEquals($isUserDefined, $actual->getIsUserDefined()); |
|||
} |
|||
} |
|||
|
|||
public function setStatusDataProvider(): array { |
|||
return [ |
|||
['john.doe', 'online', 50, true, true, true, false, false, null, null], |
|||
['john.doe', 'online', 50, true, false, true, false, false, null, null], |
|||
['john.doe', 'online', 50, false, true, true, false, false, null, null], |
|||
['john.doe', 'online', 50, false, false, true, false, false, null, null], |
|||
['john.doe', 'online', null, true, true, true, true, false, null, null], |
|||
['john.doe', 'online', null, true, false, true, true, false, null, null], |
|||
['john.doe', 'online', null, false, true, true, true, false, null, null], |
|||
['john.doe', 'online', null, false, false, true, true, false, null, null], |
|||
|
|||
['john.doe', 'away', 50, true, true, true, false, false, null, null], |
|||
['john.doe', 'away', 50, true, false, true, false, false, null, null], |
|||
['john.doe', 'away', 50, false, true, true, false, false, null, null], |
|||
['john.doe', 'away', 50, false, false, true, false, false, null, null], |
|||
['john.doe', 'away', null, true, true, true, true, false, null, null], |
|||
['john.doe', 'away', null, true, false, true, true, false, null, null], |
|||
['john.doe', 'away', null, false, true, true, true, false, null, null], |
|||
['john.doe', 'away', null, false, false, true, true, false, null, null], |
|||
|
|||
['john.doe', 'dnd', 50, true, true, true, false, false, null, null], |
|||
['john.doe', 'dnd', 50, true, false, true, false, false, null, null], |
|||
['john.doe', 'dnd', 50, false, true, true, false, false, null, null], |
|||
['john.doe', 'dnd', 50, false, false, true, false, false, null, null], |
|||
['john.doe', 'dnd', null, true, true, true, true, false, null, null], |
|||
['john.doe', 'dnd', null, true, false, true, true, false, null, null], |
|||
['john.doe', 'dnd', null, false, true, true, true, false, null, null], |
|||
['john.doe', 'dnd', null, false, false, true, true, false, null, null], |
|||
|
|||
['john.doe', 'invisible', 50, true, true, true, false, false, null, null], |
|||
['john.doe', 'invisible', 50, true, false, true, false, false, null, null], |
|||
['john.doe', 'invisible', 50, false, true, true, false, false, null, null], |
|||
['john.doe', 'invisible', 50, false, false, true, false, false, null, null], |
|||
['john.doe', 'invisible', null, true, true, true, true, false, null, null], |
|||
['john.doe', 'invisible', null, true, false, true, true, false, null, null], |
|||
['john.doe', 'invisible', null, false, true, true, true, false, null, null], |
|||
['john.doe', 'invisible', null, false, false, true, true, false, null, null], |
|||
|
|||
['john.doe', 'offline', 50, true, true, true, false, false, null, null], |
|||
['john.doe', 'offline', 50, true, false, true, false, false, null, null], |
|||
['john.doe', 'offline', 50, false, true, true, false, false, null, null], |
|||
['john.doe', 'offline', 50, false, false, true, false, false, null, null], |
|||
['john.doe', 'offline', null, true, true, true, true, false, null, null], |
|||
['john.doe', 'offline', null, true, false, true, true, false, null, null], |
|||
['john.doe', 'offline', null, false, true, true, true, false, null, null], |
|||
['john.doe', 'offline', null, false, false, true, true, false, null, null], |
|||
|
|||
['john.doe', 'illegal-status', 50, true, true, false, false, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'], |
|||
['john.doe', 'illegal-status', 50, true, false, false, false, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'], |
|||
['john.doe', 'illegal-status', 50, false, true, false, false, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'], |
|||
['john.doe', 'illegal-status', 50, false, false, false, false, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'], |
|||
['john.doe', 'illegal-status', null, true, true, false, true, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'], |
|||
['john.doe', 'illegal-status', null, true, false, false, true, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'], |
|||
['john.doe', 'illegal-status', null, false, true, false, true, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'], |
|||
['john.doe', 'illegal-status', null, false, false, false, true, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'], |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* @param string $userId |
|||
* @param string $messageId |
|||
* @param bool $isValidMessageId |
|||
* @param int|null $clearAt |
|||
* @param bool $expectExisting |
|||
* @param bool $expectSuccess |
|||
* @param bool $expectException |
|||
* @param string|null $expectedExceptionClass |
|||
* @param string|null $expectedExceptionMessage |
|||
* |
|||
* @dataProvider setPredefinedMessageDataProvider |
|||
*/ |
|||
public function testSetPredefinedMessage(string $userId, |
|||
string $messageId, |
|||
bool $isValidMessageId, |
|||
?int $clearAt, |
|||
bool $expectExisting, |
|||
bool $expectSuccess, |
|||
bool $expectException, |
|||
?string $expectedExceptionClass, |
|||
?string $expectedExceptionMessage): void { |
|||
$userStatus = new UserStatus(); |
|||
|
|||
if ($expectExisting) { |
|||
$userStatus->setId(42); |
|||
$userStatus->setUserId($userId); |
|||
$userStatus->setStatus('offline'); |
|||
$userStatus->setStatusTimestamp(0); |
|||
$userStatus->setIsUserDefined(false); |
|||
$userStatus->setCustomIcon('😀'); |
|||
$userStatus->setCustomMessage('Foo'); |
|||
|
|||
$this->mapper->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with($userId) |
|||
->willReturn($userStatus); |
|||
} else { |
|||
$this->mapper->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with($userId) |
|||
->willThrowException(new DoesNotExistException('')); |
|||
} |
|||
|
|||
$this->predefinedStatusService->expects($this->once()) |
|||
->method('isValidId') |
|||
->with($messageId) |
|||
->willReturn($isValidMessageId); |
|||
|
|||
$this->timeFactory |
|||
->method('getTime') |
|||
->willReturn(40); |
|||
|
|||
if ($expectException) { |
|||
$this->expectException($expectedExceptionClass); |
|||
$this->expectExceptionMessage($expectedExceptionMessage); |
|||
|
|||
$this->service->setPredefinedMessage($userId, $messageId, $clearAt); |
|||
} |
|||
|
|||
if ($expectSuccess) { |
|||
if ($expectExisting) { |
|||
$this->mapper->expects($this->once()) |
|||
->method('update') |
|||
->willReturnArgument(0); |
|||
} else { |
|||
$this->mapper->expects($this->once()) |
|||
->method('insert') |
|||
->willReturnArgument(0); |
|||
} |
|||
|
|||
$actual = $this->service->setPredefinedMessage($userId, $messageId, $clearAt); |
|||
|
|||
$this->assertEquals('john.doe', $actual->getUserId()); |
|||
$this->assertEquals('offline', $actual->getStatus()); |
|||
$this->assertEquals(0, $actual->getStatusTimestamp()); |
|||
$this->assertEquals(false, $actual->getIsUserDefined()); |
|||
$this->assertEquals($messageId, $actual->getMessageId()); |
|||
$this->assertNull($actual->getCustomIcon()); |
|||
$this->assertNull($actual->getCustomMessage()); |
|||
$this->assertEquals($clearAt, $actual->getClearAt()); |
|||
} |
|||
} |
|||
|
|||
public function setPredefinedMessageDataProvider(): array { |
|||
return [ |
|||
['john.doe', 'sick-leave', true, null, true, true, false, null, null], |
|||
['john.doe', 'sick-leave', true, null, false, true, false, null, null], |
|||
['john.doe', 'sick-leave', true, 20, true, false, true, InvalidClearAtException::class, 'ClearAt is in the past'], |
|||
['john.doe', 'sick-leave', true, 20, false, false, true, InvalidClearAtException::class, 'ClearAt is in the past'], |
|||
['john.doe', 'sick-leave', true, 60, true, true, false, null, null], |
|||
['john.doe', 'sick-leave', true, 60, false, true, false, null, null], |
|||
['john.doe', 'illegal-message-id', false, null, true, false, true, InvalidMessageIdException::class, 'Message-Id "illegal-message-id" is not supported'], |
|||
['john.doe', 'illegal-message-id', false, null, false, false, true, InvalidMessageIdException::class, 'Message-Id "illegal-message-id" is not supported'], |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* @param string $userId |
|||
* @param string|null $statusIcon |
|||
* @param bool $supportsEmoji |
|||
* @param string $message |
|||
* @param int|null $clearAt |
|||
* @param bool $expectExisting |
|||
* @param bool $expectSuccess |
|||
* @param bool $expectException |
|||
* @param string|null $expectedExceptionClass |
|||
* @param string|null $expectedExceptionMessage |
|||
* |
|||
* @dataProvider setCustomMessageDataProvider |
|||
*/ |
|||
public function testSetCustomMessage(string $userId, |
|||
?string $statusIcon, |
|||
bool $supportsEmoji, |
|||
string $message, |
|||
?int $clearAt, |
|||
bool $expectExisting, |
|||
bool $expectSuccess, |
|||
bool $expectException, |
|||
?string $expectedExceptionClass, |
|||
?string $expectedExceptionMessage): void { |
|||
$userStatus = new UserStatus(); |
|||
|
|||
if ($expectExisting) { |
|||
$userStatus->setId(42); |
|||
$userStatus->setUserId($userId); |
|||
$userStatus->setStatus('offline'); |
|||
$userStatus->setStatusTimestamp(0); |
|||
$userStatus->setIsUserDefined(false); |
|||
$userStatus->setMessageId('messageId-42'); |
|||
|
|||
$this->mapper->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with($userId) |
|||
->willReturn($userStatus); |
|||
} else { |
|||
$this->mapper->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with($userId) |
|||
->willThrowException(new DoesNotExistException('')); |
|||
} |
|||
|
|||
$this->emojiService->method('isValidEmoji') |
|||
->with($statusIcon) |
|||
->willReturn($supportsEmoji); |
|||
|
|||
$this->timeFactory |
|||
->method('getTime') |
|||
->willReturn(40); |
|||
|
|||
if ($expectException) { |
|||
$this->expectException($expectedExceptionClass); |
|||
$this->expectExceptionMessage($expectedExceptionMessage); |
|||
|
|||
$this->service->setCustomMessage($userId, $statusIcon, $message, $clearAt); |
|||
} |
|||
|
|||
if ($expectSuccess) { |
|||
if ($expectExisting) { |
|||
$this->mapper->expects($this->once()) |
|||
->method('update') |
|||
->willReturnArgument(0); |
|||
} else { |
|||
$this->mapper->expects($this->once()) |
|||
->method('insert') |
|||
->willReturnArgument(0); |
|||
} |
|||
|
|||
$actual = $this->service->setCustomMessage($userId, $statusIcon, $message, $clearAt); |
|||
|
|||
$this->assertEquals('john.doe', $actual->getUserId()); |
|||
$this->assertEquals('offline', $actual->getStatus()); |
|||
$this->assertEquals(0, $actual->getStatusTimestamp()); |
|||
$this->assertEquals(false, $actual->getIsUserDefined()); |
|||
$this->assertNull($actual->getMessageId()); |
|||
$this->assertEquals($statusIcon, $actual->getCustomIcon()); |
|||
$this->assertEquals($message, $actual->getCustomMessage()); |
|||
$this->assertEquals($clearAt, $actual->getClearAt()); |
|||
} |
|||
} |
|||
|
|||
public function setCustomMessageDataProvider(): array { |
|||
return [ |
|||
['john.doe', '😁', true, 'Custom message', null, true, true, false, null, null], |
|||
['john.doe', '😁', true, 'Custom message', null, false, true, false, null, null], |
|||
['john.doe', null, false, 'Custom message', null, true, true, false, null, null], |
|||
['john.doe', null, false, 'Custom message', null, false, true, false, null, null], |
|||
['john.doe', '😁', false, 'Custom message', null, true, false, true, InvalidStatusIconException::class, 'Status-Icon is longer than one character'], |
|||
['john.doe', '😁', false, 'Custom message', null, false, false, true, InvalidStatusIconException::class, 'Status-Icon is longer than one character'], |
|||
['john.doe', null, false, 'Custom message that is way too long and violates the maximum length and hence should be rejected', null, true, false, true, StatusMessageTooLongException::class, 'Message is longer than supported length of 80 characters'], |
|||
['john.doe', null, false, 'Custom message that is way too long and violates the maximum length and hence should be rejected', null, false, false, true, StatusMessageTooLongException::class, 'Message is longer than supported length of 80 characters'], |
|||
['john.doe', '😁', true, 'Custom message', 80, true, true, false, null, null], |
|||
['john.doe', '😁', true, 'Custom message', 80, false, true, false, null, null], |
|||
['john.doe', '😁', true, 'Custom message', 20, true, false, true, InvalidClearAtException::class, 'ClearAt is in the past'], |
|||
['john.doe', '😁', true, 'Custom message', 20, false, false, true, InvalidClearAtException::class, 'ClearAt is in the past'], |
|||
]; |
|||
} |
|||
|
|||
public function testClearStatus(): void { |
|||
$status = new UserStatus(); |
|||
$status->setId(1); |
|||
$status->setUserId('john.doe'); |
|||
$status->setStatus('dnd'); |
|||
$status->setStatusTimestamp(1337); |
|||
$status->setIsUserDefined(true); |
|||
$status->setMessageId('messageId-42'); |
|||
$status->setCustomIcon('🙊'); |
|||
$status->setCustomMessage('My custom status message'); |
|||
$status->setClearAt(42); |
|||
|
|||
$this->mapper->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with('john.doe') |
|||
->willReturn($status); |
|||
|
|||
$this->mapper->expects($this->once()) |
|||
->method('update') |
|||
->with($status); |
|||
|
|||
$actual = $this->service->clearStatus('john.doe'); |
|||
$this->assertTrue($actual); |
|||
$this->assertEquals('offline', $status->getStatus()); |
|||
$this->assertEquals(0, $status->getStatusTimestamp()); |
|||
$this->assertFalse($status->getIsUserDefined()); |
|||
} |
|||
|
|||
public function testClearStatusDoesNotExist(): void { |
|||
$this->mapper->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with('john.doe') |
|||
->willThrowException(new DoesNotExistException('')); |
|||
|
|||
$this->mapper->expects($this->never()) |
|||
->method('update'); |
|||
|
|||
$actual = $this->service->clearStatus('john.doe'); |
|||
$this->assertFalse($actual); |
|||
} |
|||
|
|||
public function testClearMessage(): void { |
|||
$status = new UserStatus(); |
|||
$status->setId(1); |
|||
$status->setUserId('john.doe'); |
|||
$status->setStatus('dnd'); |
|||
$status->setStatusTimestamp(1337); |
|||
$status->setIsUserDefined(true); |
|||
$status->setMessageId('messageId-42'); |
|||
$status->setCustomIcon('🙊'); |
|||
$status->setCustomMessage('My custom status message'); |
|||
$status->setClearAt(42); |
|||
|
|||
$this->mapper->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with('john.doe') |
|||
->willReturn($status); |
|||
|
|||
$this->mapper->expects($this->once()) |
|||
->method('update') |
|||
->with($status); |
|||
|
|||
$actual = $this->service->clearMessage('john.doe'); |
|||
$this->assertTrue($actual); |
|||
$this->assertNull($status->getMessageId()); |
|||
$this->assertNull($status->getCustomMessage()); |
|||
$this->assertNull($status->getCustomIcon()); |
|||
$this->assertNull($status->getClearAt()); |
|||
} |
|||
|
|||
public function testClearMessageDoesNotExist(): void { |
|||
$this->mapper->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with('john.doe') |
|||
->willThrowException(new DoesNotExistException('')); |
|||
|
|||
$this->mapper->expects($this->never()) |
|||
->method('update'); |
|||
|
|||
$actual = $this->service->clearMessage('john.doe'); |
|||
$this->assertFalse($actual); |
|||
} |
|||
|
|||
public function testRemoveUserStatus(): void { |
|||
$status = $this->createMock(UserStatus::class); |
|||
$this->mapper->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with('john.doe') |
|||
->willReturn($status); |
|||
|
|||
$this->mapper->expects($this->once()) |
|||
->method('delete') |
|||
->with($status); |
|||
|
|||
$actual = $this->service->removeUserStatus('john.doe'); |
|||
$this->assertTrue($actual); |
|||
} |
|||
|
|||
public function testRemoveUserStatusDoesNotExist(): void { |
|||
$this->mapper->expects($this->once()) |
|||
->method('findByUserId') |
|||
->with('john.doe') |
|||
->willThrowException(new DoesNotExistException('')); |
|||
|
|||
$this->mapper->expects($this->never()) |
|||
->method('delete'); |
|||
|
|||
$actual = $this->service->removeUserStatus('john.doe'); |
|||
$this->assertFalse($actual); |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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/> |
|||
* |
|||
*/ |
|||
|
|||
if (!defined('PHPUNIT_RUN')) { |
|||
define('PHPUNIT_RUN', 1); |
|||
} |
|||
|
|||
require_once __DIR__.'/../../../lib/base.php'; |
|||
|
|||
\OC::$composerAutoloader->addPsr4('Test\\', OC::$SERVERROOT . '/tests/lib/', true); |
|||
|
|||
\OC_App::loadApp('user_status'); |
|||
|
|||
OC_Hook::clear(); |
|||
@ -0,0 +1,18 @@ |
|||
const path = require('path') |
|||
|
|||
module.exports = { |
|||
entry: { |
|||
'user-status-menu': path.join(__dirname, 'src', 'main-user-status-menu') |
|||
}, |
|||
output: { |
|||
path: path.resolve(__dirname, './js'), |
|||
publicPath: '/js/', |
|||
filename: '[name].js?v=[chunkhash]', |
|||
jsonpFunction: 'webpackJsonpUserStatus' |
|||
}, |
|||
optimization: { |
|||
splitChunks: { |
|||
automaticNameDelimiter: '-', |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright Copyright (c) 2020, Georg Ehrke |
|||
* |
|||
* @author Georg Ehrke <oc.list@georgehrke.com> |
|||
* |
|||
* @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 OCP\User\Events; |
|||
|
|||
use OCP\EventDispatcher\Event; |
|||
use OCP\IUser; |
|||
|
|||
/** |
|||
* @since 20.0.0 |
|||
*/ |
|||
class UserLiveStatusEvent extends Event { |
|||
|
|||
/** |
|||
* @var string |
|||
* @since 20.0.0 |
|||
*/ |
|||
public const STATUS_ONLINE = 'online'; |
|||
|
|||
/** |
|||
* @var string |
|||
* @since 20.0.0 |
|||
*/ |
|||
public const STATUS_AWAY = 'away'; |
|||
|
|||
/** |
|||
* @var string |
|||
* @since 20.0.0 |
|||
*/ |
|||
public const STATUS_OFFLINE = 'offline'; |
|||
|
|||
/** @var IUser */ |
|||
private $user; |
|||
|
|||
/** @var string */ |
|||
private $status; |
|||
|
|||
/** @var int */ |
|||
private $timestamp; |
|||
|
|||
/** |
|||
* @param IUser $user |
|||
* @param string $status |
|||
* @param int $timestamp |
|||
* @since 20.0.0 |
|||
*/ |
|||
public function __construct(IUser $user, |
|||
string $status, |
|||
int $timestamp) { |
|||
parent::__construct(); |
|||
$this->user = $user; |
|||
$this->status = $status; |
|||
$this->timestamp = $timestamp; |
|||
} |
|||
|
|||
/** |
|||
* @return IUser |
|||
* @since 20.0.0 |
|||
*/ |
|||
public function getUser(): IUser { |
|||
return $this->user; |
|||
} |
|||
|
|||
/** |
|||
* @return string |
|||
* @since 20.0.0 |
|||
*/ |
|||
public function getStatus(): string { |
|||
return $this->status; |
|||
} |
|||
|
|||
/** |
|||
* @return int |
|||
* @since 20.0.0 |
|||
*/ |
|||
public function getTimestamp(): int { |
|||
return $this->timestamp; |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue