diff --git a/README.md b/README.md new file mode 100644 index 0000000..1d83373 --- /dev/null +++ b/README.md @@ -0,0 +1,198 @@ +This library allows you to easily communicate with BIS API. + +# Installation + +## Composer + +Add path to `repositories`: + +``` +{ + "url": "https://github.com/hnuti-brontosaurus/php-bis-api-client.git", + "type": "vcs" +} +``` + +and install package: +``` +composer require hnuti-brontosaurus/php-bis-api-client +``` + +## Manually + +Download latest version from [github](https://github.com/hnuti-brontosaurus/php-bis-api-client/releases) to your computer. + + +# Usage + +First you need to create client instance. Note that you need to know the API URL and obtain client ID and secret from BIS administrator +to be able to authenticate against BIS. + +```php +$client = (new BisClientFactory( + 'apiUrl', + 'clientId', + 'clientSecret', +))->create(); +``` + +Now you can perform any of available operations. + +## Events + +### Single event + +Retrieve all information about single event: + +```php +$event = $client->getEvent($id); + +// examples of reading data +$event->getName(); +$event->getOrganizer()->getResponsiblePerson(); +$event->getRegistrationType()->isOfTypeCustomWebpage(); +$event->getPlace()->getCoordinates(); +``` + +### More events + +Retrieve all information about multiple events. + +Basic usage: + +```php +$parameters = new \HnutiBrontosaurus\BisClient\Request\Event\EventParameters(); +$events = $client->getEvents($parameters); // $parameters are optional + +// example of reading data +foreach ($events as $event) { + $event->getName(); +} +``` + +#### Filters + +You can filter in many ways: + +```php +$parameters = new \HnutiBrontosaurus\BisClient\Request\Event\EventParameters(); + +// only events of "voluntary" type +$parameters->setType(\HnutiBrontosaurus\BisClient\Enums\EventType::VOLUNTARY()); + +// only events of "PsB" program +$parameters->setProgram(\HnutiBrontosaurus\BisClient\Enums\Program::PSB()); + +// only events of "first time attendees" target group +$parameters->setTargetGroup(\HnutiBrontosaurus\BisClient\Enums\TargetGroup::FIRST_TIME_ATTENDEES()); + +// only events organized by organizational unit with ID 123 +$parameters->setOrganizedBy(123); + +// excludes running events +$parameters->excludeRunning(); + +$events = $client->getEvents($parameters); +``` + +For type, program and target group, you can set more values at once: + +```php +$parameters = new \HnutiBrontosaurus\BisClient\Request\Event\EventParameters(); + +$parameters->setTypes([ + \HnutiBrontosaurus\BisClient\Enums\EventType::VOLUNTARY(), + \HnutiBrontosaurus\BisClient\Enums\EventType::SPORT(), +]); + +$events = $client->getEvents($parameters); +``` + +Note that each method call rewrites the previous one: + +```php +$parameters = new \HnutiBrontosaurus\BisClient\Request\Event\EventParameters(); + +// sets "voluntary" type +$parameters->setType(\HnutiBrontosaurus\BisClient\Enums\EventType::VOLUNTARY()); +// rewrites type with "sport" +$parameters->setType(\HnutiBrontosaurus\BisClient\Enums\EventType::SPORT()); + +$events = $client->getEvents($parameters); +``` + +#### Sorting + +You can even use some basic sorting options: + +```php +$parameters = new \HnutiBrontosaurus\BisClient\Request\Event\EventParameters(); + +// sort events by date from or date to +$parameters->orderByDateFrom(); +$parameters->orderByDateTo(); // default + +$events = $client->getEvents($parameters); +``` + +### Adding attendee + +You can add attendee to an event: + +```php +$client->addAttendee(new \HnutiBrontosaurus\BisClient\Request\Event\EventAttendee( + 123, // event ID + 'Jan', // first name + 'Novák', // last name + '12.3.2004', // birth date + '123 456 789', // phone number + 'jan@novak.cz', // e-mail address + 'poznámka', // note + ['odpověď na otázku č. 1', '', 'odpověď na otázku č. 3'], // answers to optional questions (optional) +)); +``` + +## Organizational units + +Retrieve all information about all organizational units: + +```php +$organizationalUnits = $client->getOrganizationalUnits(); + +// example of reading data +foreach ($organizationalUnits as $organizationalUnit) { + $organizationalUnit->getName(); + $organizationalUnit->getCity(); + $organizationalUnit->getChairman(); + $organizationalUnit->getCoordinates(); +} +``` + +# Development + +## Installation + +``` +composer install +``` + +## Structure + +- `docs` – instruction on how connection between brontoweb and BIS works (todo: move to brontoweb repo) +- `src` – source code + - `Enums` – basic enum types + - `Request` – request-related value objects + - `Response` – request-related value objects and exceptions + - `BisClient` – client itself, serves for making requests to BIS API + - `BisClientFactory` – collects configuration data, ensures authentication against BIS and returns `BisClient` + - `HttpClient` – wrapper around Guzzle client which adds BIS API specific pieces into the request +- `tests` – test code + +**Note that this library bundles Guzzle HTTP client as we can not rely on having composer in user's codebase.** + +## Tests + +This library has just `tests/index.php` which – if run on a webserver – will +pass or fail visually – no error and results output or an exception. + +Note that you have to obtain client ID and secret as well to be able to run the test. Ask BIS administrator to get it, copy `tests/secret.template.php` to `tests/secret.php` and insert credentials there. diff --git a/composer.json b/composer.json index 0718a46..0fc5db9 100644 --- a/composer.json +++ b/composer.json @@ -11,9 +11,8 @@ "minimum-stability": "stable", "require": { "php": ">=8.0", - "ext-dom": "*", "grifart/enum": "^0.2.1", - "guzzlehttp/guzzle": "^6.3" + "guzzlehttp/guzzle": "^7.3" }, "require-dev": { "phpstan/phpstan": "^0.12.88", @@ -21,12 +20,10 @@ }, "autoload": { "psr-4": { - "HnutiBrontosaurus\\BisApiClient\\": "src/" + "HnutiBrontosaurus\\BisClient\\": "src/" }, "classmap": [ - "src/exceptions.php", - "src/Response/exceptions.php", - "src/Response/OrganizationalUnit/exceptions.php" + "src/exceptions.php" ] }, "config": { diff --git a/src/Authenticator.php b/src/Authenticator.php new file mode 100644 index 0000000..9a96927 --- /dev/null +++ b/src/Authenticator.php @@ -0,0 +1,65 @@ +token = null; // not yet authenticated + } + + + /** + * @throws UnableToAuthorize + * @throws ConnectionToBisFailed + */ + public function authenticate(): AuthorizationToken + { + if ($this->token !== null) { + return $this->token; + } + + try { + $response = $this->httpClient->send(new Request( + 'POST', + Endpoint::AUTHENTICATION(), + [ + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ], + \json_encode([ + 'grant_type' => 'client_credentials', + 'client_id' => $this->clientId, + 'client_secret' => $this->clientSecret, + ]), + )); + } catch (ClientException $e) { + if ($e->getCode() === 401) { + throw UnableToAuthorize::withPrevious($e); + } + + throw ConnectionToBisFailed::withPrevious($e); + + } catch (GuzzleException $e) { + throw ConnectionToBisFailed::withPrevious($e); + } + + $payload = \json_decode($response->getBody()->getContents()); + $this->token = AuthorizationToken::from($payload->access_token); + return $this->token; + } + +} diff --git a/src/AuthorizationToken.php b/src/AuthorizationToken.php new file mode 100644 index 0000000..35b6c71 --- /dev/null +++ b/src/AuthorizationToken.php @@ -0,0 +1,28 @@ +value; + } + + public function __toString(): string + { + return $this->toString(); + } + +} diff --git a/src/BisClient.php b/src/BisClient.php new file mode 100644 index 0000000..74c1247 --- /dev/null +++ b/src/BisClient.php @@ -0,0 +1,90 @@ +httpClient->send('GET', Endpoint::EVENT($id)); + + \assert($data instanceof \stdClass); + return Event::fromResponseData($data); + } + + + /** + * @return Event[] + * @throws ConnectionToBisFailed + */ + public function getEvents(?EventParameters $params = null): array + { + $data = $this->httpClient->send( + 'GET', Endpoint::EVENTS(), + $params !== null + ? $params + : new EventParameters(), + ); + + if ($data === null) { + return []; + } + + \assert(\is_array($data)); + return \array_map(Event::class . '::fromResponseData', $data); + } + + + /** + * @throws ConnectionToBisFailed + */ + public function addAttendee(EventAttendee $eventAttendee): void + { + $this->httpClient->send('POST', Endpoint::ADD_ATTENDEE_TO_EVENT(), null, $eventAttendee); + } + + + // organizational units + + /** + * @return OrganizationalUnit[] + * @throws ConnectionToBisFailed + */ + public function getOrganizationalUnits(): array + { + $data = $this->httpClient->send('GET', Endpoint::ADMINISTRATIVE_UNITS()); + + if ($data === null) { + return []; + } + + \assert(\is_array($data)); + return \array_map(OrganizationalUnit::class . '::fromResponseData', $data); + } + + + // adoption + + // not yet implemented +// public function saveRequestForAdoption(Adoption $adoption): void +// {} + +} diff --git a/src/BisClientFactory.php b/src/BisClientFactory.php new file mode 100644 index 0000000..551a140 --- /dev/null +++ b/src/BisClientFactory.php @@ -0,0 +1,47 @@ +httpClient = new Client(['base_uri' => \rtrim($apiUrl, '/') . '/']); + $this->bisAuthenticator = new Authenticator( + $this->clientId, + $this->clientSecret, + $this->httpClient, + ); + } + + + /** + * @throws UnableToAuthorize + * @throws ConnectionToBisFailed + */ + public function create(): BisClient + { + $token = $this->bisAuthenticator->authenticate(); + return new BisClient(new HttpClient( + $token, + $this->httpClient, + )); + } + +} diff --git a/src/Client.php b/src/Client.php deleted file mode 100644 index 9c3c237..0000000 --- a/src/Client.php +++ /dev/null @@ -1,260 +0,0 @@ -url = \rtrim($url, '/'); - $this->username = $username; - $this->password = $password; - $this->httpClient = $httpClient; - } - - - // events - - /** - * @throws NotFoundException - * @throws TransferErrorException - * @throws ResponseErrorException - */ - public function getEvent(int $id, EventParameters $params = null): Event - { - $params = ($params !== null ? $params : new EventParameters()); - $params->setId($id); - $response = $this->processRequest($params); - - $data = $response->getData(); - - if (\count($data) === 0) { - throw new NotFoundException('No result for event with id `' . $id . '`.'); - } - - return Event::fromResponseData(\reset($data)); - } - - - /** - * @return Event[] - * @throws NotFoundException - * @throws TransferErrorException - * @throws ResponseErrorException - */ - public function getEvents(EventParameters $params = null): array - { - $response = $this->processRequest($params !== null ? $params : new EventParameters()); - $data = $response->getData(); - - if ($data === null) { - return []; - } - - return \array_map(Event::class . '::fromResponseData', $data); - } - - - /** - * @throws ResponseErrorException - */ - public function addAttendeeToEvent(EventAttendee $eventAttendee): void - { - $eventAttendee->setCredentials($this->username, $this->password); - $response = $this->httpClient->send($this->createRequest($eventAttendee)); - - $this->checkForResponseContentType($response); - - $domDocument = $this->generateDOM($response); - - $this->checkForResponseErrors($domDocument); - } - - - // organizational units - - /** - * @return OrganizationalUnit[] - * @throws NotFoundException - * @throws TransferErrorException - * @throws ResponseErrorException - */ - public function getOrganizationalUnits(OrganizationalUnitParameters $params = null): array - { - $response = $this->processRequest($params !== null ? $params : new OrganizationalUnitParameters()); - - $organizationalUnits = []; - foreach ($response->getData() as $organizationalUnit) { - try { - $organizationalUnits[] = OrganizationalUnit::fromResponseData($organizationalUnit); - - } catch (UnknownOrganizationUnitTypeException $e) { - continue; // In case of unknown type - just ignore it. - - } - } - - return $organizationalUnits; - } - - - // adoption - - /** - * @throws ResponseErrorException - */ - public function saveRequestForAdoption(Adoption $adoption): void - { - $adoption->setCredentials($this->username, $this->password); - - $response = $this->httpClient->send($this->createRequest($adoption)); - - $this->checkForResponseContentType($response); - - $domDocument = $this->generateDOM($response); - - $this->checkForResponseErrors($domDocument); - } - - - /** - * @throws NotFoundException - * @throws TransferErrorException - * @throws ResponseErrorException - */ - private function processRequest(Parameters $requestParameters): Response - { - $requestParameters->setCredentials($this->username, $this->password); - - try { - $response = $this->httpClient->send($this->createRequest($requestParameters)); - - } catch (ClientException $e) { - throw new NotFoundException('Bis client could not find the queried resource.', 0, $e); - - } catch (GuzzleException $e) { - throw new TransferErrorException('Unable to process request: transfer error.', 0, $e); - } - - $this->checkForResponseContentType($response); - - $domDocument = $this->generateDOM($response); - $this->checkForResponseErrors($domDocument); - - return new Response($response, $domDocument); - } - - - /** - * @throws InvalidContentTypeException - */ - private function checkForResponseContentType(ResponseInterface $response): void - { - if (\strncmp($response->getHeaderLine('Content-Type'), 'text/xml', \strlen('text/xml')) !== 0) { - throw new InvalidContentTypeException('Unable to process response: the response Content-Type is invalid or missing.'); - } - } - - - /** - * @throws InvalidXMLStructureException - */ - private function generateDOM(ResponseInterface $response): \DOMDocument - { - try { - $domDocument = new \DOMDocument(); - $domDocument->loadXML($response->getBody()->getContents()); - - } catch (\Exception $e) { - throw new InvalidXMLStructureException('Unable to process response: response body contains invalid XML.', 0, $e); - } - - return $domDocument; - } - - - /** - * @throws ResponseErrorException - */ - private function checkForResponseErrors(\DOMDocument $domDocument): void - { - $resultNode = $domDocument->getElementsByTagName(Response::TAG_RESULT)->item(0); - \assert($resultNode instanceof \DOMElement); - - if ($resultNode->hasAttribute(Response::TAG_RESULT_ATTRIBUTE_ERROR)) { - switch ($resultNode->getAttribute(Response::TAG_RESULT_ATTRIBUTE_ERROR)) { - case 'success': // In case of POST request with form data, BIS returns `` for some reason... Let's pretend that there is no error in such case because... you know... there is no error! - break; - - case 'user': - throw new InvalidUserInputException($resultNode); - - case 'forbidden': - throw new UnauthorizedAccessException(); - - case 'params': - throw new InvalidParametersException(); - - default: - throw new UnknownErrorException($resultNode->getAttribute(Response::TAG_RESULT_ATTRIBUTE_ERROR)); - } - } - } - - - private function createRequest(Parameters $parameters): Request - { - return new Request( - 'POST', - $this->url, - [ - 'Content-Type' => 'application/x-www-form-urlencoded', - ], - \http_build_query($parameters->getAll()) - ); - } - -} diff --git a/src/Endpoint.php b/src/Endpoint.php new file mode 100644 index 0000000..83f21d8 --- /dev/null +++ b/src/Endpoint.php @@ -0,0 +1,34 @@ +toArray()) + : ''; + + // see Guzzle exceptions docs: https://docs.guzzlephp.org/en/stable/quickstart.html#exceptions + try { + $response = $this->client->send(new Request( + $method, + $endpoint . $queryString, + [ + 'Authorization' => 'Bearer ' . $this->authorizationToken->toString(), + 'Content-Type' => 'application/json', + ], + \json_encode($data !== null ? $data->toArray() : []), + )); + + } catch (ClientException $e) { // 4xx errors + if ($e->getCode() === 400) { + throw UnableToProcessRequest::withPrevious($e); + } + + if ($e->getCode() === 404) { + throw NotFound::withPrevious($e); + } + + throw ConnectionToBisFailed::withPrevious($e); + + } catch (ServerException $e) { // 5xx errors + throw ConnectionToBisFailed::withPrevious($e); + + } catch (TooManyRedirectsException $e) { + throw ConnectionToBisFailed::withPrevious($e); + + } catch (NetworkExceptionInterface $e) { // problem with connection + throw ConnectionToBisFailed::withPrevious($e); + + } catch (GuzzleException $e) { // fallback catch-all exception + throw ConnectionToBisFailed::withPrevious($e); + } + + return \json_decode($response->getBody()->getContents()); + } + +} diff --git a/src/Request/Adoption.php b/src/Request/Adoption.php index 9ccc658..6c84ba1 100644 --- a/src/Request/Adoption.php +++ b/src/Request/Adoption.php @@ -1,38 +1,34 @@ 'adopce', - 'f_jmeno' => $firstName, - 'f_prijmeni' => $lastName, - 'f_ulice' => $streetAddress . ' ' . $streetNumber, - 'f_mesto' => $city, - 'f_psc' => $postalCode, - 'f_email' => $emailAddress, - 'f_pohlavi' => null, // not required, but accepted by BIS (values muz/zena) - 'f_uvest_v_seznamu' => $excludeFromPublic ? 'off' : 'on', - 'f_clanek' => $preferredUnitOfTypeBase, - 'f_rc' => $preferredUnitOfTypeRegional, - 'f_castka' => $amount, - ]); + throw new UsageException('This is not implemented yet.'); } + + public function toArray(): array + { + return []; + } + + } diff --git a/src/Request/Event/EventAttendee.php b/src/Request/Event/EventAttendee.php new file mode 100644 index 0000000..3973143 --- /dev/null +++ b/src/Request/Event/EventAttendee.php @@ -0,0 +1,48 @@ +questionAnswers as $questionAnswer) { + $questionAnswers['additional_question_' . $i] = $questionAnswer; + $i++; + } + + return \array_merge([ + 'event' => $this->eventId, + 'first_name' => $this->firstName, + 'last_name' => $this->lastName, + 'telephone' => $this->phoneNumber, + 'email' => $this->emailAddress, + 'age_group' => $this->birthDate->format('Y'), + 'birth_month' => $this->birthDate->format('n'), + 'birth_day' => $this->birthDate->format('j'), + 'note' => $this->note, + ], $questionAnswers); + } + +} diff --git a/src/Request/Event/EventParameters.php b/src/Request/Event/EventParameters.php new file mode 100644 index 0000000..65f1def --- /dev/null +++ b/src/Request/Event/EventParameters.php @@ -0,0 +1,176 @@ +orderByDateTo(); + } + + + + // filter + + public const FILTER_ACTIONS_ONLY = 'action'; + public const FILTER_CAMPS_ONLY = 'camp'; + + private string $filter = ''; + + public function setFilter($filter): self + { + if ( ! \in_array($filter, [ + self::FILTER_ACTIONS_ONLY, + self::FILTER_CAMPS_ONLY, + ])) { + throw new UsageException('Value `' . $filter . '` is not of valid filters'); + } + + $this->filter = $filter; + return $this; + } + + + // type + + /** @var EventType[] */ + private array $types = []; + + public function setType(EventType $type): self + { + $this->types = [$type]; + return $this; + } + + /** + * @param EventType[] $types + */ + public function setTypes(array $types): self + { + $this->types = $types; + return $this; + } + + + // program + + /** @var Program[] */ + private array $programs = []; + + public function setProgram(Program $program): self + { + $this->programs = [$program]; + return $this; + } + + /** + * @param Program[] $programs + */ + public function setPrograms(array $programs): self + { + $this->programs = $programs; + return $this; + } + + + // target group + + /** @var TargetGroup[] */ + private array $targetGroups = []; + + public function setTargetGroup(TargetGroup $targetGroup): self + { + $this->targetGroups = [$targetGroup]; + return $this; + } + + /** + * @param TargetGroup[] $targetGroups + */ + public function setTargetGroups(array $targetGroups): self + { + $this->targetGroups = $targetGroups; + return $this; + } + + + + // miscellaneous + + private \DateTimeImmutable $dateFromGreaterThan; + + /** + * Excludes events which are running (started, but not yet ended). Defaults to include them. + */ + public function excludeRunning(): self + { + $this->dateFromGreaterThan = new \DateTimeImmutable(); + return $this; + } + + + public function orderByDateFrom(): self + { + $this->ordering = Ordering::DATE_FROM(); + return $this; + } + + public function orderByDateTo(): self + { + $this->ordering = Ordering::DATE_TO(); + return $this; + } + + + /** @var int[] */ + private array $organizedBy = []; + + /** + * @param int|int[] $unitIds + */ + public function setOrganizedBy(array|int $unitIds): self + { + // If just single value, wrap it into an array. + if ( ! \is_array($unitIds)) { + $this->organizedBy = [$unitIds]; + return $this; + } + + $this->organizedBy = $unitIds; + return $this; + } + + + + // getters + + public function toArray(): array + { + $array = [ + 'basic_purpose' => $this->filter, + 'event_type_array' => \implode(',', $this->types), + 'program_array' => \implode(',', $this->programs), + 'indended_for_array' => \implode(',', $this->targetGroups), + 'ordering' => $this->ordering, + 'administrative_unit' => \implode(',', $this->organizedBy), + ]; + + if (isset($this->dateFromGreaterThan)) { + $array['date_from__gte'] = $this->dateFromGreaterThan->format('Y-m-d'); + } + + return $array; + } + +} diff --git a/src/Request/Event/Ordering.php b/src/Request/Event/Ordering.php new file mode 100644 index 0000000..2666107 --- /dev/null +++ b/src/Request/Event/Ordering.php @@ -0,0 +1,19 @@ + 'prihlaska', - 'akce' => $eventId, - 'jmeno' => $firstName, - 'prijmeni' => $lastName, - 'telefon' => $phoneNumber, - 'email' => $emailAddress, - 'datum_narozeni' => $birthDate, - 'poznamka' => $note, - ]); - - - if (\count($questionAnswers) === 0) { - return; - } - - // currently 3 questions are supported - $i = 1; - foreach ($questionAnswers as $questionAnswer) { - $this->params['add_info' . ($i >= 2 ? '_' . $i : '')] = $questionAnswer; // key syntax is `add_info` for first key and `add_info_X` for any other - $i++; - } - } - -} diff --git a/src/Request/EventParameters.php b/src/Request/EventParameters.php deleted file mode 100644 index 56e2dd9..0000000 --- a/src/Request/EventParameters.php +++ /dev/null @@ -1,304 +0,0 @@ - 'akce', - self::PARAM_DISPLAY_ALREADY_STARTED_KEY => self::PARAM_DISPLAY_ALREADY_STARTED_VALUE, - self::PARAM_ORDER_BY_KEY => self::PARAM_ORDER_BY_END_DATE, - ]); - } - - - public function setId(int $id): static - { - $this->params['id'] = (int) $id; - return $this; - } - - - // filter - - const FILTER_CLUB = 1; - const FILTER_WEEKEND = 2; - const FILTER_CAMP = 4; - const FILTER_EKOSTAN = 8; - - /** - * This parameter serves as combinator for multiple conditions, which can not be achieved with concatenating type, program, target group or any other available parameters. - * For example you can not make an union among different parameters. Let's say you want all events which are of type=ohb or of program=brdo. This is not possible with API parameters. - * Thus you can take advantage of preset filters which are documented here: https://bis.brontosaurus.cz/myr.php - * - * Beside standard constant usage as a parameter, you can pass bitwise operation argument, e.g. `EventParameters::FILTER_WEEKEND|EventParameters::FILTER_CAMP`. - * - * @throws InvalidArgumentException - */ - public function setFilter(int $filter): static - { - $keys = [ - self::FILTER_CLUB => 'klub', - self::FILTER_WEEKEND => 'vik', - self::FILTER_CAMP => 'tabor', - self::FILTER_EKOSTAN => 'ekostan', - ]; - - switch ($filter) { - case self::FILTER_CLUB: - case self::FILTER_WEEKEND: - case self::FILTER_CAMP: - case self::FILTER_EKOSTAN: - $param = $keys[$filter]; - break; - - case self::FILTER_WEEKEND | self::FILTER_CAMP: - $param = $keys[self::FILTER_WEEKEND] . $keys[self::FILTER_CAMP]; - break; - - case self::FILTER_WEEKEND | self::FILTER_EKOSTAN: - $param = $keys[self::FILTER_WEEKEND] . $keys[self::FILTER_EKOSTAN]; - break; - - default: - throw new InvalidArgumentException('Value `' . $filter . '` is not of valid types and their combinations for `filter` parameter. Only `weekend+camp` and `weekend+ekostan` can be combined.'); - break; - } - - $this->params['filter'] = $param; - - return $this; - } - - - // type - - const TYPE_VOLUNTARY = 'pracovni'; // dobrovolnická - const TYPE_EXPERIENCE = 'prozitkova'; // zážitková - const TYPE_SPORT = 'sportovni'; - - const TYPE_EDUCATIONAL_TALK = 'prednaska'; // vzdělávací - přednášky - const TYPE_EDUCATIONAL_COURSES = 'vzdelavaci'; // vzdělávací - kurzy, školení - const TYPE_EDUCATIONAL_OHB = 'ohb'; // vzdělávací - kurz ohb - const TYPE_LEARNING_PROGRAM = 'vyuka'; // výukový program - const TYPE_RESIDENTIAL_LEARNING_PROGRAM = 'pobyt'; // pobytový výukový program - - const TYPE_CLUB_MEETUP = 'klub'; // klub - setkání - const TYPE_CLUB_TALK = 'klub-predn'; // klub - přednáška - const TYPE_FOR_PUBLIC = 'verejnost'; // akce pro veřejnost - const TYPE_EKOSTAN = 'ekostan'; - const TYPE_EXHIBITION = 'vystava'; - const TYPE_ACTION_GROUP = 'akcni'; // akční skupina - const TYPE_INTERNAL = 'jina'; // interní akce (VH a jiné) - const TYPE_GROUP_MEETING = 'schuzka'; // oddílová, družinová schůzka - - /** - * @throws InvalidArgumentException - */ - public function setType(string $type): static - { - if ( ! \in_array($type, [ - self::TYPE_VOLUNTARY, - self::TYPE_EXPERIENCE, - self::TYPE_SPORT, - - self::TYPE_EDUCATIONAL_TALK, - self::TYPE_EDUCATIONAL_COURSES, - self::TYPE_EDUCATIONAL_OHB, - self::TYPE_LEARNING_PROGRAM, - self::TYPE_RESIDENTIAL_LEARNING_PROGRAM, - - self::TYPE_CLUB_MEETUP, - self::TYPE_CLUB_TALK, - self::TYPE_FOR_PUBLIC, - self::TYPE_EKOSTAN, - self::TYPE_EXHIBITION, - self::TYPE_ACTION_GROUP, - self::TYPE_INTERNAL, - self::TYPE_GROUP_MEETING, - ], true)) { - throw new InvalidArgumentException('Value `' . $type . '` is not of valid types for `type` parameter.'); - } - - $this->params['typ'][] = $type; - return $this; - } - - /** - * @param string[] $types - */ - public function setTypes(array $types): static - { - foreach ($types as $type) { - $this->setType($type); - } - - return $this; - } - - - // program - - const PROGRAM_NOT_SELECTED = 'none'; - const PROGRAM_NATURE = 'ap'; - const PROGRAM_SIGHTS = 'pamatky'; - const PROGRAM_BRDO = 'brdo'; - const PROGRAM_EKOSTAN = 'ekostan'; - const PROGRAM_PSB = 'psb'; - const PROGRAM_EDUCATION = 'vzdelavani'; - - /** - * @throws InvalidArgumentException - */ - public function setProgram(string $program): static - { - if ( ! \in_array($program, [ - self::PROGRAM_NOT_SELECTED, - self::PROGRAM_NATURE, - self::PROGRAM_SIGHTS, - self::PROGRAM_BRDO, - self::PROGRAM_EKOSTAN, - self::PROGRAM_PSB, - self::PROGRAM_EDUCATION, - ], true)) { - throw new InvalidArgumentException('Value `' . $program . '` is not of valid types for `program` parameter.'); - } - - $this->params['program'][] = $program; - return $this; - } - - /** - * @param string[] $programs - */ - public function setPrograms(array $programs): static - { - foreach ($programs as $program) { - $this->setProgram($program); - } - - return $this; - } - - - // target group - - const TARGET_GROUP_EVERYONE = 'vsichni'; - const TARGET_GROUP_ADULTS = 'dospeli'; - const TARGET_GROUP_CHILDREN = 'deti'; - const TARGET_GROUP_FAMILIES = 'detirodice'; - const TARGET_GROUP_FIRST_TIME_ATTENDEES = 'prvouc'; - - /** - * @throws InvalidArgumentException - */ - public function setTargetGroup(string $targetGroup): static - { - if ( ! \in_array($targetGroup, [ - self::TARGET_GROUP_EVERYONE, - self::TARGET_GROUP_ADULTS, - self::TARGET_GROUP_CHILDREN, - self::TARGET_GROUP_FAMILIES, - self::TARGET_GROUP_FIRST_TIME_ATTENDEES, - ], true)) { - throw new InvalidArgumentException('Value `' . $targetGroup . '` is not of valid types for `for` parameter.'); - } - - $this->params['pro'][] = $targetGroup; - return $this; - } - - /** - * @param string[] $targetGroups - */ - public function setTargetGroups(array $targetGroups): static - { - foreach ($targetGroups as $targetGroup) { - $this->setTargetGroup($targetGroup); - } - - return $this; - } - - - // date constraints - - const PARAM_DATE_FORMAT = 'Y-m-d'; - - public function setFrom(\DateTimeImmutable $dateFrom): static - { - $this->params['od'] = $dateFrom->format(self::PARAM_DATE_FORMAT); - return $this; - } - - public function setUntil(\DateTimeImmutable $dateFrom): static - { - $this->params['do'] = $dateFrom->format(self::PARAM_DATE_FORMAT); - return $this; - } - - public function setYear(int $year): static - { - $this->params['rok'] = (int) $year; - return $this; - } - - public function hideTheseAlreadyStarted(): static - { - unset($this->params[self::PARAM_DISPLAY_ALREADY_STARTED_KEY]); - return $this; - } - - - // miscellaneous - - public function orderByStartDate(): static - { - unset($this->params[self::PARAM_ORDER_BY_KEY]); - return $this; - } - - /** - * @param int|int[] $unitIds - */ - public function setOrganizedBy(array|int $unitIds): static - { - $organizedByKey = 'zc'; - - // If just single value, wrap it into an array. - if ( ! \is_array($unitIds)) { - $unitIds = [$unitIds]; - } - - foreach ($unitIds as $unitId) { - // If such value is not present yet, initialize it with an empty array. - if ( ! \is_array($this->params[$organizedByKey])) { - $this->params[$organizedByKey] = []; - } - - $this->params[$organizedByKey][] = (int) $unitId; - } - - return $this; - } - - public function includeNonPublic(): static - { - $this->params['vse'] = 1; - return $this; - } - -} diff --git a/src/Request/OrganizationalUnitParameters.php b/src/Request/OrganizationalUnitParameters.php deleted file mode 100644 index 011bced..0000000 --- a/src/Request/OrganizationalUnitParameters.php +++ /dev/null @@ -1,44 +0,0 @@ - 'zc' - ]); - } - - - /** - * @throws InvalidArgumentException - */ - public function setType(string $type): static - { - if (!\in_array($type, [ - self::TYPE_CLUB, - self::TYPE_BASE, - self::TYPE_REGIONAL, - ], true)) { - throw new InvalidArgumentException('Type `' . $type . '` is not of valid types.'); - } - - $this->params[self::PARAM_FILTER] = $type; - - return $this; - } - -} diff --git a/src/Request/Parameters.php b/src/Request/Parameters.php deleted file mode 100644 index 5fd5b4c..0000000 --- a/src/Request/Parameters.php +++ /dev/null @@ -1,51 +0,0 @@ -params = $params; - } - - - public function getAll(): array - { - return $this->params; - } - - - public function getQueryString(): string - { - return \http_build_query($this->params); - } - - - public function setCredentials(string $username, string $password): static - { - $this->params[self::PARAM_USERNAME] = $username; - $this->params[self::PARAM_PASSWORD] = $password; - - return $this; - } - -} diff --git a/src/Request/ToArray.php b/src/Request/ToArray.php new file mode 100644 index 0000000..b36e372 --- /dev/null +++ b/src/Request/ToArray.php @@ -0,0 +1,11 @@ +latitude; + } + + + public function getLongitude(): float + { + return $this->longitude; + } + +} diff --git a/src/Response/Event/Event.php b/src/Response/Event/Event.php index 7c37fa9..0e8d41c 100644 --- a/src/Response/Event/Event.php +++ b/src/Response/Event/Event.php @@ -1,17 +1,20 @@ contact_person_email; + $registrationCustomUrl = $data->entry_form_url; $registrationQuestions = \array_filter([ // exclude all null items - $webRegistrationQuestion1, - $webRegistrationQuestion2, - $webRegistrationQuestion3, - ], fn($v, $k) => $v !== null, \ARRAY_FILTER_USE_BOTH); + $data->additional_question_1, + $data->additional_question_2, + $data->additional_question_3, + ], fn($v, $k) => $v !== '', \ARRAY_FILTER_USE_BOTH); $registrationType = RegistrationType::from( - $registrationType, + RegistrationTypeEnum::fromScalar($data->registration_method), \array_map(fn(string $question) => RegistrationQuestion::from($question), $registrationQuestions), $contactEmail, $registrationCustomUrl, ); - // price - $price = 0; - if (isset($data['poplatek']) && $data['poplatek'] !== '') { - $price = $data['poplatek']; - - if (\preg_match('|^[0-9]+$|', $price)) { - $price = (int) $price; - } - } // organizers - $organizationalUnitId = (isset($data['porada_id']) && $data['porada_id'] !== '') ? ((int) $data['porada_id']) : null; - $organizationalUnitName= (isset($data['porada']) && $data['porada'] !== '') ? $data['porada'] : null; - $organizers = (isset($data['org']) && $data['org'] !== '') ? $data['org'] : null; - $contactPersonName = (isset($data['kontakt']) && $data['kontakt'] !== '') ? $data['kontakt'] : null; - $contactPhone = $data['kontakt_telefon']; - $responsiblePerson = (isset($data['odpovedna']) && $data['odpovedna'] !== '') ? $data['odpovedna'] : null; + $organizationalUnitName = $data->administrative_unit_name !== '' ? $data->administrative_unit_name : null; + $organizationalUnitWebsite = $data->administrative_unit_web_url !== '' ? $data->administrative_unit_web_url : null; + $organizers = $data->looking_forward_to_you !== '' ? $data->looking_forward_to_you : null; + $responsiblePerson = $data->responsible_person !== '' ? $data->responsible_person : null; $organizer = Organizer::from( - ($organizationalUnitId !== null && $organizationalUnitName !== null) - ? OrganizerOrganizationalUnit::from($organizationalUnitId, $organizationalUnitName) + ($organizationalUnitName !== null) + ? OrganizerOrganizationalUnit::from($organizationalUnitName, $organizationalUnitWebsite) : null, $responsiblePerson, $organizers, - $contactPersonName, - $contactPhone, - $contactEmail, + ContactPerson::from( + $data->contact_person_name, + $contactEmail, + $data->contact_person_telephone, + ), ); // invitation - // BIS API returns "0", "1", "2" etc. for real options and "" when nothing is set - $food = (isset($data['strava']) && $data['strava'] !== '') - ? Food::fromScalar((int) $data['strava']) - : Food::NOT_LISTED(); - /** @var Photo[] $invitationPresentationPhotos */ $invitationPresentationPhotos = []; for ($i = 1; $i <= 6; $i++) { - if (isset($data['ochutnavka_' . $i]) && $data['ochutnavka_' . $i] !== '') { - $invitationPresentationPhotos[] = Photo::from($data['ochutnavka_' . $i]); + $photo = $data->{'additional_photo_' . $i}; + if ($photo !== null) { + $invitationPresentationPhotos[] = Photo::from($photo); } } - $invitationOrganizationalInformation = (isset($data['text_info']) && $data['text_info'] !== '') ? $data['text_info'] : ''; // this will not be needed in BIS but now it has to be there as somehow obligatory fields are not required anymore in old BIS - $invitationIntroduction = (isset($data['text_uvod']) && $data['text_uvod'] !== '') ? $data['text_uvod'] : ''; // this will not be needed in BIS but now it has to be there as somehow obligatory fields are not required anymore in old BIS - $invitationPresentationText = (isset($data['text_mnam']) && $data['text_mnam'] !== '') ? $data['text_mnam'] : null; - $invitationWorkDescription = (isset($data['text_dobr']) && $data['text_dobr'] !== '') ? $data['text_dobr'] : null; - $workHoursPerDay = (isset($data['pracovni_doba']) && $data['pracovni_doba'] !== '') ? ((int) $data['pracovni_doba']) : null; - $accommodation = (isset($data['ubytovani']) && $data['ubytovani'] !== '') ? $data['ubytovani'] : null; + $invitationPresentationText = $data->invitation_text_4; $invitation = Invitation::from( - $invitationIntroduction, - $invitationOrganizationalInformation, - $accommodation, - $food, - $invitationWorkDescription, - $workHoursPerDay, + $data->invitation_text_1, + $data->invitation_text_2, + $data->accommodation !== '' ? $data->accommodation : null, + \array_map(static fn($diet) => Food::fromScalar($diet), $data->diet), + $data->invitation_text_3, + $data->working_hours, ($invitationPresentationText !== null || \count($invitationPresentationPhotos) > 0) ? Presentation::from($invitationPresentationText, $invitationPresentationPhotos) : null, @@ -159,7 +103,7 @@ public static function fromResponseData(array $data): static // related website - $relatedWebsite = (isset($data['web']) && $data['web'] !== '') ? $data['web'] : null; + $relatedWebsite = $data->web_url; $_relatedWebsite = null; if ($relatedWebsite !== null) { if ( ! self::startsWith($relatedWebsite, 'http')) { // count with no protocol typed URLs @@ -170,24 +114,34 @@ public static function fromResponseData(array $data): static } return new self( - (int) $data['id'], - $data['nazev'], - (isset($data['foto_hlavni']) && $data['foto_hlavni'] !== '') ? $data['foto_hlavni'] : null, - \DateTimeImmutable::createFromFormat('Y-m-d', $data['od']), - \DateTimeImmutable::createFromFormat('Y-m-d', $data['do']), - $data['typ'], - $program, - $place, + $data->id, + $data->name, + $data->main_photo, + $data->date_from === null + ? \DateTimeImmutable::createFromFormat('Y-m-d', '1970-01-01') // todo temp, until there are nullable dates coming from API + : \DateTimeImmutable::createFromFormat('Y-m-d', $data->date_from), + $data->date_to === null + ? \DateTimeImmutable::createFromFormat('Y-m-d', '1970-01-02') // todo temp, until there are nullable dates coming from API + : \DateTimeImmutable::createFromFormat('Y-m-d', $data->date_to), + Program::fromScalar($data->program), + $data->location === null + ? Place::from('nezadáno', null) // todo temp, until there are nullable locations coming from API + : Place::from( + $data->location->name, + $data->location->gps_latitude !== null && $data->location->gps_longitude !== null + ? Coordinates::from($data->location->gps_latitude, $data->location->gps_longitude) + : null, + ), $registrationType, - (isset($data['vek_od']) && $data['vek_od'] !== '') ? ((int) $data['vek_od']) : null, - (isset($data['vek_do']) && $data['vek_do'] !== '') ? ((int) $data['vek_do']) : null, - $price, + $data->age_from, + $data->age_to, + $data->participation_fee !== null ? $data->participation_fee : null, $organizer, - TargetGroup::from((int) $data['prokoho']), + TargetGroup::fromScalar($data->indended_for), $invitation, - (isset($data['sraz']) && $data['sraz'] !== '') ? $data['sraz'] : null, - (isset($data['popis_programu']) && $data['popis_programu'] !== '') ? $data['popis_programu'] : null, - (isset($data['jak_se_prihlasit']) && $data['jak_se_prihlasit'] !== '') ? $data['jak_se_prihlasit'] : null, + $data->start_date !== null + ? new \DateTimeImmutable($data->start_date) + : null, $_relatedWebsite, ); } @@ -211,12 +165,6 @@ public function getCoverPhotoPath(): ?string } - public function hasCoverPhoto(): bool - { - return $this->coverPhotoPath !== null; - } - - public function getDateFrom(): \DateTimeImmutable { return $this->dateFrom; @@ -229,12 +177,6 @@ public function getDateUntil(): \DateTimeImmutable } - public function getType(): string - { - return $this->type; - } - - public function getProgram(): Program { return $this->program; @@ -265,18 +207,12 @@ public function getAgeUntil(): ?int } - public function getPrice(): int|string + public function getPrice(): ?string { return $this->price; } - public function isPaid(): bool - { - return $this->price !== 0; - } - - public function getOrganizer(): Organizer { return $this->organizer; @@ -295,33 +231,9 @@ public function getInvitation(): Invitation } - public function hasTimeFrom(): bool - { - return $this->timeFrom !== null; - } - - - public function getTimeFrom(): ?string - { - return $this->timeFrom; - } - - - public function getProgramDescription(): ?string - { - return $this->programDescription; - } - - - public function getNotes(): ?string - { - return $this->notes; - } - - - public function hasRelatedWebsite(): bool + public function getStartDate(): ?\DateTimeImmutable { - return $this->relatedWebsite !== null; + return $this->startDate; } diff --git a/src/Response/Event/Invitation/Food.php b/src/Response/Event/Invitation/Food.php index 49a2c3b..cdea814 100644 --- a/src/Response/Event/Invitation/Food.php +++ b/src/Response/Event/Invitation/Food.php @@ -1,23 +1,27 @@ accommodation !== null; - } - - public function getAccommodation(): ?string { return $this->accommodation; } - public function getFood(): Food + /** + * @return Food[] + */ + public function getFood(): array { return $this->food; } @@ -75,24 +78,12 @@ public function getWorkDescription(): ?string } - public function areWorkHoursPerDayListed(): bool - { - return $this->workHoursPerDay !== null; - } - - public function getWorkHoursPerDay(): ?int { return $this->workHoursPerDay; } - public function hasPresentation(): bool - { - return $this->presentation !== null; - } - - public function getPresentation(): ?Presentation { return $this->presentation; diff --git a/src/Response/Event/Invitation/Photo.php b/src/Response/Event/Invitation/Photo.php index 582c3be..df44878 100644 --- a/src/Response/Event/Invitation/Photo.php +++ b/src/Response/Event/Invitation/Photo.php @@ -1,6 +1,6 @@ text !== null; - } - - public function getText(): ?string { return $this->text; } - public function hasAnyPhotos(): bool - { - return \count($this->photos) > 0; - } - /** * @return Photo[] */ diff --git a/src/Response/Event/Organizer/ContactPerson.php b/src/Response/Event/Organizer/ContactPerson.php new file mode 100644 index 0000000..b8cb186 --- /dev/null +++ b/src/Response/Event/Organizer/ContactPerson.php @@ -0,0 +1,43 @@ +name; + } + + + public function getEmailAddress(): string + { + return $this->emailAddress; + } + + + public function getPhoneNumber(): string + { + return $this->phoneNumber; + } + +} diff --git a/src/Response/Event/Organizer.php b/src/Response/Event/Organizer/Organizer.php similarity index 54% rename from src/Response/Event/Organizer.php rename to src/Response/Event/Organizer/Organizer.php index 7c4c436..454802a 100644 --- a/src/Response/Event/Organizer.php +++ b/src/Response/Event/Organizer/Organizer.php @@ -1,6 +1,6 @@ organizers !== null; - } - - public function getOrganizers(): ?string { return $this->organizers; } - public function getContactPersonName(): ?string - { - return $this->contactPersonName; - } - - - public function getContactPhone(): string - { - return $this->contactPhone; - } - - - public function getContactEmail(): string + public function getContactPerson(): ContactPerson { - return $this->contactEmail; + return $this->contactPerson; } } diff --git a/src/Response/Event/Organizer/OrganizerOrganizationalUnit.php b/src/Response/Event/Organizer/OrganizerOrganizationalUnit.php new file mode 100644 index 0000000..0564d57 --- /dev/null +++ b/src/Response/Event/Organizer/OrganizerOrganizationalUnit.php @@ -0,0 +1,32 @@ +name; + } + + + public function getWebsite(): ?string + { + return $this->website; + } + +} diff --git a/src/Response/Event/OrganizerOrganizationalUnit.php b/src/Response/Event/OrganizerOrganizationalUnit.php deleted file mode 100644 index b94883a..0000000 --- a/src/Response/Event/OrganizerOrganizationalUnit.php +++ /dev/null @@ -1,37 +0,0 @@ -id; - } - - - public function getName(): string - { - return $this->name; - } - -} diff --git a/src/Response/Event/Place.php b/src/Response/Event/Place.php index 7cdc0ae..c625757 100644 --- a/src/Response/Event/Place.php +++ b/src/Response/Event/Place.php @@ -1,6 +1,8 @@ coordinates !== null; - } - - - public function getCoordinates(): ?string + public function getCoordinates(): ?Coordinates { return $this->coordinates; } diff --git a/src/Response/Event/Program.php b/src/Response/Event/Program.php deleted file mode 100644 index cbf4746..0000000 --- a/src/Response/Event/Program.php +++ /dev/null @@ -1,77 +0,0 @@ -slug->toScalar(); - } - - - public function getName(): ?string - { - return $this->name; - } - - - public function isNotSelected(): bool - { - return $this->slug->equals(ProgramType::NONE()); - } - - - public function isOfTypeNature(): bool - { - return $this->slug->equals(ProgramType::NATURE()); - } - - - public function isOfTypeSights(): bool - { - return $this->slug->equals(ProgramType::SIGHTS()); - } - - - public function isOfTypeBrdo(): bool - { - return $this->slug->equals(ProgramType::BRDO()); - } - - - public function isOfTypeEkostan(): bool - { - return $this->slug->equals(ProgramType::EKOSTAN()); - } - - - public function isOfTypePsb(): bool - { - return $this->slug->equals(ProgramType::PSB()); - } - - - public function isOfTypeEducation(): bool - { - return $this->slug->equals(ProgramType::EDUCATION()); - } - -} diff --git a/src/Response/Event/ProgramType.php b/src/Response/Event/ProgramType.php deleted file mode 100644 index df29adf..0000000 --- a/src/Response/Event/ProgramType.php +++ /dev/null @@ -1,29 +0,0 @@ -hasValidData = match (true) { - $type->equals(RegistrationTypeEnum::EMAIL()) && $email === null, - $type->equals(RegistrationTypeEnum::EXTERNAL_WEBPAGE()) && $url === null, - => false, - default => true - }; - } + ) {} /** * @param RegistrationQuestion[] $questions @@ -51,11 +41,6 @@ public function isOfTypeBrontoWeb(): bool } - public function areAnyQuestions(): bool - { - return \count($this->questions) > 0; - } - /** * @return RegistrationQuestion[] */ @@ -72,18 +57,19 @@ public function isOfTypeEmail(): bool return $this->type->equals(RegistrationTypeEnum::EMAIL()); } - /** - * @throws RegistrationTypeException - * @throws BadUsageException - */ public function getEmail(): ?string { - if ( ! $this->hasValidData()) { - throw RegistrationTypeException::missingAdditionalData('email', $this->type); + if ( ! $this->isOfTypeEmail()) { + throw new UsageException('This method can not be called when the registration is not of `via e-mail` type.'); } - if ( ! $this->isOfTypeEmail()) { - throw new BadUsageException('This method can not be called when the registration is not of `via e-mail` type.'); + if ($this->email === null) { + /* + * Ideally, this should not happen, but we can not rely on it. If it happens, we want to know about it -> + * assert() is not enough. We can just log it, but that would lead user into clicking a button which does nothing. + * Thus rendering error page covers both – logging and preventing user from accessing non-working page. + */ + throw new RuntimeException('E-mail must not be null in case of registration via e-mail.'); } return $this->email; @@ -97,18 +83,19 @@ public function isOfTypeCustomWebpage(): bool return $this->type->equals(RegistrationTypeEnum::EXTERNAL_WEBPAGE()); } - /** - * @throws RegistrationTypeException - * @throws BadUsageException - */ public function getUrl(): ?string { - if ( ! $this->hasValidData()) { - throw RegistrationTypeException::missingAdditionalData('url', $this->type); + if ( ! $this->isOfTypeCustomWebpage()) { + throw new UsageException('This method can not be called when the registration is not of `via custom webpage` type.'); } - if ( ! $this->isOfTypeCustomWebpage()) { - throw new BadUsageException('This method can not be called when the registration is not of `via custom webpage` type.'); + if ($this->url === null) { + /* + * Ideally, this should not happen, but we can not rely on it. If it happens, we want to know about it -> + * assert() is not enough. We can just log it, but that would lead user into clicking a button which does nothing. + * Thus rendering error page covers both – logging and preventing user from accessing non-working page. + */ + throw new RuntimeException('URL must not be null in case of registration via custom webpage.'); } return $this->url; @@ -130,10 +117,4 @@ public function isOfTypeDisabled(): bool return $this->type->equals(RegistrationTypeEnum::DISABLED()); } - - public function hasValidData(): bool - { - return $this->hasValidData; - } - } diff --git a/src/Response/Event/Registration/RegistrationTypeEnum.php b/src/Response/Event/Registration/RegistrationTypeEnum.php index a974aa5..8966b69 100644 --- a/src/Response/Event/Registration/RegistrationTypeEnum.php +++ b/src/Response/Event/Registration/RegistrationTypeEnum.php @@ -1,6 +1,6 @@ id = $id; - } - - - public static function from(int $id): self - { - return new self($id); - } - - - - public function isOfTypeEveryone(): bool - { - return $this->id === self::EVERYONE; - } - - - public function isOfTypeAdults(): bool - { - return $this->id === self::ADULTS; - } - - - public function isOfTypeChildren(): bool - { - return $this->id === self::CHILDREN; - } - - - public function isOfTypeFamilies(): bool - { - return $this->id === self::FAMILIES; - } - - - public function isOfTypeFirstTimeAttendees(): bool - { - return $this->id === self::FIRST_TIME_ATTENDEES; - } - -} diff --git a/src/Response/OrganizationalUnit/OrganizationalUnit.php b/src/Response/OrganizationalUnit/OrganizationalUnit.php index a34f044..7da0386 100644 --- a/src/Response/OrganizationalUnit/OrganizationalUnit.php +++ b/src/Response/OrganizationalUnit/OrganizationalUnit.php @@ -1,8 +1,8 @@ id, + $data->name, + $data->street, + $data->city, + $data->zip_code, + $data->gps_latitude !== null && $data->gps_longitude !== null + ? Coordinates::from($data->gps_latitude, $data->gps_longitude) + : null, + $data->telephone, + $data->from_email_address, + $data->web_url, + OrganizationalUnitType::fromScalar($data->level), + $data->president_name, + $data->manager_name, ); } @@ -82,13 +75,19 @@ public function getPostCode(): string } - public function getPhone(): ?string + public function getCoordinates(): ?Coordinates + { + return $this->coordinates; + } + + + public function getPhone(): string { return $this->phone; } - public function getEmail(): ?string + public function getEmail(): string { return $this->email; } @@ -100,13 +99,13 @@ public function getWebsite(): ?string } - public function getChairman(): ?string + public function getChairman(): string { return $this->chairman; } - public function getManager(): ?string + public function getManager(): string { return $this->manager; } diff --git a/src/Response/OrganizationalUnit/OrganizationalUnitType.php b/src/Response/OrganizationalUnit/OrganizationalUnitType.php index e294b73..617bf63 100644 --- a/src/Response/OrganizationalUnit/OrganizationalUnitType.php +++ b/src/Response/OrganizationalUnit/OrganizationalUnitType.php @@ -1,6 +1,6 @@ httpResponse = $httpResponse; - - $this->parseDom($domDocument); - } - - - private function parseDom(\DOMDocument $domDocument): void - { - $domFinder = new \DOMXPath($domDocument); - $rowNodeList = $domFinder->query('*', $domDocument->getElementsByTagName(self::TAG_RESULT)->item(0)); - - $this->data = []; - foreach ($rowNodeList as $rowNode) { - \assert($rowNode instanceof \DOMElement); - - $row = []; - foreach ($domFinder->query('*', $rowNode) as $node) { - \assert($node instanceof \DOMElement); - - // if there is an ID attribute, use this one a the value as it is numeric representation (thus more technically reliable) of element's content - $row[$node->nodeName] = $node->hasAttribute(self::TAG_ATTRIBUTE_ID) ? - $node->getAttribute(self::TAG_ATTRIBUTE_ID) - : - $node->nodeValue; - } - - $this->data[] = $row; - } - } - - - public function getHttpResponse(): ResponseInterface - { - return $this->httpResponse; - } - - - public function getData(): array - { - return $this->data; - } - -} diff --git a/src/Response/exceptions.php b/src/Response/exceptions.php deleted file mode 100644 index a08b202..0000000 --- a/src/Response/exceptions.php +++ /dev/null @@ -1,70 +0,0 @@ -nodeValue); - } - -} - -final class UnauthorizedAccessException extends ResponseErrorException -{ - - public function __construct() - { - parent::__construct('You are not authorized to make such request with given credentials. Or you have simply wrong credentials. :-)'); - } - -} - -final class UnknownErrorException extends ResponseErrorException -{ - - /** - * @param string $unknownErrorTypeKey - */ - public function __construct($unknownErrorTypeKey) - { - parent::__construct('Unknown error. Error type returned from BIS: ' . $unknownErrorTypeKey); - } - -} - -final class RegistrationTypeException extends ResponseErrorException -{ - - public static function missingAdditionalData($key, $type) - { - return new self(\sprintf('Missing additional data `%s` for selected type %d.', $key, $type)); - } - -} diff --git a/src/exceptions.php b/src/exceptions.php index d9e64d9..b3605ae 100644 --- a/src/exceptions.php +++ b/src/exceptions.php @@ -1,33 +1,54 @@ getMessage(), 0, $previous); + } +} + +final class UnableToAuthorize extends BisClientRuntimeException +{ + public static function withPrevious(\Throwable $previous): self + { + return new self("You are not authorized to make such request with given secrets.\nCheck that you passed correct secrets or that you have access to the resource you requested.", 0, $previous); + } +} + +final class NotFound extends BisClientRuntimeException +{ + public static function withPrevious(\Throwable $previous): self + { + return new self('The target you requested was not found. Check again that you\'ve typed correct URL or that the resource exists.', 0, $previous); + } +} +final class ConnectionToBisFailed extends BisClientRuntimeException +{ + public static function withPrevious(\Throwable $previous): self + { + return new self($previous->getMessage(), 0, $previous); + } +} diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..d8dd038 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1 @@ +secret.php diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..3ab7d73 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,22 @@ + $apiUrl, 'clientId' => $clientId, 'clientSecret' => $clientSecret] = $secret; + + return (new BisClientFactory( + $apiUrl, + $clientId, + $clientSecret, + ))->create(); +})(); diff --git a/tests/index.php b/tests/index.php new file mode 100644 index 0000000..46cf922 --- /dev/null +++ b/tests/index.php @@ -0,0 +1,55 @@ +addAttendee(new EventAttendee( + $eventId, + 'Jan', + 'Novák', + DateTimeImmutable::createFromFormat('Y-m-d', '2000-05-01'), + '123 456 789', + 'jan.novak@example.com', + 'prosím, abych tam měl nachystanou teplou peřinu', + ['odpověď č. 1', '', 'odpověď č. 3'], + )); +}; +// uncomment if you need to test it otherwise it would post on every page load +//$addAttendee(eventId: 9513); // ⚠ do not forget to customize event id not to pollute real events with testing data +//exit; +// ----------------------------- +// retrieving information test +// ----------------------------- + +echo '
'; + + echo '
'; + echo '

Event

'; + dump($client->getEvent(13063)); + echo '
'; + + echo '
'; + echo '

Events

'; + + foreach ($client->getEvents() as $event) { + dump($event); + } + echo '
'; + + echo '
'; + echo '

Organizational units

'; + + foreach ($client->getOrganizationalUnits() as $unit) { + dump($unit); + } + echo '
'; + +echo '
'; diff --git a/tests/secret.template.php b/tests/secret.template.php new file mode 100644 index 0000000..dcafece --- /dev/null +++ b/tests/secret.template.php @@ -0,0 +1,7 @@ + '', + 'clientId' => '', + 'clientSecret' => '', +];