From 6fa6c1ae891da1a67ffeeeb4764930a04cc3bc6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danyi=20D=C3=A1vid?= Date: Thu, 26 Jul 2018 09:02:48 +0200 Subject: [PATCH] * strict type fixes * news action added --- config/routes.php | 2 + deploy.php | 4 +- src/App/Action/ActivityAction.php | 2 +- src/App/Action/ActivitySignoffAction.php | 2 +- src/App/Action/ActivitySignupAction.php | 2 +- src/App/Action/NewsAction.php | 29 +++- src/App/Entity/News.php | 208 ++++++++++++++++++++++- src/App/Service/SkiesClientService.php | 166 +++++++++++++++++- 8 files changed, 405 insertions(+), 10 deletions(-) diff --git a/config/routes.php b/config/routes.php index 1adb64f..d4485cb 100644 --- a/config/routes.php +++ b/config/routes.php @@ -39,4 +39,6 @@ return function (Application $app, MiddlewareFactory $factory, ContainerInterfac $app->get('/api/activity[/{id:\d+}]', App\Action\ActivityAction::class, 'api.activity.get'); $app->get('/api/activity/signup/{id:\d+}', App\Action\ActivitySignupAction::class, 'api.activity.signup'); $app->get('/api/activity/signoff/{id:\d+}', App\Action\ActivitySignoffAction::class, 'api.activity.signoff'); + + $app->get('/api/news[/{id:\d+}]', App\Action\NewsAction::class, 'api.news.get'); }; diff --git a/deploy.php b/deploy.php index fc43469..19d4d3b 100644 --- a/deploy.php +++ b/deploy.php @@ -9,6 +9,8 @@ set('ssh_multiplexing', true); set('repository', 'ssh://gogs@gogs.ragnarok.yvan.hu:2206/yvan/skies-api.git'); set('shared_dirs', [ 'data/tmp', + 'data/logs', + 'data/cache', ]); set('shared_files', [ // 'config/autoload/doctrine.local.php', @@ -22,7 +24,7 @@ host('alfheim.ragnarok.yvan.hu') ->stage('production') ->user('yvan') ->forwardAgent() - ->set('php_service_name', 'php7.1-fpm') + ->set('php_service_name', 'php7.2-fpm') ->set('deploy_path', '/mnt/apps/skies-proxy-api'); desc('Restart PHP-FPM service'); diff --git a/src/App/Action/ActivityAction.php b/src/App/Action/ActivityAction.php index 59c1bc3..039a8b9 100644 --- a/src/App/Action/ActivityAction.php +++ b/src/App/Action/ActivityAction.php @@ -40,7 +40,7 @@ class ActivityAction extends AbstractAction */ public function get(ServerRequestInterface $request) : ResponseInterface { - $id = $request->getAttribute(self::IDENTIFIER_NAME); + $id = (int)$request->getAttribute(self::IDENTIFIER_NAME); $authHeader = $request->getHeaderLine("x-passthru-auth"); return new JsonResponse($this->skiesClient->setAuthHeader($authHeader)->getActivity($id)); } diff --git a/src/App/Action/ActivitySignoffAction.php b/src/App/Action/ActivitySignoffAction.php index 75077e0..f364227 100644 --- a/src/App/Action/ActivitySignoffAction.php +++ b/src/App/Action/ActivitySignoffAction.php @@ -27,7 +27,7 @@ class ActivitySignoffAction implements RequestHandlerInterface */ public function handle(ServerRequestInterface $request) : ResponseInterface { - $authHeader = $request->getHeaderLine("x-passthru-auth"); + $authHeader = (int)$request->getHeaderLine("x-passthru-auth"); $id = $request->getAttribute("id"); return new JsonResponse($this->skiesClient->setAuthHeader($authHeader)->signOffActivity($id)); } diff --git a/src/App/Action/ActivitySignupAction.php b/src/App/Action/ActivitySignupAction.php index 5c80e71..87433ee 100644 --- a/src/App/Action/ActivitySignupAction.php +++ b/src/App/Action/ActivitySignupAction.php @@ -27,7 +27,7 @@ class ActivitySignupAction implements RequestHandlerInterface */ public function handle(ServerRequestInterface $request) : ResponseInterface { - $authHeader = $request->getHeaderLine("x-passthru-auth"); + $authHeader = (int)$request->getHeaderLine("x-passthru-auth"); $id = $request->getAttribute("id"); return new JsonResponse($this->skiesClient->setAuthHeader($authHeader)->signUpActivity($id)); } diff --git a/src/App/Action/NewsAction.php b/src/App/Action/NewsAction.php index 5764831..63e3fcf 100644 --- a/src/App/Action/NewsAction.php +++ b/src/App/Action/NewsAction.php @@ -3,8 +3,12 @@ namespace App\Action; use App\Service\SkiesClientService; +use GuzzleHttp\Exception\GuzzleException; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Zend\Diactoros\Response\JsonResponse; -class NewsAction +class NewsAction extends AbstractAction { /** * @var SkiesClientService @@ -15,4 +19,27 @@ class NewsAction { $this->skiesClient = $skiesClient; } + + /** + * @param ServerRequestInterface $request + * @return ResponseInterface + * @throws GuzzleException + */ + public function getList(ServerRequestInterface $request) : ResponseInterface + { + $authHeader = $request->getHeaderLine("x-passthru-auth"); + return new JsonResponse($this->skiesClient->setAuthHeader($authHeader)->getNews()); + } + + /** + * @param ServerRequestInterface $request + * @return ResponseInterface + * @throws GuzzleException + */ + public function get(ServerRequestInterface $request) : ResponseInterface + { + $id = (int)$request->getAttribute(self::IDENTIFIER_NAME); + $authHeader = $request->getHeaderLine("x-passthru-auth"); + return new JsonResponse($this->skiesClient->setAuthHeader($authHeader)->getNewsItem($id), 200, [], 0); + } } diff --git a/src/App/Entity/News.php b/src/App/Entity/News.php index 5e47f51..7ee930d 100644 --- a/src/App/Entity/News.php +++ b/src/App/Entity/News.php @@ -2,7 +2,211 @@ namespace App\Entity; -class News -{ +use Doctrine\Common\Collections\ArrayCollection; +class News 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; + + public function __construct() + { + $this->comments = new ArrayCollection(); + } + + /** + * @return int + */ + public function getId(): int + { + return $this->id; + } + + /** + * @param int $id + * @return News + */ + public function setId(int $id): News + { + $this->id = $id; + return $this; + } + + /** + * @return string + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * @param string $name + * @return News + */ + public function setName(?string $name): News + { + $this->name = $name; + return $this; + } + + /** + * @return \DateTime + */ + public function getDate(): ?\DateTime + { + return $this->date; + } + + /** + * @param \DateTime $date + * @return News + */ + public function setDate(?\DateTime $date): News + { + $this->date = $date; + return $this; + } + + /** + * @return string + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @param string $description + * @return News + */ + public function setDescription(?string $description): News + { + $this->description = $description; + return $this; + } + + /** + * @return \DateTime + */ + public function getFinalEntry(): ?\DateTime + { + return $this->finalEntry; + } + + /** + * @param \DateTime $finalEntry + * @return News + */ + public function setFinalEntry(\DateTime $finalEntry): News + { + $this->finalEntry = $finalEntry; + return $this; + } + + /** + * @return string + */ + public function getAccountable(): ?string + { + return $this->accountable; + } + + /** + * @param string $accountable + * @return News + */ + public function setAccountable(string $accountable): News + { + $this->accountable = $accountable; + return $this; + } + + /** + * @return Comment[]|ArrayCollection + */ + public function getComments() + { + return $this->comments; + } + + /** + * @param Comment[]|ArrayCollection $comments + * @return News + */ + public function setComments($comments): News + { + $this->comments = $comments; + return $this; + } + + /** + * @param Comment $comment + * @return News + */ + public function addComment(Comment $comment): News + { + if (!$this->comments->contains($comment)) { + $this->comments->add($comment); + } + return $this; + } + + /** + * @param Comment $comment + * @return News + */ + public function removeComment(Comment $comment): News + { + if ($this->comments->contains($comment)) { + $this->comments->removeElement($comment); + } + return $this; + } + + 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(), + 'accountable' => $this->getAccountable(), + 'comments' => $this->getComments()->getValues(), + ]; + } } diff --git a/src/App/Service/SkiesClientService.php b/src/App/Service/SkiesClientService.php index f6afa05..21b3e3d 100644 --- a/src/App/Service/SkiesClientService.php +++ b/src/App/Service/SkiesClientService.php @@ -4,6 +4,7 @@ namespace App\Service; use App\Entity\Activity; use App\Entity\Comment; +use App\Entity\News; use App\Entity\User; use GuzzleHttp\Client; use GuzzleHttp\Cookie\CookieJarInterface; @@ -25,6 +26,7 @@ class SkiesClientService 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"; + const SKIES_NEWS_URL = "https://skies.sigmatechnology.se/main.asp?rID=200&coreID=%s"; /** @var Client */ private $client; @@ -90,13 +92,16 @@ class SkiesClientService */ public function setAuthHeader(string $authHeader): SkiesClientService { + if ($authHeader == '') { + throw new \InvalidArgumentException("Empty auth header", 500); + } list($this->authUser, $this->authPass) = explode(':', base64_decode($authHeader)); $this->cookieJar = new FileCookieJar("data/cache/".$this->authUser, true); return $this; } /** - * @return bool + * @return News[] * @throws \GuzzleHttp\Exception\GuzzleException */ public function getNews() @@ -389,9 +394,161 @@ class SkiesClientService return $formNodes->count() == 1; } + /** + * @param string $htmlBody + * @return News[] + * @throws \GuzzleHttp\Exception\GuzzleException + */ private function parseMainPage(string $htmlBody) { - return false; + $domDocument = new Document($htmlBody); + $newsNodes = Document\Query::execute( + "div.container div.row div.box div.one_block > a", + $domDocument, + Document\Query::TYPE_CSS + ); + + $news = []; + for ($i = 1; $i < $newsNodes->count(); $i++) { + $href = $newsNodes[$i]->attributes->getNamedItem("href")->textContent; + $queryString = parse_url($href, PHP_URL_QUERY); + parse_str($queryString, $queryParams); + if ($queryParams['rID'] == 200 && isset($queryParams['coreID'])) { + $news[] = $this->getNewsItem($queryParams["coreID"]); + } + } + return $news; + } + + /** + * @param int $id + * @return News + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getNewsItem(int $id): News + { + $response = $this->doSkiesRequest('GET', sprintf(self::SKIES_NEWS_URL, $id)); + $htmlBody = $response->getBody(); + return $this->parseNewsPage($htmlBody, $id); + } + + /** + * @param string $htmlBody + * @param int $id + * @return News + */ + private function parseNewsPage(string $htmlBody, int $id): News + { + $domDocument = new Document($htmlBody); + /** News body */ + $activityNode = Document\Query::execute( + "div.col-md-10 div.box", + $domDocument, + Document\Query::TYPE_CSS + ); + + /** News name */ + $h5Nodes = Document\Query::execute( + "./h5", + $domDocument, + Document\Query::TYPE_XPATH, + $activityNode[0] + ); + + /** Creator */ + $aNodes = Document\Query::execute( + "./a", + $domDocument, + Document\Query::TYPE_XPATH, + $activityNode[0] + ); + + $textNodes = Document\Query::execute( + "./a/following::text()", + $domDocument, + Document\Query::TYPE_XPATH, + $activityNode[0] + ); + preg_match("#\(Published: ([0-9]{4}-[0-9]{2}-[0-9]{2})\)#msi", $this->clearTextNode($textNodes[0]->textContent), $matches); + + $contentDivNodes = Document\Query::execute( + './/div[@class="row"]/div[@class="col-md-12"]', + $domDocument, + Document\Query::TYPE_XPATH, + $activityNode[0] + ); + + /** Comment block */ + $commentNodes = Document\Query::execute( + './/div[@class="onecomment"]', + $domDocument, + Document\Query::TYPE_XPATH, + $activityNode[0] + ); + + $news = new News(); + $news->setId($id) + ->setName($this->clearTextNode($h5Nodes[0]->textContent)) + ->setDescription($this->parseNewsDescription($contentDivNodes[0])) + ->setDate(isset($matches[1]) ? new \DateTime($matches[1]) : null) + ->setAccountable($this->clearTextNode($aNodes[0]->textContent)); +// $this->parseNewsComments($commentNodes, $news); + + return $news; + } + + /** + * @param \DOMNode $element + * @return null|string + */ + private function parseNewsDescription(\DOMNode $element): ?string + { + $description = ""; + /** @var \DOMElement $childNode */ + foreach ($element->childNodes as $childNode) { + $description .= $childNode->nodeName == "br" + ? "\n" + : rtrim($childNode->textContent); + } + return $this->clearTextNode($description); + } + + /** + * @param Document\NodeList $commentElements + * @param News $news + */ + private function parseNewsComments(Document\NodeList $commentElements, News $news) + { + /** @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] + ))); + $news->addComment($comment); + } } /** @@ -421,10 +578,13 @@ class SkiesClientService */ private function clearTextNode(string $text): string { + $text = str_replace("\r", "", $text); $text = str_replace(" ", " ", $text); $text = str_replace("–", "-", $text); +// $text = str_replace(chr(0xC2).chr(0x95), "-", $text); + $text = str_replace([chr(0xC2).chr(0x93), chr(0xC2).chr(0x94)], '"', $text); $text = preg_replace("#[ \t]+#msiu", " ", $text); - return trim($text, " \t\n\r\0\x0B" . chr(0xC2) . chr(0xA0)); + return trim($text, " \t\n\r\0\x0B " . chr(0xC2) . chr(0xA0)); } /**