324 lines
8.9 KiB
PHP
324 lines
8.9 KiB
PHP
<?php
|
|
|
|
namespace classes;
|
|
|
|
require_once WD . '/modules/workfeed/workfeed_c.php';
|
|
|
|
use Exception;
|
|
use interfaces\workfeed_i;
|
|
use workfeed\config\workfeed_api_url_c;
|
|
use workfeed\workfeed_c;
|
|
|
|
class workfeed implements workfeed_i
|
|
{
|
|
public workfeed_c $config;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->config = new workfeed_c();
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
public function requireModuleEnabled(): void
|
|
{
|
|
if (!$this->config->enabled->isTrue()) {
|
|
throw new Exception('The workfeed module is not enabled.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
public function listEmployees(array $filters = []): array|object
|
|
{
|
|
return $this->sendApiRequest('GET', '/employees');
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
public function getEmployee(string $id): object
|
|
{
|
|
$this->requireValidIdentifier($id, 'employee id');
|
|
|
|
return $this->sendApiRequest('GET', '/employees/' . rawurlencode($id));
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
public function listShifts(array $filters = []): array|object
|
|
{
|
|
$normalizedFilters = $this->normalizeShiftFilters($filters);
|
|
|
|
return $this->sendApiRequest('GET', '/shifts', $normalizedFilters);
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
public function getShift(string $id): object
|
|
{
|
|
$this->requireValidIdentifier($id, 'shift id');
|
|
|
|
return $this->sendApiRequest('GET', '/shifts/' . rawurlencode($id));
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
public function listDepartments(array $filters = []): array|object
|
|
{
|
|
return $this->sendApiRequest('GET', '/departments');
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function sendApiRequest(string $method, string $path, array $query = []): array|object
|
|
{
|
|
$this->requireModuleEnabled();
|
|
$this->requireConfiguredApiUrl();
|
|
$this->requireConfiguredApiKey();
|
|
$companyId = $this->requireConfiguredCompanyId();
|
|
|
|
$url = $this->buildUrl(
|
|
workfeed_api_url_c::normalizeApiUrlForValidation((string)$this->config->api_url->getVariableValue()),
|
|
'/companies/' . rawurlencode($companyId) . '/' . ltrim($path, '/'),
|
|
$query
|
|
);
|
|
$headers = [
|
|
'Accept: application/json',
|
|
'Authorization: ' . trim((string)$this->config->api_key->getVariableValue()),
|
|
];
|
|
|
|
return $this->executeJsonRequest($method, $url, $headers);
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function executeJsonRequest(string $method, string $url, array $headers): array|object
|
|
{
|
|
$response = $this->executeRequest($method, $url, $headers);
|
|
$decoded = json_decode($response['body']);
|
|
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
throw new Exception('Invalid JSON response from Workfeed (HTTP ' . $response['status'] . ').');
|
|
}
|
|
|
|
if ($response['status'] >= 400) {
|
|
throw new Exception($this->extractErrorMessage($decoded, $response['status'], 'Workfeed API request failed'));
|
|
}
|
|
|
|
if (is_object($decoded) || is_array($decoded)) {
|
|
return $decoded;
|
|
}
|
|
|
|
return (object)[
|
|
'value' => $decoded,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function executeRequest(string $method, string $url, array $headers, ?string $body = null): array
|
|
{
|
|
$curl = curl_init();
|
|
curl_setopt_array($curl, [
|
|
CURLOPT_URL => $url,
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_TIMEOUT => 30,
|
|
CURLOPT_CUSTOMREQUEST => $method,
|
|
CURLOPT_HTTPHEADER => $headers,
|
|
]);
|
|
|
|
if ($body !== null) {
|
|
curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
|
|
}
|
|
|
|
$responseBody = curl_exec($curl);
|
|
$statusCode = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
|
$error = curl_error($curl);
|
|
curl_close($curl);
|
|
|
|
if ($error !== '') {
|
|
throw new Exception('cURL request to Workfeed failed: ' . $error);
|
|
}
|
|
|
|
if ($responseBody === false) {
|
|
throw new Exception('Workfeed request returned an empty response.');
|
|
}
|
|
|
|
return [
|
|
'status' => $statusCode,
|
|
'body' => (string)$responseBody,
|
|
];
|
|
}
|
|
|
|
private function buildUrl(string $baseUrl, string $path = '', array $query = []): string
|
|
{
|
|
$url = rtrim(trim($baseUrl), '/');
|
|
if ($path !== '') {
|
|
$url .= '/' . ltrim($path, '/');
|
|
}
|
|
|
|
$query = array_filter($query, static function (mixed $value): bool {
|
|
return $value !== null && $value !== '';
|
|
});
|
|
|
|
if ($query !== []) {
|
|
$url .= '?' . http_build_query($query);
|
|
}
|
|
|
|
return $url;
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function requireConfiguredApiUrl(): void
|
|
{
|
|
$url = trim((string)$this->config->api_url->getVariableValue());
|
|
if ($url === ''
|
|
|| filter_var(workfeed_api_url_c::normalizeApiUrlForValidation($url), FILTER_VALIDATE_URL) === false
|
|
|| !workfeed_api_url_c::isTrustedApiUrl($url)
|
|
) {
|
|
throw new Exception('Invalid Workfeed API URL configured.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function requireConfiguredApiKey(): void
|
|
{
|
|
if (trim((string)$this->config->api_key->getVariableValue()) === '') {
|
|
throw new Exception('Invalid Workfeed API key configured.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function requireConfiguredCompanyId(): string
|
|
{
|
|
$companyId = trim((string)$this->config->company_id->getVariableValue());
|
|
if ($companyId === '') {
|
|
throw new Exception('Invalid Workfeed CompanyID configured.');
|
|
}
|
|
|
|
return $companyId;
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function requireValidIdentifier(string $value, string $label): void
|
|
{
|
|
if (trim($value) === '') {
|
|
throw new Exception('Invalid ' . $label . '.');
|
|
}
|
|
}
|
|
|
|
private function filterAllowed(array $filters, array $allowedKeys): array
|
|
{
|
|
$allowed = array_flip($allowedKeys);
|
|
$filtered = [];
|
|
|
|
foreach ($filters as $key => $value) {
|
|
if (isset($allowed[$key])) {
|
|
$filtered[$key] = $value;
|
|
}
|
|
}
|
|
|
|
return $filtered;
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function normalizeShiftFilters(array $filters): array
|
|
{
|
|
$filtered = $this->filterAllowed($filters, [
|
|
'startFrom',
|
|
'startTo',
|
|
'from',
|
|
'to',
|
|
'employeeID',
|
|
'employeeId',
|
|
'released',
|
|
]);
|
|
|
|
if (!isset($filtered['startFrom']) && isset($filtered['from'])) {
|
|
$filtered['startFrom'] = $filtered['from'];
|
|
}
|
|
if (!isset($filtered['startTo']) && isset($filtered['to'])) {
|
|
$filtered['startTo'] = $filtered['to'];
|
|
}
|
|
if (!isset($filtered['employeeID']) && isset($filtered['employeeId'])) {
|
|
$filtered['employeeID'] = $filtered['employeeId'];
|
|
}
|
|
|
|
unset($filtered['from'], $filtered['to'], $filtered['employeeId']);
|
|
|
|
if (!isset($filtered['startFrom']) || trim((string)$filtered['startFrom']) === '') {
|
|
throw new Exception('Workfeed shift query requires startFrom.');
|
|
}
|
|
|
|
if (!isset($filtered['startTo']) || trim((string)$filtered['startTo']) === '') {
|
|
throw new Exception('Workfeed shift query requires startTo.');
|
|
}
|
|
|
|
if (isset($filtered['released'])) {
|
|
$filtered['released'] = $this->normalizeBooleanQueryValue($filtered['released']);
|
|
}
|
|
|
|
return $filtered;
|
|
}
|
|
|
|
private function normalizeBooleanQueryValue(mixed $value): string
|
|
{
|
|
if (is_bool($value)) {
|
|
return $value ? 'true' : 'false';
|
|
}
|
|
|
|
if (is_string($value)) {
|
|
$normalized = strtolower(trim($value));
|
|
if (in_array($normalized, ['1', 'true', 'yes', 'on'], true)) {
|
|
return 'true';
|
|
}
|
|
if (in_array($normalized, ['0', 'false', 'no', 'off'], true)) {
|
|
return 'false';
|
|
}
|
|
}
|
|
|
|
if (is_int($value)) {
|
|
return $value === 1 ? 'true' : 'false';
|
|
}
|
|
|
|
return (string)$value;
|
|
}
|
|
|
|
private function extractErrorMessage(mixed $decoded, int $statusCode, string $fallback): string
|
|
{
|
|
if (is_object($decoded)) {
|
|
if (isset($decoded->message) && is_string($decoded->message)) {
|
|
return $decoded->message;
|
|
}
|
|
if (isset($decoded->error) && is_string($decoded->error)) {
|
|
return $decoded->error;
|
|
}
|
|
}
|
|
|
|
if (is_string($decoded) && trim($decoded) !== '') {
|
|
return $decoded;
|
|
}
|
|
|
|
return $fallback . ' (HTTP ' . $statusCode . ').';
|
|
}
|
|
}
|