diff --git a/.env.dist b/.env.dist index 6d526f32..4caf09f1 100644 --- a/.env.dist +++ b/.env.dist @@ -28,6 +28,7 @@ TWITTER_CLIENT_SECRET=ThisTokenIsNotSoSecretChangeIt YOURLS_API_URL=http://sqi.be/yourls-api.php YOURLS_API_USERNAME=username YOURLS_API_PASSWORD=password +STATICMAPS_HOST=https://maps.caldera.cc/ ADMIN_PASSWORD=123 diff --git a/config/services.yaml b/config/services.yaml index 6da63814..34e2c355 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -7,6 +7,7 @@ parameters: yourls.api_url: '%env(YOURLS_HOSTNAME)%' yourls.api_username: '%env(YOURLS_USERNAME)%' yourls.api_password: '%env(YOURLS_PASSWORD)%' + staticmaps.host: '%env(STATICMAPS_HOST)%' assets_version: '%env(ASSETS_VERSION)%' router.request_context.host: 'luft.jetzt' router.request_context.scheme: 'https' @@ -27,6 +28,7 @@ services: $apiPassword: '%env(YOURLS_API_PASSWORD)%' $assetsVersion: '%assets_version%' $graphCacheDirectory: '%env(GRAPH_CACHE_DIRECTORY)%' + $staticmapsHost: '%staticmaps.host%' # makes classes in src/ available to be used as services # this creates a service per class whose id is the fully-qualified class name @@ -43,6 +45,11 @@ services: # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones + Sonata\SeoBundle\Seo\SeoPageInterface: + alias: sonata.seo.page.default + + App\StationLoader\StationLoader: ~ + App\Twitter\MessageFactory\MessageFactoryInterface: alias: App\Twitter\MessageFactory\ExtendedEmojiMessageFactory @@ -83,4 +90,4 @@ services: $producer: '@old_sound_rabbit_mq.luft_value_producer' App\Producer\Value\ValueProducerInterface: - alias: App\Producer\Value\CacheValueProducer \ No newline at end of file + alias: App\Producer\Value\CacheValueProducer diff --git a/src/Controller/DisplayController.php b/src/Controller/DisplayController.php index d7e005dc..5cadb9ce 100644 --- a/src/Controller/DisplayController.php +++ b/src/Controller/DisplayController.php @@ -16,6 +16,32 @@ class DisplayController extends AbstractController { public function indexAction(Request $request, SeoPage $seoPage, RequestConverterInterface $requestConverter, PollutionDataFactoryInterface $pollutionDataFactory, CityGuesserInterface $cityGuesser, Breadcrumbs $breadcrumbs, RouterInterface $router): Response { + /** @var Station $station */ + $station = $this->getDoctrine()->getRepository(Station::class)->findOneByStationCode($stationCode); + + if (!$station) { + throw $this->createNotFoundException(); + } + + $boxList = $pollutionDataFactory->setCoord($station)->createDecoratedBoxList(); + + if ($station->getCity()) { + $seoPage->setTitle(sprintf('Luftmesswerte für die Station %s in %s', $station->getStationCode(), $station->getCity()->getName())); + } else { + $seoPage->setTitle(sprintf('Luftmesswerte für die Station %s', $station->getStationCode())); + } + + $seoPage->setPreviewMap($station); + + return $this->render('Default/station.html.twig', [ + 'station' => $station, + 'boxList' => $boxList, + ]); + } + + public function indexAction(Request $request, SeoPage $seoPage, PollutionDataFactory $pollutionDataFactory, StationFinderInterface $stationFinder): Response + { + $coord = $this->getCoordByRequest($request); $coord = $requestConverter->getCoordByRequest($request); if (!$coord) { diff --git a/src/DependencyInjection/AppExtension.php b/src/DependencyInjection/AppExtension.php new file mode 100644 index 00000000..1ecf556c --- /dev/null +++ b/src/DependencyInjection/AppExtension.php @@ -0,0 +1,15 @@ +processConfiguration($configuration, $configs); + } +} diff --git a/src/DependencyInjection/Compiler/TwigSeoExtensionPass.php b/src/DependencyInjection/Compiler/TwigSeoExtensionPass.php new file mode 100644 index 00000000..8432ac89 --- /dev/null +++ b/src/DependencyInjection/Compiler/TwigSeoExtensionPass.php @@ -0,0 +1,19 @@ +hasDefinition('sonata.seo.twig.extension')) { + return; + } + + $container->removeDefinition('sonata.seo.twig.extension'); + } +} diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php new file mode 100644 index 00000000..e2913aaa --- /dev/null +++ b/src/DependencyInjection/Configuration.php @@ -0,0 +1,17 @@ +root('caldera'); + + return $treeBuilder; + } +} diff --git a/src/Entity/City.php b/src/Entity/City.php index aa068969..c6eb6557 100644 --- a/src/Entity/City.php +++ b/src/Entity/City.php @@ -2,6 +2,7 @@ namespace App\Entity; +use App\StaticMap\StaticMapableInterface; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; @@ -12,7 +13,7 @@ * @ORM\Table(name="city") * @JMS\ExclusionPolicy("ALL") */ -class City +class City implements StaticMapableInterface { /** * @ORM\Id @@ -46,6 +47,18 @@ class City */ protected $description; + /** + * @ORM\Column(type="float", nullable=false) + * @JMS\Expose() + */ + protected $latitude; + + /** + * @ORM\Column(type="float", nullable=false) + * @JMS\Expose() + */ + protected $longitude; + /** * @ORM\Column(type="string", nullable=true) * @JMS\Expose() @@ -93,6 +106,30 @@ public function setCreatedAt(\DateTime $createdAt): City return $this; } + public function getLatitude(): float + { + return $this->latitude; + } + + public function setLatitude(float $latitude): City + { + $this->latitude = $latitude; + + return $this; + } + + public function getLongitude(): float + { + return $this->longitude; + } + + public function setLongitude(float $longitude): City + { + $this->longitude = $longitude; + + return $this; + } + public function getName(): ?string { return $this->name; diff --git a/src/Entity/Station.php b/src/Entity/Station.php index fc5abe1d..ab27d4e3 100644 --- a/src/Entity/Station.php +++ b/src/Entity/Station.php @@ -2,6 +2,7 @@ namespace App\Entity; +use App\StaticMap\StaticMapableInterface; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Caldera\GeoBasic\Coord\Coord; @@ -17,7 +18,7 @@ * @UniqueEntity("stationCode") * @JMS\ExclusionPolicy("ALL") */ -class Station extends Coord +class Station extends Coord implements StaticMapableInterface { /** * @ORM\Id diff --git a/src/Kernel.php b/src/Kernel.php index 89e091b6..18aae47f 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -2,6 +2,7 @@ namespace App; +use App\DependencyInjection\Compiler\TwigSeoExtensionPass; use App\Air\AirQuality\PollutionLevel\PollutionLevelInterface; use App\Air\Measurement\MeasurementInterface; use App\DependencyInjection\Compiler\PollutionLevelCompilerPass; @@ -10,6 +11,7 @@ use App\DependencyInjection\Compiler\PollutantCompilerPass; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Kernel as BaseKernel; use Symfony\Component\Routing\RouteCollectionBuilder; @@ -54,6 +56,8 @@ protected function configureContainer(ContainerBuilder $container, LoaderInterfa $loader->load($confDir.'/services/*'.self::CONFIG_EXTS, 'glob'); $loader->load($confDir.'/{services}_'.$this->environment.self::CONFIG_EXTS, 'glob'); + $container->addCompilerPass(new TwigSeoExtensionPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 100); + $container->addCompilerPass(new PollutionLevelCompilerPass()); $container->registerForAutoconfiguration(PollutionLevelInterface::class)->addTag('pollution_level'); diff --git a/src/Migrations/Version20181002172100.php b/src/Migrations/Version20181002172100.php new file mode 100644 index 00000000..8ecdd3ff --- /dev/null +++ b/src/Migrations/Version20181002172100.php @@ -0,0 +1,28 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('ALTER TABLE city ADD latitude DOUBLE PRECISION NOT NULL, ADD longitude DOUBLE PRECISION NOT NULL'); + } + + public function down(Schema $schema) : void + { + // this down() migration is auto-generated, please modify it to your needs + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('ALTER TABLE city DROP latitude, DROP longitude'); + } +} diff --git a/src/SeoPage/SeoPage.php b/src/SeoPage/SeoPage.php index f29fdf69..d6a490c2 100644 --- a/src/SeoPage/SeoPage.php +++ b/src/SeoPage/SeoPage.php @@ -2,8 +2,23 @@ namespace App\SeoPage; +use App\StaticMap\UrlGenerator\UrlGeneratorInterface; +use Sonata\SeoBundle\Seo\SeoPageInterface; + class SeoPage extends AbstractSeoPage { + /** @var SeoPageInterface */ + protected $sonataSeoPage; + + /** @var UrlGeneratorInterface $urlGenerator */ + protected $urlGenerator; + + public function __construct(SeoPageInterface $sonataSeoPage, UrlGeneratorInterface $urlGenerator) + { + $this->sonataSeoPage = $sonataSeoPage; + $this->urlGenerator = $urlGenerator; + } + public function setTitle(string $title): SeoPageInterface { $this->sonataSeoPage @@ -32,6 +47,7 @@ public function setStandardPreviewPhoto(): SeoPageInterface return $this; } + public function setPreviewMap(StaticMapableInterface $staticMapable): SeoPage public function setOpenGraphPreviewPhoto(string $assetUrl): SeoPageInterface { $this->sonataSeoPage->addMeta('property', 'og:image', $this->asset($assetUrl)); @@ -42,6 +58,11 @@ public function setOpenGraphPreviewPhoto(string $assetUrl): SeoPageInterface public function setTwitterPreviewPhoto(string $assetUrl): SeoPageInterface { $this->sonataSeoPage + ->addMeta('property', 'og:image', $this->urlGenerator->generate($staticMapable, 600, 315), + ['escape' => false]) + ->addMeta('name', 'twitter:image', $this->urlGenerator->generate($staticMapable, 800, 320), + ['escape' => false]) + ->addMeta('name', 'twitter:card', 'summary_large_image'); ->addMeta('name', 'twitter:image', $this->asset($assetUrl)) ->addMeta('name', 'twitter:card', 'summary_large_image'); diff --git a/src/StaticMap/StaticMapableInterface.php b/src/StaticMap/StaticMapableInterface.php new file mode 100644 index 00000000..21ec9f22 --- /dev/null +++ b/src/StaticMap/StaticMapableInterface.php @@ -0,0 +1,7 @@ + '865x512', + ]; + + public function __construct(string $staticmapsHost) + { + $this->staticmapsHost = $staticmapsHost; + } +} diff --git a/src/StaticMap/UrlGenerator/UrlGenerator.php b/src/StaticMap/UrlGenerator/UrlGenerator.php new file mode 100644 index 00000000..9816076e --- /dev/null +++ b/src/StaticMap/UrlGenerator/UrlGenerator.php @@ -0,0 +1,67 @@ +staticmapsStation($object, $width, $height, $zoom); + } elseif ($object instanceof City) { + return $this->staticmapsCity($object, $width, $height, $zoom); + } + + return ''; + } + + public function staticmapsCity(City $city, int $width = null, int $height = null, int $zoom = null): string + { + $parameters = [ + 'markers' => sprintf('%f,%f,%s,%s,%s', $city->getLatitude(), $city->getLongitude(), 'circle', 'blue', 'university'), + ]; + + return $this->generateMapUrl($parameters, $width, $height, $zoom); + } + + public function staticmapsStation(Station $station, int $width = null, int $height = null, int $zoom = null): string + { + $parameters = [ + 'markers' => sprintf('%f,%f,%s,%s,%s', $station->getLatitude(), $station->getLongitude(), 'circle', 'blue', 'thermometer-full'), + ]; + + return $this->generateMapUrl($parameters, $width, $height, $zoom); + } + + protected function generateMapUrl(array $parameters = [], int $width = null, int $height = null, int $zoom = null): string + { + $viewParameters = []; + + if ($width && $height) { + $viewParameters['size'] = sprintf('%dx%d', $width, $height); + } + + if ($zoom) { + $viewParameters['zoom'] = sprintf('%d', $zoom); + } + + $parameters = array_merge($parameters, $this->defaultParameters, $viewParameters); + + return sprintf('%sstaticmap.php?%s', $this->staticmapsHost, $this->generateMapUrlParameters($parameters)); + } + + protected function generateMapUrlParameters(array $parameters = []): string + { + $list = []; + + foreach ($parameters as $key => $value) { + $list [] = sprintf('%s=%s', $key, $value); + } + + return implode('&', $list); + } +} diff --git a/src/StaticMap/UrlGenerator/UrlGeneratorInterface.php b/src/StaticMap/UrlGenerator/UrlGeneratorInterface.php new file mode 100644 index 00000000..77ee42d5 --- /dev/null +++ b/src/StaticMap/UrlGenerator/UrlGeneratorInterface.php @@ -0,0 +1,10 @@ +page->getMetas() as $type => $metas) { + foreach ((array) $metas as $name => $meta) { + list($content, $extras) = $meta; + + if (!empty($content)) { + $html .= sprintf("\n", + $type, + $this->normalize($name), + array_key_exists('escape', $extras) && $extras['escape'] === false ? $content : $this->normalize($content) + ); + } else { + $html .= sprintf("\n", + $type, + $this->normalize($name) + ); + } + } + } + + return $html; + } + + private function normalize($string): string + { + return htmlentities(strip_tags($string), ENT_COMPAT, $this->encoding); + } +}