Signed-off-by: Carl Schwan <carl@carlschwan.eu>tags/v25.0.0beta1
@@ -674,10 +674,9 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription | |||
} | |||
/** | |||
* @param $calendarId | |||
* @return array|null | |||
* @return array{id: int, uri: string, '{http://calendarserver.org/ns/}getctag': string, '{http://sabredav.org/ns}sync-token': int, '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set': SupportedCalendarComponentSet, '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp': ScheduleCalendarTransp }|null | |||
*/ | |||
public function getCalendarById($calendarId) { | |||
public function getCalendarById(int $calendarId): ?array { | |||
$fields = array_column($this->propertyMap, 0); | |||
$fields[] = 'id'; | |||
$fields[] = 'uri'; | |||
@@ -710,7 +709,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription | |||
'uri' => $row['uri'], | |||
'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint), | |||
'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'), | |||
'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0', | |||
'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?? 0, | |||
'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components), | |||
'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'), | |||
]; | |||
@@ -866,7 +865,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription | |||
$calendarData = $this->getCalendarById($calendarId); | |||
$shares = $this->getShares($calendarId); | |||
$this->dispatcher->dispatchTyped(new CalendarUpdatedEvent((int)$calendarId, $calendarData, $shares, $mutations)); | |||
$this->dispatcher->dispatchTyped(new CalendarUpdatedEvent($calendarId, $calendarData, $shares, $mutations)); | |||
return true; | |||
}); | |||
@@ -916,7 +915,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription | |||
// Only dispatch if we actually deleted anything | |||
if ($calendarData) { | |||
$this->dispatcher->dispatchTyped(new CalendarDeletedEvent((int)$calendarId, $calendarData, $shares)); | |||
$this->dispatcher->dispatchTyped(new CalendarDeletedEvent($calendarId, $calendarData, $shares)); | |||
} | |||
} else { | |||
$qbMarkCalendarDeleted = $this->db->getQueryBuilder(); | |||
@@ -929,7 +928,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription | |||
$shares = $this->getShares($calendarId); | |||
if ($calendarData) { | |||
$this->dispatcher->dispatchTyped(new CalendarMovedToTrashEvent( | |||
(int)$calendarId, | |||
$calendarId, | |||
$calendarData, | |||
$shares | |||
)); | |||
@@ -1265,15 +1264,17 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription | |||
$this->addChange($calendarId, $objectUri, 1, $calendarType); | |||
$objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType); | |||
assert($objectRow !== null); | |||
if ($calendarType === self::CALENDAR_TYPE_CALENDAR) { | |||
$calendarRow = $this->getCalendarById($calendarId); | |||
$shares = $this->getShares($calendarId); | |||
$this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow)); | |||
$this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent($calendarId, $calendarRow, $shares, $objectRow)); | |||
} else { | |||
$subscriptionRow = $this->getSubscriptionById($calendarId); | |||
$this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow)); | |||
$this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent($calendarId, $subscriptionRow, [], $objectRow)); | |||
} | |||
return '"' . $extraData['etag'] . '"'; | |||
@@ -1325,11 +1326,11 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription | |||
$calendarRow = $this->getCalendarById($calendarId); | |||
$shares = $this->getShares($calendarId); | |||
$this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow)); | |||
$this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent($calendarId, $calendarRow, $shares, $objectRow)); | |||
} else { | |||
$subscriptionRow = $this->getSubscriptionById($calendarId); | |||
$this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow)); | |||
$this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent($calendarId, $subscriptionRow, [], $objectRow)); | |||
} | |||
} | |||
@@ -1435,11 +1436,11 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription | |||
$calendarRow = $this->getCalendarById($calendarId); | |||
$shares = $this->getShares($calendarId); | |||
$this->dispatcher->dispatchTyped(new CalendarObjectDeletedEvent((int)$calendarId, $calendarRow, $shares, $data)); | |||
$this->dispatcher->dispatchTyped(new CalendarObjectDeletedEvent($calendarId, $calendarRow, $shares, $data)); | |||
} else { | |||
$subscriptionRow = $this->getSubscriptionById($calendarId); | |||
$this->dispatcher->dispatchTyped(new CachedCalendarObjectDeletedEvent((int)$calendarId, $subscriptionRow, [], $data)); | |||
$this->dispatcher->dispatchTyped(new CachedCalendarObjectDeletedEvent($calendarId, $subscriptionRow, [], $data)); | |||
} | |||
} else { | |||
$pathInfo = pathinfo($data['uri']); | |||
@@ -1479,7 +1480,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription | |||
if ($calendarData !== null) { | |||
$this->dispatcher->dispatchTyped( | |||
new CalendarObjectMovedToTrashEvent( | |||
(int)$calendarId, | |||
$calendarId, | |||
$calendarData, | |||
$this->getShares($calendarId), | |||
$data | |||
@@ -2799,25 +2800,26 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription | |||
} | |||
/** | |||
* @param IShareable $shareable | |||
* @param array $add | |||
* @param array $remove | |||
* @param list<array{href: string, commonName: string, readOnly: bool}> $add | |||
* @param list<string> $remove | |||
*/ | |||
public function updateShares($shareable, $add, $remove) { | |||
public function updateShares(IShareable $shareable, array $add, array $remove): void { | |||
$calendarId = $shareable->getResourceId(); | |||
$calendarRow = $this->getCalendarById($calendarId); | |||
if ($calendarRow === null) { | |||
throw new \RuntimeException('Trying to update shares for innexistant calendar: ' . $calendarId); | |||
} | |||
$oldShares = $this->getShares($calendarId); | |||
$this->calendarSharingBackend->updateShares($shareable, $add, $remove); | |||
$this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent((int)$calendarId, $calendarRow, $oldShares, $add, $remove)); | |||
$this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent($calendarId, $calendarRow, $oldShares, $add, $remove)); | |||
} | |||
/** | |||
* @param int $resourceId | |||
* @return array | |||
* @return list<array{href: string, commonName: string, status: int, readOnly: bool, '{http://owncloud.org/ns}principal': string, '{http://owncloud.org/ns}group-share': bool}> | |||
*/ | |||
public function getShares($resourceId) { | |||
public function getShares(int $resourceId): array { | |||
return $this->calendarSharingBackend->getShares($resourceId); | |||
} | |||
@@ -2843,7 +2845,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription | |||
]); | |||
$query->executeStatement(); | |||
$this->dispatcher->dispatchTyped(new CalendarPublishedEvent((int)$calendarId, $calendarData, $publicUri)); | |||
$this->dispatcher->dispatchTyped(new CalendarPublishedEvent($calendarId, $calendarData, $publicUri)); | |||
return $publicUri; | |||
} | |||
$query->delete('dav_shares') | |||
@@ -2851,7 +2853,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription | |||
->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC))); | |||
$query->executeStatement(); | |||
$this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent((int)$calendarId, $calendarData)); | |||
$this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent($calendarId, $calendarData)); | |||
return null; | |||
} | |||
@@ -2874,15 +2876,13 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription | |||
/** | |||
* @param int $resourceId | |||
* @param array $acl | |||
* @return array | |||
* @param list<array{privilege: string, principal: string, protected: bool}> $acl | |||
* @return list<array{privilege: string, principal: string, protected: bool}> | |||
*/ | |||
public function applyShareAcl($resourceId, $acl) { | |||
public function applyShareAcl(int $resourceId, array $acl): array { | |||
return $this->calendarSharingBackend->applyShareAcl($resourceId, $acl); | |||
} | |||
/** | |||
* update properties table | |||
* |
@@ -51,27 +51,11 @@ use Sabre\DAV\PropPatch; | |||
* @property CalDavBackend $caldavBackend | |||
*/ | |||
class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable, IMoveTarget { | |||
private IConfig $config; | |||
protected IL10N $l10n; | |||
private bool $useTrashbin = true; | |||
private LoggerInterface $logger; | |||
/** @var IConfig */ | |||
private $config; | |||
/** @var IL10N */ | |||
protected $l10n; | |||
/** @var bool */ | |||
private $useTrashbin = true; | |||
/** @var LoggerInterface */ | |||
private $logger; | |||
/** | |||
* Calendar constructor. | |||
* | |||
* @param BackendInterface $caldavBackend | |||
* @param $calendarInfo | |||
* @param IL10N $l10n | |||
* @param IConfig $config | |||
*/ | |||
public function __construct(BackendInterface $caldavBackend, $calendarInfo, IL10N $l10n, IConfig $config, LoggerInterface $logger) { | |||
// Convert deletion date to ISO8601 string | |||
if (isset($calendarInfo[TrashbinPlugin::PROPERTY_DELETED_AT])) { | |||
@@ -96,25 +80,10 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable | |||
} | |||
/** | |||
* Updates the list of shares. | |||
* | |||
* The first array is a list of people that are to be added to the | |||
* resource. | |||
* | |||
* Every element in the add array has the following properties: | |||
* * href - A url. Usually a mailto: address | |||
* * commonName - Usually a first and last name, or false | |||
* * summary - A description of the share, can also be false | |||
* * readOnly - A boolean value | |||
* | |||
* Every element in the remove array is just the address string. | |||
* | |||
* @param array $add | |||
* @param array $remove | |||
* @return void | |||
* {@inheritdoc} | |||
* @throws Forbidden | |||
*/ | |||
public function updateShares(array $add, array $remove) { | |||
public function updateShares(array $add, array $remove): void { | |||
if ($this->isShared()) { | |||
throw new Forbidden(); | |||
} | |||
@@ -131,19 +100,16 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable | |||
* * readOnly - boolean | |||
* * summary - Optional, a description for the share | |||
* | |||
* @return array | |||
* @return list<array{href: string, commonName: string, status: int, readOnly: bool, '{http://owncloud.org/ns}principal': string, '{http://owncloud.org/ns}group-share': bool}> | |||
*/ | |||
public function getShares() { | |||
public function getShares(): array { | |||
if ($this->isShared()) { | |||
return []; | |||
} | |||
return $this->caldavBackend->getShares($this->getResourceId()); | |||
} | |||
/** | |||
* @return int | |||
*/ | |||
public function getResourceId() { | |||
public function getResourceId(): int { | |||
return $this->calendarInfo['id']; | |||
} | |||
@@ -155,7 +121,9 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable | |||
} | |||
/** | |||
* @return array | |||
* @param int $resourceId | |||
* @param list<array{privilege: string, principal: string, protected: bool}> $acl | |||
* @return list<array{privilege: string, principal: ?string, protected: bool}> | |||
*/ | |||
public function getACL() { | |||
$acl = [ | |||
@@ -246,16 +214,18 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable | |||
parent::getOwner(), | |||
'principals/system/public' | |||
]; | |||
return array_filter($acl, function ($rule) use ($allowedPrincipals) { | |||
/** @var list<array{privilege: string, principal: string, protected: bool}> $acl */ | |||
$acl = array_filter($acl, function (array $rule) use ($allowedPrincipals): bool { | |||
return \in_array($rule['principal'], $allowedPrincipals, true); | |||
}); | |||
return $acl; | |||
} | |||
public function getChildACL() { | |||
return $this->getACL(); | |||
} | |||
public function getOwner() { | |||
public function getOwner(): ?string { | |||
if (isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal'])) { | |||
return $this->calendarInfo['{http://owncloud.org/ns}owner-principal']; | |||
} |
@@ -67,17 +67,15 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable { | |||
* Every element in the add array has the following properties: | |||
* * href - A url. Usually a mailto: address | |||
* * commonName - Usually a first and last name, or false | |||
* * summary - A description of the share, can also be false | |||
* * readOnly - A boolean value | |||
* | |||
* Every element in the remove array is just the address string. | |||
* | |||
* @param array $add | |||
* @param array $remove | |||
* @return void | |||
* @param list<array{href: string, commonName: string, readOnly: bool}> $add | |||
* @param list<string> $remove | |||
* @throws Forbidden | |||
*/ | |||
public function updateShares(array $add, array $remove) { | |||
public function updateShares(array $add, array $remove): void { | |||
if ($this->isShared()) { | |||
throw new Forbidden(); | |||
} | |||
@@ -92,11 +90,10 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable { | |||
* * commonName - Optional, for example a first + last name | |||
* * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants. | |||
* * readOnly - boolean | |||
* * summary - Optional, a description for the share | |||
* | |||
* @return array | |||
* @return list<array{href: string, commonName: string, status: int, readOnly: bool, '{http://owncloud.org/ns}principal': string, '{http://owncloud.org/ns}group-share': bool}> | |||
*/ | |||
public function getShares() { | |||
public function getShares(): array { | |||
if ($this->isShared()) { | |||
return []; | |||
} | |||
@@ -163,14 +160,11 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable { | |||
return new Card($this->carddavBackend, $this->addressBookInfo, $obj); | |||
} | |||
/** | |||
* @return int | |||
*/ | |||
public function getResourceId() { | |||
public function getResourceId(): int { | |||
return $this->addressBookInfo['id']; | |||
} | |||
public function getOwner() { | |||
public function getOwner(): ?string { | |||
if (isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) { | |||
return $this->addressBookInfo['{http://owncloud.org/ns}owner-principal']; | |||
} | |||
@@ -207,7 +201,7 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable { | |||
return $this->carddavBackend->collectCardProperties($this->getResourceId(), 'CATEGORIES'); | |||
} | |||
private function isShared() { | |||
private function isShared(): bool { | |||
if (!isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) { | |||
return false; | |||
} | |||
@@ -215,7 +209,7 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable { | |||
return $this->addressBookInfo['{http://owncloud.org/ns}owner-principal'] !== $this->addressBookInfo['principaluri']; | |||
} | |||
private function canWrite() { | |||
private function canWrite(): bool { | |||
if (isset($this->addressBookInfo['{http://owncloud.org/ns}read-only'])) { | |||
return !$this->addressBookInfo['{http://owncloud.org/ns}read-only']; | |||
} |
@@ -371,12 +371,12 @@ class CardDavBackend implements BackendInterface, SyncSupport { | |||
$query->set($key, $query->createNamedParameter($value)); | |||
} | |||
$query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId))) | |||
->execute(); | |||
->executeStatement(); | |||
$this->addChange($addressBookId, "", 2); | |||
$addressBookRow = $this->getAddressBookById((int)$addressBookId); | |||
$shares = $this->getShares($addressBookId); | |||
$shares = $this->getShares((int)$addressBookId); | |||
$this->dispatcher->dispatchTyped(new AddressBookUpdatedEvent((int)$addressBookId, $addressBookRow, $shares, $mutations)); | |||
return true; | |||
@@ -446,30 +446,31 @@ class CardDavBackend implements BackendInterface, SyncSupport { | |||
* @return void | |||
*/ | |||
public function deleteAddressBook($addressBookId) { | |||
$addressBookId = (int)$addressBookId; | |||
$addressBookData = $this->getAddressBookById($addressBookId); | |||
$shares = $this->getShares($addressBookId); | |||
$query = $this->db->getQueryBuilder(); | |||
$query->delete($this->dbCardsTable) | |||
->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid'))) | |||
->setParameter('addressbookid', $addressBookId) | |||
->execute(); | |||
->setParameter('addressbookid', $addressBookId, IQueryBuilder::PARAM_INT) | |||
->executeStatement(); | |||
$query->delete('addressbookchanges') | |||
->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid'))) | |||
->setParameter('addressbookid', $addressBookId) | |||
->execute(); | |||
->setParameter('addressbookid', $addressBookId, IQueryBuilder::PARAM_INT) | |||
->executeStatement(); | |||
$query->delete('addressbooks') | |||
->where($query->expr()->eq('id', $query->createParameter('id'))) | |||
->setParameter('id', $addressBookId) | |||
->execute(); | |||
->setParameter('id', $addressBookId, IQueryBuilder::PARAM_INT) | |||
->executeStatement(); | |||
$this->sharingBackend->deleteAllShares($addressBookId); | |||
$query->delete($this->dbCardsPropertiesTable) | |||
->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId))) | |||
->execute(); | |||
->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId, IQueryBuilder::PARAM_INT))) | |||
->executeStatement(); | |||
if ($addressBookData) { | |||
$this->dispatcher->dispatchTyped(new AddressBookDeletedEvent($addressBookId, $addressBookData, $shares)); | |||
@@ -964,11 +965,10 @@ class CardDavBackend implements BackendInterface, SyncSupport { | |||
} | |||
/** | |||
* @param IShareable $shareable | |||
* @param string[] $add | |||
* @param string[] $remove | |||
* @param list<array{href: string, commonName: string, readOnly: bool}> $add | |||
* @param list<string> $remove | |||
*/ | |||
public function updateShares(IShareable $shareable, $add, $remove) { | |||
public function updateShares(IShareable $shareable, array $add, array $remove): void { | |||
$addressBookId = $shareable->getResourceId(); | |||
$addressBookData = $this->getAddressBookById($addressBookId); | |||
$oldShares = $this->getShares($addressBookId); | |||
@@ -1199,11 +1199,10 @@ class CardDavBackend implements BackendInterface, SyncSupport { | |||
* * commonName - Optional, for example a first + last name | |||
* * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants. | |||
* * readOnly - boolean | |||
* * summary - Optional, a description for the share | |||
* | |||
* @return array | |||
* @return list<array{href: string, commonName: string, status: int, readOnly: bool, '{http://owncloud.org/ns}principal': string, '{http://owncloud.org/ns}group-share': bool}> | |||
*/ | |||
public function getShares($addressBookId) { | |||
public function getShares(int $addressBookId): array { | |||
return $this->sharingBackend->getShares($addressBookId); | |||
} | |||
@@ -1283,13 +1282,9 @@ class CardDavBackend implements BackendInterface, SyncSupport { | |||
} | |||
/** | |||
* get ID from a given contact | |||
* | |||
* @param int $addressBookId | |||
* @param string $uri | |||
* @return int | |||
* Get ID from a given contact | |||
*/ | |||
protected function getCardId($addressBookId, $uri) { | |||
protected function getCardId(int $addressBookId, string $uri): int { | |||
$query = $this->db->getQueryBuilder(); | |||
$query->select('id')->from($this->dbCardsTable) | |||
->where($query->expr()->eq('uri', $query->createNamedParameter($uri))) | |||
@@ -1309,15 +1304,15 @@ class CardDavBackend implements BackendInterface, SyncSupport { | |||
/** | |||
* For shared address books the sharee is set in the ACL of the address book | |||
* | |||
* @param $addressBookId | |||
* @param $acl | |||
* @return array | |||
* @param int $addressBookId | |||
* @param list<array{privilege: string, principal: string, protected: bool}> $acl | |||
* @return list<array{privilege: string, principal: string, protected: bool}> | |||
*/ | |||
public function applyShareAcl($addressBookId, $acl) { | |||
public function applyShareAcl(int $addressBookId, array $acl): array { | |||
return $this->sharingBackend->applyShareAcl($addressBookId, $acl); | |||
} | |||
private function convertPrincipal($principalUri, $toV2) { | |||
private function convertPrincipal(string $principalUri, bool $toV2): string { | |||
if ($this->principalBackend->getPrincipalPrefix() === 'principals') { | |||
[, $name] = \Sabre\Uri\split($principalUri); | |||
if ($toV2 === true) { | |||
@@ -1328,7 +1323,7 @@ class CardDavBackend implements BackendInterface, SyncSupport { | |||
return $principalUri; | |||
} | |||
private function addOwnerPrincipal(&$addressbookInfo) { | |||
private function addOwnerPrincipal(array &$addressbookInfo): void { | |||
$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal'; | |||
$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname'; | |||
if (isset($addressbookInfo[$ownerPrincipalKey])) { | |||
@@ -1348,10 +1343,10 @@ class CardDavBackend implements BackendInterface, SyncSupport { | |||
* | |||
* @param string $cardData the vcard raw data | |||
* @return string the uid | |||
* @throws BadRequest if no UID is available | |||
* @throws BadRequest if no UID is available or vcard is empty | |||
*/ | |||
private function getUID($cardData) { | |||
if ($cardData != '') { | |||
private function getUID(string $cardData): string { | |||
if ($cardData !== '') { | |||
$vCard = Reader::read($cardData); | |||
if ($vCard->UID) { | |||
$uid = $vCard->UID->getValue(); |
@@ -42,41 +42,17 @@ use Symfony\Component\Console\Output\OutputInterface; | |||
use Symfony\Component\Console\Style\SymfonyStyle; | |||
class MoveCalendar extends Command { | |||
/** @var IUserManager */ | |||
private $userManager; | |||
/** @var IGroupManager */ | |||
private $groupManager; | |||
/** @var IShareManager */ | |||
private $shareManager; | |||
/** @var IConfig $config */ | |||
private $config; | |||
/** @var IL10N */ | |||
private $l10n; | |||
/** @var SymfonyStyle */ | |||
private $io; | |||
/** @var CalDavBackend */ | |||
private $calDav; | |||
/** @var LoggerInterface */ | |||
private $logger; | |||
private IUserManager $userManager; | |||
private IGroupManager $groupManager; | |||
private IShareManager $shareManager; | |||
private IConfig $config; | |||
private IL10N $l10n; | |||
private ?SymfonyStyle $io = null; | |||
private CalDavBackend $calDav; | |||
private LoggerInterface $logger; | |||
public const URI_USERS = 'principals/users/'; | |||
/** | |||
* @param IUserManager $userManager | |||
* @param IGroupManager $groupManager | |||
* @param IShareManager $shareManager | |||
* @param IConfig $config | |||
* @param IL10N $l10n | |||
* @param CalDavBackend $calDav | |||
*/ | |||
public function __construct( | |||
IUserManager $userManager, | |||
IGroupManager $groupManager, | |||
@@ -224,7 +200,7 @@ class MoveCalendar extends Command { | |||
*/ | |||
if ($this->shareManager->shareWithGroupMembersOnly() === true && 'groups' === $prefix && !$this->groupManager->isInGroup($userDestination, $userOrGroup)) { | |||
if ($force) { | |||
$this->calDav->updateShares(new Calendar($this->calDav, $calendar, $this->l10n, $this->config, $this->logger), [], ['href' => 'principal:principals/groups/' . $userOrGroup]); | |||
$this->calDav->updateShares(new Calendar($this->calDav, $calendar, $this->l10n, $this->config, $this->logger), [], ['principal:principals/groups/' . $userOrGroup]); | |||
} else { | |||
throw new \InvalidArgumentException("User <$userDestination> is not part of the group <$userOrGroup> with whom the calendar <" . $calendar['uri'] . "> was shared. You may use -f to move the calendar while deleting this share."); | |||
} | |||
@@ -235,7 +211,7 @@ class MoveCalendar extends Command { | |||
*/ | |||
if ($userOrGroup === $userDestination) { | |||
if ($force) { | |||
$this->calDav->updateShares(new Calendar($this->calDav, $calendar, $this->l10n, $this->config, $this->logger), [], ['href' => 'principal:principals/users/' . $userOrGroup]); | |||
$this->calDav->updateShares(new Calendar($this->calDav, $calendar, $this->l10n, $this->config, $this->logger), [], ['principal:principals/users/' . $userOrGroup]); | |||
} else { | |||
throw new \InvalidArgumentException("The calendar <" . $calendar['uri'] . "> is already shared to user <$userDestination>.You may use -f to move the calendar while deleting this share."); | |||
} |
@@ -32,32 +32,20 @@ use OCA\DAV\Connector\Sabre\Principal; | |||
use OCP\IDBConnection; | |||
use OCP\IGroupManager; | |||
use OCP\IUserManager; | |||
use OCP\DB\QueryBuilder\IQueryBuilder; | |||
class Backend { | |||
/** @var IDBConnection */ | |||
private $db; | |||
/** @var IUserManager */ | |||
private $userManager; | |||
/** @var IGroupManager */ | |||
private $groupManager; | |||
/** @var Principal */ | |||
private $principalBackend; | |||
/** @var string */ | |||
private $resourceType; | |||
private IDBConnection $db; | |||
private IUserManager $userManager; | |||
private IGroupManager $groupManager; | |||
private Principal $principalBackend; | |||
private string $resourceType; | |||
public const ACCESS_OWNER = 1; | |||
public const ACCESS_READ_WRITE = 2; | |||
public const ACCESS_READ = 3; | |||
/** | |||
* @param IDBConnection $db | |||
* @param IUserManager $userManager | |||
* @param IGroupManager $groupManager | |||
* @param Principal $principalBackend | |||
* @param string $resourceType | |||
*/ | |||
public function __construct(IDBConnection $db, IUserManager $userManager, IGroupManager $groupManager, Principal $principalBackend, $resourceType) { | |||
public function __construct(IDBConnection $db, IUserManager $userManager, IGroupManager $groupManager, Principal $principalBackend, string $resourceType) { | |||
$this->db = $db; | |||
$this->userManager = $userManager; | |||
$this->groupManager = $groupManager; | |||
@@ -66,11 +54,10 @@ class Backend { | |||
} | |||
/** | |||
* @param IShareable $shareable | |||
* @param string[] $add | |||
* @param string[] $remove | |||
* @param list<array{href: string, commonName: string, readOnly: bool}> $add | |||
* @param list<string> $remove | |||
*/ | |||
public function updateShares(IShareable $shareable, array $add, array $remove) { | |||
public function updateShares(IShareable $shareable, array $add, array $remove): void { | |||
foreach ($add as $element) { | |||
$principal = $this->principalBackend->findByUri($element['href'], ''); | |||
if ($principal !== '') { | |||
@@ -86,10 +73,9 @@ class Backend { | |||
} | |||
/** | |||
* @param IShareable $shareable | |||
* @param string $element | |||
* @param array{href: string, commonName: string, readOnly: bool} $element | |||
*/ | |||
private function shareWith($shareable, $element) { | |||
private function shareWith(IShareable $shareable, array $element): void { | |||
$user = $element['href']; | |||
$parts = explode(':', $user, 2); | |||
if ($parts[0] !== 'principal') { | |||
@@ -129,33 +115,26 @@ class Backend { | |||
'access' => $query->createNamedParameter($access), | |||
'resourceid' => $query->createNamedParameter($shareable->getResourceId()) | |||
]); | |||
$query->execute(); | |||
$query->executeStatement(); | |||
} | |||
/** | |||
* @param $resourceId | |||
*/ | |||
public function deleteAllShares($resourceId) { | |||
public function deleteAllShares(int $resourceId): void { | |||
$query = $this->db->getQueryBuilder(); | |||
$query->delete('dav_shares') | |||
->where($query->expr()->eq('resourceid', $query->createNamedParameter($resourceId))) | |||
->andWhere($query->expr()->eq('type', $query->createNamedParameter($this->resourceType))) | |||
->execute(); | |||
->executeStatement(); | |||
} | |||
public function deleteAllSharesByUser($principaluri) { | |||
public function deleteAllSharesByUser(string $principaluri): void { | |||
$query = $this->db->getQueryBuilder(); | |||
$query->delete('dav_shares') | |||
->where($query->expr()->eq('principaluri', $query->createNamedParameter($principaluri))) | |||
->andWhere($query->expr()->eq('type', $query->createNamedParameter($this->resourceType))) | |||
->execute(); | |||
->executeStatement(); | |||
} | |||
/** | |||
* @param IShareable $shareable | |||
* @param string $element | |||
*/ | |||
private function unshare($shareable, $element) { | |||
private function unshare(IShareable $shareable, string $element): void { | |||
$parts = explode(':', $element, 2); | |||
if ($parts[0] !== 'principal') { | |||
return; | |||
@@ -172,7 +151,7 @@ class Backend { | |||
->andWhere($query->expr()->eq('type', $query->createNamedParameter($this->resourceType))) | |||
->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($parts[1]))) | |||
; | |||
$query->execute(); | |||
$query->executeStatement(); | |||
} | |||
/** | |||
@@ -183,29 +162,28 @@ class Backend { | |||
* * commonName - Optional, for example a first + last name | |||
* * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants. | |||
* * readOnly - boolean | |||
* * summary - Optional, a description for the share | |||
* | |||
* @param int $resourceId | |||
* @return array | |||
* @return list<array{href: string, commonName: string, status: int, readOnly: bool, '{http://owncloud.org/ns}principal': string, '{http://owncloud.org/ns}group-share': bool}> | |||
*/ | |||
public function getShares($resourceId) { | |||
public function getShares(int $resourceId): array { | |||
$query = $this->db->getQueryBuilder(); | |||
$result = $query->select(['principaluri', 'access']) | |||
->from('dav_shares') | |||
->where($query->expr()->eq('resourceid', $query->createNamedParameter($resourceId))) | |||
->where($query->expr()->eq('resourceid', $query->createNamedParameter($resourceId, IQueryBuilder::PARAM_INT))) | |||
->andWhere($query->expr()->eq('type', $query->createNamedParameter($this->resourceType))) | |||
->groupBy(['principaluri', 'access']) | |||
->execute(); | |||
->executeQuery(); | |||
$shares = []; | |||
while ($row = $result->fetch()) { | |||
$p = $this->principalBackend->getPrincipalByPath($row['principaluri']); | |||
$shares[] = [ | |||
'href' => "principal:{$row['principaluri']}", | |||
'commonName' => isset($p['{DAV:}displayname']) ? $p['{DAV:}displayname'] : '', | |||
'href' => "principal:${row['principaluri']}", | |||
'commonName' => isset($p['{DAV:}displayname']) ? (string)$p['{DAV:}displayname'] : '', | |||
'status' => 1, | |||
'readOnly' => (int) $row['access'] === self::ACCESS_READ, | |||
'{http://owncloud.org/ns}principal' => $row['principaluri'], | |||
'{http://owncloud.org/ns}principal' => (string)$row['principaluri'], | |||
'{http://owncloud.org/ns}group-share' => is_null($p) | |||
]; | |||
} | |||
@@ -217,10 +195,10 @@ class Backend { | |||
* For shared resources the sharee is set in the ACL of the resource | |||
* | |||
* @param int $resourceId | |||
* @param array $acl | |||
* @return array | |||
* @param list<array{privilege: string, principal: string, protected: bool}> $acl | |||
* @return list<array{privilege: string, principal: string, protected: bool}> | |||
*/ | |||
public function applyShareAcl($resourceId, $acl) { | |||
public function applyShareAcl(int $resourceId, array $acl): array { | |||
$shares = $this->getShares($resourceId); | |||
foreach ($shares as $share) { | |||
$acl[] = [ |
@@ -40,16 +40,14 @@ interface IShareable extends INode { | |||
* Every element in the add array has the following properties: | |||
* * href - A url. Usually a mailto: address | |||
* * commonName - Usually a first and last name, or false | |||
* * summary - A description of the share, can also be false | |||
* * readOnly - A boolean value | |||
* | |||
* Every element in the remove array is just the address string. | |||
* | |||
* @param array $add | |||
* @param array $remove | |||
* @return void | |||
* @param list<array{href: string, commonName: string, readOnly: bool}> $add | |||
* @param list<string> $remove | |||
*/ | |||
public function updateShares(array $add, array $remove); | |||
public function updateShares(array $add, array $remove): void; | |||
/** | |||
* Returns the list of people whom this resource is shared with. | |||
@@ -59,19 +57,15 @@ interface IShareable extends INode { | |||
* * commonName - Optional, for example a first + last name | |||
* * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants. | |||
* * readOnly - boolean | |||
* * summary - Optional, a description for the share | |||
* | |||
* @return array | |||
* @return list<array{href: string, commonName: string, status: int, readOnly: bool, '{http://owncloud.org/ns}principal': string, '{http://owncloud.org/ns}group-share': bool}> | |||
*/ | |||
public function getShares(); | |||
public function getShares(): array; | |||
/** | |||
* @return int | |||
*/ | |||
public function getResourceId(); | |||
public function getResourceId(): int; | |||
/** | |||
* @return string | |||
* @return ?string | |||
*/ | |||
public function getOwner(); | |||
} |
@@ -26,6 +26,8 @@ declare(strict_types=1); | |||
namespace OCA\DAV\Events; | |||
use OCP\EventDispatcher\Event; | |||
use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp; | |||
use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet; | |||
/** | |||
* Class CalendarShareUpdatedEvent | |||
@@ -34,30 +36,28 @@ use OCP\EventDispatcher\Event; | |||
* @since 20.0.0 | |||
*/ | |||
class CalendarShareUpdatedEvent extends Event { | |||
private int $calendarId; | |||
/** @var int */ | |||
private $calendarId; | |||
/** @var array{id: int, uri: string, '{http://calendarserver.org/ns/}getctag': string, '{http://sabredav.org/ns}sync-token': int, '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set': SupportedCalendarComponentSet, '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp': ScheduleCalendarTransp } */ | |||
private array $calendarData; | |||
/** @var array */ | |||
private $calendarData; | |||
/** @var list<array{href: string, commonName: string, status: int, readOnly: bool, '{http://owncloud.org/ns}principal': string, '{http://owncloud.org/ns}group-share': bool}> */ | |||
private array $oldShares; | |||
/** @var array */ | |||
private $oldShares; | |||
/** @var list<array{href: string, commonName: string, readOnly: bool}> */ | |||
private array $added; | |||
/** @var array */ | |||
private $added; | |||
/** @var array */ | |||
private $removed; | |||
/** @var list<string> */ | |||
private array $removed; | |||
/** | |||
* CalendarShareUpdatedEvent constructor. | |||
* | |||
* @param int $calendarId | |||
* @param array $calendarData | |||
* @param array $oldShares | |||
* @param array $added | |||
* @param array $removed | |||
* @param array{id: int, uri: string, '{http://calendarserver.org/ns/}getctag': string, '{http://sabredav.org/ns}sync-token': int, '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set': SupportedCalendarComponentSet, '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp': ScheduleCalendarTransp } $calendarData | |||
* @param list<array{href: string, commonName: string, status: int, readOnly: bool, '{http://owncloud.org/ns}principal': string, '{http://owncloud.org/ns}group-share': bool}> $oldShares | |||
* @param list<array{href: string, commonName: string, readOnly: bool}> $added | |||
* @param list<string> $removed | |||
* @since 20.0.0 | |||
*/ | |||
public function __construct(int $calendarId, | |||
@@ -74,7 +74,6 @@ class CalendarShareUpdatedEvent extends Event { | |||
} | |||
/** | |||
* @return int | |||
* @since 20.0.0 | |||
*/ | |||
public function getCalendarId(): int { | |||
@@ -82,7 +81,7 @@ class CalendarShareUpdatedEvent extends Event { | |||
} | |||
/** | |||
* @return array | |||
* @return array{id: int, uri: string, '{http://calendarserver.org/ns/}getctag': string, '{http://sabredav.org/ns}sync-token': int, '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set': SupportedCalendarComponentSet, '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp': ScheduleCalendarTransp } | |||
* @since 20.0.0 | |||
*/ | |||
public function getCalendarData(): array { | |||
@@ -90,7 +89,7 @@ class CalendarShareUpdatedEvent extends Event { | |||
} | |||
/** | |||
* @return array | |||
* @return list<array{href: string, commonName: string, status: int, readOnly: bool, '{http://owncloud.org/ns}principal': string, '{http://owncloud.org/ns}group-share': bool}> | |||
* @since 20.0.0 | |||
*/ | |||
public function getOldShares(): array { | |||
@@ -98,7 +97,7 @@ class CalendarShareUpdatedEvent extends Event { | |||
} | |||
/** | |||
* @return array | |||
* @return list<array{href: string, commonName: string, readOnly: bool}> | |||
* @since 20.0.0 | |||
*/ | |||
public function getAdded(): array { | |||
@@ -106,7 +105,7 @@ class CalendarShareUpdatedEvent extends Event { | |||
} | |||
/** | |||
* @return array | |||
* @return list<string> | |||
* @since 20.0.0 | |||
*/ | |||
public function getRemoved(): array { |
@@ -32,6 +32,7 @@ namespace OCA\DAV\Tests\unit\BackgroundJob; | |||
use OCA\DAV\BackgroundJob\CleanupInvitationTokenJob; | |||
use OCP\AppFramework\Utility\ITimeFactory; | |||
use OCP\DB\QueryBuilder\IQueryBuilder; | |||
use OCP\DB\QueryBuilder\IQueryFunction; | |||
use OCP\IDBConnection; | |||
use Test\TestCase; | |||
@@ -77,10 +78,11 @@ class CleanupInvitationTokenJobTest extends TestCase { | |||
[1337, \PDO::PARAM_STR, null, 'namedParameter1337'] | |||
]); | |||
$function = $this->createMock(IQueryFunction::class); | |||
$expr->expects($this->once()) | |||
->method('lt') | |||
->with('expiration', 'namedParameter1337') | |||
->willReturn('LT STATEMENT'); | |||
->willReturn($function); | |||
$this->dbConnection->expects($this->once()) | |||
->method('getQueryBuilder') | |||
@@ -93,7 +95,7 @@ class CleanupInvitationTokenJobTest extends TestCase { | |||
->willReturn($queryBuilder); | |||
$queryBuilder->expects($this->at(3)) | |||
->method('where') | |||
->with('LT STATEMENT') | |||
->with($function) | |||
->willReturn($queryBuilder); | |||
$queryBuilder->expects($this->at(4)) | |||
->method('execute') |
@@ -37,6 +37,7 @@ use OCP\AppFramework\Utility\ITimeFactory; | |||
use OCP\DB\IResult; | |||
use OCP\DB\QueryBuilder\IExpressionBuilder; | |||
use OCP\DB\QueryBuilder\IQueryBuilder; | |||
use OCP\DB\QueryBuilder\IQueryFunction; | |||
use OCP\IDBConnection; | |||
use OCP\IRequest; | |||
use Sabre\VObject\ITip\Message; | |||
@@ -477,10 +478,11 @@ EOF; | |||
->with(\PDO::FETCH_ASSOC) | |||
->willReturn($return); | |||
$function = $this->createMock(IQueryFunction::class); | |||
$expr->expects($this->once()) | |||
->method('eq') | |||
->with('token', 'namedParameterToken') | |||
->willReturn('EQ STATEMENT'); | |||
->willReturn($function); | |||
$this->dbConnection->expects($this->once()) | |||
->method('getQueryBuilder') | |||
@@ -497,7 +499,7 @@ EOF; | |||
->willReturn($queryBuilder); | |||
$queryBuilder->expects($this->at(4)) | |||
->method('where') | |||
->with('EQ STATEMENT') | |||
->with($function) | |||
->willReturn($queryBuilder); | |||
$queryBuilder->expects($this->at(5)) | |||
->method('execute') |
@@ -136,16 +136,6 @@ | |||
</ParamNameMismatch> | |||
</file> | |||
<file src="apps/dav/lib/CalDAV/CalDavBackend.php"> | |||
<InvalidArgument occurrences="8"> | |||
<code>'\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject'</code> | |||
<code>'\OCA\DAV\CalDAV\CalDavBackend::createSubscription'</code> | |||
<code>'\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject'</code> | |||
<code>'\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription'</code> | |||
<code>'\OCA\DAV\CalDAV\CalDavBackend::publishCalendar'</code> | |||
<code>'\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject'</code> | |||
<code>'\OCA\DAV\CalDAV\CalDavBackend::updateShares'</code> | |||
<code>'\OCA\DAV\CalDAV\CalDavBackend::updateSubscription'</code> | |||
</InvalidArgument> | |||
<InvalidNullableReturnType occurrences="2"> | |||
<code>array</code> | |||
<code>array</code> | |||
@@ -161,16 +151,6 @@ | |||
<RedundantCast occurrences="1"> | |||
<code>(int)$calendarId</code> | |||
</RedundantCast> | |||
<TooManyArguments occurrences="8"> | |||
<code>dispatch</code> | |||
<code>dispatch</code> | |||
<code>dispatch</code> | |||
<code>dispatch</code> | |||
<code>dispatch</code> | |||
<code>dispatch</code> | |||
<code>dispatch</code> | |||
<code>dispatch</code> | |||
</TooManyArguments> | |||
<UndefinedFunction occurrences="4"> | |||
<code>Uri\split($principalUri)</code> | |||
<code>Uri\split($row['principaluri'])</code> | |||
@@ -341,9 +321,6 @@ | |||
</InvalidArgument> | |||
</file> | |||
<file src="apps/dav/lib/CardDAV/AddressBookImpl.php"> | |||
<InvalidArgument occurrences="1"> | |||
<code>$id</code> | |||
</InvalidArgument> | |||
<InvalidScalarArgument occurrences="2"> | |||
<code>$this->getKey()</code> | |||
<code>$this->getKey()</code> | |||
@@ -358,16 +335,6 @@ | |||
<FalsableReturnStatement occurrences="1"> | |||
<code>false</code> | |||
</FalsableReturnStatement> | |||
<InvalidArgument occurrences="3"> | |||
<code>'\OCA\DAV\CardDAV\CardDavBackend::createCard'</code> | |||
<code>'\OCA\DAV\CardDAV\CardDavBackend::deleteCard'</code> | |||
<code>'\OCA\DAV\CardDAV\CardDavBackend::updateCard'</code> | |||
</InvalidArgument> | |||
<TooManyArguments occurrences="3"> | |||
<code>dispatch</code> | |||
<code>dispatch</code> | |||
<code>dispatch</code> | |||
</TooManyArguments> | |||
<TypeDoesNotContainType occurrences="1"> | |||
<code>$addressBooks[$row['id']][$readOnlyPropertyName] === 0</code> | |||
</TypeDoesNotContainType> | |||
@@ -672,14 +639,6 @@ | |||
<code>null</code> | |||
</NullableReturnStatement> | |||
</file> | |||
<file src="apps/dav/lib/DAV/Sharing/Backend.php"> | |||
<InvalidArrayOffset occurrences="4"> | |||
<code>$element['href']</code> | |||
<code>$element['href']</code> | |||
<code>$element['href']</code> | |||
<code>$element['readOnly']</code> | |||
</InvalidArrayOffset> | |||
</file> | |||
<file src="apps/dav/lib/DAV/SystemPrincipalBackend.php"> | |||
<InvalidNullableReturnType occurrences="1"> | |||
<code>array</code> | |||
@@ -889,7 +848,6 @@ | |||
</RedundantCondition> | |||
<TypeDoesNotContainType occurrences="2"> | |||
<code>get_class($res) === 'OpenSSLAsymmetricKey'</code> | |||
<code>is_object($res)</code> | |||
</TypeDoesNotContainType> | |||
</file> | |||
<file src="apps/encryption/lib/Crypto/EncryptAll.php"> | |||
@@ -1732,14 +1690,6 @@ | |||
<code>0</code> | |||
</InvalidScalarArgument> | |||
</file> | |||
<file src="apps/updatenotification/lib/ResetTokenBackgroundJob.php"> | |||
<InvalidOperand occurrences="1"> | |||
<code>$this->config->getAppValue('core', 'updater.secret.created', $this->timeFactory->getTime())</code> | |||
</InvalidOperand> | |||
<InvalidScalarArgument occurrences="1"> | |||
<code>$this->timeFactory->getTime()</code> | |||
</InvalidScalarArgument> | |||
</file> | |||
<file src="apps/updatenotification/lib/Settings/Admin.php"> | |||
<InvalidScalarArgument occurrences="1"> | |||
<code>$lastUpdateCheckTimestamp</code> | |||
@@ -3366,16 +3316,13 @@ | |||
<code>$result</code> | |||
<code>$this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file, false, $isRename)</code> | |||
</InvalidOperand> | |||
<InvalidReturnStatement occurrences="6"> | |||
<InvalidReturnStatement occurrences="4"> | |||
<code>$newUnencryptedSize</code> | |||
<code>$result</code> | |||
<code>$stat</code> | |||
<code>$this->storage->file_get_contents($path)</code> | |||
<code>$this->storage->filesize($path)</code> | |||
<code>$this->storage->getLocalFile($path)</code> | |||
</InvalidReturnStatement> | |||
<InvalidReturnType occurrences="5"> | |||
<code>array</code> | |||
<InvalidReturnType occurrences="3"> | |||
<code>bool</code> | |||
<code>int</code> | |||
<code>string</code> |