* uplift to latest expressive

This commit is contained in:
Danyi Dávid
2017-11-06 13:18:39 +01:00
parent b5c307166e
commit 8c5b58acd1
44 changed files with 3419 additions and 1022 deletions

View File

@@ -2,16 +2,17 @@
namespace App\Action;
use Psr\Http\Message\ResponseInterface;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface as ServerMiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Stratigility\MiddlewareInterface;
use Zend\Json\Json;
abstract class AbstractAction implements MiddlewareInterface
abstract class AbstractAction implements ServerMiddlewareInterface
{
const IDENTIFIER_NAME = 'id';
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
$requestMethod = strtoupper($request->getMethod());
$id = $request->getAttribute(static::IDENTIFIER_NAME);
@@ -19,68 +20,68 @@ abstract class AbstractAction implements MiddlewareInterface
switch ($requestMethod) {
case 'GET':
return isset($id)
? $this->get($request, $response, $next)
: $this->getList($request, $response, $next);
? $this->get($request, $delegate)
: $this->getList($request, $delegate);
case 'POST':
return $this->create($request, $response, $next);
return $this->create($request, $delegate);
case 'PUT':
return $this->update($request, $response, $next);
return $this->update($request, $delegate);
case 'DELETE':
return isset($id)
? $this->delete($request, $response, $next)
: $this->deleteList($request, $response, $next);
? $this->delete($request, $delegate)
: $this->deleteList($request, $delegate);
case 'HEAD':
return $this->head($request, $response, $next);
return $this->head($request, $delegate);
case 'OPTIONS':
return $this->options($request, $response, $next);
return $this->options($request, $delegate);
case 'PATCH':
return $this->patch($request, $response, $next);
return $this->patch($request, $delegate);
default:
return $next($request, $response);
return $delegate->process($request);
}
}
public function get(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
public function get(ServerRequestInterface $request, DelegateInterface $delegate)
{
return $this->createResponse(['content' => 'Method not allowed'], 405);
}
public function getList(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
public function getList(ServerRequestInterface $request, DelegateInterface $delegate)
{
return $this->createResponse(['content' => 'Method not allowed'], 405);
}
public function create(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
public function create(ServerRequestInterface $request, DelegateInterface $delegate)
{
return $this->createResponse(['content' => 'Method not allowed'], 405);
}
public function update(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
public function update(ServerRequestInterface $request, DelegateInterface $delegate)
{
return $this->createResponse(['content' => 'Method not allowed'], 405);
}
public function delete(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
public function delete(ServerRequestInterface $request, DelegateInterface $delegate)
{
return $this->createResponse(['content' => 'Method not allowed'], 405);
}
public function deleteList(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
public function deleteList(ServerRequestInterface $request, DelegateInterface $delegate)
{
return $this->createResponse(['content' => 'Method not allowed'], 405);
}
public function head(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
public function head(ServerRequestInterface $request, DelegateInterface $delegate)
{
return $this->createResponse(['content' => 'Method not allowed'], 405);
}
public function options(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
public function options(ServerRequestInterface $request, DelegateInterface $delegate)
{
return $this->createResponse(['content' => 'Method not allowed'], 405);
}
public function patch(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
public function patch(ServerRequestInterface $request, DelegateInterface $delegate)
{
return $this->createResponse(['content' => 'Method not allowed'], 405);
}
@@ -89,4 +90,69 @@ abstract class AbstractAction implements MiddlewareInterface
{
return new JsonResponse($data, $status);
}
/**
*
* @param ServerRequestInterface $request
* @return array|object
*/
public function getRequestData(ServerRequestInterface $request)
{
$body = $request->getParsedBody();
if (!empty($body)) {
return $body;
}
return $this->parseRequestData(
$request->getBody()->getContents(),
$request->getHeaderLine('content-type')
);
}
/**
*
* @param string $input
* @param string $contentType
* @return mixed
*/
private function parseRequestData($input, $contentType)
{
$contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType);
$parser = $this->returnParserContentType($contentTypeParts[0]);
return $parser($input);
}
/**
*
* @param string $contentType
* @return callable
*/
private function returnParserContentType(string $contentType)
{
if ($contentType === 'application/x-www-form-urlencoded') {
return function ($input) {
parse_str($input, $data);
return $data;
};
} elseif ($contentType === 'application/json') {
return function ($input) {
$jsonDecoder = new Json();
try {
return $jsonDecoder->decode($input, Json::TYPE_ARRAY);
} catch (\Exception $e) {
return false;
}
};
} elseif ($contentType === 'multipart/form-data') {
return function ($input) {
return $input;
};
}
return function ($input) {
return $input;
};
}
}

View File

@@ -2,33 +2,34 @@
namespace App\Action;
use App\Service\SkiesService;
use Psr\Http\Message\ResponseInterface;
use App\Service\SkiesClientService;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Diactoros\Response\TextResponse;
class ActivityAction extends AbstractAction
{
private $skiesService;
/**
* @var SkiesClientService
*/
private $skiesClient;
public function __construct(SkiesService $skiesService)
public function __construct(SkiesClientService $skiesClient)
{
$this->skiesService = $skiesService;
$this->skiesClient = $skiesClient;
}
public function getList(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
public function getList(ServerRequestInterface $request, DelegateInterface $delegate)
{
return new JsonResponse($this->skiesService->getActivityList());
$authHeader = $request->getHeaderLine("x-passthru-auth");
return new JsonResponse($this->skiesClient->setAuthHeader($authHeader)->getActivities());
}
public function get(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
public function get(ServerRequestInterface $request, DelegateInterface $delegate)
{
$id = $request->getAttribute(self::IDENTIFIER_NAME);
return new JsonResponse($this->skiesService->getActivity($id));
}
public function options(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
{
return new JsonResponse(true);
$authHeader = $request->getHeaderLine("x-passthru-auth");
return new JsonResponse($this->skiesClient->setAuthHeader($authHeader)->getActivity($id));
}
}

View File

@@ -2,14 +2,14 @@
namespace App\Action;
use App\Service\SkiesService;
use App\Service\SkiesClientService;
use Interop\Container\ContainerInterface;
class ActivityFactory
{
public function __invoke(ContainerInterface $container)
{
$skiesService = $container->get(SkiesService::class);
return new ActivityAction($skiesService);
$skiesClient = $container->get(SkiesClientService::class);
return new ActivityAction($skiesClient);
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Action;
use App\Service\SkiesClientService;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface as ServerMiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\JsonResponse;
class ActivitySignoffAction implements ServerMiddlewareInterface
{
/**
* @var SkiesClientService
*/
private $skiesClient;
public function __construct(SkiesClientService $skiesClient)
{
$this->skiesClient = $skiesClient;
}
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
$authHeader = $request->getHeaderLine("x-passthru-auth");
$id = $request->getAttribute("id");
return new JsonResponse($this->skiesClient->setAuthHeader($authHeader)->signOffActivity($id));
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Action;
use App\Service\SkiesClientService;
use Interop\Container\ContainerInterface;
class ActivitySignoffFactory
{
public function __invoke(ContainerInterface $container)
{
$skiesClient = $container->get(SkiesClientService::class);
return new ActivitySignoffAction($skiesClient);
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Action;
use App\Service\SkiesClientService;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface as ServerMiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\JsonResponse;
class ActivitySignupAction implements ServerMiddlewareInterface
{
/**
* @var SkiesClientService
*/
private $skiesClient;
public function __construct(SkiesClientService $skiesClient)
{
$this->skiesClient = $skiesClient;
}
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
$authHeader = $request->getHeaderLine("x-passthru-auth");
$id = $request->getAttribute("id");
return new JsonResponse($this->skiesClient->setAuthHeader($authHeader)->signUpActivity($id));
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Action;
use App\Service\SkiesClientService;
use Interop\Container\ContainerInterface;
class ActivitySignupFactory
{
public function __invoke(ContainerInterface $container)
{
$skiesClient = $container->get(SkiesClientService::class);
return new ActivitySignupAction($skiesClient);
}
}

View File

@@ -2,7 +2,8 @@
namespace App\Action;
use Psr\Http\Message\ResponseInterface;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface as ServerMiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Diactoros\Response\JsonResponse;
@@ -12,7 +13,7 @@ use Zend\Expressive\Plates\PlatesRenderer;
use Zend\Expressive\Twig\TwigRenderer;
use Zend\Expressive\ZendView\ZendViewRenderer;
class HomePageAction
class HomePageAction implements ServerMiddlewareInterface
{
private $router;
@@ -24,8 +25,15 @@ class HomePageAction
$this->template = $template;
}
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
if (! $this->template) {
return new JsonResponse([
'welcome' => 'Congratulations! You have installed the zend-expressive skeleton application.',
'docsUrl' => 'https://docs.zendframework.com/zend-expressive/',
]);
}
$data = [];
if ($this->router instanceof Router\AuraRouter) {
@@ -36,7 +44,7 @@ class HomePageAction
$data['routerDocs'] = 'https://github.com/nikic/FastRoute';
} elseif ($this->router instanceof Router\ZendRouter) {
$data['routerName'] = 'Zend Router';
$data['routerDocs'] = 'http://framework.zend.com/manual/current/en/modules/zend.mvc.routing.html';
$data['routerDocs'] = 'https://docs.zendframework.com/zend-router/';
}
if ($this->template instanceof PlatesRenderer) {
@@ -47,14 +55,7 @@ class HomePageAction
$data['templateDocs'] = 'http://twig.sensiolabs.org/documentation';
} elseif ($this->template instanceof ZendViewRenderer) {
$data['templateName'] = 'Zend View';
$data['templateDocs'] = 'http://framework.zend.com/manual/current/en/modules/zend.view.quick-start.html';
}
if (!$this->template) {
return new JsonResponse([
'welcome' => 'Congratulations! You have installed the zend-expressive skeleton application.',
'docsUrl' => 'zend-expressive.readthedocs.org',
]);
$data['templateDocs'] = 'https://docs.zendframework.com/zend-view/';
}
return new HtmlResponse($this->template->render('app::home-page', $data));

View File

@@ -11,7 +11,7 @@ class HomePageFactory
public function __invoke(ContainerInterface $container)
{
$router = $container->get(RouterInterface::class);
$template = ($container->has(TemplateRendererInterface::class))
$template = $container->has(TemplateRendererInterface::class)
? $container->get(TemplateRendererInterface::class)
: null;

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Action;
use App\Service\SkiesClientService;
class NewsAction
{
/**
* @var SkiesClientService
*/
private $skiesClient;
public function __construct(SkiesClientService $skiesClient)
{
$this->skiesClient = $skiesClient;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Action;
use App\Service\SkiesClientService;
use Interop\Container\ContainerInterface;
class NewsFactory
{
public function __invoke(ContainerInterface $container)
{
$skiesClient = $container->get(SkiesClientService::class);
return new NewsAction($skiesClient);
}
}

View File

@@ -2,16 +2,15 @@
namespace App\Action;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface as ServerMiddlewareInterface;
use Zend\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class PingAction
class PingAction implements ServerMiddlewareInterface
{
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
return new JsonResponse([
'ack' => time(),
]);
return new JsonResponse(['ack' => time()]);
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace App;
/**
* The configuration provider for the App module
*
* @see https://docs.zendframework.com/zend-component-installer/
*/
class ConfigProvider
{
/**
* Returns the configuration array
*
* To add a bit of a structure, each section is defined in a separate
* method which returns an array with its configuration.
*
* @return array
*/
public function __invoke()
{
return [
'dependencies' => $this->getDependencies(),
'templates' => $this->getTemplates(),
];
}
/**
* Returns the container dependencies
*
* @return array
*/
public function getDependencies()
{
return [
'invokables' => [
Action\PingAction::class => Action\PingAction::class,
],
'factories' => [
Action\HomePageAction::class => Action\HomePageFactory::class,
Action\ActivityAction::class => Action\ActivityFactory::class,
Action\ActivitySignupAction::class => Action\ActivitySignupFactory::class,
Action\ActivitySignoffAction::class => Action\ActivitySignoffFactory::class,
Action\NewsAction::class => Action\NewsFactory::class,
Service\SkiesClientService::class => Service\SkiesClientServiceFactory::class,
],
];
}
/**
* Returns the templates configuration
*
* @return array
*/
public function getTemplates()
{
return [
'paths' => [
'app' => ['templates/app'],
'error' => ['templates/error'],
'layout' => ['templates/layout'],
],
];
}
}

305
src/App/Entity/Activity.php Normal file
View File

@@ -0,0 +1,305 @@
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
class Activity implements \JsonSerializable
{
/**
* @var int
*/
private $id;
/**
* @var string
*/
private $name;
/**
* @var \DateTime
*/
private $date;
/**
* @var string
*/
private $description;
/**
* @var \DateTime
*/
private $finalEntry;
/**
* @var string
*/
private $accountable;
/**
* @var ArrayCollection|Comment[]
*/
private $comments;
/**
* @var string[]
*/
private $signedUsers;
/**
* @var bool
*/
private $isSignedup = false;
/**
* Can sign up or sign off activity
* @var bool
*/
private $canChangeSignup = false;
public function __construct()
{
$this->comments = new ArrayCollection();
}
/**
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* @param int $id
* @return Activity
*/
public function setId(int $id)
{
$this->id = $id;
return $this;
}
/**
* @return string
*/
public function getName(): ?string
{
return $this->name;
}
/**
* @param string $name
* @return Activity
*/
public function setName(?string $name): Activity
{
$this->name = $name;
return $this;
}
/**
* @return \DateTime
*/
public function getDate(): ?\DateTime
{
return $this->date;
}
/**
* @param \DateTime $date
* @return Activity
*/
public function setDate(?\DateTime $date): Activity
{
$this->date = $date;
return $this;
}
/**
* @return string
*/
public function getDescription(): ?string
{
return $this->description;
}
/**
* @param string $description
* @return Activity
*/
public function setDescription(?string $description): Activity
{
$this->description = $description;
return $this;
}
/**
* @return \DateTime
*/
public function getFinalEntry(): ?\DateTime
{
return $this->finalEntry;
}
/**
* @param \DateTime $finalEntry
* @return Activity
*/
public function setFinalEntry(\DateTime $finalEntry): Activity
{
$this->finalEntry = $finalEntry;
return $this;
}
/**
* @return string
*/
public function getAccountable(): ?string
{
return $this->accountable;
}
/**
* @param string $accountable
* @return Activity
*/
public function setAccountable(string $accountable): Activity
{
$this->accountable = $accountable;
return $this;
}
/**
* @return Comment[]|ArrayCollection
*/
public function getComments()
{
return $this->comments;
}
/**
* @param Comment[]|ArrayCollection $comments
* @return Activity
*/
public function setComments($comments): Activity
{
$this->comments = $comments;
return $this;
}
/**
* @param Comment $comment
* @return Activity
*/
public function addComment(Comment $comment): Activity
{
if(!$this->comments->contains($comment)) {
$this->comments->add($comment);
}
return $this;
}
/**
* @param Comment $comment
* @return Activity
*/
public function removeComment(Comment $comment): Activity
{
if($this->comments->contains($comment)) {
$this->comments->removeElement($comment);
}
return $this;
}
/**
* @return string[]
*/
public function getSignedUsers(): ?array
{
return $this->signedUsers;
}
/**
* @param string[] $signedUsers
* @return Activity
*/
public function setSignedUsers(array $signedUsers): Activity
{
$this->signedUsers = $signedUsers;
return $this;
}
/**
* @param string $signed
* @return Activity
*/
public function addSignedUser(string $signed): Activity
{
$this->signedUsers[] = $signed;
return $this;
}
/**
* @return bool
*/
public function isSignedup(): bool
{
return $this->isSignedup;
}
/**
* @param bool $isSignedup
* @return Activity
*/
public function setIsSignedup(bool $isSignedup): Activity
{
$this->isSignedup = $isSignedup;
return $this;
}
/**
* @return bool
*/
public function isCanChangeSignup(): bool
{
return $this->canChangeSignup;
}
/**
* @param bool $canChangeSignup
* @return Activity
*/
public function setCanChangeSignup(bool $canChangeSignup): Activity
{
$this->canChangeSignup = $canChangeSignup;
return $this;
}
/**
* Specify data which should be serialized to JSON
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
* @return mixed data which can be serialized by <b>json_encode</b>,
* which is a value of any type other than a resource.
* @since 5.4.0
*/
public function jsonSerialize()
{
return [
'id' => $this->getId(),
'name' => $this->getName(),
'date' => $this->getDate()
? $this->getDate()->format("Y-m-d H:i:s")
: null,
'description' => $this->getDescription(),
'finalEntry' => $this->getFinalEntry()
? $this->getFinalEntry()->format("Y-m-d H:i:s")
: null,
'accountable' => $this->getAccountable(),
'comments' => $this->getComments()->getValues(),
'signedUsers' => $this->getSignedUsers(),
'canChangeSignup' => $this->isCanChangeSignup(),
'isSignedUp' => $this->isSignedup()
];
}
}

View File

@@ -0,0 +1,93 @@
<?php
namespace App\Entity;
class Comment implements \JsonSerializable
{
/**
* @var string
*/
private $text;
/**
* @var \DateTime
*/
private $createdAt;
/**
* @var User
*/
private $user;
/**
* @return string
*/
public function getText(): string
{
return $this->text;
}
/**
* @param string $text
* @return Comment
*/
public function setText(string $text): Comment
{
$this->text = $text;
return $this;
}
/**
* @return \DateTime
*/
public function getCreatedAt(): \DateTime
{
return $this->createdAt;
}
/**
* @param \DateTime $createdAt
* @return Comment
*/
public function setCreatedAt(\DateTime $createdAt): Comment
{
$this->createdAt = $createdAt;
return $this;
}
/**
* @return User
*/
public function getUser(): User
{
return $this->user;
}
/**
* @param User $user
* @return Comment
*/
public function setUser(User $user): Comment
{
$this->user = $user;
return $this;
}
/**
* Specify data which should be serialized to JSON
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
* @return mixed data which can be serialized by <b>json_encode</b>,
* which is a value of any type other than a resource.
* @since 5.4.0
*/
public function jsonSerialize()
{
return [
'text' => $this->getText(),
'user' => $this->getUser(),
'createdAt' => $this->getCreatedAt()
? $this->getCreatedAt()->format("Y-m-d H:i:s")
: null,
];
}
}

8
src/App/Entity/News.php Normal file
View File

@@ -0,0 +1,8 @@
<?php
namespace App\Entity;
class News
{
}

67
src/App/Entity/User.php Normal file
View File

@@ -0,0 +1,67 @@
<?php
namespace App\Entity;
class User implements \JsonSerializable
{
/**
* @var string
*/
private $username;
/**
* @var string
*/
private $displayName;
/**
* @return string
*/
public function getUsername(): string
{
return $this->username;
}
/**
* @param string $username
* @return User
*/
public function setUsername(string $username): User
{
$this->username = $username;
return $this;
}
/**
* @return string
*/
public function getDisplayName(): string
{
return $this->displayName;
}
/**
* @param string $displayName
* @return User
*/
public function setDisplayName(string $displayName): User
{
$this->displayName = $displayName;
return $this;
}
/**
* Specify data which should be serialized to JSON
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
* @return mixed data which can be serialized by <b>json_encode</b>,
* which is a value of any type other than a resource.
* @since 5.4.0
*/
public function jsonSerialize()
{
return [
'username' => $this->getUsername(),
'displayName' => $this->getDisplayName(),
];
}
}

View File

@@ -0,0 +1,397 @@
<?php
namespace App\Service;
use App\Entity\Activity;
use App\Entity\Comment;
use App\Entity\User;
use GuzzleHttp\Client;
use Psr\Http\Message\ResponseInterface;
use Zend\Dom\Document;
use Zend\Expressive\Exception\MissingDependencyException;
class SkiesClientService
{
const SKIES_MAIN_URL = "https://skies.sigmatechnology.se/main.asp";
const SKIES_PROFILE_URL = "https://skies.sigmatechnology.se/main.asp?rID=1&alt=2&username=%s";
const SKIES_ACTIVITIES_URL = "https://skies.sigmatechnology.se/main.asp?rID=2";
const SKIES_ACTIVITY_URL = "https://skies.sigmatechnology.se/main.asp?rID=2&alt=1&aktID=%s";
const SKIES_ACTIVITY_SIGNUP_URL = "https://skies.sigmatechnology.se/main.asp?rID=2&alt=1&aktID=%s&doJoin=1";
const SKIES_ACTIVITY_SIGNOFF_URL = "https://skies.sigmatechnology.se/main.asp?rID=2&alt=1&aktID=%s&doCancel=1&user=%s";
/**
* @var Client
*/
private $client;
/**
* @var string
*/
private $authHeader = null;
public function __construct(Client $client)
{
$this->client = $client;
}
/**
* @param string $authHeader
* @return SkiesClientService
*/
public function setAuthHeader(string $authHeader): SkiesClientService
{
$this->authHeader = $authHeader;
return $this;
}
public function getNews()
{
$response = $this->doSkiesRequest('GET', self::SKIES_MAIN_URL);
$htmlBody = $response->getBody();
return $this->parseMainPage($htmlBody);
}
/**
* @return Activity[]
*/
public function getActivities()
{
$response = $this->doSkiesRequest('GET', self::SKIES_ACTIVITIES_URL);
$htmlBody = $response->getBody();
return $this->parseActivitiesPage($htmlBody);
}
/**
* @param string $htmlBody
* @return Activity[]
*/
private function parseActivitiesPage(string $htmlBody)
{
$domDocument = new Document($htmlBody);
$eventNodes = Document\Query::execute(
"div.container div.row div.box > table tr",
$domDocument,
Document\Query::TYPE_CSS
);
$activities = [];
for ($i = 1; $i < $eventNodes->count(); $i++) {
$eventRow = $eventNodes[$i];
$rowCells = Document\Query::execute(
"./td",
$domDocument,
Document\Query::TYPE_XPATH,
$eventRow
);
$href = $rowCells[0]->childNodes->item(0)->getAttribute("href");
$queryString = parse_url($href, PHP_URL_QUERY);
parse_str($queryString, $queryParams);
$activities[] = $this->getActivity($queryParams["aktID"]);
}
return $activities;
}
/**
* @param int $id
* @return Activity
*/
public function getActivity(int $id): Activity
{
$response = $this->doSkiesRequest('GET', sprintf(self::SKIES_ACTIVITY_URL, $id));
$htmlBody = $response->getBody();
return $this->parseActivityPage($htmlBody, $id);
}
/**
* @param int $id
* @return Activity
*/
public function signUpActivity(int $id): Activity
{
$this->doSkiesRequest('POST', sprintf(self::SKIES_ACTIVITY_SIGNUP_URL, $id), [
'form_params' => [
'user' => $this->getUsername(),
],
]);
return $this->getActivity($id);
}
/**
* @param int $id
* @return Activity
*/
public function signOffActivity(int $id): Activity
{
$username = $this->getUsername();
$this->doSkiesRequest('GET', sprintf(self::SKIES_ACTIVITY_SIGNOFF_URL, $id, $username));
return $this->getActivity($id);
}
/**
* @param string $htmlBody
* @param int $id
* @return Activity
*/
private function parseActivityPage(string $htmlBody, int $id): Activity
{
$domDocument = new Document($htmlBody);
/** Activity body */
$activityNode = Document\Query::execute(
"div.col-md-10 div.box",
$domDocument,
Document\Query::TYPE_CSS
);
/** Activity name */
$h5Nodes = Document\Query::execute(
"./h5",
$domDocument,
Document\Query::TYPE_XPATH,
$activityNode[0]
);
$divNodes = Document\Query::execute(
".//div",
$domDocument,
Document\Query::TYPE_XPATH,
$activityNode[0]
);
/** Comment block */
$commentNodes = Document\Query::execute(
'.//div[@class="onecomment"]',
$domDocument,
Document\Query::TYPE_XPATH,
$activityNode[0]
);
/** Signed users block */
$signedUserParagraph = Document\Query::execute(
'//div[@class="portlet"]//div[@class="paragraph"]/text()',
$domDocument,
Document\Query::TYPE_XPATH
);
preg_match("#Date:.*?([0-9]{4}-[0-9]{2}-[0-9]{2}).*?Time: ([0-9]{1,2}:[0-9]{2}).*?Final entry day:.*?([0-9]{4}-[0-9]{2}-[0-9]{2}).*?Accountable: (.*?) / ([0-9 +]*)#msi", $divNodes[1]->textContent, $matches);
$signedUsers = $this->parseActivitySignedUsers($signedUserParagraph);
$isSignedUp = in_array($this->getDisplayName($this->getUsername()), $signedUsers);
$canChangeSignup = $isSignedUp
? $this->canSignOff($domDocument, $activityNode[0])
: $this->canSignup($domDocument, $activityNode[0]);
$activity = new Activity();
$activity->setId($id)
->setName($this->clearTextNode($h5Nodes[0]->textContent))
->setDescription($this->parseActivityDescription($activityNode[0]))
->setDate(new \DateTime(sprintf("%s %s", $matches[1], $matches[2])))
->setFinalEntry(new \DateTime($matches[3]))
->setAccountable(str_replace(
" ",
" ",
$matches[5]
? sprintf("%s (%s)", $matches[4], $matches[5])
: $matches[4])
)
->setSignedUsers($signedUsers)
->setIsSignedup($isSignedUp)
->setCanChangeSignup($canChangeSignup);
$this->parseActivityComments($commentNodes, $activity);
return $activity;
}
/**
* @param \DOMNode $element
* @return null|string
*/
private function parseActivityDescription(\DOMNode $element): ?string
{
$description = "";
$isContent = false;
/** @var \DOMElement $childNode */
foreach ($element->childNodes as $childNode) {
if ($childNode->nodeName == "hr") {
$isContent = true;
}
if ($childNode->nodeName == "form") {
break;
}
if ($childNode->nodeName == "span") {
break;
}
if ($childNode->nodeName == "script") {
break;
}
if ($isContent) {
$description .= $childNode->nodeName == "br"
? "\n"
: rtrim($childNode->textContent);
}
}
return $this->clearTextNode($description);
}
/**
* @param Document\NodeList $commentElements
* @param Activity $activity
*/
private function parseActivityComments(Document\NodeList $commentElements, Activity $activity)
{
/** @var \DOMElement $commentElement */
foreach ($commentElements as $commentElement) {
$divElements = $commentElement->getElementsByTagName("div");
$queryString = parse_url(
$commentElement->getElementsByTagName("a")->item(0)->getAttribute("href"),
PHP_URL_QUERY
);
parse_str($queryString, $queryParams);
preg_match(
"#(.*)\s/\s([0-9]{4}-[0-9]{2}-[0-9]{2})\s([0-9]{1,2}:[0-9]{1,2})#msi",
str_replace(" ", " ", $divElements->item(2)->textContent),
$matches
);
$user = new User();
$user->setDisplayName($matches[1])
->setUsername($queryParams['username']);
$comment = new Comment();
$comment
->setText($divElements->item(1)->textContent)
->setUser($user)
->setCreatedAt(new \DateTime(sprintf(
"%s %s",
$matches[2],
$matches[3]
)));
$activity->addComment($comment);
}
}
/**
* @param Document\NodeList $usersBlockText
* @return array
* @todo remove unsign button if it is present
*/
private function parseActivitySignedUsers(Document\NodeList $usersBlockText): array
{
$signed = [];
for ($i = 1; $i < $usersBlockText->count(); $i++) {
preg_match(
"#[0-9]+\.[^\p{L}]([\p{L}\s]+)#msiu",
$usersBlockText[$i]->textContent,
$usernameMatch
);
$signed[] = $usernameMatch[1];
}
$collator = new \Collator("hu_HU");
$collator->sort($signed);
return $signed;
}
/**
* Can sign off if there is no red span inside the node
* @param Document $domDocument
* @param \DOMNode $domNode
* @return bool
*/
private function canSignOff(Document $domDocument, \DOMNode $domNode): bool
{
$redSpanNodes = Document\Query::execute(
'./span[@class="red"]',
$domDocument,
Document\Query::TYPE_XPATH,
$domNode
);
return $redSpanNodes->count() == 0;
}
/**
* Can sign up if there is a form direct descendant
* @param Document $domDocument
* @param \DOMNode $domNode
* @return bool
*/
private function canSignUp(Document $domDocument, \DOMNode $domNode): bool
{
$formNodes = Document\Query::execute(
"./form",
$domDocument,
Document\Query::TYPE_XPATH,
$domNode
);
return $formNodes->count() == 1;
}
private function parseMainPage(string $htmlBody)
{
return false;
}
private function getDisplayName(string $username): string
{
$response = $this->doSkiesRequest("GET", sprintf(self::SKIES_PROFILE_URL, $username));
$profilePage = $response->getBody();
$domDocument = new Document($profilePage);
$h1Nodes = Document\Query::execute(
'//div[@class="box"]/h1',
$domDocument,
Document\Query::TYPE_XPATH
);
return $this->clearTextNode($h1Nodes[0]->textContent);
}
/**
* Clear junk from text nodes
*
* @param string $text
* @return string
*/
private function clearTextNode(string $text): string
{
$text = str_replace(" ", " ", $text);
$text = str_replace("–", "-", $text);
$text = preg_replace("#[ \t]+#msiu", " ", $text);
return trim($text, " \t\n\r\0\x0B" . chr(0xC2) . chr(0xA0));
}
/**
* Do an http request adding the Authorization header
*
* @param string $method
* @param string $url
* @param array $options
* @return ResponseInterface
*/
private function doSkiesRequest(string $method, string $url, $options = []): ResponseInterface
{
if ($this->authHeader == null) {
throw new MissingDependencyException("X-Passthru-Auth header is missing");
}
return $this->client
->request($method, $url, [
'headers' => [
'Authorization' => "Basic {$this->authHeader}",
]
] + $options);
}
/**
* @return string
*/
private function getUsername(): string
{
if (null == $this->authHeader) {
throw new MissingDependencyException("X-Passthru-Auth header is missing");
}
$decodedHeader = base64_decode($this->authHeader);
list($username) = explode(":", $decodedHeader);
return $username;
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Service;
use GuzzleHttp\Client;
use Interop\Container\ContainerInterface;
class SkiesClientServiceFactory
{
public function __invoke(ContainerInterface $container)
{
$httpClient = new Client([
'cookies' => true,
]);
return new SkiesClientService($httpClient);
}
}

View File

@@ -1,187 +0,0 @@
<?php
namespace App\Service;
use Interop\Container\ContainerInterface;
use Symfony\Component\CssSelector\CssSelectorConverter;
use Zend\Http\Client;
/**
* Class SkiesService
*
* @package App\Service
* @todo handle errors in http response
*/
class SkiesService
{
const BASE_URI = 'http://skies.sigmatechnology.se/';
/**
* @var Client
*/
private $httpClient;
/**
* @var ContainerInterface
*/
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
$this->httpClient = new Client();
$headers = $this->httpClient->getRequest()->getHeaders();
$headers->addHeaderLine('Authorization', 'Basic ' . $this->getAuthToken());
}
/**
* Get a list of all activities
*
* @return array
*/
public function getActivityList()
{
$this->httpClient->setUri(self::BASE_URI . "main.asp?rID=2");
$skiesResponse = $this->httpClient->send();
$skiesHtmlBody = $skiesResponse->getBody();
$cssToXpathConverter = new CssSelectorConverter();
$xpathQuery = $cssToXpathConverter->toXPath('html > body > div > div.row > div.col-md-10 > div.row > div.col-md-9 > div.box > table.table-striped > tr');
$doc = new \DOMDocument();
$doc->loadHTML($skiesHtmlBody);
$xpath = new \DOMXPath($doc);
$elements = $xpath->query($xpathQuery);
$parsed = [];
/** @var \DOMNode $domElement */
foreach ($elements as $domElement) {
if($domElement->childNodes->item(0)->nodeName != 'td') {
continue;
}
$url = $domElement->childNodes->item(0)->childNodes->item(0)->attributes->getNamedItem('href')->textContent;
preg_match("/aktid=([0-9]+)/msi", $url, $match);
$parsed[] = [
'id' => (int)$match[1],
'label' => $domElement->childNodes->item(0)->childNodes->item(0)->textContent,
'date' => $domElement->childNodes->item(2)->textContent,
'time' => trim($domElement->childNodes->item(3)->textContent, " \t\n\r\0\x0B\xc2\xa0"),
];
}
return $parsed;
}
/**
* Get a single activity
*
* @param int $id
* @return array
*/
public function getActivity(int $id)
{
$this->httpClient->setUri(self::BASE_URI . "main.asp?rID=2&alt=1&aktID=" . $id);
$skiesResponse = $this->httpClient->send();
$skiesHtmlBody = $skiesResponse->getBody();
$cssToXpathConverter = new CssSelectorConverter();
$xpathQuery = $cssToXpathConverter->toXPath('div.container > div.row > div.col-md-10 > div.row > div.col-md-9 > div.box');
$xpathCommentsQuery = $cssToXpathConverter->toXPath('div.container div.paragraph > div.onecomment');
$doc = new \DOMDocument();
$doc->loadHTML($skiesHtmlBody);
$xpath = new \DOMXPath($doc);
$elements = $xpath->query($xpathQuery);
$h5Elements = $elements->item(0)->getElementsByTagName('h5');
$title = $h5Elements->item(0)->textContent;
$detailDivElements = $elements->item(0)->getElementsByTagName('div');
$commentElements = $xpath->query($xpathCommentsQuery);
$eventDetails = $this->parseActivityDetails($detailDivElements->item(1)->textContent);
$eventDescription = $this->parseActivityDescription($elements->item(0));
$eventComments = $this->parseActivityComments($commentElements);
return [
'title' => $title,
'details' => $eventDetails,
'description' => $eventDescription,
'comments' => $eventComments,
];
}
/**
* @return null|string
* @todo real auth token from whatever...
*/
private function getAuthToken(): ?string
{
$config = $this->container->get('config');
return $config['authKey'];
}
/**
* Parse the activity information details returning the date, time finaly entry and the person accountable
*
* @param string $divText
* @return array|null
*/
private function parseActivityDetails(string $divText): ?array
{
preg_match("#Date:.*?([0-9]{4}-[0-9]{2}-[0-9]{2}).*?Time: ([0-9]{1,2}:[0-9]{2}).*?Final entry day:.*?([0-9]{4}-[0-9]{2}-[0-9]{2}).*?Accountable: (.*?) / ([0-9 +]*)#msi", $divText, $matches);
return [
'date' => $matches[1],
'time' => $matches[2],
'finalEntry' => $matches[3],
'accountable' => str_replace(" ", " ", $matches[5] ? sprintf("%s (%s)", $matches[4], $matches[5]) : $matches[4]),
];
}
/**
* Parses comment block. Comment section is pretty much unformatted text
* between a <hr> tag and a <form> tag in the passed $element
*
* @param \DOMElement $element
* @return null|string
*/
private function parseActivityDescription(\DOMElement $element): ?string
{
$description = "";
$isContent = false;
/** @var \DOMElement $childNode */
foreach($element->childNodes as $childNode) {
if ($childNode->nodeName == "hr") { $isContent = true; }
if ($childNode->nodeName == "form") { break; }
if ($isContent) {
$description .= $childNode->nodeName == "br"
? "\n"
: rtrim($childNode->textContent);
}
}
return trim($description);
}
/**
* Parse $commentElements
*
* @param \DOMNodeList $commentElements
* @return array|null
*/
private function parseActivityComments(\DOMNodeList $commentElements): ?array
{
$comments = [];
/** @var \DOMElement $commentElement */
foreach($commentElements as $commentElement) {
preg_match("#(.*)\s/\s([0-9]{4}-[0-9]{2}-[0-9]{2})\s([0-9]{1,2}:[0-9]{1,2})#msi", str_replace(" "," ", $commentElement->getElementsByTagName("div")->item(2)->textContent), $matches);
$comments[] = [
'comment' => $commentElement->getElementsByTagName("div")->item(1)->textContent,
'user' => $matches[1],
'date' => $matches[2],
'time' => $matches[3],
];
}
return $comments;
}
}

View File

@@ -1,13 +0,0 @@
<?php
namespace App\Service;
use Interop\Container\ContainerInterface;
class SkiesServiceFactory
{
public function __invoke(ContainerInterface $container)
{
return new SkiesService($container);
}
}