diff --git a/lib/Controller/ActivityPubController.php b/lib/Controller/ActivityPubController.php index b2302232..8cc0f6a4 100644 --- a/lib/Controller/ActivityPubController.php +++ b/lib/Controller/ActivityPubController.php @@ -50,7 +50,6 @@ use OCA\Social\Service\StreamQueueService; use OCA\Social\Service\StreamService; use OCA\Social\Tools\Traits\TAsync; use OCA\Social\Tools\Traits\TNCDataResponse; -use OCA\Social\Tools\Traits\TNCLogger; use OCA\Social\Tools\Traits\TStringTools; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; @@ -63,7 +62,6 @@ class ActivityPubController extends Controller { use TNCDataResponse; use TStringTools; use TAsync; - use TNCLogger; private SocialPubController $socialPubController; private FediverseService $fediverseService; diff --git a/lib/Listeners/ProfileSectionListener.php b/lib/Listeners/ProfileSectionListener.php index fc12d592..2f3713b5 100644 --- a/lib/Listeners/ProfileSectionListener.php +++ b/lib/Listeners/ProfileSectionListener.php @@ -12,6 +12,9 @@ use OCP\EventDispatcher\IEventListener; use OCP\Profile\BeforeTemplateRenderedEvent; use OCP\Util; +/** + * * @template-implements IEventListener<\OCP\Profile\BeforeTemplateRenderedEvent> + */ class ProfileSectionListener implements IEventListener { public function handle(Event $event): void { if (!($event instanceof BeforeTemplateRenderedEvent)) { diff --git a/lib/Model/ActivityPub/ACore.php b/lib/Model/ActivityPub/ACore.php index 1451cb2d..e6aac2df 100644 --- a/lib/Model/ActivityPub/ACore.php +++ b/lib/Model/ActivityPub/ACore.php @@ -70,17 +70,12 @@ class ACore extends Item implements JsonSerializable { private $parent = null; private string $requestToken = ''; - private array $entries = []; - - private ?\OCA\Social\Model\ActivityPub\ACore $object = null; - + private ?ACore $object = null; private ?Document $icon = null; private bool $displayW3ContextSecurity = false; - private ?LinkedDataSignature $signature = null; - private int $format = self::FORMAT_ACTIVITYPUB; @@ -151,9 +146,9 @@ class ACore extends Item implements JsonSerializable { } /** - * @return ACore + * @return null|self */ - public function getObject(): ACore { + public function getObject(): ?ACore { return $this->object; } @@ -162,7 +157,7 @@ class ACore extends Item implements JsonSerializable { * * @return ACore */ - public function setObject(ACore &$object): ACore { + public function setObject(ACore $object): self { $object->setParent($this); $this->object = $object; @@ -205,10 +200,7 @@ class ACore extends Item implements JsonSerializable { return ($this->icon !== null); } - /** - * @return Document - */ - public function getIcon(): Document { + public function getIcon(): ?Document { return $this->icon; } @@ -217,7 +209,7 @@ class ACore extends Item implements JsonSerializable { * * @return ACore */ - public function setIcon(Document &$icon): ACore { + public function setIcon(Document $icon): ACore { $icon->setParent($this); $this->icon = $icon; @@ -255,14 +247,11 @@ class ACore extends Item implements JsonSerializable { /** * @return bool */ - public function gotSignature(): bool { + public function hasSignature(): bool { return ($this->signature !== null); } - /** - * @return LinkedDataSignature - */ - public function getSignature(): LinkedDataSignature { + public function getSignature(): ?LinkedDataSignature { return $this->signature; } @@ -694,14 +683,14 @@ class ACore extends Item implements JsonSerializable { * @return array */ public function exportAsActivityPub(): array { - if ($this->gotSignature()) { + if ($this->hasSignature()) { $this->entries['signature'] = $this->getSignature(); } if ($this->isRoot()) { $context = [self::CONTEXT_ACTIVITYSTREAMS]; - if ($this->gotSignature() || $this->isDisplayW3ContextSecurity()) { + if ($this->hasSignature() || $this->isDisplayW3ContextSecurity()) { array_push($context, self::CONTEXT_SECURITY); } diff --git a/lib/Model/Instance.php b/lib/Model/Instance.php index 19603060..02ffbda0 100644 --- a/lib/Model/Instance.php +++ b/lib/Model/Instance.php @@ -228,10 +228,7 @@ class Instance implements IQueryRow, JsonSerializable { return ($this->contactAccount !== null); } - /** - * @return Person - */ - public function getContactAccount(): Person { + public function getContactAccount(): ?Person { return $this->contactAccount; } diff --git a/lib/Model/RequestQueue.php b/lib/Model/RequestQueue.php index be650004..05fd4a15 100644 --- a/lib/Model/RequestQueue.php +++ b/lib/Model/RequestQueue.php @@ -158,10 +158,7 @@ class RequestQueue implements JsonSerializable { } - /** - * @return InstancePath - */ - public function getInstance(): InstancePath { + public function getInstance(): ?InstancePath { return $this->instance; } diff --git a/lib/Search/UnifiedSearchProvider.php b/lib/Search/UnifiedSearchProvider.php index 2578be0d..c8efaea5 100644 --- a/lib/Search/UnifiedSearchProvider.php +++ b/lib/Search/UnifiedSearchProvider.php @@ -30,7 +30,6 @@ declare(strict_types=1); namespace OCA\Social\Search; -use OCA\Social\Tools\Traits\TNCLogger; use OCA\Social\Tools\Traits\TArrayTools; use Exception; use OCA\Social\Exceptions\AccountDoesNotExistException; @@ -58,30 +57,17 @@ class UnifiedSearchProvider implements IProvider { public const PROVIDER_ID = 'social'; public const ORDER = 12; - use TArrayTools; - use TNCLogger; - private IL10N $l10n; - private IURLGenerator $urlGenerator; - private StreamService $streamService; - private FollowService $followService; - private CacheActorService $cacheActorService; - private AccountService $accountService; - private SearchService $searchService; - private ConfigService $configService; - private MiscService $miscService; - - private ?Person $viewer = null; diff --git a/lib/Service/CacheActorService.php b/lib/Service/CacheActorService.php index f68d0d59..45edcd97 100644 --- a/lib/Service/CacheActorService.php +++ b/lib/Service/CacheActorService.php @@ -30,14 +30,6 @@ declare(strict_types=1); namespace OCA\Social\Service; -use OCA\Social\Tools\Exceptions\MalformedArrayException; -use OCA\Social\Tools\Exceptions\RequestContentException; -use OCA\Social\Tools\Exceptions\RequestNetworkException; -use OCA\Social\Tools\Exceptions\RequestResultNotJsonException; -use OCA\Social\Tools\Exceptions\RequestResultSizeException; -use OCA\Social\Tools\Exceptions\RequestServerException; -use OCA\Social\Tools\Traits\TNCLogger; -use OCA\Social\Tools\Traits\TArrayTools; use Exception; use OCA\Social\AP; use OCA\Social\Db\CacheActorsRequest; @@ -51,7 +43,15 @@ use OCA\Social\Exceptions\RetrieveAccountFormatException; use OCA\Social\Exceptions\SocialAppConfigException; use OCA\Social\Exceptions\UnauthorizedFediverseException; use OCA\Social\Model\ActivityPub\Actor\Person; +use OCA\Social\Tools\Exceptions\MalformedArrayException; +use OCA\Social\Tools\Exceptions\RequestContentException; +use OCA\Social\Tools\Exceptions\RequestNetworkException; +use OCA\Social\Tools\Exceptions\RequestResultNotJsonException; +use OCA\Social\Tools\Exceptions\RequestResultSizeException; +use OCA\Social\Tools\Exceptions\RequestServerException; +use OCA\Social\Tools\Traits\TArrayTools; use OCP\IURLGenerator; +use Psr\Log\LoggerInterface; /** * Class CacheActorService @@ -60,28 +60,31 @@ use OCP\IURLGenerator; */ class CacheActorService { use TArrayTools; - use TNCLogger; private \OCP\IURLGenerator $urlGenerator; private CacheActorsRequest $cacheActorsRequest; private CurlService $curlService; private FediverseService $fediverseService; private ConfigService $configService; - private MiscService $miscService; + private LoggerInterface $logger; /** - * CacheService constructor. + * CacheActorService constructor. */ public function __construct( - IUrlGenerator $urlGenerator, CacheActorsRequest $cacheActorsRequest, CurlService $curlService, - FediverseService $fediverseService, ConfigService $configService, MiscService $miscService + IUrlGenerator $urlGenerator, + CacheActorsRequest $cacheActorsRequest, + CurlService $curlService, + FediverseService $fediverseService, + ConfigService $configService, + LoggerInterface $logger ) { $this->urlGenerator = $urlGenerator; $this->cacheActorsRequest = $cacheActorsRequest; $this->curlService = $curlService; $this->fediverseService = $fediverseService; $this->configService = $configService; - $this->miscService = $miscService; + $this->logger = $logger; } @@ -127,7 +130,7 @@ class CacheActorService { } catch (CacheActorDoesNotExistException $e) { $object = $this->curlService->retrieveObject($id); - $this->debug('object retrieved', ['id' => $id, 'object' => $object]); + $this->logger->debug('object retrieved', ['id' => $id, 'object' => $object]); /** @var Person $actor */ $actor = AP::$activityPub->getItemFromData($object); @@ -205,14 +208,14 @@ class CacheActorService { } catch (CacheActorDoesNotExistException $e) { } - $this->debug('getFromAccount', ['account' => $account, 'retrieve' => $retrieve]); + $this->logger->debug('getFromAccount', ['account' => $account, 'retrieve' => $retrieve]); try { $actor = $this->cacheActorsRequest->getFromAccount($account); - $this->debug('Found Actor', ['account' => $account, 'actor' => $actor]); + $this->logger->debug('Found Actor', ['account' => $account, 'actor' => $actor]); } catch (CacheActorDoesNotExistException $e) { - $this->debug('Actor not found', ['account' => $account]); + $this->logger->debug('Actor not found', ['account' => $account]); if (!$retrieve) { throw new CacheActorDoesNotExistException(); @@ -221,7 +224,7 @@ class CacheActorService { $actor = $this->curlService->retrieveAccount($account); $actor->setAccount($account); try { - $this->warning('Saving Actor', false, ['actor' => $actor]); + $this->logger->debug('Saving Actor', ['actor' => $actor]); $this->save($actor); } catch (Exception $e) { diff --git a/lib/Service/CacheDocumentService.php b/lib/Service/CacheDocumentService.php index 80412c81..40044f02 100644 --- a/lib/Service/CacheDocumentService.php +++ b/lib/Service/CacheDocumentService.php @@ -203,16 +203,19 @@ class CacheDocumentService { /** - * @param $content + * @param string $content */ - private function resizeImage(&$content) { + private function resizeImage(string &$content) { try { $image = ImageResize::createFromString($content); $image->quality_jpg = 100; $image->quality_png = 9; $image->resizeToBestFit(self::RESIZED_WIDTH, self::RESIZED_HEIGHT); - $content = $image->getImageAsString(); + $newContent = $image->getImageAsString(); + if (!$newContent) { + $content = $newContent; + } } catch (ImageResizeException $e) { } } diff --git a/lib/Service/ConfigService.php b/lib/Service/ConfigService.php index 8ab275bd..841d4223 100644 --- a/lib/Service/ConfigService.php +++ b/lib/Service/ConfigService.php @@ -276,8 +276,10 @@ class ConfigService { * @param $key * * @return mixed + * + * @psalm-param string $key */ - public function getSystemValue($key) { + public function getSystemValue(string $key) { return $this->config->getSystemValue($key, ''); } diff --git a/lib/Service/CurlService.php b/lib/Service/CurlService.php index 82b79996..13c8ca7d 100644 --- a/lib/Service/CurlService.php +++ b/lib/Service/CurlService.php @@ -238,7 +238,7 @@ class CurlService { * @throws SocialAppConfigException * @throws UnauthorizedFediverseException */ - public function retrieveObject($id): array { + public function retrieveObject(string $id): array { $this->logger->debug('retrieveObject id=' . $id); $url = parse_url($id); $this->mustContains(['path', 'host', 'scheme'], $url); @@ -430,8 +430,6 @@ class CurlService { /** * @param Request $request - * - * @return resource */ private function generateCurlRequest(Request $request) { $url = $request->getUsedProtocol() . '://' . $request->getHost() . $request->getParsedUrl(); diff --git a/lib/Service/FollowService.php b/lib/Service/FollowService.php index 8c11f661..93157b42 100644 --- a/lib/Service/FollowService.php +++ b/lib/Service/FollowService.php @@ -232,7 +232,9 @@ class FollowService { /** * @param Person $actor * - * @return Person[] + * @return Follow[] + * + * @psalm-return array */ public function getFollowers(Person $actor): array { return $this->followsRequest->getFollowersByActorId($actor->getId()); @@ -257,7 +259,9 @@ class FollowService { /** * @param Person $actor * - * @return Person[] + * @return Follow[] + * + * @psalm-return array */ public function getFollowing(Person $actor): array { return $this->followsRequest->getFollowingByActorId($actor->getId()); diff --git a/lib/Service/HashtagService.php b/lib/Service/HashtagService.php index 3b8ae8ab..0c00190e 100644 --- a/lib/Service/HashtagService.php +++ b/lib/Service/HashtagService.php @@ -39,7 +39,6 @@ use OCA\Social\Exceptions\HashtagDoesNotExistException; use OCA\Social\Exceptions\ItemUnknownException; use OCA\Social\Exceptions\SocialAppConfigException; use OCA\Social\Model\ActivityPub\Object\Note; -use OCA\Social\Model\ActivityPub\Stream; class HashtagService { public const TREND_1H = 3600; @@ -155,10 +154,9 @@ class HashtagService { /** * @param int $timestamp * - * @return Stream[] + * @return int[] * @throws DateTimeException - * @throws ItemUnknownException - * @throws SocialAppConfigException + * @psalm-return array */ private function getTrendSince(int $timestamp): array { $result = []; diff --git a/lib/Service/MiscService.php b/lib/Service/MiscService.php index 83a81dfe..eb0aa444 100644 --- a/lib/Service/MiscService.php +++ b/lib/Service/MiscService.php @@ -58,7 +58,7 @@ class MiscService { * @param $message * @param int $level */ - public function log($message, $level = 2) { + public function log(string $message, $level = 2) { $data = array( 'app' => Application::APP_NAME, 'level' => $level diff --git a/lib/Service/PostService.php b/lib/Service/PostService.php index b4b1663a..4c275b1c 100644 --- a/lib/Service/PostService.php +++ b/lib/Service/PostService.php @@ -97,7 +97,7 @@ class PostService { * @throws StreamNotFoundException * @throws UnauthorizedFediverseException */ - public function createPost(Post $post, string &$token = ''): ACore { + public function createPost(Post $post, string &$token = ''): ?ACore { $this->fixRecipientAndHashtags($post); $note = new Note(); @@ -130,10 +130,10 @@ class PostService { private function generateDocumentsFromAttachments(Note $note, Post $post) { $documents = []; if (!isset($_FILES['attachments'])) { - return []; + return; } - if (is_array($_FILES["attachments"]["error"])) { - foreach ($_FILES["attachments"]["error"] as $key => $error) { + if (is_array($_FILES['attachments']['error'])) { + foreach ($_FILES['attachments']['error'] as $key => $error) { if ($error == UPLOAD_ERR_OK) { try { $document = $this->generateDocumentFromAttachment($note, $key); diff --git a/lib/Service/SearchService.php b/lib/Service/SearchService.php index 91867895..094d4064 100644 --- a/lib/Service/SearchService.php +++ b/lib/Service/SearchService.php @@ -31,10 +31,10 @@ declare(strict_types=1); namespace OCA\Social\Service; -use OCA\Social\Tools\Traits\TNCLogger; -use OCA\Social\Tools\Traits\TArrayTools; use Exception; use OCA\Social\Model\ActivityPub\Actor\Person; +use OCA\Social\Tools\Traits\TArrayTools; +use Psr\Log\LoggerInterface; /** * Class SearchService @@ -43,7 +43,6 @@ use OCA\Social\Model\ActivityPub\Actor\Person; */ class SearchService { use TArrayTools; - use TNCLogger; public const SEARCH_ACCOUNTS = 1; @@ -51,14 +50,10 @@ class SearchService { public const SEARCH_CONTENT = 4; public const SEARCH_ALL = 7; - private CacheActorService $cacheActorService; - private HashtagService $hashtagService; - private ConfigService $configService; - - private MiscService $miscService; + private LoggerInterface $logger; /** @@ -67,16 +62,18 @@ class SearchService { * @param CacheActorService $cacheActorService * @param HashtagService $hashtagService * @param ConfigService $configService - * @param MiscService $miscService + * @param LoggerInterface $logger */ public function __construct( - CacheActorService $cacheActorService, HashtagService $hashtagService, - ConfigService $configService, MiscService $miscService + CacheActorService $cacheActorService, + HashtagService $hashtagService, + ConfigService $configService, + LoggerInterface $logger ) { $this->cacheActorService = $cacheActorService; $this->hashtagService = $hashtagService; $this->configService = $configService; - $this->miscService = $miscService; + $this->logger = $logger; } @@ -100,7 +97,7 @@ class SearchService { try { $this->cacheActorService->getFromAccount($search); } catch (Exception $e) { - $this->exception($e, self::$NOTICE, ['search' => $search]); + $this->logger->notice('searchAccounts', ['exception' => $e, 'search' => $search]); } return $this->cacheActorService->searchCachedAccounts($search); diff --git a/lib/Service/SignatureService.php b/lib/Service/SignatureService.php index ba1936cf..96f876ee 100644 --- a/lib/Service/SignatureService.php +++ b/lib/Service/SignatureService.php @@ -451,7 +451,7 @@ class SignatureService { * * @return array */ - private function parseSignatureHeader($signatureHeader) { + private function parseSignatureHeader(string $signatureHeader) { $sign = []; $entries = explode(',', $signatureHeader); @@ -504,7 +504,7 @@ class SignatureService { * @return string * @throws InvalidOriginException */ - private function getKeyOrigin($id) { + private function getKeyOrigin(string $id) { $host = parse_url($id, PHP_URL_HOST); if (is_string($host) && ($host !== '')) { return $host; diff --git a/lib/Service/StreamService.php b/lib/Service/StreamService.php index 473f049c..46fa3904 100644 --- a/lib/Service/StreamService.php +++ b/lib/Service/StreamService.php @@ -544,7 +544,7 @@ class StreamService { * @throws RequestResultNotJsonException * @throws UnauthorizedFediverseException */ - public function getAuthorFromPostId($noteId) { + public function getAuthorFromPostId(string $noteId) { $note = $this->streamRequest->getStreamById($noteId); return $this->cacheActorService->getFromId($note->getAttributedTo()); diff --git a/lib/Tools/Model/Request.php b/lib/Tools/Model/Request.php index 3e93c66b..58c76c76 100644 --- a/lib/Tools/Model/Request.php +++ b/lib/Tools/Model/Request.php @@ -455,7 +455,10 @@ class Request implements JsonSerializable { } - public function addHeader($key, $value): Request { + /** + * @psalm-param string $key + */ + public function addHeader(string $key, string $value): Request { $header = $this->get($key, $this->headers); if ($header !== '') { $header .= ', ' . $value; diff --git a/lib/Tools/Traits/TNCLogger.php b/lib/Tools/Traits/TNCLogger.php deleted file mode 100644 index 2fa7d451..00000000 --- a/lib/Tools/Traits/TNCLogger.php +++ /dev/null @@ -1,200 +0,0 @@ - - * @copyright 2020, Maxence Lange - * @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 . - * - */ - - -namespace OCA\Social\Tools\Traits; - -use Exception; -use OC\HintException; -use OCP\Server; -use Psr\Log\LoggerInterface; -use Throwable; - -trait TNCLogger { - use TNCSetup; - - public static int $EMERGENCY = 4; - public static int $ALERT = 3; - public static int $CRITICAL = 3; - public static int $ERROR = 3; - public static int $WARNING = 2; - public static int $NOTICE = 1; - public static int $INFO = 1; - public static int $DEBUG = 0; - - - /** - * @param Throwable $t - * @param array $serializable - */ - public function t(Throwable $t, array $serializable = []): void { - $this->throwable($t, self::$ERROR, $serializable); - } - - /** - * @param Throwable $t - * @param int $level - * @param array $serializable - */ - public function throwable(Throwable $t, int $level = 3, array $serializable = []): void { - $message = ''; - if (!empty($serializable)) { - $message = json_encode($serializable); - } - - $this->logger() - ->log( - $level, - $message, - [ - 'app' => $this->setup('app'), - 'exception' => $t - ] - ); - } - - - /** - * @param Exception $e - * @param array $serializable - */ - public function e(Exception $e, array $serializable = []): void { - $this->exception($e, self::$ERROR, $serializable); - } - - /** - * @param Exception $e - * @param int|array $level - * @param array $serializable - */ - public function exception(Exception $e, $level = 3, array $serializable = []): void { - if (is_array($level) && empty($serializable)) { - $serializable = $level; - $level = 3; - } - - $message = ''; - if (!empty($serializable)) { - $message = json_encode($serializable); - } - - if ($level === self::$DEBUG) { - $level = (int)$this->appConfig('debug_level'); - } - - $this->logger() - ->log( - $level, - $message, - [ - 'app' => $this->setup('app'), - 'exception' => $e - ] - ); - } - - - /** - * @param string $message - * @param bool $trace - * @param array $serializable - */ - public function emergency(string $message, bool $trace = false, array $serializable = []): void { - $this->log(self::$EMERGENCY, '[emergency] ' . $message, $trace, $serializable); - } - - /** - * @param string $message - * @param bool $trace - * @param array $serializable - */ - public function alert(string $message, bool $trace = false, array $serializable = []): void { - $this->log(self::$ALERT, '[alert] ' . $message, $trace, $serializable); - } - - /** - * @param string $message - * @param bool $trace - * @param array $serializable - */ - public function warning(string $message, bool $trace = false, array $serializable = []): void { - $this->log(self::$WARNING, '[warning] ' . $message, $trace, $serializable); - } - - /** - * @param string $message - * @param bool $trace - * @param array $serializable - */ - public function notice(string $message, bool $trace = false, array $serializable = []): void { - $this->log(self::$NOTICE, '[notice] ' . $message, $trace, $serializable); - } - - /** - * @param string $message - * @param array $serializable - */ - public function debug(string $message, array $serializable = []): void { - $message = '[debug] ' . $message; - $debugLevel = (int)$this->appConfig('debug_level'); - $this->log($debugLevel, $message, ($this->appConfig('debug_trace') === '1'), $serializable); - } - - - /** - * @param int $level - * @param string $message - * @param bool $trace - * @param array $serializable - */ - public function log(int $level, string $message, bool $trace = false, array $serializable = []): void { - $opts = ['app' => $this->setup('app')]; - if ($trace) { - $opts['exception'] = new HintException($message, json_encode($serializable)); - } elseif (!empty($serializable)) { - $message .= ' -- ' . json_encode($serializable); - } - - $this->logger() - ->log($level, $message, $opts); - } - - - /** - * @return LoggerInterface - */ - public function logger(): LoggerInterface { - if (isset($this->logger) && $this->logger instanceof LoggerInterface) { - return $this->logger; - } else { - return Server::get(LoggerInterface::class); - } - } -} diff --git a/tests/psalm-baseline.xml b/tests/psalm-baseline.xml index 46d5c462..af53a7c5 100644 --- a/tests/psalm-baseline.xml +++ b/tests/psalm-baseline.xml @@ -1,10 +1,5 @@ - - - - $level - - + ['a', 'p', 'span', 'br'] @@ -12,20 +7,15 @@ Acore - + ACore ACore - Document - LinkedDataSignature $parent - - $this->icon - $this->object + $this->parent - $this->signature $v === null @@ -40,28 +30,13 @@ $object = $cache->getItem($this->getObjectId()) - - - Person - - - $this->contactAccount - - - - - InstancePath - - - $this->instance - - dropTable hasTable hasTable + @@ -81,36 +56,16 @@ $this->maxDownloadSizeReached === true - - - - $this->followsRequest->getFollowersByActorId($actor->getId()) - $this->followsRequest->getFollowingByActorId($actor->getId()) - - - Person[] - Person[] - - - - - $result - - - Stream[] - + - - ACore - - - $activity - setAttributedTo setContent + + is_array($_FILES['attachments']['error']) + @@ -131,9 +86,4 @@ $username === null - - - HintException - -