Add events functionality to the website.

This commit is contained in:
Jonathan H. Wage
2019-05-11 19:22:00 -05:00
parent 711450becf
commit b5dc1a00db
110 changed files with 5084 additions and 620 deletions

View File

@@ -12,8 +12,15 @@ cache:
php:
- 7.2
services:
- mysql
before_install:
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available"
- mysql -e 'CREATE DATABASE doctrine_website_test;'
- sudo mysql -e "use mysql; update user set authentication_string=PASSWORD('VdtLtifRh4gt37T') where User='root'; update user set plugin='mysql_native_password'; FLUSH PRIVILEGES;"
- sudo mysql_upgrade -u root -pVdtLtifRh4gt37T
- sudo service mysql restart
install:
- rm composer.lock
@@ -37,11 +44,7 @@ jobs:
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{.disabled,}
- if [[ ! $(php -m | grep -si xdebug) ]]; then echo "xdebug required for coverage"; exit 1; fi
script:
- ./bin/console sync-repositories --env=test
- ./bin/console build-website-data --env=test
- ./bin/console build-docs --env=test
- ./bin/console build-website --env=test
- ./vendor/bin/phpunit --coverage-clover clover.xml
- ./phpunit --build-all --coverage-clover clover.xml
after_script:
- wget https://scrutinizer-ci.com/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover clover.xml
@@ -54,4 +57,4 @@ jobs:
- stage: Code Quality
env: STATIC_ANALYSIS
install: travis_retry composer install --prefer-dist
script: vendor/bin/phpstan analyse
script: ./vendor/bin/phpstan analyse

View File

@@ -15,13 +15,18 @@
"algolia/algoliasearch-client-php": "^1.27",
"cache/doctrine-adapter": "^1.0",
"doctrine/inflector": "^1.3",
"doctrine/migrations": "^2.0",
"doctrine/orm": "^2.6",
"doctrine/rst-parser": "^0.1",
"doctrine/skeleton-mapper": "^1.0",
"doctrine/static-website-generator": "^1.0",
"erusev/parsedown": "^1.7",
"knplabs/github-api": "^2.10",
"pelago/emogrifier": "^2.1",
"php-http/guzzle6-adapter": "^1.1",
"scrivo/highlight.php": "^9.14",
"sendgrid/sendgrid": "^7.3",
"stripe/stripe-php": "^6.34",
"symfony/config": "^4.2",
"symfony/console": "^4.2",
"symfony/dependency-injection": "^4.2",
@@ -38,8 +43,9 @@
"phpstan/phpstan-phpunit": "^0.10",
"phpstan/phpstan-strict-rules": "^0.10",
"phpunit/phpunit": "^7.0",
"symfony/css-selector": "^4.0",
"symfony/dom-crawler": "^4.0",
"symfony/css-selector": "^4.0"
"symfony/var-dumper": "^4.2"
},
"config": {
"sort-packages": true

1051
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,9 @@
parameters:
doctrine.website.algolia.app_id: 'YVYTFT9XMW'
doctrine.website.algolia.admin_api_key: '1234'
doctrine.website.stripe.publishable_key: 'pk_test_V7rmF8ac3GbakeSJYvp4u0a8003RPO66vC'
doctrine.website.stripe.secret_key: ''
doctrine.website.send_grid.api_key: ''
doctrine.website.webpack_build_dir: '%doctrine.website.root_dir%/.webpack-build'
doctrine.website.projects_dir: '%doctrine.website.root_dir%/projects'
@@ -10,7 +13,8 @@ parameters:
doctrine.website.title: Doctrine
doctrine.website.subtitle: PHP Database Tools
doctrine.website.url: http://lcl.doctrine-project.org
doctrine.website.url: https://lcl.doctrine-project.org
doctrine.website.assets_url: https://www.doctrine-project.org
doctrine.website.keywords: [php, mysql, orm, dbal, database, mongodb, odm, annotations, migrations, cache, couchdb]
doctrine.website.description: >
The Doctrine Project is the home to several PHP libraries primarily

View File

@@ -4,3 +4,4 @@ imports:
parameters:
doctrine.website.url: 'https://www.doctrine-project.org'
doctrine.website.google_analytics_tracking_id: 'UA-288343-7'
doctrine.website.stripe.publishable_key: 'pk_live_54QFCgqHDYHZ1tFxg95JhYRR00kICoxvNS'

View File

@@ -1,6 +1,9 @@
imports:
- { resource: config.yml }
parameters:
doctrine.website.mysql.password: 'VdtLtifRh4gt37T'
services:
Doctrine\Website\Github\GithubProjectContributors:
alias: Doctrine\Website\Github\TestGithubProjectContributors

View File

@@ -23,6 +23,32 @@ parameters:
defaults:
title: RST Example
events:
path: /events.html
controller: Doctrine\Website\Controllers\EventsController::index
defaults:
menuSlug: events
event_suggest:
path: /events/suggest.html
controller: Doctrine\Website\Controllers\EventsController::suggest
defaults:
menuSlug: events
event:
path: /events/{id}/{slug}.html
controller: Doctrine\Website\Controllers\EventsController::view
provider: Doctrine\Website\Requests\EventRequests::getEvents
defaults:
menuSlug: events
event_cfp:
path: /events/{id}/{slug}/cfp.html
controller: Doctrine\Website\Controllers\EventsController::cfp
provider: Doctrine\Website\Requests\EventRequests::getEvents
defaults:
menuSlug: events
styleguide:
path: /styleguide.html
defaults:
@@ -46,6 +72,8 @@ parameters:
path: /partners/{slug}.html
controller: Doctrine\Website\Controllers\PartnersController::view
provider: Doctrine\Website\Requests\PartnerRequests::getPartners
defaults:
menuSlug: partners
consulting:
path: /consulting.html

View File

@@ -3,6 +3,11 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="doctrine.website.mysql.user">root</parameter>
<parameter key="doctrine.website.mysql.password"></parameter>
</parameters>
<services>
<defaults autowire="true" autoconfigure="true" public="false">
<bind key="$rootDir">%doctrine.website.root_dir%</bind>
@@ -13,13 +18,10 @@
<bind key="$sourceDir">%doctrine.website.source_dir%</bind>
<bind key="$projectsData">%doctrine.website.projects_data%</bind>
<bind key="$webpackBuildDir">%doctrine.website.webpack_build_dir%</bind>
<bind key="$teamMembers">%doctrine.website.team_members%</bind>
<bind key="$templatesDir">%doctrine.website.templates_dir%</bind>
<bind key="$doctrineUsers">%doctrine.website.doctrine_users%</bind>
<bind key="$sponsors">%doctrine.website.sponsors%</bind>
<bind key="$partners">%doctrine.website.partners%</bind>
<bind key="$projectIntegrationTypes">%doctrine.website.project_integration.types%</bind>
<bind key="$routes">%doctrine.website.routes%</bind>
<bind key="$stripePublishableKey">%doctrine.website.stripe.publishable_key%</bind>
</defaults>
<prototype namespace="Doctrine\Website\" resource="../lib/*" />
@@ -55,6 +57,20 @@
</argument>
</service>
<service id="Pelago\Emogrifier">
<call method="disableStyleBlocksParsing" />
</service>
<service id="Doctrine\Website\Email\RenderEmail" autowire="false">
<argument type="service" id="Pelago\Emogrifier" />
<argument>%doctrine.website.templates_dir%</argument>
<argument type="collection">
<argument type="service" id="Doctrine\Website\Twig\MainExtension" />
<argument type="service" id="Doctrine\Website\Twig\ProjectExtension" />
<argument type="service" id="Doctrine\StaticWebsiteGenerator\Twig\RoutingExtension" />
</argument>
</service>
<service id="Doctrine\StaticWebsiteGenerator\Twig\TwigRenderer" alias="Doctrine\StaticWebsiteGenerator\Twig\StringTwigRenderer" />
<service id="Doctrine\Website\Github\GithubProjectContributors" alias="Doctrine\Website\Github\ProdGithubProjectContributors" />
@@ -121,6 +137,9 @@
<service id="Doctrine\Common\Cache\FilesystemCache" autowire="false">
<argument>%doctrine.website.cache_dir%/fscache</argument>
</service>
<service id="SendGrid" class="SendGrid" autowire="true">
<argument>%doctrine.website.send_grid.api_key%</argument>
</service>
<service id="Doctrine\StaticWebsiteGenerator\Controller\ControllerProvider">
<argument type="collection">
@@ -128,6 +147,7 @@
<argument type="service" id="Doctrine\Website\Controllers\BlogController" />
<argument type="service" id="Doctrine\Website\Controllers\ConsultingController" />
<argument type="service" id="Doctrine\Website\Controllers\DocumentationController" />
<argument type="service" id="Doctrine\Website\Controllers\EventsController" />
<argument type="service" id="Doctrine\Website\Controllers\HomepageController" />
<argument type="service" id="Doctrine\Website\Controllers\PartnersController" />
<argument type="service" id="Doctrine\Website\Controllers\ProjectController" />
@@ -140,6 +160,7 @@
<service id="Doctrine\StaticWebsiteGenerator\Request\RequestCollectionProvider">
<argument type="collection">
<argument type="service" id="Doctrine\Website\Requests\ContributorRequests" />
<argument type="service" id="Doctrine\Website\Requests\EventRequests" />
<argument type="service" id="Doctrine\Website\Requests\PartnerRequests" />
<argument type="service" id="Doctrine\Website\Requests\ProjectRequests" />
<argument type="service" id="Doctrine\Website\Requests\ProjectVersionRequests" />
@@ -151,7 +172,7 @@
<argument>%doctrine.website.algolia.admin_api_key%</argument>
</service>
<service id="Doctrine\StaticWebsiteGenerator\Site">
<service id="Doctrine\Website\Site">
<argument>%doctrine.website.title%</argument>
<argument>%doctrine.website.subtitle%</argument>
<argument>%doctrine.website.url%</argument>
@@ -159,8 +180,11 @@
<argument>%doctrine.website.description%</argument>
<argument>%doctrine.website.env%</argument>
<argument>%doctrine.website.google_analytics_tracking_id%</argument>
<argument>%doctrine.website.assets_url%</argument>
</service>
<service id="Doctrine\StaticWebsiteGenerator\Site" alias="Doctrine\Website\Site" />
<service id="Doctrine\RST\Kernel">
<argument type="service" id="Doctrine\RST\Configuration" />
@@ -208,7 +232,7 @@
</argument>
<argument type="service" id="Doctrine\SkeletonMapper\ObjectFactory" />
<argument type="service">
<service class="Doctrine\SkeletonMapper\Hydrator\BasicObjectHydrator">
<service class="Doctrine\Website\Hydrators\BlogPostHydrator">
<argument type="service" id="Doctrine\SkeletonMapper\ObjectManager" />
</service>
</argument>
@@ -216,7 +240,7 @@
<argument>Doctrine\Website\Model\BlogPost</argument>
</service>
<service id="Doctrine\Website\Repositories\ContributorRepository" autowire="false">
<service id="Doctrine\Website\Repositories\ContributorRepository" autowire="false" public="true">
<argument type="service" id="Doctrine\SkeletonMapper\ObjectManager" />
<argument type="service">
<service class="Doctrine\SkeletonMapper\DataSource\DataSourceObjectDataRepository" autowire="false">
@@ -227,7 +251,7 @@
</argument>
<argument type="service" id="Doctrine\SkeletonMapper\ObjectFactory" />
<argument type="service">
<service class="Doctrine\SkeletonMapper\Hydrator\BasicObjectHydrator">
<service class="Doctrine\Website\Hydrators\ContributorHydrator">
<argument type="service" id="Doctrine\SkeletonMapper\ObjectManager" />
</service>
</argument>
@@ -240,13 +264,17 @@
<argument type="service">
<service class="Doctrine\SkeletonMapper\DataSource\DataSourceObjectDataRepository" autowire="false">
<argument type="service" id="Doctrine\SkeletonMapper\ObjectManager" />
<argument type="service" id="Doctrine\Website\DataSources\DoctrineUsers" />
<argument type="service">
<service class="Doctrine\Website\DataSources\ArrayDataSource">
<argument>%doctrine.website.doctrine_users%</argument>
</service>
</argument>
<argument>Doctrine\Website\Model\DoctrineUser</argument>
</service>
</argument>
<argument type="service" id="Doctrine\SkeletonMapper\ObjectFactory" />
<argument type="service">
<service class="Doctrine\SkeletonMapper\Hydrator\BasicObjectHydrator">
<service class="Doctrine\Website\Hydrators\DoctrineUserHydrator">
<argument type="service" id="Doctrine\SkeletonMapper\ObjectManager" />
</service>
</argument>
@@ -259,13 +287,17 @@
<argument type="service">
<service class="Doctrine\SkeletonMapper\DataSource\DataSourceObjectDataRepository" autowire="false">
<argument type="service" id="Doctrine\SkeletonMapper\ObjectManager" />
<argument type="service" id="Doctrine\Website\DataSources\Sponsors" />
<argument type="service">
<service class="Doctrine\Website\DataSources\ArrayDataSource">
<argument>%doctrine.website.sponsors%</argument>
</service>
</argument>
<argument>Doctrine\Website\Model\Sponsor</argument>
</service>
</argument>
<argument type="service" id="Doctrine\SkeletonMapper\ObjectFactory" />
<argument type="service">
<service class="Doctrine\SkeletonMapper\Hydrator\BasicObjectHydrator">
<service class="Doctrine\Website\Hydrators\SponsorHydrator">
<argument type="service" id="Doctrine\SkeletonMapper\ObjectManager" />
</service>
</argument>
@@ -278,13 +310,17 @@
<argument type="service">
<service class="Doctrine\SkeletonMapper\DataSource\DataSourceObjectDataRepository" autowire="false">
<argument type="service" id="Doctrine\SkeletonMapper\ObjectManager" />
<argument type="service" id="Doctrine\Website\DataSources\Partners" />
<argument type="service">
<service class="Doctrine\Website\DataSources\ArrayDataSource">
<argument>%doctrine.website.partners%</argument>
</service>
</argument>
<argument>Doctrine\Website\Model\Partner</argument>
</service>
</argument>
<argument type="service" id="Doctrine\SkeletonMapper\ObjectFactory" />
<argument type="service">
<service class="Doctrine\SkeletonMapper\Hydrator\BasicObjectHydrator">
<service class="Doctrine\Website\Hydrators\PartnerHydrator">
<argument type="service" id="Doctrine\SkeletonMapper\ObjectManager" />
</service>
</argument>
@@ -303,7 +339,7 @@
</argument>
<argument type="service" id="Doctrine\SkeletonMapper\ObjectFactory" />
<argument type="service">
<service class="Doctrine\SkeletonMapper\Hydrator\BasicObjectHydrator">
<service class="Doctrine\Website\Hydrators\ProjectHydrator">
<argument type="service" id="Doctrine\SkeletonMapper\ObjectManager" />
</service>
</argument>
@@ -322,7 +358,7 @@
</argument>
<argument type="service" id="Doctrine\SkeletonMapper\ObjectFactory" />
<argument type="service">
<service class="Doctrine\SkeletonMapper\Hydrator\BasicObjectHydrator">
<service class="Doctrine\Website\Hydrators\ProjectContributorHydrator">
<argument type="service" id="Doctrine\SkeletonMapper\ObjectManager" />
</service>
</argument>
@@ -341,7 +377,7 @@
</argument>
<argument type="service" id="Doctrine\SkeletonMapper\ObjectFactory" />
<argument type="service">
<service class="Doctrine\SkeletonMapper\Hydrator\BasicObjectHydrator">
<service class="Doctrine\Website\Hydrators\SitemapPageHydrator">
<argument type="service" id="Doctrine\SkeletonMapper\ObjectManager" />
</service>
</argument>
@@ -354,13 +390,17 @@
<argument type="service">
<service class="Doctrine\SkeletonMapper\DataSource\DataSourceObjectDataRepository" autowire="false">
<argument type="service" id="Doctrine\SkeletonMapper\ObjectManager" />
<argument type="service" id="Doctrine\Website\DataSources\TeamMembers" />
<argument type="service">
<service class="Doctrine\Website\DataSources\ArrayDataSource">
<argument>%doctrine.website.team_members%</argument>
</service>
</argument>
<argument>Doctrine\Website\Model\TeamMember</argument>
</service>
</argument>
<argument type="service" id="Doctrine\SkeletonMapper\ObjectFactory" />
<argument type="service">
<service class="Doctrine\SkeletonMapper\Hydrator\BasicObjectHydrator">
<service class="Doctrine\Website\Hydrators\TeamMemberHydrator">
<argument type="service" id="Doctrine\SkeletonMapper\ObjectManager" />
</service>
</argument>
@@ -368,6 +408,31 @@
<argument>Doctrine\Website\Model\TeamMember</argument>
</service>
<service id="Doctrine\Website\Repositories\EventRepository" autowire="false" public="true">
<argument type="service" id="Doctrine\SkeletonMapper\ObjectManager" />
<argument type="service">
<service class="Doctrine\SkeletonMapper\DataSource\DataSourceObjectDataRepository" autowire="false">
<argument type="service" id="Doctrine\SkeletonMapper\ObjectManager" />
<argument type="service">
<service class="Doctrine\Website\DataSources\ArrayDataSource">
<argument>%doctrine.website.events%</argument>
</service>
</argument>
<argument>Doctrine\Website\Model\Event</argument>
</service>
</argument>
<argument type="service" id="Doctrine\SkeletonMapper\ObjectFactory" />
<argument type="service">
<service class="Doctrine\Website\Hydrators\EventHydrator">
<argument type="service" id="Doctrine\SkeletonMapper\ObjectManager" />
<argument type="service" id="Doctrine\Website\Model\Entity\EventParticipantRepository" />
<argument>%doctrine.website.env%</argument>
</service>
</argument>
<argument type="service" id="Doctrine\Common\EventManager" />
<argument>Doctrine\Website\Model\Event</argument>
</service>
<service id="Doctrine\SkeletonMapper\ObjectRepository\ObjectRepositoryFactory" autowire="false">
<call method="addObjectRepository">
<argument>Doctrine\Website\Model\BlogPost</argument>
@@ -409,6 +474,11 @@
<argument type="service" id="Doctrine\Website\Repositories\TeamMemberRepository" />
</call>
<call method="addObjectRepository">
<argument>Doctrine\Website\Model\Event</argument>
<argument type="service" id="Doctrine\Website\Repositories\EventRepository" />
</call>
<call method="addObjectRepository">
<argument>Doctrine\Website\Model\SitemapPage</argument>
<argument type="service" id="Doctrine\Website\Repositories\SitemapPageRepository" />
@@ -422,5 +492,50 @@
<argument type="service" id="Doctrine\SkeletonMapper\Mapping\ClassMetadataFactory" />
<argument type="service" id="Doctrine\Common\EventManager" />
</service>
<service id="Doctrine\ORM\Configuration">
<factory class="Doctrine\ORM\Tools\Setup" method="createAnnotationMetadataConfiguration" />
<argument type="collection">
<argument>%doctrine.website.root_dir%/lib/Model/Entity</argument>
</argument>
<argument>%doctrine.website.debug%</argument>
</service>
<service id="Doctrine\ORM\EntityManager">
<factory class="Doctrine\ORM\EntityManager" method="create" />
<argument type="service" id="Doctrine\DBAL\Connection" />
<argument type="service" id="Doctrine\ORM\Configuration" />
</service>
<service id="Doctrine\Website\Model\Entity\EventParticipantRepository">
<factory service="Doctrine\ORM\EntityManager" method="getRepository" />
<argument>Doctrine\Website\Model\Entity\EventParticipant</argument>
</service>
<service id="Doctrine\DBAL\Connection">
<factory class="Doctrine\DBAL\DriverManager" method="getConnection" />
<argument type="collection">
<argument key="dbname">doctrine_website_%doctrine.website.env%</argument>
<argument key="user">%doctrine.website.mysql.user%</argument>
<argument key="password">%doctrine.website.mysql.password%</argument>
<argument key="host">localhost</argument>
<argument key="driver">pdo_mysql</argument>
</argument>
</service>
<service id="Doctrine\Migrations\Configuration\Configuration">
<call method="setName">
<argument>Doctrine Website Migrations</argument>
</call>
<call method="setMigrationsNamespace">
<argument>Doctrine\Website\Migrations</argument>
</call>
<call method="setAllOrNothing">
<argument>true</argument>
</call>
<call method="setMigrationsDirectory">
<argument>%doctrine.website.root_dir%/lib/Migrations</argument>
</call>
</service>
</services>
</container>

99
data/events.yml Normal file
View File

@@ -0,0 +1,99 @@
parameters:
# New events go at the top and get an incremented ID.
doctrine.website.events:
-
id: 3
sku:
test: 'sku_F4LUc9SGRiCBUK'
prod: 'sku_F4M3RdEwiscrtD'
name: 'Getting started with Doctrine MongoDB ODM'
slug: 'getting-started-with-doctrine-mongodb-odm'
price: 5.00
joinUrl: 'https://zoom.us/webinar/register/9215578873469/WN_fwSHnCzCQ7WO_8Tao5rr7Q'
startDate: '2019-07-16'
endDate: '2019-07-16'
registrationStartDate: '2019-05-01'
registrationEndDate: '2019-07-16'
sponsors: []
speakers:
-
name: 'alcaeus'
topic: 'Getting started with Doctrine MongoDB ODM'
topicSlug: 'getting-started-with-doctrine-mongodb-odm'
description: >
You've used Doctrine ORM but never used MongoDB? With MongoDB ODM you can use familiar
tools with a different kind of database. This webinar will focus on getting MongoDB
set up and designing our first documents for MongoDB. We'll cover differences in schema
design between relational databases and MongoDB and also use more advanced features like
aggregation pipelines and document validation.
schedule:
-
topicSlug: 'getting-started-with-doctrine-mongodb-odm'
startDate: '2019-07-16 11:00'
endDate: '2019-07-16 12:00'
-
id: 2
sku:
test: 'sku_F4IaFRrVzZ4m4G'
prod: 'sku_F4M21kt1HC36by'
name: 'Doctrine for Beginners'
slug: 'doctrine-for-beginners'
price: 5.00
joinUrl: 'https://zoom.us/webinar/register/1315578887109/WN_4yo28dTASHyTqibhpuvo-Q'
startDate: '2019-05-28'
endDate: '2019-05-28'
registrationStartDate: '2019-05-01'
registrationEndDate: '2019-05-27'
sponsors: []
speakers:
-
name: 'jwage'
topic: 'Doctrine for Beginners'
topicSlug: 'doctrine-for-beginners'
description: >
Come to this talk prepared to learn about the Doctrine PHP open source project.
The Doctrine project has been around for over a decade and has evolved from database
abstraction software that dates back to the PEAR days. The packages provided by the
Doctrine project have been downloaded almost a billion times from packagist. In
this talk we will take you through how to get started with Doctrine and how to take
advantage of some of the more advanced features.
schedule:
-
topicSlug: 'doctrine-for-beginners'
startDate: '2019-05-28 11:00'
endDate: '2019-05-28 12:00'
-
id: 1
sku:
test: 'sku_F4LV13ZF4c5aUn'
prod: 'sku_F4M2PSGAcfOGyO'
name: 'PHP Internals for the Inquisitive Developer'
slug: 'php-internals-for-the-inquisitive-developer'
price: 5.00
joinUrl: 'https://zoom.us/webinar/register/8515577702351/WN_OBdv-AOHRWyNeIC2FXZ-IQ'
startDate: '2019-09-10'
endDate: '2019-09-10'
registrationStartDate: '2019-05-01'
registrationEndDate: '2019-09-10'
sponsors: []
speakers:
-
name: 'jmikola'
topic: 'PHP Internals for the Inquisitive Developer'
topicSlug: 'php-internals-for-the-inquisitive-developer'
description: >
Even if you have no intention of becoming a PHP core developer or creating a PECL
extension, cursory knowledge of PHP's inner workings can prove useful. This session
will examine the lifecycle of a PHP request and equip you with essential knowledge
and tools that can be used to diagnose the occasional segfault or language bug,
decipher what a poorly documented SPL class actually does, and confidently answer why
a PHP or C implementation is most warranted for a given problem.
schedule:
-
topicSlug: 'php-internals-for-the-inquisitive-developer'
startDate: '2019-09-10 11:00'
endDate: '2019-09-10 12:00'

View File

@@ -4,19 +4,34 @@ declare(strict_types=1);
namespace Doctrine\Website;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Tools\Console\Command as DBALCommand;
use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper;
use Doctrine\Migrations\Configuration\Configuration as MigrationsConfiguration;
use Doctrine\Migrations\Tools\Console\Command as MigrationsCommand;
use Doctrine\Migrations\Tools\Console\Helper\ConfigurationHelper;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Tools\Console\Command as ORMCommand;
use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper;
use Doctrine\Website\Commands\BuildAllCommand;
use Doctrine\Website\Commands\BuildDocsCommand;
use Doctrine\Website\Commands\BuildWebsiteCommand;
use Doctrine\Website\Commands\BuildWebsiteDataCommand;
use Doctrine\Website\Commands\ClearBuildCacheCommand;
use Doctrine\Website\Commands\DeployCommand;
use Doctrine\Website\Commands\EventParticipantsCommand;
use Doctrine\Website\Commands\SyncRepositoriesCommand;
use Stripe;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Console\Application as BaseApplication;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use function date_default_timezone_set;
use function file_exists;
use function getenv;
use function realpath;
@@ -27,40 +42,72 @@ class Application
/** @var BaseApplication */
private $application;
/** @var BuildDocsCommand */
private $buildDocsCommand;
/** @var BuildWebsiteCommand */
private $buildWebsiteCommand;
/** @var BuildWebsiteDataCommand */
private $buildWebsiteDataCommand;
/** @var ClearBuildCacheCommand */
private $clearBuildCacheCommand;
/** @var DeployCommand */
private $deployCommand;
/** @var SyncRepositoriesCommand */
private $syncRepositoriesCommand;
public function __construct(
BaseApplication $application,
EntityManager $em,
Connection $connection,
MigrationsConfiguration $migrationsConfiguration,
BuildAllCommand $buildAllCommand,
BuildDocsCommand $buildDocsCommand,
BuildWebsiteCommand $buildWebsiteCommand,
BuildWebsiteDataCommand $buildWebsiteDataCommand,
ClearBuildCacheCommand $clearBuildCacheCommand,
DeployCommand $deployCommand,
SyncRepositoriesCommand $syncRepositoriesCommand
SyncRepositoriesCommand $syncRepositoriesCommand,
EventParticipantsCommand $eventParticipantsCommand
) {
$this->application = $application;
$this->buildDocsCommand = $buildDocsCommand;
$this->buildWebsiteCommand = $buildWebsiteCommand;
$this->buildWebsiteDataCommand = $buildWebsiteDataCommand;
$this->clearBuildCacheCommand = $clearBuildCacheCommand;
$this->deployCommand = $deployCommand;
$this->syncRepositoriesCommand = $syncRepositoriesCommand;
$this->application = $application;
$this->application->add($buildAllCommand);
$this->application->add($buildDocsCommand);
$this->application->add($buildWebsiteCommand);
$this->application->add($buildWebsiteDataCommand);
$this->application->add($clearBuildCacheCommand);
$this->application->add($deployCommand);
$this->application->add($syncRepositoriesCommand);
$this->application->add($eventParticipantsCommand);
$this->application->setHelperSet(new HelperSet([
'question' => new QuestionHelper(),
'db' => new ConnectionHelper($connection),
'em' => new EntityManagerHelper($em),
'configuration' => new ConfigurationHelper($connection, $migrationsConfiguration),
]));
$this->application->addCommands([
// DBAL Commands
new DBALCommand\ReservedWordsCommand(),
new DBALCommand\RunSqlCommand(),
// ORM Commands
new ORMCommand\ClearCache\CollectionRegionCommand(),
new ORMCommand\ClearCache\EntityRegionCommand(),
new ORMCommand\ClearCache\MetadataCommand(),
new ORMCommand\ClearCache\QueryCommand(),
new ORMCommand\ClearCache\QueryRegionCommand(),
new ORMCommand\ClearCache\ResultCommand(),
new ORMCommand\SchemaTool\CreateCommand(),
new ORMCommand\SchemaTool\UpdateCommand(),
new ORMCommand\SchemaTool\DropCommand(),
new ORMCommand\EnsureProductionSettingsCommand(),
new ORMCommand\GenerateProxiesCommand(),
new ORMCommand\RunDqlCommand(),
new ORMCommand\ValidateSchemaCommand(),
new ORMCommand\InfoCommand(),
new ORMCommand\MappingDescribeCommand(),
// Migrations Commands
new MigrationsCommand\DumpSchemaCommand(),
new MigrationsCommand\ExecuteCommand(),
new MigrationsCommand\GenerateCommand(),
new MigrationsCommand\LatestCommand(),
new MigrationsCommand\MigrateCommand(),
new MigrationsCommand\RollupCommand(),
new MigrationsCommand\StatusCommand(),
new MigrationsCommand\VersionCommand(),
new MigrationsCommand\UpToDateCommand(),
new MigrationsCommand\DiffCommand(),
]);
}
public function run(InputInterface $input) : int
@@ -74,43 +121,49 @@ class Application
);
$this->application->getDefinition()->addOption($inputOption);
$this->application->add($this->buildDocsCommand);
$this->application->add($this->buildWebsiteCommand);
$this->application->add($this->buildWebsiteDataCommand);
$this->application->add($this->clearBuildCacheCommand);
$this->application->add($this->deployCommand);
$this->application->add($this->syncRepositoriesCommand);
return $this->application->run($input);
}
public function getConsoleApplication() : BaseApplication
{
return $this->application;
}
public static function getContainer(string $env) : ContainerBuilder
{
$container = new ContainerBuilder();
$container->setParameter('doctrine.website.env', $env);
$container->setParameter('doctrine.website.debug', $env !== 'prod');
$container->setParameter('doctrine.website.root_dir', realpath(__DIR__ . '/..'));
$container->setParameter('doctrine.website.config_dir', realpath(__DIR__ . '/../config'));
$container->setParameter('doctrine.website.cache_dir', realpath(__DIR__ . '/../cache'));
$container->setParameter('doctrine.website.github.http_token', getenv('doctrine_website_github_http_token'));
$loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../config'));
$loader->load('services.xml');
$xmlConfigLoader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../config'));
$xmlConfigLoader->load('services.xml');
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../config'));
$loader->load('partners.yml');
$loader->load('projects.yml');
$loader->load('routes.yml');
$loader->load('sponsors.yml');
$loader->load('team_members.yml');
$yamlConfigLoader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../config'));
$yamlConfigLoader->load('routes.yml');
$loader->load(sprintf('config_%s.yml', $env));
$yamlConfigLoader->load(sprintf('config_%s.yml', $env));
if (file_exists($container->getParameter('doctrine.website.config_dir') . '/local.yml')) {
$loader->load('local.yml');
$yamlConfigLoader->load('local.yml');
}
$dataLoader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../data'));
$dataLoader->load('events.yml');
$dataLoader->load('partners.yml');
$dataLoader->load('projects.yml');
$dataLoader->load('sponsors.yml');
$dataLoader->load('team_members.yml');
$container->compile();
Stripe\Stripe::setApiKey($container->getParameter('doctrine.website.stripe.secret_key'));
date_default_timezone_set('America/New_York');
return $container;
}
}

View File

@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use function array_merge;
use function array_unshift;
use function assert;
use function is_array;
use function is_bool;
use function is_string;
use function sprintf;
class BuildAllCommand extends Command
{
/** @var string */
private $rootDir;
/** @var string */
private $env;
public function __construct(
string $rootDir,
string $env
) {
$this->rootDir = $rootDir;
$this->env = $env;
parent::__construct();
}
protected function configure() : void
{
$this
->setName('build-all')
->setDescription('Build all website components.')
->addArgument(
'build-dir',
InputArgument::OPTIONAL,
'The directory where the build repository is cloned.',
sprintf('%s/build-%s', $this->rootDir, $this->env)
)
->addOption(
'publish',
null,
InputOption::VALUE_NONE,
'Publish the build to GitHub Pages.'
)
->addOption(
'clear-build-cache',
null,
InputOption::VALUE_NONE,
'Clear the build cache before building everything.'
);
}
protected function execute(InputInterface $input, OutputInterface $output) : int
{
$commands = [
'sync-repositories' => [],
'build-website-data' => [],
'build-docs' => [],
'build-website' => [
'build-dir' => $input->getArgument('build-dir'),
'--publish' => $input->getOption('publish'),
],
];
$clearBuildCache = $input->getOption('clear-build-cache');
assert(is_bool($clearBuildCache));
if ($clearBuildCache) {
array_unshift($commands, 'clear-build-cache');
}
foreach ($commands as $command => $arguments) {
assert(is_string($command));
assert(is_array($arguments));
$output->writeln(sprintf('Executing <info>./doctrine %s</info>', $command));
if ($this->runCommand($command, $arguments) === 1) {
$output->writeln(sprintf('Failed running command "%s".', $command));
return 1;
}
}
return 0;
}
/**
* @param mixed[] $arguments
*/
private function runCommand(string $command, array $arguments) : int
{
$input = new ArrayInput(array_merge(['command' => $command], $arguments));
return $this->getApplication()->find($command)
->run($input, new ConsoleOutput());
}
}

View File

@@ -11,7 +11,11 @@ use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Process\Process;
use function array_map;
use function assert;
use function date;
use function in_array;
use function ini_set;
use function is_bool;
@@ -20,9 +24,18 @@ use function is_string;
use function mkdir;
use function realpath;
use function sprintf;
use function time;
class BuildWebsiteCommand extends Command
{
private const WATCH_DIRS = [
'config',
'data',
'lib',
'source',
'templates',
];
/** @var WebsiteBuilder */
private $websiteBuilder;
@@ -60,6 +73,12 @@ class BuildWebsiteCommand extends Command
null,
InputOption::VALUE_NONE,
'Publish the build to GitHub Pages.'
)
->addOption(
'watch',
null,
InputOption::VALUE_NONE,
'Watch for changes and build the website when changes are detected.'
);
}
@@ -87,8 +106,60 @@ class BuildWebsiteCommand extends Command
throw new InvalidArgumentException(sprintf('Could not find build directory'));
}
$this->websiteBuilder->build($output, $buildDir, $this->env, $publish);
$watch = $input->getOption('watch');
assert(is_bool($watch));
if ($watch) {
$this->watch($output);
} else {
$this->websiteBuilder->build($output, $buildDir, $this->env, $publish);
}
return 0;
}
private function watch(OutputInterface $output) : void
{
$lastWebsiteBuild = time();
while (true) {
$finder = $this->createWatchFinder($lastWebsiteBuild);
if (! $finder->hasResults()) {
continue;
}
$output->writeln('Found changes');
$this->buildWebsiteSubProcess($output);
$lastWebsiteBuild = time();
}
}
private function createWatchFinder(int $lastWebsiteBuild) : Finder
{
return (new Finder())
->in($this->getWatchDirs())
->date(sprintf('>= %s', date('Y-m-d H:i:s', $lastWebsiteBuild)));
}
/**
* @return string[]
*/
private function getWatchDirs() : array
{
return array_map(function (string $dir) : string {
return $this->rootDir . '/' . $dir;
}, self::WATCH_DIRS);
}
private function buildWebsiteSubProcess(OutputInterface $output) : void
{
(new Process(['bin/console', 'build-website'], $this->rootDir))
->setTty(true)
->mustRun(static function ($type, $buffer) use ($output) : void {
$output->write($buffer);
});
}
}

View File

@@ -0,0 +1,183 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Commands;
use Doctrine\ORM\EntityManager;
use Doctrine\Website\Event\EmailParticipants;
use Doctrine\Website\Event\GetStripeEventParticipants;
use Doctrine\Website\Model\Entity\EventParticipant;
use Doctrine\Website\Model\Entity\EventParticipantRepository;
use Doctrine\Website\Model\Event;
use Doctrine\Website\Repositories\EventRepository;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use function array_filter;
use function array_map;
use function assert;
use function count;
use function in_array;
use function is_bool;
use function sprintf;
class EventParticipantsCommand extends Command
{
/** @var EventRepository */
private $eventRepository;
/** @var EventParticipantRepository */
private $eventParticipantRepository;
/** @var GetStripeEventParticipants */
private $getStripeEventParticipants;
/** @var EmailParticipants */
private $emailParticipants;
/** @var EntityManager */
private $entityManager;
public function __construct(
EventRepository $eventRepository,
EventParticipantRepository $eventParticipantRepository,
GetStripeEventParticipants $getStripeEventParticipants,
EmailParticipants $emailParticipants,
EntityManager $entityManager
) {
$this->eventRepository = $eventRepository;
$this->eventParticipantRepository = $eventParticipantRepository;
$this->getStripeEventParticipants = $getStripeEventParticipants;
$this->emailParticipants = $emailParticipants;
$this->entityManager = $entityManager;
parent::__construct();
}
protected function configure() : void
{
$this
->setName('event-participants')
->setDescription('Command to check for event participants using the Stripe API.')
->addOption(
'save',
null,
InputOption::VALUE_NONE,
'Save new participants that are found.'
)
->addOption(
'email',
null,
InputOption::VALUE_NONE,
'E-Mail new participants that are found.'
);
}
protected function execute(InputInterface $input, OutputInterface $output) : int
{
$io = new SymfonyStyle($input, $output);
$events = $this->eventRepository->findUpcomingEvents();
foreach ($events as $event) {
$io->title(sprintf('%s Participants', $event->getName()));
$eventParticipants = $this->getStripeEventParticipants->__invoke($event);
if ($eventParticipants === []) {
$io->warning(sprintf('No participants found for "%s". Get out there and market!', $event->getName()));
continue;
}
$newEventParticipants = $this->getNewEventParticipants($eventParticipants);
$save = $input->getOption('save');
assert(is_bool($save));
if ($save) {
$this->saveEventParticipants($newEventParticipants);
}
$email = $input->getOption('email');
assert(is_bool($email));
if ($email) {
$this->emailEventParticipants($io, $event, $newEventParticipants);
}
$header = ['E-Mail', 'Quantity', 'New'];
$rows = $this->createEventParticipantsTableRows(
$eventParticipants,
$newEventParticipants
);
$io->table($header, $rows);
}
return 0;
}
/**
* @param EventParticipant[] $eventParticipants
* @param EventParticipant[] $newEventParticipants
*
* @return mixed[][]
*/
private function createEventParticipantsTableRows(
array $eventParticipants,
array $newEventParticipants
) : array {
return array_map(
static function (EventParticipant $participant) use ($newEventParticipants) : array {
$isNew = in_array($participant, $newEventParticipants, true) ? 'Yes' : 'No';
return [$participant->getEmail(), $participant->getQuantity(), $isNew];
},
$eventParticipants
);
}
/**
* @param EventParticipant[] $eventParticipants
*
* @return EventParticipant[]
*/
private function getNewEventParticipants(array $eventParticipants) : array
{
return array_filter($eventParticipants, function (EventParticipant $eventParticipant) {
return $this->eventParticipantRepository
->findOneByEmail($eventParticipant->getEmail()) === null;
});
}
/**
* @param EventParticipant[] $eventParticipants
*/
private function saveEventParticipants(array $eventParticipants) : void
{
foreach ($eventParticipants as $eventParticipant) {
$this->entityManager->persist($eventParticipant);
}
$this->entityManager->flush();
}
/**
* @param EventParticipant[] $eventParticipants
*/
private function emailEventParticipants(
SymfonyStyle $io,
Event $event,
array $eventParticipants
) : void {
$this->emailParticipants->__invoke($event, $eventParticipants);
$io->text(sprintf('E-mailed <info>%d</info> participants.', count($eventParticipants)));
}
}

View File

@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Controllers;
use Doctrine\StaticWebsiteGenerator\Controller\Response;
use Doctrine\Website\Repositories\EventRepository;
final class EventsController
{
/** @var EventRepository */
private $eventRepository;
public function __construct(EventRepository $eventRepository)
{
$this->eventRepository = $eventRepository;
}
public function index() : Response
{
$upcomingEvents = $this->eventRepository->findUpcomingEvents();
$pastEvents = $this->eventRepository->findPastEvents();
return new Response([
'upcomingEvents' => $upcomingEvents,
'pastEvents' => $pastEvents,
]);
}
public function view(string $id, string $slug) : Response
{
$event = $this->eventRepository->findOneById((int) $id);
return new Response(['event' => $event], '/event.html.twig');
}
public function cfp(string $id, string $slug) : Response
{
$event = $this->eventRepository->findOneById((int) $id);
return new Response(['event' => $event], '/event-cfp.html.twig');
}
public function suggest() : Response
{
return new Response([]);
}
}

View File

@@ -6,17 +6,17 @@ namespace Doctrine\Website\DataSources;
use Doctrine\SkeletonMapper\DataSource\DataSource;
class TeamMembers implements DataSource
final class ArrayDataSource implements DataSource
{
/** @var mixed[] */
private $teamMembers;
private $sourceRows;
/**
* @param mixed[] $teamMembers
* @param mixed[] $sourceRows
*/
public function __construct(array $teamMembers)
public function __construct(array $sourceRows)
{
$this->teamMembers = $teamMembers;
$this->sourceRows = $sourceRows;
}
/**
@@ -24,6 +24,6 @@ class TeamMembers implements DataSource
*/
public function getSourceRows() : array
{
return $this->teamMembers;
return $this->sourceRows;
}
}

View File

@@ -1,29 +0,0 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\DataSources;
use Doctrine\SkeletonMapper\DataSource\DataSource;
class DoctrineUsers implements DataSource
{
/** @var mixed[][] */
private $doctrineUsers;
/**
* @param mixed[][] $doctrineUsers
*/
public function __construct(array $doctrineUsers)
{
$this->doctrineUsers = $doctrineUsers;
}
/**
* @return mixed[][]
*/
public function getSourceRows() : array
{
return $this->doctrineUsers;
}
}

View File

@@ -1,29 +0,0 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\DataSources;
use Doctrine\SkeletonMapper\DataSource\DataSource;
class Partners implements DataSource
{
/** @var mixed[][] */
private $partners;
/**
* @param mixed[][] $partners
*/
public function __construct(array $partners)
{
$this->partners = $partners;
}
/**
* @return mixed[][]
*/
public function getSourceRows() : array
{
return $this->partners;
}
}

View File

@@ -1,29 +0,0 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\DataSources;
use Doctrine\SkeletonMapper\DataSource\DataSource;
class Sponsors implements DataSource
{
/** @var mixed[][] */
private $sponsors;
/**
* @param mixed[][] $sponsors
*/
public function __construct(array $sponsors)
{
$this->sponsors = $sponsors;
}
/**
* @return mixed[][]
*/
public function getSourceRows() : array
{
return $this->sponsors;
}
}

View File

@@ -72,9 +72,10 @@ class Deployer
$output->write($buffer);
});
// build the docs, website and publish
// execute migrations, build the website and publish it.
$deployCommand = sprintf(
'cd /data/doctrine-website-%s && ./bin/console sync-repositories && ./bin/console build-website-data && ./bin/console build-docs && ./bin/console build-website /data/doctrine-website-build-%s --env=%s --publish',
'cd /data/doctrine-website-%s && ./bin/console migrations:migrate --no-interaction --env=%s && ./bin/console build-all /data/doctrine-website-build-%s --env=%s --publish',
$this->env,
$this->env,
$this->env,
$this->env

80
lib/Email/RenderEmail.php Normal file
View File

@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Email;
use Pelago\Emogrifier;
use Twig\Environment;
use Twig\Extension\AbstractExtension;
use Twig\Loader\FilesystemLoader;
use Twig\Loader\LoaderInterface;
use function strip_tags;
use function trim;
final class RenderEmail
{
/** @var Emogrifier */
private $emogrifier;
/** @var string */
private $templatesDir;
/** @var AbstractExtension[] */
private $extensions;
/**
* @param AbstractExtension[] $extensions
*/
public function __construct(
Emogrifier $emogrifier,
string $templatesDir,
array $extensions
) {
$this->emogrifier = $emogrifier;
$this->templatesDir = $templatesDir;
$this->extensions = $extensions;
}
/**
* @param mixed[] $parameters
*/
public function __invoke(string $template, array $parameters) : RenderedEmail
{
$twig = $this->createTwigEnvironment($this->createFilesystemLoader());
$template = $twig->loadTemplate($template);
$subject = $template->renderBlock('subject', $parameters);
$inlineCss = $template->renderBlock('inline_css', $parameters);
$bodyText = $template->renderBlock('full_body_text', $parameters);
$bodyHtml = $template->renderBlock('full_body_html', $parameters);
if (trim($bodyText) === '') {
$bodyText = strip_tags($template->renderBlock('body_html', $parameters));
}
$this->emogrifier->setHtml($bodyHtml);
$this->emogrifier->setCss($inlineCss);
$mergedHtml = $this->emogrifier->emogrify();
return new RenderedEmail($subject, $bodyText, $mergedHtml);
}
private function createTwigEnvironment(LoaderInterface $loader) : Environment
{
$twig = new Environment($loader, ['strict_variables' => true]);
foreach ($this->extensions as $extension) {
$twig->addExtension($extension);
}
return $twig;
}
private function createFilesystemLoader() : FilesystemLoader
{
return new FilesystemLoader($this->templatesDir);
}
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Email;
final class RenderedEmail
{
/** @var string */
private $subject;
/** @var string */
private $bodyText;
/** @var string */
private $bodyHtml;
public function __construct(string $subject, string $bodyText, string $bodyHtml)
{
$this->subject = $subject;
$this->bodyText = $bodyText;
$this->bodyHtml = $bodyHtml;
}
public function getSubject() : string
{
return $this->subject;
}
public function getBodyText() : string
{
return $this->bodyText;
}
public function getBodyHtml() : string
{
return $this->bodyHtml;
}
}

49
lib/Email/SendEmail.php Normal file
View File

@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Email;
use Doctrine\Website\Site;
use SendGrid;
final class SendEmail
{
/** @var Site */
private $site;
/** @var SendGrid */
private $sendGrid;
/** @var RenderEmail */
private $renderEmail;
public function __construct(
Site $site,
SendGrid $sendGrid,
RenderEmail $renderEmail
) {
$this->site = $site;
$this->sendGrid = $sendGrid;
$this->renderEmail = $renderEmail;
}
/**
* @param mixed[] $parameters
*/
public function __invoke(string $to, string $template, array $parameters = []) : void
{
$parameters['site'] = $this->site;
$renderedEmail = $this->renderEmail->__invoke($template, $parameters);
$email = new SendGrid\Mail\Mail();
$email->setFrom('doctrine@doctrine-project.org', 'Doctrine');
$email->setSubject($renderedEmail->getSubject());
$email->addTo($to);
$email->addContent('text/plain', $renderedEmail->getBodyText());
$email->addContent('text/html', $renderedEmail->getBodyHtml());
$this->sendGrid->send($email);
}
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Event;
use Doctrine\Website\Email\SendEmail;
use Doctrine\Website\Model\Entity\EventParticipant;
use Doctrine\Website\Model\Event;
final class EmailParticipants
{
/** @var SendEmail */
private $sendEmail;
public function __construct(SendEmail $sendEmail)
{
$this->sendEmail = $sendEmail;
}
/**
* @param EventParticipant[] $participants
*/
public function __invoke(Event $event, array $participants) : void
{
foreach ($participants as $participant) {
$this->sendEmail->__invoke(
$participant->getEmail(),
'emails/events/participant-ticket.html.twig',
[
'event' => $event,
'participant' => $participant,
]
);
}
}
}

View File

@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Event;
use Doctrine\Website\Model\Entity\EventParticipant;
use Doctrine\Website\Model\Event;
use Stripe;
use function array_map;
use function array_merge;
use function array_values;
use function end;
use function iterator_to_array;
use function strtotime;
final class GetStripeEventParticipants
{
/**
* @return EventParticipant[]
*/
public function __invoke(Event $event) : array
{
$stripeCheckouts = $this->getAllEventStripeCheckouts($event);
$participants = [];
$customers = [];
foreach ($stripeCheckouts as $stripeCheckout) {
$item = $stripeCheckout['data']['object']['display_items'][0];
$sku = $item['sku']['id'];
$quantity = $item['quantity'];
$customerId = $stripeCheckout['data']['object']['customer'];
if ($sku !== $event->getSku()) {
continue;
}
if (! isset($customers[$customerId])) {
$customers[$customerId] = Stripe\Customer::retrieve($customerId);
}
$customer = $customers[$customerId];
if (! isset($participants[$customer['email']])) {
$participants[$customer['email']] = [
'email' => $customer['email'],
'quantity' => $quantity,
];
} else {
$participants[$customer['email']]['quantity'] += $quantity;
}
}
return array_map(static function (array $participant) use ($event) : EventParticipant {
return new EventParticipant(
$event,
$participant['email'],
$participant['quantity']
);
}, array_values($participants));
}
/**
* @return mixed[][]
*/
private function getAllEventStripeCheckouts(Event $event) : array
{
$allEventStripeCheckouts = [];
$startingAfter = null;
while (true) {
$eventStripeCheckouts = $this->getEventStripeCheckouts($event, $startingAfter);
$eventStripeCheckoutsArray = iterator_to_array($eventStripeCheckouts);
$allEventStripeCheckouts = array_merge(
$allEventStripeCheckouts,
$eventStripeCheckoutsArray
);
if ($eventStripeCheckouts['has_more'] === false) {
break;
}
$startingAfter = end($eventStripeCheckoutsArray)['id'];
}
return $allEventStripeCheckouts;
}
private function getEventStripeCheckouts(
Event $event,
?string $startingAfter = null
) : Stripe\Collection {
$parameters = [
'created' => ['gt' => strtotime('1 year ago')],
'limit' => 100,
'type' => 'checkout.session.completed',
];
if ($startingAfter !== null) {
$parameters['starting_after'] = $startingAfter;
}
return Stripe\Event::all($parameters);
}
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Hydrators;
use DateTimeImmutable;
use Doctrine\Website\Model\BlogPost;
/**
* @property string $url
* @property string $slug
* @property string $title
* @property string $authorName
* @property string $authorEmail
* @property string $contents
* @property DateTimeImmutable $date
*/
final class BlogPostHydrator extends ModelHydrator
{
protected function getClassName() : string
{
return BlogPost::class;
}
/**
* @param mixed[] $data
*/
protected function doHydrate(array $data) : void
{
$this->url = (string) $data['url'] ?? '';
$this->slug = (string) $data['slug'] ?? '';
$this->title = (string) $data['title'] ?? '';
$this->authorName = (string) $data['authorName'] ?? '';
$this->authorEmail = (string) $data['authorEmail'] ?? '';
$this->contents = (string) $data['contents'] ?? '';
$this->date = $data['date'] ?? new DateTimeImmutable();
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Hydrators;
use Doctrine\Website\Model\Contributor;
/**
* @property TeamMember|null $teamMember
* @property string $github
* @property string $avatarUrl
* @property int $numCommits
* @property int $numAdditions
* @property int $numDeletions
* @property Project[] $projects
*/
final class ContributorHydrator extends ModelHydrator
{
protected function getClassName() : string
{
return Contributor::class;
}
/**
* @param mixed[] $data
*/
protected function doHydrate(array $data) : void
{
$this->teamMember = $data['teamMember'] ?? null;
$this->github = (string) ($data['github'] ?? '');
$this->avatarUrl = (string) ($data['avatarUrl'] ?? '');
$this->numCommits = (int) ($data['numCommits'] ?? 0);
$this->numAdditions = (int) ($data['numAdditions'] ?? 0);
$this->numDeletions = (int) ($data['numDeletions'] ?? 0);
$this->projects = $data['projects'] ?? [];
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Hydrators;
use Doctrine\Website\Model\DoctrineUser;
/**
* @property string $name
* @property string $url
*/
final class DoctrineUserHydrator extends ModelHydrator
{
protected function getClassName() : string
{
return DoctrineUser::class;
}
/**
* @param mixed[] $data
*/
protected function doHydrate(array $data) : void
{
$this->name = (string) $data['name'];
$this->url = (string) $data['url'];
}
}

View File

@@ -0,0 +1,169 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Hydrators;
use DateTimeImmutable;
use Doctrine\SkeletonMapper\ObjectManagerInterface;
use Doctrine\Website\Model\Address;
use Doctrine\Website\Model\DateTimeRange;
use Doctrine\Website\Model\Entity\EventParticipantRepository;
use Doctrine\Website\Model\Event;
use Doctrine\Website\Model\EventCfp;
use Doctrine\Website\Model\EventLocation;
use Doctrine\Website\Model\EventParticipants;
use Doctrine\Website\Model\EventSchedule;
use Doctrine\Website\Model\EventSpeakers;
use Doctrine\Website\Model\EventSponsors;
use Doctrine\Website\Model\EventType;
use InvalidArgumentException;
use function current;
use function end;
use function sprintf;
/**
* @property int $id
* @property string $name
* @property string $slug
* @property string $type
* @property EventLocation|null $location
* @property string $sku
* @property string $joinUrl
* @property EventCfp $cfp
* @property EventSponsors $sponsors
* @property EventSpeakers $speakers
* @property EventSchedule $schedule
* @property EventParticipants $participants
* @property DateTimeRange $dateTimeRange
* @property DateTimeRange $registrationDateTimeRange
* @property string $description
* @property float $price
*/
final class EventHydrator extends ModelHydrator
{
private const ENV_SKU_MAP = [
'dev' => 'test',
'prod' => 'prod',
'staging' => 'test',
'test' => 'test',
];
/** @var EventParticipantRepository */
private $eventParticipantRepository;
/** @var string */
private $env;
public function __construct(
ObjectManagerInterface $objectManager,
EventParticipantRepository $eventParticipantRepository,
string $env
) {
parent::__construct($objectManager);
$this->eventParticipantRepository = $eventParticipantRepository;
$this->env = $env;
}
protected function getClassName() : string
{
return Event::class;
}
/**
* @param mixed[] $data
*/
protected function doHydrate(array $data) : void
{
$this->id = (int) ($data['id'] ?? 0);
$this->type = (string) ($data['type'] ?? EventType::WEBINAR);
if ($this->type === EventType::CONFERENCE) {
if (! isset($data['location'])) {
throw new InvalidArgumentException(
sprintf('Event type of "%s" must provide a "location" field.', $this->type)
);
}
$this->location = new EventLocation(
(string) ($data['location']['name'] ?? ''),
new Address(
(string) ($data['location']['address']['line1'] ?? ''),
(string) ($data['location']['address']['line2'] ?? ''),
(string) ($data['location']['address']['city'] ?? ''),
(string) ($data['location']['address']['state'] ?? ''),
(string) ($data['location']['address']['zipCode'] ?? ''),
(string) ($data['location']['address']['countryCode'] ?? '')
)
);
}
if (isset($data['sku'])) {
if (! isset(self::ENV_SKU_MAP[$this->env])) {
throw new InvalidArgumentException(sprintf('Invalid env "%s".', $this->env));
}
$skuKey = self::ENV_SKU_MAP[$this->env];
if (! isset($data['sku'][$skuKey])) {
throw new InvalidArgumentException(
sprintf('Sku key with "%s" does not exist.', $skuKey)
);
}
$this->sku = (string) ($data['sku'][$skuKey] ?? '');
} else {
$this->sku = '';
}
$this->name = (string) ($data['name'] ?? '');
$this->slug = (string) ($data['slug'] ?? '');
$this->joinUrl = (string) ($data['joinUrl'] ?? '');
$this->cfp = new EventCfp(
(string) ($data['cfp']['googleFormId'] ?? ''),
new DateTimeRange(
new DateTimeImmutable($data['cfp']['startDate'] ?? ''),
new DateTimeImmutable($data['cfp']['endDate'] ?? '')
)
);
$this->sponsors = new EventSponsors($data);
$this->speakers = new EventSpeakers($data, $this->objectManager);
$this->schedule = new EventSchedule($data, $this->speakers);
if ($data['schedule'] !== []) {
$firstSlot = current($data['schedule']);
$lastSlot = end($data['schedule']);
$this->dateTimeRange = new DateTimeRange(
new DateTimeImmutable($firstSlot['startDate'] ?? ''),
new DateTimeImmutable($lastSlot['endDate'] ?? '')
);
} else {
$this->dateTimeRange = new DateTimeRange(
new DateTimeImmutable($data['startDate'] ?? ''),
new DateTimeImmutable($data['endDate'] ?? '')
);
}
$this->registrationDateTimeRange = new DateTimeRange(
isset($data['registrationStartDate'])
? new DateTimeImmutable($data['registrationStartDate'])
: $this->dateTimeRange->getStart(),
isset($data['registrationEndDate'])
? new DateTimeImmutable($data['registrationEndDate'])
: $this->dateTimeRange->getEnd()
);
$this->description = (string) ($data['description'] ?? '');
$this->price = (float) ($data['price'] ?? 0.00);
$this->participants = new EventParticipants(
$data['id'],
$this->eventParticipantRepository
);
}
}

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Hydrators;
use Doctrine\SkeletonMapper\Hydrator\ObjectHydrator;
use Doctrine\SkeletonMapper\Mapping\ClassMetadataInterface;
use Doctrine\SkeletonMapper\ObjectManagerInterface;
use ReflectionProperty;
abstract class ModelHydrator extends ObjectHydrator
{
/** @var ObjectManagerInterface */
protected $objectManager;
/** @var object */
private $object;
/** @var ClassMetadataInterface */
private $classMetadata;
/** @var ReflectionProperty[] */
private $reflectionProperties;
public function __construct(ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
$this->classMetadata = $this->objectManager->getClassMetadata($this->getClassName());
}
/**
* @param mixed[] $data
*/
abstract protected function doHydrate(array $data) : void;
abstract protected function getClassName() : string;
/**
* @param object $object
* @param mixed[] $data
*
* @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
*/
public function hydrate($object, array $data) : void
{
$this->object = $object;
$this->doHydrate($data);
}
/**
* @return mixed
*/
public function __get(string $field)
{
return $this->getReflectionProperty($field)->getValue($this->object);
}
/**
* @param mixed $value
*/
public function __set(string $field, $value) : void
{
$this->getReflectionProperty($field)->setValue($this->object, $value);
}
private function getReflectionProperty(string $field) : ReflectionProperty
{
$key = $this->getClassName() . '::' . $field;
if (! isset($this->reflectionProperties[$key])) {
$reflectionProperty = $this->classMetadata->getReflectionClass()->getProperty($field);
$reflectionProperty->setAccessible(true);
$this->reflectionProperties[$key] = $reflectionProperty;
}
return $this->reflectionProperties[$key];
}
}

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Hydrators;
use Doctrine\Website\Model\Partner;
use Doctrine\Website\Model\PartnerDetails;
use Doctrine\Website\Model\UtmParameters;
use function array_merge;
/**
* @property string $name
* @property string $slug
* @property string $url
* @property string $logo
* @property string $bio
* @property bool $featured
* @property PartnerDetails $details
* @property UtmParameters $utmParameters
*/
final class PartnerHydrator extends ModelHydrator
{
protected function getClassName() : string
{
return Partner::class;
}
/**
* @param mixed[] $data
*/
protected function doHydrate(array $data) : void
{
$this->name = (string) ($data['name'] ?? '');
$this->slug = (string) ($data['slug'] ?? '');
$this->url = (string) ($data['url'] ?? '');
$this->logo = (string) ($data['logo'] ?? '');
$this->bio = (string) ($data['bio'] ?? '');
$this->featured = (bool) ($data['featured'] ?? false);
$this->details = new PartnerDetails(
(string) ($data['details']['label'] ?? ''),
$data['details']['items'] ?? []
);
$this->utmParameters = new UtmParameters(
array_merge(
[
'utm_source' => 'doctrine',
'utm_medium' => 'website',
'utm_campaign' => 'partners',
],
$data['utmParameters'] ?? []
)
);
}
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Hydrators;
use Doctrine\Website\Model\Project;
use Doctrine\Website\Model\ProjectContributor;
/**
* @property TeamMember|null $teamMember
* @property string $projectSlug
* @property Project $project
* @property string $github
* @property string $avatarUrl
* @property int $numCommits
* @property int $numAdditions
* @property int $numDeletions
*/
final class ProjectContributorHydrator extends ModelHydrator
{
protected function getClassName() : string
{
return ProjectContributor::class;
}
/**
* @param mixed[] $data
*/
protected function doHydrate(array $data) : void
{
$this->teamMember = $data['teamMember'] ?? null;
$this->projectSlug = (string) ($data['projectSlug'] ?? '');
$this->project = $data['project'] ?? new Project();
$this->github = (string) ($data['github'] ?? '');
$this->avatarUrl = (string) ($data['avatarUrl'] ?? '');
$this->numCommits = (int) ($data['numCommits'] ?? 0);
$this->numAdditions = (int) ($data['numAdditions'] ?? 0);
$this->numDeletions = (int) ($data['numDeletions'] ?? 0);
}
}

View File

@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Hydrators;
use Doctrine\Website\Model\Project;
use Doctrine\Website\Model\ProjectIntegrationType;
use Doctrine\Website\Model\ProjectStats;
use Doctrine\Website\Model\ProjectVersion;
/**
* @property bool $active
* @property bool $archived
* @property string $name
* @property string $shortName
* @property string $slug
* @property string $docsSlug
* @property string $composerPackageName
* @property string $repositoryName
* @property bool $isIntegration
* @property string $integrationFor
* @property string $docsRepositoryName
* @property string $docsPath
* @property string $codePath
* @property string $description
* @property string[] $keywords
* @property ProjectVersion[] $versions
* @property ProjectIntegrationType $projectIntegrationType
* @property ProjectStats $projectStats
*/
final class ProjectHydrator extends ModelHydrator
{
protected function getClassName() : string
{
return Project::class;
}
/**
* @param mixed[] $data
*/
protected function doHydrate(array $data) : void
{
$this->active = (bool) ($data['active'] ?? true);
$this->archived = (bool) ($data['archived'] ?? false);
$this->name = (string) ($data['name'] ?? '');
$this->shortName = (string) ($data['shortName'] ?? $this->name);
$this->slug = (string) ($data['slug'] ?? '');
$this->docsSlug = (string) ($data['docsSlug'] ?? $this->slug);
$this->composerPackageName = (string) ($data['composerPackageName'] ?? '');
$this->repositoryName = (string) ($data['repositoryName'] ?? '');
$this->isIntegration = (bool) ($data['integration'] ?? false);
$this->integrationFor = (string) ($data['integrationFor'] ?? '');
$this->docsRepositoryName = (string) ($data['docsRepositoryName'] ?? $this->repositoryName);
$this->docsPath = (string) ($data['docsPath'] ?? '/docs');
$this->codePath = (string) ($data['codePath'] ?? '/lib');
$this->description = (string) ($data['description'] ?? '');
$this->keywords = $data['keywords'] ?? [];
if (! isset($data['versions'])) {
return;
}
$versions = [];
foreach ($data['versions'] as $version) {
$versions[] = $version instanceof ProjectVersion
? $version
: new ProjectVersion($version);
}
$this->versions = $versions;
if ($this->isIntegration) {
$this->projectIntegrationType = new ProjectIntegrationType($data['integrationType']);
}
$this->projectStats = new ProjectStats(
(int) ($data['packagistData']['package']['github_stars'] ?? 0),
(int) ($data['packagistData']['package']['github_watchers'] ?? 0),
(int) ($data['packagistData']['package']['github_forks'] ?? 0),
(int) ($data['packagistData']['package']['github_open_issues'] ?? 0),
(int) ($data['packagistData']['package']['dependents'] ?? 0),
(int) ($data['packagistData']['package']['suggesters'] ?? 0),
(int) ($data['packagistData']['package']['downloads']['total'] ?? 0),
(int) ($data['packagistData']['package']['downloads']['monthly'] ?? 0),
(int) ($data['packagistData']['package']['downloads']['daily'] ?? 0)
);
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Hydrators;
use DateTimeImmutable;
use Doctrine\Website\Model\SitemapPage;
/**
* @property string $url
* @property DateTimeImmutable $date
*/
final class SitemapPageHydrator extends ModelHydrator
{
protected function getClassName() : string
{
return SitemapPage::class;
}
/**
* @param mixed[] $data
*/
protected function doHydrate(array $data) : void
{
$this->url = (string) ($sitemapPage['url'] ?? '');
$this->date = $sitemapPage['date'] ?? new DateTimeImmutable();
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Hydrators;
use Doctrine\Website\Model\Sponsor;
use Doctrine\Website\Model\UtmParameters;
use function array_merge;
/**
* @property string $name
* @property string $url
* @property UtmParameters $utmParameters
* @property bool $highlighted
*/
final class SponsorHydrator extends ModelHydrator
{
protected function getClassName() : string
{
return Sponsor::class;
}
/**
* @param mixed[] $data
*/
protected function doHydrate(array $data) : void
{
$this->name = (string) ($data['name'] ?? '');
$this->url = (string) ($data['url'] ?? '');
$this->utmParameters = new UtmParameters(
array_merge(
[
'utm_source' => 'doctrine',
'utm_medium' => 'website',
'utm_campaign' => 'sponsors',
],
$data['utmParameters'] ?? []
)
);
$this->highlighted = (bool) ($data['highlighted'] ?? '');
}
}

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Hydrators;
use Closure;
use Doctrine\Website\Model\Contributor;
use Doctrine\Website\Model\TeamMember;
use Doctrine\Website\Repositories\ContributorRepository;
use function assert;
/**
* @property string $name
* @property string $github
* @property string $twitter
* @property string $avatarUrl
* @property string $website
* @property string $location
* @property string[] $maintains
* @property bool $consultant
* @property string $headshot
* @property string $bio
* @property Closure|Contributor $contributor
*/
final class TeamMemberHydrator extends ModelHydrator
{
protected function getClassName() : string
{
return TeamMember::class;
}
/**
* @param mixed[] $data
*/
protected function doHydrate(array $data) : void
{
$this->name = (string) ($data['name'] ?? '');
$this->github = (string) ($data['github'] ?? '');
$this->twitter = (string) ($data['twitter'] ?? '');
$this->avatarUrl = (string) ($data['avatarUrl'] ?? '');
$this->website = (string) ($data['website'] ?? '');
$this->location = (string) ($data['location'] ?? '');
$this->maintains = $data['maintains'] ?? [];
$this->consultant = (bool) ($data['consultant'] ?? false);
$this->headshot = (string) ($data['headshot'] ?? '');
$this->bio = (string) ($data['bio'] ?? '');
$this->contributor = function (string $github) : Contributor {
$contributorRepository = $this->objectManager
->getRepository(Contributor::class);
assert($contributorRepository instanceof ContributorRepository);
return $contributorRepository->findOneByGithub($github);
};
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Migrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20190515005142 extends AbstractMigration
{
public function getDescription() : string
{
return 'Create event_participants table.';
}
public function up(Schema $schema) : void
{
$this->addSql('CREATE TABLE event_participants (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(255) NOT NULL, quantity INT NOT NULL, eventId INT NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
}
public function down(Schema $schema) : void
{
$this->addSql('DROP TABLE event_participants');
}
}

87
lib/Model/Address.php Normal file
View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Model;
use function sprintf;
final class Address
{
/** @var string */
private $line1;
/** @var string */
private $line2;
/** @var string */
private $city;
/** @var string */
private $state;
/** @var string */
private $zipCode;
/** @var string */
private $countryCode;
public function __construct(
string $line1,
string $line2,
string $city,
string $state,
string $zipCode,
string $countryCode
) {
$this->line1 = $line1;
$this->line2 = $line2;
$this->city = $city;
$this->state = $state;
$this->zipCode = $zipCode;
$this->countryCode = $countryCode;
}
public function getLine1() : string
{
return $this->line1;
}
public function getLine2() : string
{
return $this->line2;
}
public function getCity() : string
{
return $this->city;
}
public function getState() : string
{
return $this->state;
}
public function getZipCode() : string
{
return $this->zipCode;
}
public function getCountryCode() : string
{
return $this->countryCode;
}
public function getString() : string
{
return sprintf(
'%s %s %s, %s %s %s',
$this->line1,
$this->line2,
$this->city,
$this->state,
$this->zipCode,
$this->countryCode
);
}
}

View File

@@ -5,12 +5,10 @@ declare(strict_types=1);
namespace Doctrine\Website\Model;
use DateTimeImmutable;
use Doctrine\SkeletonMapper\Hydrator\HydratableInterface;
use Doctrine\SkeletonMapper\Mapping\ClassMetadataInterface;
use Doctrine\SkeletonMapper\Mapping\LoadMetadataInterface;
use Doctrine\SkeletonMapper\ObjectManagerInterface;
class BlogPost implements HydratableInterface, LoadMetadataInterface
class BlogPost implements LoadMetadataInterface
{
/** @var string */
private $url;
@@ -56,20 +54,6 @@ class BlogPost implements HydratableInterface, LoadMetadataInterface
$metadata->setIdentifier(['slug']);
}
/**
* @param mixed[] $project
*/
public function hydrate(array $project, ObjectManagerInterface $objectManager) : void
{
$this->url = (string) $project['url'] ?? '';
$this->slug = (string) $project['slug'] ?? '';
$this->title = (string) $project['title'] ?? '';
$this->authorName = (string) $project['authorName'] ?? '';
$this->authorEmail = (string) $project['authorEmail'] ?? '';
$this->contents = (string) $project['contents'] ?? '';
$this->date = $project['date'] ?? new DateTimeImmutable();
}
public function getUrl() : string
{
return $this->url;

View File

@@ -4,12 +4,10 @@ declare(strict_types=1);
namespace Doctrine\Website\Model;
use Doctrine\SkeletonMapper\Hydrator\HydratableInterface;
use Doctrine\SkeletonMapper\Mapping\ClassMetadataInterface;
use Doctrine\SkeletonMapper\Mapping\LoadMetadataInterface;
use Doctrine\SkeletonMapper\ObjectManagerInterface;
class Contributor implements HydratableInterface, LoadMetadataInterface, CommitterStats
class Contributor implements LoadMetadataInterface, CommitterStats
{
/** @var TeamMember|null */
private $teamMember;
@@ -37,20 +35,6 @@ class Contributor implements HydratableInterface, LoadMetadataInterface, Committ
$metadata->setIdentifier(['github']);
}
/**
* @param mixed[] $contributor
*/
public function hydrate(array $contributor, ObjectManagerInterface $objectManager) : void
{
$this->teamMember = $contributor['teamMember'] ?? null;
$this->github = (string) ($contributor['github'] ?? '');
$this->avatarUrl = (string) ($contributor['avatarUrl'] ?? '');
$this->numCommits = (int) ($contributor['numCommits'] ?? 0);
$this->numAdditions = (int) ($contributor['numAdditions'] ?? 0);
$this->numDeletions = (int) ($contributor['numDeletions'] ?? 0);
$this->projects = $contributor['projects'] ?? [];
}
public function getTeamMember() : ?TeamMember
{
return $this->teamMember;

109
lib/Model/DateTimeRange.php Normal file
View File

@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Model;
use DateTimeImmutable;
final class DateTimeRange
{
/** @var DateTimeImmutable */
private $start;
/** @var DateTimeImmutable */
private $end;
/** @var DateTimeImmutable */
private $now;
public function __construct(
DateTimeImmutable $start,
DateTimeImmutable $end,
?DateTimeImmutable $now = null
) {
$this->start = $start;
$this->end = $end;
$this->now = $now ?? new DateTimeImmutable();
}
public function getStart() : DateTimeImmutable
{
return $this->start;
}
public function getEnd() : DateTimeImmutable
{
return $this->end;
}
public function isNow() : bool
{
return $this->start <= $this->now
&& $this->end > $this->now;
}
public function isOver() : bool
{
return $this->end < $this->now;
}
public function isUpcoming() : bool
{
return $this->start > $this->now;
}
public function getNumDays() : int
{
$days = (int) $this->end
->diff($this->start)
->days;
if ($days > 0) {
return $days + 1;
}
return 0;
}
public function getNumHours() : int
{
$diff = $this->end->diff($this->start);
$numDays = $this->getNumDays();
if ($numDays === 1) {
return $diff->h;
}
return $diff->h + ($this->getNumDays() * 24);
}
public function getNumMinutes() : int
{
$diff = $this->end->diff($this->start);
$minutes = $diff->days * 24 * 60;
$minutes += $diff->h * 60;
$minutes += $diff->i;
return (int) $minutes;
}
public function getDuration() : string
{
$numDays = $this->getNumDays();
if ($numDays === 0) {
$numMinutes = $this->getNumMinutes();
if ($numMinutes >= 60) {
return $this->getNumHours() . '-hour';
}
return $numMinutes . '-minute';
}
return $this->getNumDays() . '-day';
}
}

View File

@@ -4,12 +4,10 @@ declare(strict_types=1);
namespace Doctrine\Website\Model;
use Doctrine\SkeletonMapper\Hydrator\HydratableInterface;
use Doctrine\SkeletonMapper\Mapping\ClassMetadataInterface;
use Doctrine\SkeletonMapper\Mapping\LoadMetadataInterface;
use Doctrine\SkeletonMapper\ObjectManagerInterface;
class DoctrineUser implements HydratableInterface, LoadMetadataInterface
class DoctrineUser implements LoadMetadataInterface
{
/** @var string */
private $name;
@@ -22,15 +20,6 @@ class DoctrineUser implements HydratableInterface, LoadMetadataInterface
$metadata->setIdentifier(['name']);
}
/**
* @param mixed[] $doctrineUser
*/
public function hydrate(array $doctrineUser, ObjectManagerInterface $objectManager) : void
{
$this->name = (string) $doctrineUser['name'];
$this->url = (string) $doctrineUser['url'];
}
public function getName() : string
{
return $this->name;

View File

@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Model\Entity;
use Doctrine\Website\Model\Event;
/**
* @Entity(repositoryClass="EventParticipantRepository")
* @Table(name="event_participants")
*/
final class EventParticipant
{
/**
* @var int|null
* @Id @Column(type="integer") @GeneratedValue
**/
private $id;
/**
* @var string
* @Column(type="string")
**/
private $email;
/**
* @var int
* @Column(type="integer")
**/
private $quantity;
/**
* @var int
* @Column(type="integer")
**/
private $eventId;
public function __construct(Event $event, string $email, int $quantity)
{
$this->eventId = $event->getId();
$this->email = $email;
$this->quantity = $quantity;
}
public function getId() : ?int
{
return $this->id;
}
public function getEmail() : string
{
return $this->email;
}
public function getQuantity() : int
{
return $this->quantity;
}
public function getEventId() : int
{
return $this->eventId;
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Model\Entity;
use Doctrine\ORM\EntityRepository;
final class EventParticipantRepository extends EntityRepository
{
public function findOneByEmail(string $email) : ?EventParticipant
{
/** @var EventParticipant $eventParticipant */
$eventParticipant = $this->findOneBy(['email' => $email]);
return $eventParticipant;
}
/**
* @return EventParticipant[]
*/
public function findByEventId(int $eventId) : array
{
/** @var EventParticipant[] $eventParticipants */
$eventParticipants = $this->findBy(['eventId' => $eventId]);
return $eventParticipants;
}
}

165
lib/Model/Event.php Normal file
View File

@@ -0,0 +1,165 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Model;
use DateTimeImmutable;
use Doctrine\SkeletonMapper\Mapping\ClassMetadataInterface;
use Doctrine\SkeletonMapper\Mapping\LoadMetadataInterface;
final class Event implements LoadMetadataInterface
{
/** @var int */
private $id;
/** @var string */
private $type;
/** @var string */
private $sku;
/** @var string */
private $name;
/** @var string */
private $slug;
/** @var string */
private $joinUrl;
/** @var DateTimeRange */
private $dateTimeRange;
/** @var DateTimeRange */
private $registrationDateTimeRange;
/** @var EventCfp */
private $cfp;
/** @var EventLocation|null */
private $location;
/** @var EventSponsors */
private $sponsors;
/** @var EventSpeakers */
private $speakers;
/** @var EventSchedule */
private $schedule;
/** @var EventParticipants */
private $participants;
/** @var string */
private $description;
/** @var float */
private $price;
public static function loadMetadata(ClassMetadataInterface $metadata) : void
{
$metadata->setIdentifier(['id']);
}
public function getId() : int
{
return $this->id;
}
public function isWebinar() : bool
{
return $this->type === EventType::WEBINAR;
}
public function isConference() : bool
{
return $this->type === EventType::CONFERENCE;
}
public function getSku() : string
{
return $this->sku;
}
public function getName() : string
{
return $this->name;
}
public function getSlug() : string
{
return $this->slug;
}
public function getJoinUrl() : string
{
return $this->joinUrl;
}
public function getDates() : DateTimeRange
{
return $this->dateTimeRange;
}
public function getRegistrationDates() : DateTimeRange
{
return $this->registrationDateTimeRange;
}
public function getStartDate() : DateTimeImmutable
{
return $this->dateTimeRange->getStart();
}
public function getEndDate() : DateTimeImmutable
{
return $this->dateTimeRange->getEnd();
}
public function getCfp() : EventCfp
{
return $this->cfp;
}
public function getLocation() : ?EventLocation
{
return $this->location;
}
public function getSponsors() : EventSponsors
{
return $this->sponsors;
}
public function getSpeakers() : EventSpeakers
{
return $this->speakers;
}
public function getSchedule() : EventSchedule
{
return $this->schedule;
}
public function getParticipants() : EventParticipants
{
return $this->participants;
}
public function getDescription() : string
{
return $this->description;
}
public function getPrice() : float
{
return $this->price;
}
public function isFree() : bool
{
return $this->price === 0.00;
}
}

47
lib/Model/EventCfp.php Normal file
View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Model;
use LogicException;
use function sprintf;
final class EventCfp
{
/** @var string */
private $googleFormId;
/** @var DateTimeRange */
private $dateTimeRange;
public function __construct(string $googleFormId, DateTimeRange $dateTimeRange)
{
$this->googleFormId = $googleFormId;
$this->dateTimeRange = $dateTimeRange;
}
public function exists() : bool
{
return $this->googleFormId !== '';
}
public function getGoogleFormUrl() : string
{
if (! $this->exists()) {
throw new LogicException('Cannot call EventCfp::getGoogleFormUrl() when no googleFormId is set.');
}
return sprintf('https://docs.google.com/forms/d/e/%s/viewform', $this->googleFormId);
}
public function getEmbeddedGoogleFormUrl() : string
{
return sprintf('%s?embedded=true', $this->getGoogleFormUrl());
}
public function getDates() : DateTimeRange
{
return $this->dateTimeRange;
}
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Model;
final class EventLocation
{
/** @var string */
private $name;
/** @var Address */
private $address;
public function __construct(string $name, Address $address)
{
$this->name = $name;
$this->address = $address;
}
public function getName() : string
{
return $this->name;
}
public function getAddress() : Address
{
return $this->address;
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Model;
use Doctrine\Common\Collections\AbstractLazyCollection;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Website\Model\Entity\EventParticipantRepository;
final class EventParticipants extends AbstractLazyCollection
{
/** @var int */
private $eventId;
/** @var EventParticipantRepository */
private $eventParticipantRepository;
public function __construct(int $eventId, EventParticipantRepository $eventParticipantRepository)
{
$this->eventId = $eventId;
$this->eventParticipantRepository = $eventParticipantRepository;
}
protected function doInitialize() : void
{
$eventParticipants = $this->eventParticipantRepository
->findByEventId($this->eventId);
$this->collection = new ArrayCollection($eventParticipants);
}
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Model;
use DateTimeImmutable;
use Doctrine\Common\Collections\AbstractLazyCollection;
use Doctrine\Common\Collections\ArrayCollection;
use InvalidArgumentException;
use function sprintf;
final class EventSchedule extends AbstractLazyCollection
{
/** @var mixed[] */
private $event;
/** @var EventSpeakers */
private $speakers;
/**
* @param mixed[] $event
*/
public function __construct(array $event, EventSpeakers $speakers)
{
$this->event = $event;
$this->speakers = $speakers;
}
protected function doInitialize() : void
{
$slots = [];
foreach ($this->event['schedule'] as $slot) {
if (! isset($this->speakers[$slot['topicSlug']])) {
throw new InvalidArgumentException(sprintf(
'Could not find speaker with topicSlug "%s".',
$slot['topicSlug']
));
}
$eventSpeaker = $this->speakers[$slot['topicSlug']];
$slots[] = new EventScheduleSlot(
$eventSpeaker,
new DateTimeImmutable($slot['startDate'] ?? ''),
new DateTimeImmutable($slot['endDate'] ?? '')
);
}
$this->collection = new ArrayCollection($slots);
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Model;
use DateTimeImmutable;
final class EventScheduleSlot
{
/** @var EventSpeaker */
private $speaker;
/** @var DateTimeImmutable */
private $startDate;
/** @var DateTimeImmutable */
private $endDate;
public function __construct(
EventSpeaker $speaker,
DateTimeImmutable $startDate,
DateTimeImmutable $endDate
) {
$this->speaker = $speaker;
$this->startDate = $startDate;
$this->endDate = $endDate;
}
public function getSpeaker() : EventSpeaker
{
return $this->speaker;
}
public function getStartDate() : DateTimeImmutable
{
return $this->startDate;
}
public function getEndDate() : DateTimeImmutable
{
return $this->endDate;
}
}

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Model;
use function sprintf;
final class EventSpeaker
{
/** @var string */
private $name;
/** @var string */
private $avatarUrl;
/** @var string */
private $topic;
/** @var string */
private $topicSlug;
/** @var string */
private $description;
/** @var string */
private $youTubeVideoId;
public function __construct(
string $name,
string $avatarUrl,
string $topic,
string $topicSlug,
string $description,
string $youTubeVideoId
) {
$this->name = $name;
$this->avatarUrl = $avatarUrl;
$this->topic = $topic;
$this->topicSlug = $topicSlug;
$this->description = $description;
$this->youTubeVideoId = $youTubeVideoId;
}
public function getName() : string
{
return $this->name;
}
public function getAvatarUrl() : string
{
return $this->avatarUrl;
}
public function getTopic() : string
{
return $this->topic;
}
public function getTopicSlug() : string
{
return $this->topicSlug;
}
public function getDescription() : string
{
return $this->description;
}
public function hasYouTubeVideo() : bool
{
return $this->youTubeVideoId !== '';
}
public function getYouTubeVideoId() : string
{
return $this->youTubeVideoId;
}
public function getYouTubeUrl() : string
{
return sprintf('https://www.youtube.com/watch?v=%s', $this->youTubeVideoId);
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Model;
use Doctrine\Common\Collections\AbstractLazyCollection;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\SkeletonMapper\ObjectManagerInterface;
use Doctrine\Website\Repositories\TeamMemberRepository;
use function assert;
final class EventSpeakers extends AbstractLazyCollection
{
/** @var mixed[] */
private $event;
/** @var ObjectManagerInterface */
private $objectManager;
/**
* @param mixed[] $event
*/
public function __construct(array $event, ObjectManagerInterface $objectManager)
{
$this->event = $event;
$this->objectManager = $objectManager;
}
protected function doInitialize() : void
{
$teamMemberRepository = $this->objectManager->getRepository(TeamMember::class);
assert($teamMemberRepository instanceof TeamMemberRepository);
$speakers = [];
foreach ($this->event['speakers'] ?? [] as $speaker) {
$speakerName = (string) ($speaker['name'] ?? '');
$teamMember = $speakerName !== ''
? $teamMemberRepository->findOneByGithub($speakerName)
: null;
$topicSlug = (string) ($speaker['topicSlug'] ?? '');
$speakers[$topicSlug] = new EventSpeaker(
$teamMember !== null ? $teamMember->getName() : $speakerName,
$teamMember !== null ? $teamMember->getAvatarUrl() : (string) ($speaker['avatarUrl'] ?? ''),
(string) ($speaker['topic'] ?? ''),
$topicSlug,
(string) ($speaker['description'] ?? ''),
(string) ($speaker['youTubeVideoId'] ?? '')
);
}
$this->collection = new ArrayCollection($speakers);
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Model;
final class EventSponsor
{
/** @var string */
private $name;
/** @var string */
private $url;
/** @var string */
private $logo;
/** @var UtmParameters */
private $utmParameters;
public function __construct(string $name, string $url, string $logo, UtmParameters $utmParameters)
{
$this->name = $name;
$this->url = $url;
$this->logo = $logo;
$this->utmParameters = $utmParameters;
}
public function getName() : string
{
return $this->name;
}
public function getUrl() : string
{
return $this->url;
}
/**
* @param string[] $parameters
*/
public function getUrlWithUtmParameters(array $parameters = []) : string
{
return $this->utmParameters->buildUrl($this->url, $parameters);
}
public function getLogo() : string
{
return $this->logo;
}
}

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Model;
use Doctrine\Common\Collections\AbstractLazyCollection;
use Doctrine\Common\Collections\ArrayCollection;
use function array_merge;
final class EventSponsors extends AbstractLazyCollection
{
/** @var mixed[] */
private $event;
/**
* @param mixed[] $event
*/
public function __construct(array $event)
{
$this->event = $event;
}
protected function doInitialize() : void
{
$sponsors = [];
foreach ($this->event['sponsors'] ?? [] as $sponsor) {
$sponsors[] = new EventSponsor(
(string) ($sponsor['name'] ?? ''),
(string) ($sponsor['url'] ?? ''),
(string) ($sponsor['logo'] ?? ''),
new UtmParameters(
array_merge(
[
'utm_source' => 'doctrine',
'utm_medium' => 'website',
'utm_campaign' => $this->event['slug'],
],
$sponsor['utmParameters'] ?? []
)
)
);
}
$this->collection = new ArrayCollection($sponsors);
}
}

15
lib/Model/EventType.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Model;
final class EventType
{
public const WEBINAR = 'webinar';
public const CONFERENCE = 'conference';
private function __construct()
{
}
}

View File

@@ -4,13 +4,10 @@ declare(strict_types=1);
namespace Doctrine\Website\Model;
use Doctrine\SkeletonMapper\Hydrator\HydratableInterface;
use Doctrine\SkeletonMapper\Mapping\ClassMetadataInterface;
use Doctrine\SkeletonMapper\Mapping\LoadMetadataInterface;
use Doctrine\SkeletonMapper\ObjectManagerInterface;
use function array_merge;
final class Partner implements HydratableInterface, LoadMetadataInterface
final class Partner implements LoadMetadataInterface
{
/** @var string */
private $name;
@@ -41,33 +38,6 @@ final class Partner implements HydratableInterface, LoadMetadataInterface
$metadata->setIdentifier(['slug']);
}
/**
* @param mixed[] $partner
*/
public function hydrate(array $partner, ObjectManagerInterface $objectManager) : void
{
$this->name = (string) ($partner['name'] ?? '');
$this->slug = (string) ($partner['slug'] ?? '');
$this->url = (string) ($partner['url'] ?? '');
$this->utmParameters = new UtmParameters(
array_merge(
[
'utm_source' => 'doctrine',
'utm_medium' => 'website',
'utm_campaign' => 'partners',
],
$partner['utmParameters'] ?? []
)
);
$this->logo = (string) ($partner['logo'] ?? '');
$this->bio = (string) ($partner['bio'] ?? '');
$this->details = new PartnerDetails(
(string) ($partner['details']['label'] ?? ''),
$partner['details']['items'] ?? []
);
$this->featured = (bool) ($partner['featured'] ?? false);
}
public function getName() : string
{
return $this->name;

View File

@@ -5,16 +5,14 @@ declare(strict_types=1);
namespace Doctrine\Website\Model;
use Closure;
use Doctrine\SkeletonMapper\Hydrator\HydratableInterface;
use Doctrine\SkeletonMapper\Mapping\ClassMetadataInterface;
use Doctrine\SkeletonMapper\Mapping\LoadMetadataInterface;
use Doctrine\SkeletonMapper\ObjectManagerInterface;
use InvalidArgumentException;
use function array_filter;
use function array_values;
use function sprintf;
class Project implements HydratableInterface, LoadMetadataInterface
class Project implements LoadMetadataInterface
{
/** @var ProjectIntegrationType|null */
private $projectIntegrationType;
@@ -70,75 +68,11 @@ class Project implements HydratableInterface, LoadMetadataInterface
/** @var ProjectVersion[] */
private $versions = [];
/**
* @param mixed[] $project
*/
public function __construct(array $project)
{
$this->doHydrate($project);
}
public static function loadMetadata(ClassMetadataInterface $metadata) : void
{
$metadata->setIdentifier(['slug']);
}
/**
* @param mixed[] $project
*/
public function hydrate(array $project, ObjectManagerInterface $objectManager) : void
{
$this->doHydrate($project);
}
/**
* @param mixed[] $project
*/
public function doHydrate(array $project) : void
{
$this->active = (bool) ($project['active'] ?? true);
$this->archived = (bool) ($project['archived'] ?? false);
$this->name = (string) ($project['name'] ?? '');
$this->shortName = (string) ($project['shortName'] ?? $this->name);
$this->slug = (string) ($project['slug'] ?? '');
$this->docsSlug = (string) ($project['docsSlug'] ?? $this->slug);
$this->composerPackageName = (string) ($project['composerPackageName'] ?? '');
$this->repositoryName = (string) ($project['repositoryName'] ?? '');
$this->isIntegration = (bool) ($project['integration'] ?? false);
$this->integrationFor = (string) ($project['integrationFor'] ?? '');
$this->docsRepositoryName = (string) ($project['docsRepositoryName'] ?? $this->repositoryName);
$this->docsPath = (string) ($project['docsPath'] ?? '/docs');
$this->codePath = (string) ($project['codePath'] ?? '/lib');
$this->description = (string) ($project['description'] ?? '');
$this->keywords = $project['keywords'] ?? [];
if (! isset($project['versions'])) {
return;
}
foreach ($project['versions'] as $version) {
$this->versions[] = $version instanceof ProjectVersion
? $version
: new ProjectVersion($version);
}
if ($this->isIntegration) {
$this->projectIntegrationType = new ProjectIntegrationType($project['integrationType']);
}
$this->projectStats = new ProjectStats(
(int) ($project['packagistData']['package']['github_stars'] ?? 0),
(int) ($project['packagistData']['package']['github_watchers'] ?? 0),
(int) ($project['packagistData']['package']['github_forks'] ?? 0),
(int) ($project['packagistData']['package']['github_open_issues'] ?? 0),
(int) ($project['packagistData']['package']['dependents'] ?? 0),
(int) ($project['packagistData']['package']['suggesters'] ?? 0),
(int) ($project['packagistData']['package']['downloads']['total'] ?? 0),
(int) ($project['packagistData']['package']['downloads']['monthly'] ?? 0),
(int) ($project['packagistData']['package']['downloads']['daily'] ?? 0)
);
}
public function getProjectIntegrationType() : ?ProjectIntegrationType
{
return $this->projectIntegrationType;

View File

@@ -4,12 +4,10 @@ declare(strict_types=1);
namespace Doctrine\Website\Model;
use Doctrine\SkeletonMapper\Hydrator\HydratableInterface;
use Doctrine\SkeletonMapper\Mapping\ClassMetadataInterface;
use Doctrine\SkeletonMapper\Mapping\LoadMetadataInterface;
use Doctrine\SkeletonMapper\ObjectManagerInterface;
class ProjectContributor implements HydratableInterface, LoadMetadataInterface, CommitterStats
class ProjectContributor implements LoadMetadataInterface, CommitterStats
{
/** @var TeamMember|null */
private $teamMember;
@@ -40,21 +38,6 @@ class ProjectContributor implements HydratableInterface, LoadMetadataInterface,
$metadata->setIdentifier(['projectSlug', 'github']);
}
/**
* @param mixed[] $projectContributor
*/
public function hydrate(array $projectContributor, ObjectManagerInterface $objectManager) : void
{
$this->teamMember = $projectContributor['teamMember'] ?? null;
$this->projectSlug = (string) ($projectContributor['projectSlug'] ?? '');
$this->project = $projectContributor['project'] ?? new Project([]);
$this->github = (string) ($projectContributor['github'] ?? '');
$this->avatarUrl = (string) ($projectContributor['avatarUrl'] ?? '');
$this->numCommits = (int) ($projectContributor['numCommits'] ?? 0);
$this->numAdditions = (int) ($projectContributor['numAdditions'] ?? 0);
$this->numDeletions = (int) ($projectContributor['numDeletions'] ?? 0);
}
public function getTeamMember() : ?TeamMember
{
return $this->teamMember;

View File

@@ -5,12 +5,10 @@ declare(strict_types=1);
namespace Doctrine\Website\Model;
use DateTimeImmutable;
use Doctrine\SkeletonMapper\Hydrator\HydratableInterface;
use Doctrine\SkeletonMapper\Mapping\ClassMetadataInterface;
use Doctrine\SkeletonMapper\Mapping\LoadMetadataInterface;
use Doctrine\SkeletonMapper\ObjectManagerInterface;
class SitemapPage implements HydratableInterface, LoadMetadataInterface
class SitemapPage implements LoadMetadataInterface
{
/** @var string */
private $url;
@@ -29,15 +27,6 @@ class SitemapPage implements HydratableInterface, LoadMetadataInterface
$metadata->setIdentifier(['url']);
}
/**
* @param mixed[] $sitemapPage
*/
public function hydrate(array $sitemapPage, ObjectManagerInterface $objectManager) : void
{
$this->url = (string) ($sitemapPage['url'] ?? '');
$this->date = $sitemapPage['date'] ?? new DateTimeImmutable();
}
public function getUrl() : string
{
return $this->url;

View File

@@ -4,13 +4,10 @@ declare(strict_types=1);
namespace Doctrine\Website\Model;
use Doctrine\SkeletonMapper\Hydrator\HydratableInterface;
use Doctrine\SkeletonMapper\Mapping\ClassMetadataInterface;
use Doctrine\SkeletonMapper\Mapping\LoadMetadataInterface;
use Doctrine\SkeletonMapper\ObjectManagerInterface;
use function array_merge;
final class Sponsor implements HydratableInterface, LoadMetadataInterface
final class Sponsor implements LoadMetadataInterface
{
/** @var string */
private $name;
@@ -29,26 +26,6 @@ final class Sponsor implements HydratableInterface, LoadMetadataInterface
$metadata->setIdentifier(['name']);
}
/**
* @param mixed[] $sponsor
*/
public function hydrate(array $sponsor, ObjectManagerInterface $objectManager) : void
{
$this->name = (string) ($sponsor['name'] ?? '');
$this->url = (string) ($sponsor['url'] ?? '');
$this->utmParameters = new UtmParameters(
array_merge(
[
'utm_source' => 'doctrine',
'utm_medium' => 'website',
'utm_campaign' => 'sponsors',
],
$sponsor['utmParameters'] ?? []
)
);
$this->highlighted = (bool) ($sponsor['highlighted'] ?? '');
}
public function getName() : string
{
return $this->name;

View File

@@ -5,15 +5,11 @@ declare(strict_types=1);
namespace Doctrine\Website\Model;
use Closure;
use Doctrine\SkeletonMapper\Hydrator\HydratableInterface;
use Doctrine\SkeletonMapper\Mapping\ClassMetadataInterface;
use Doctrine\SkeletonMapper\Mapping\LoadMetadataInterface;
use Doctrine\SkeletonMapper\ObjectManagerInterface;
use Doctrine\Website\Repositories\ContributorRepository;
use function assert;
use function in_array;
class TeamMember implements HydratableInterface, LoadMetadataInterface, CommitterStats
class TeamMember implements LoadMetadataInterface, CommitterStats
{
/** @var string */
private $name;
@@ -53,31 +49,6 @@ class TeamMember implements HydratableInterface, LoadMetadataInterface, Committe
$metadata->setIdentifier(['github']);
}
/**
* @param mixed[] $teamMember
*/
public function hydrate(array $teamMember, ObjectManagerInterface $objectManager) : void
{
$this->name = (string) ($teamMember['name'] ?? '');
$this->github = (string) ($teamMember['github'] ?? '');
$this->twitter = (string) ($teamMember['twitter'] ?? '');
$this->avatarUrl = (string) ($teamMember['avatarUrl'] ?? '');
$this->website = (string) ($teamMember['website'] ?? '');
$this->location = (string) ($teamMember['location'] ?? '');
$this->maintains = $teamMember['maintains'] ?? [];
$this->consultant = (bool) ($teamMember['consultant'] ?? false);
$this->headshot = (string) ($teamMember['headshot'] ?? '');
$this->bio = (string) ($teamMember['bio'] ?? '');
$this->contributor = static function (string $github) use ($objectManager) : Contributor {
$contributorRepository = $objectManager
->getRepository(Contributor::class);
assert($contributorRepository instanceof ContributorRepository);
return $contributorRepository->findOneByGithub($github);
};
}
public function getName() : string
{
return $this->name;

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Repositories;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\SkeletonMapper\ObjectRepository\BasicObjectRepository;
use Doctrine\Website\Model\Event;
use InvalidArgumentException;
use function sprintf;
class EventRepository extends BasicObjectRepository
{
public function findOneById(int $id) : Event
{
$event = $this->findOneBy(['id' => $id]);
if ($event === null) {
throw new InvalidArgumentException(sprintf('Could not find Event with id "%s"', $id));
}
return $event;
}
/**
* @return Event[]
*/
public function findUpcomingEvents() : array
{
/** @var Event[] $events */
$events = $this->findBy([], ['startDate' => 'asc']);
$criteria = Criteria::create()
->where(Criteria::expr()->gt('startDate', new DateTimeImmutable()));
return (new ArrayCollection($events))->matching($criteria)->toArray();
}
/**
* @return Event[]
*/
public function findPastEvents() : array
{
/** @var Event[] $events */
$events = $this->findBy([], ['endDate' => 'desc']);
$criteria = Criteria::create()
->where(Criteria::expr()->lt('endDate', new DateTimeImmutable()));
return (new ArrayCollection($events))->matching($criteria)->toArray();
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Requests;
use Doctrine\StaticWebsiteGenerator\Request\ArrayRequestCollection;
use Doctrine\StaticWebsiteGenerator\Request\RequestCollection;
use Doctrine\Website\Model\Event;
use Doctrine\Website\Repositories\EventRepository;
class EventRequests
{
/** @var EventRepository */
private $eventRepository;
public function __construct(EventRepository $eventRepository)
{
$this->eventRepository = $eventRepository;
}
public function getEvents() : RequestCollection
{
/** @var Event[] $events */
$events = $this->eventRepository->findAll();
$requests = [];
foreach ($events as $event) {
$requests[] = [
'id' => $event->getId(),
'slug' => $event->getSlug(),
];
}
return new ArrayRequestCollection($requests);
}
}

44
lib/Site.php Normal file
View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website;
use Doctrine\StaticWebsiteGenerator\Site as BaseSite;
final class Site extends BaseSite
{
/** @var string */
private $assetsUrl;
/**
* @param string[] $keywords
*/
public function __construct(
string $title,
string $subtitle,
string $url,
array $keywords,
string $description,
string $env,
string $googleAnalyticsTrackingId,
string $assetsUrl
) {
parent::__construct(
$title,
$subtitle,
$url,
$keywords,
$description,
$env,
$googleAnalyticsTrackingId
);
$this->assetsUrl = $assetsUrl;
}
public function getAssetsUrl() : string
{
return $this->assetsUrl;
}
}

View File

@@ -36,12 +36,21 @@ class MainExtension extends Twig_Extension
/** @var string */
private $webpackBuildDir;
public function __construct(Parsedown $parsedown, AssetIntegrityGenerator $assetIntegrityGenerator, string $sourceDir, string $webpackBuildDir)
{
/** @var string */
private $stripePublishableKey;
public function __construct(
Parsedown $parsedown,
AssetIntegrityGenerator $assetIntegrityGenerator,
string $sourceDir,
string $webpackBuildDir,
string $stripePublishableKey
) {
$this->parsedown = $parsedown;
$this->assetIntegrityGenerator = $assetIntegrityGenerator;
$this->sourceDir = $sourceDir;
$this->webpackBuildDir = $webpackBuildDir;
$this->stripePublishableKey = $stripePublishableKey;
}
/**
@@ -55,6 +64,7 @@ class MainExtension extends Twig_Extension
new Twig_SimpleFunction('get_webpack_asset_url', [$this, 'getWebpackAssetUrl']),
new Twig_SimpleFunction('get_asset_integrity', [$this->assetIntegrityGenerator, 'getAssetIntegrity']),
new Twig_SimpleFunction('get_webpack_asset_integrity', [$this->assetIntegrityGenerator, 'getWebpackAssetIntegrity']),
new Twig_SimpleFunction('get_stripe_publishable_key', [$this, 'getStripePublishableKey']),
];
}
@@ -108,6 +118,11 @@ class MainExtension extends Twig_Extension
return $string;
}
public function getStripePublishableKey() : string
{
return $this->stripePublishableKey;
}
private function getAssetCacheBuster(string $path, string $rootPath) : string
{
$assetPath = realpath($rootPath . '/' . $path);

View File

@@ -15,6 +15,7 @@ use Symfony\Component\Filesystem\Filesystem;
use function chdir;
use function file_exists;
use function file_put_contents;
use function getcwd;
use function glob;
use function in_array;
use function is_dir;
@@ -108,7 +109,7 @@ class WebsiteBuilder
$this->createProjectVersionAliases($buildDir);
$this->copyWebsiteBuildData($buildDir);
$this->copyWebsiteBuildData($output, $buildDir);
if ($publish) {
$output->writeln(' - publishing build');
@@ -129,27 +130,38 @@ class WebsiteBuilder
*/
private function buildWebsite(OutputInterface $output, string $buildDir, bool $isPublishableEnv) : void
{
$output->writeln(sprintf(' - clearing build directory <info>%s</info>', $buildDir));
// cleanup the build directory
$this->filesystem->remove(glob($buildDir . '/*'));
// Move webpack assets into build directory
$this->buildWebpackAssets($output, $buildDir, $isPublishableEnv);
$this->sourceFilesBuilder->buildSourceFiles(
$this->sourceFileRepository->getSourceFiles($buildDir)
);
$output->writeln(' - calculating source files to build');
$sourceFiles = $this->sourceFileRepository->getSourceFiles($buildDir);
$output->writeln(sprintf(' - building source files to <info>%s</info>', $buildDir));
$this->sourceFilesBuilder->buildSourceFiles($sourceFiles);
}
private function buildWebpackAssets(OutputInterface $output, string $buildDir, bool $isPublishableEnv) : void
{
$output->writeln(sprintf(' - running npm run %s ', $isPublishableEnv ? 'build' : 'dev'));
$output->writeln(sprintf(' - running <info>npm run %s</info> ', $isPublishableEnv ? 'build' : 'dev'));
$this->filesystem->remove(glob($this->webpackBuildDir . '/*'));
$process = $this->processFactory->run(sprintf(
'cd %s && npm run %s',
$this->rootDir,
$isPublishableEnv ? 'build' : 'dev'
));
$output->write($process->getOutput());
if ($output->isVerbose()) {
$output->write($process->getOutput());
}
// Copy built assets if this is a publishable build
if ($isPublishableEnv) {
@@ -181,9 +193,18 @@ class WebsiteBuilder
}
}
private function copyWebsiteBuildData(string $buildDir) : void
private function copyWebsiteBuildData(OutputInterface $output, string $buildDir) : void
{
$this->filesystem->mirror($this->cacheDir . '/data', $buildDir . '/website-data');
$from = $this->cacheDir . '/data';
$to = $buildDir . '/website-data';
$output->writeln(sprintf(
' - copying website build data from <info>%s</info> to <info>%s</info>.',
$from,
$to
));
$this->filesystem->mirror($from, $to);
}
private function createDocsProjectVersionAlias(
@@ -207,16 +228,22 @@ class WebsiteBuilder
return;
}
$cwd = getcwd();
chdir($dir);
if (file_exists($alias)) {
unlink($alias);
}
if (! file_exists($version->getSlug())) {
if (file_exists($version->getSlug())) {
symlink($version->getSlug(), $alias);
}
if ($cwd === false) {
return;
}
symlink($version->getSlug(), $alias);
chdir($cwd);
}
}

16
phpunit Executable file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/env php
<?php
if (isset($argv[1]) && $argv[1] === '--build-all') {
unset($argv[1]);
$argv = array_values($argv);
$argc = count($argv);
$_SERVER['argv'] = $argv;
$_SERVER['argc'] = count($argv);
require_once __DIR__.'/tests/BuildAllBootstrap.php';
}
require_once 'vendor/bin/phpunit';

43
source/events.html Normal file
View File

@@ -0,0 +1,43 @@
{% block title %}Doctrine Events{% endblock %}
{% block content %}
<h1>Events</h1>
{% if upcomingEvents|length %}
<ul class="list-group">
{% for event in upcomingEvents %}
<div class="list-group mb-4">
<a href="{{ path('event', {id:event.id, slug:event.slug}) }}" class="list-group-item list-group-item-action flex-column align-items-start">
<h5 class="mb-1">{{ event.name }}</h5>
<p class="mb-1">
This {{ event.dates.over ? 'was' : 'is' }} a {{ event.dates.duration }} event that {{ event.dates.over ? 'started' : 'starts' }} on {{ event.startDate|date('l, F jS Y') }} at {{ event.startDate|date('h:i A T') }}{% if event.dates.numDays > 1 %} and ends on {{ event.endDate|date('l, F jS Y') }} at {{ event.endDate|date('h:i A T') }}{% endif %}.
</p>
<p class="mb-1">
{% for speaker in event.speakers|slice(0, 5) %}
<img src="{{ speaker.avatarUrl }}" title="{{ speaker.name }}" alt="{{ speaker.name }}" width="50" />
{% endfor %}
</p>
</a>
</div>
{% endfor %}
</ul>
<p class="lead">Don't see something that you are interested in? <a href="{{ path('event_suggest') }}">Suggest</a> an event topic you would like to see and we will see what we can do!</p>
{% else %}
<p class="lead">No upcoming events are currently scheduled. Come back soon to check for new events! You may also <a href="{{ path('event_suggest') }}">suggest</a> an event topic you would like to see.</p>
{% endif %}
{% if pastEvents|length %}
<h2>Past Events</h2>
<ul class="list-group">
{% for event in pastEvents %}
<li class="list-group-item"><a href="{{ path('event', {id:event.id, slug:event.slug}) }}">{{ event.name }}</a></li>
{% endfor %}
</ul>
{% endif %}
{% include "carbonad-standard.html.twig" %}
{% endblock %}

View File

@@ -0,0 +1,12 @@
{% block title %}Suggest a Doctrine Event{% endblock %}
{% block content %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ path('events') }}">Events</a></li>
<li class="breadcrumb-item active" aria-current="page">Suggest an Event</li>
</ol>
</nav>
<iframe src="https://docs.google.com/forms/d/e/1FAIpQLSeDhyTE57K50Ob-VwqyE9RZO8OCe56HLDd385NPltH4k4nY0g/viewform?embedded=true" width="640" height="667" frameborder="0" marginheight="0" marginwidth="0">Loading...</iframe>
{% endblock %}

59
source/js/event.js Normal file
View File

@@ -0,0 +1,59 @@
export default function() {
function openEventModal(label, message) {
$('#event-modal-label').html(label);
$('#event-modal-body').html(message);
$('#event-modal').modal();
}
if (window.location.hash === '#success') {
openEventModal(
'Purchase Successful',
'Your ticket purchase for <strong>' + window.event.name + '</strong> was successful! You will be e-mailed a receipt for your purchase immediately and details for joining the event will e-mailed 1 week before the event is scheduled to start.'
);
window.location.hash = '';
}
if (window.location.hash === '#canceled') {
openEventModal(
'Purchase Failure',
'Oh no! Your ticket purchase for <strong>' + window.event.name + '</strong> was not successful. Please give it another try.'
);
window.location.hash = '';
}
if (window.location.hash === '#thanks') {
openEventModal(
'Event Finished',
'Thanks for attending <strong>' + window.event.name + '</strong>! Keep your eyes open for more events in the future.'
);
window.location.hash = '';
}
$('#checkout-button').on('click', function() {
$(this).addClass('disabled');
});
$.getScript('https://js.stripe.com/v3', () => {
var stripe = Stripe(window.stripePublishableKey);
var checkoutButton = document.getElementById('checkout-button');
checkoutButton.addEventListener('click', function () {
stripe.redirectToCheckout({
items: [{sku: window.event.sku, quantity: 1}],
successUrl: window.event.url + '#success',
cancelUrl: window.event.url + '#canceled',
})
.then(function (result) {
if (result.error) {
var displayError = document.getElementById('stripe-error-message');
displayError.textContent = result.error.message;
}
});
});
});
};

View File

@@ -20,6 +20,12 @@ if ($('#sidebar').length > 0) {
});
}
if (typeof window.event === 'object') {
import(/* webpackChunkName: "event" */ './event').then(module => {
module.default();
});
}
window.googleTranslateElementInit = () => {
$('#google_translate_element').html('');
@@ -33,4 +39,5 @@ window.googleTranslateElementInit = () => {
googleAnalyticsEvent('Translate', 'click', language);
});
};
$.getScript('https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit');
$.getScript('https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit');

View File

@@ -464,6 +464,10 @@ div.jsactive pre {
height: 14px;
}
.modal-backdrop.in {
opacity: 0.7;
}
@media (max-width: 900px) {
.sidebar {
display: none;

View File

@@ -0,0 +1,286 @@
/* -------------------------------------
GLOBAL RESETS
------------------------------------- */
/*All the styling goes here*/
img {
border: none;
-ms-interpolation-mode: bicubic;
max-width: 100%;
}
body {
background-color: #f6f6f6;
font-family: sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
line-height: 1.4;
margin: 0;
padding: 0;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
table {
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
width: 100%; }
table td {
font-family: sans-serif;
font-size: 14px;
vertical-align: top;
}
/* -------------------------------------
BODY & CONTAINER
------------------------------------- */
.body {
background-color: #f6f6f6;
width: 100%;
}
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
.container {
display: block;
margin: 0 auto !important;
/* makes it centered */
max-width: 580px;
padding: 10px;
width: 580px;
}
/* This should also be a block element, so that it will fill 100% of the .container */
.content {
box-sizing: border-box;
display: block;
margin: 0 auto;
max-width: 580px;
padding: 10px;
}
/* -------------------------------------
HEADER, FOOTER, MAIN
------------------------------------- */
.main {
background: #ffffff;
border-radius: 3px;
width: 100%;
}
.wrapper {
box-sizing: border-box;
padding: 20px;
}
.content-block {
padding-bottom: 10px;
padding-top: 10px;
}
.footer {
clear: both;
margin-top: 10px;
text-align: center;
width: 100%;
}
.footer td,
.footer p,
.footer span,
.footer a {
color: #999999;
font-size: 12px;
text-align: center;
}
/* -------------------------------------
TYPOGRAPHY
------------------------------------- */
h1,
h2,
h3,
h4 {
color: #000000;
font-family: sans-serif;
font-weight: 400;
line-height: 1.4;
margin: 0;
margin-bottom: 30px;
}
h1 {
font-size: 35px;
font-weight: 300;
text-align: center;
text-transform: capitalize;
}
p,
ul,
ol {
font-family: sans-serif;
font-size: 14px;
font-weight: normal;
margin: 0;
margin-bottom: 15px;
}
p li,
ul li,
ol li {
list-style-position: inside;
margin-left: 5px;
}
a {
color: #3498db;
text-decoration: underline;
}
/* -------------------------------------
BUTTONS
------------------------------------- */
.btn {
box-sizing: border-box;
width: 100%; }
.btn > tbody > tr > td {
padding-bottom: 15px; }
.btn table {
width: auto;
}
.btn table td {
background-color: #ffffff;
border-radius: 5px;
text-align: center;
}
.btn a {
background-color: #ffffff;
border: solid 1px #3498db;
border-radius: 5px;
box-sizing: border-box;
color: #3498db;
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: bold;
margin: 0;
padding: 12px 25px;
text-decoration: none;
text-transform: capitalize;
}
.btn-primary table td {
background-color: #3498db;
}
.btn-primary a {
background-color: #3498db;
border-color: #3498db;
color: #ffffff;
}
/* -------------------------------------
OTHER STYLES THAT MIGHT BE USEFUL
------------------------------------- */
.last {
margin-bottom: 0;
}
.first {
margin-top: 0;
}
.align-center {
text-align: center;
}
.align-right {
text-align: right;
}
.align-left {
text-align: left;
}
.clear {
clear: both;
}
.mt0 {
margin-top: 0;
}
.mb0 {
margin-bottom: 0;
}
.preheader {
color: transparent;
display: none;
height: 0;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
mso-hide: all;
visibility: hidden;
width: 0;
}
.powered-by a {
text-decoration: none;
}
hr {
border: 0;
border-bottom: 1px solid #f6f6f6;
margin: 20px 0;
}
/* -------------------------------------
RESPONSIVE AND MOBILE FRIENDLY STYLES
------------------------------------- */
@media only screen and (max-width: 620px) {
table[class=body] h1 {
font-size: 28px !important;
margin-bottom: 10px !important;
}
table[class=body] p,
table[class=body] ul,
table[class=body] ol,
table[class=body] td,
table[class=body] span,
table[class=body] a {
font-size: 16px !important;
}
table[class=body] .wrapper,
table[class=body] .article {
padding: 10px !important;
}
table[class=body] .content {
padding: 0 !important;
}
table[class=body] .container {
padding: 0 !important;
width: 100% !important;
}
table[class=body] .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important;
}
table[class=body] .btn table {
width: 100% !important;
}
table[class=body] .btn a {
width: 100% !important;
}
table[class=body] .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important;
}
}
/* -------------------------------------
PRESERVE THESE STYLES IN THE HEAD
------------------------------------- */
@media all {
.ExternalClass {
width: 100%;
}
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%;
}
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important;
}
.btn-primary table td:hover {
background-color: #34495e !important;
}
.btn-primary a:hover {
background-color: #34495e !important;
border-color: #34495e !important;
}
}

View File

@@ -0,0 +1,8 @@
.header {
text-align: center;
}
.header img {
height: 60px;
margin-top: 20px;
}

View File

@@ -0,0 +1,13 @@
{% block subject '' %}
{% block head_css '' %}
{% block inline_css '' %}
{% block full_body_text %}
{% block body_text '' %}
{% endblock %}
{% block full_body_html %}
{% block body_html '' %}
{% endblock %}

View File

@@ -0,0 +1,21 @@
{% extends 'emails/layout.html.twig' %}
{% block subject %}
[Doctrine Event] {{ event.name }}
{% endblock %}
{% block body_html %}
<p>Hi {{ participant.email }},</p>
<p>You are receiving this e-mail because you registered for the Doctrine Event named <strong>{{ event.name }}</strong>.</p>
{% if event.conference %}
<p>This event is hosted at <strong>{{ event.location.name }}</strong> which is located at <a href="https://www.google.com/maps?q={{ event.location.address.string }}" target="_blank" >{{ event.location.address.string }}</a>.</p>
{% else %}
<p>You can join the event at the following url: <a href="{{ event.joinUrl }}">{{ event.joinUrl }}</a></p>
{% endif %}
<p>If you need help, e-mail us at <a href="mailto:events@doctrine-project.org?subject={{ event.name }} Help">events@doctrine-project.org</a>.</p>
<p>Thanks, Doctrine Team!</p>
{% endblock %}

View File

@@ -0,0 +1,64 @@
{% extends 'emails/email.html.twig' %}
{% block inline_css %}
{% include 'emails/css/framework.css' %}
{% include 'emails/css/layout.css' %}
{% endblock %}
{% block full_body_html %}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Doctrine</title>
<style type="text/css">
{% block head_css '' %}
</style>
</head>
<body>
<span class="preheader">{% block preheader '' %}</span>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body">
<tr>
<td>&nbsp;</td>
<td class="header">
<a href="{{ url('homepage') }}"><img src="{{ get_asset_url('/images/favicon-196x196.png', site.assetsUrl) }}" /></a>
</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td class="container">
<div class="content">
<table role="presentation" class="main">
<tr>
<td class="wrapper">
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
{% block body_html '' %}
</td>
</tr>
</table>
</td>
</tr>
</table>
<div class="footer">
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td class="content-block">
<span class="apple-link"><a href="{{ url('homepage') }}">Doctrine Company, LLC</a></span>
</td>
</tr>
</table>
</div>
</div>
</td>
<td>&nbsp;</td>
</tr>
</table>
</body>
</html>
{% endblock %}

View File

@@ -0,0 +1,27 @@
{% block title %}{{ event.name }}{% endblock %}
{% block meta_description event.description %}
{% block content %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ path('events') }}">Events</a></li>
<li class="breadcrumb-item"><a href="{{ path('event', {id:event.id, slug:event.slug}) }}">{{ event.name }}</a></li>
<li class="breadcrumb-item active" aria-current="page">Call For Papers</li>
</ol>
</nav>
{% if event.cfp.exists %}
{% if event.cfp.dates.now %}
<p class="lead">Call for Papers started on <strong>{{ event.cfp.dates.start|date('l, F jS Y') }}</strong> and ends on <strong>{{ event.cfp.dates.end|date('l, F jS Y') }}</strong>.</p>
<iframe src="{{ event.cfp.embeddedGoogleFormUrl }}" width="640" height="930" frameborder="0" marginheight="0" marginwidth="0">Loading...</iframe>
{% elseif event.cfp.dates.upcoming %}
<p class="lead">Call for Papers starts on <strong>{{ event.cfp.dates.start|date('l, F jS Y') }}</strong> and ends on <strong>{{ event.cfp.dates.end|date('l, F jS Y') }}</strong>.</p>
{% else %}
{% include "alert.html.twig" with {alertMessage:event.name ~' call for papers has ended!'} %}
{% endif %}
{% else %}
{% include "alert.html.twig" with {alertMessage:event.name ~' does not have a call for papers!'} %}
{% endif %}
{% endblock %}

173
templates/event.html.twig Normal file
View File

@@ -0,0 +1,173 @@
{% block title %}{{ event.name }}{% endblock %}
{% block meta_description event.description %}
{% block content %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ path('events') }}">Events</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ event.name }}</li>
</ol>
</nav>
{% if event.dates.over %}
{% set alertMessage %}
This event has ended. Watch for additional <a href="{{ path('events') }}">events</a> in the future.
{% endset %}
{% include "alert.html.twig" with {alertMessage:alertMessage} %}
{% endif %}
<p class="lead">{{ event.description }}</p>
<div class="card mb-4 box-shadow">
<div class="card-body">
<div class="row">
<div class="col-lg-8 col-sm-12">
<h1 class="card-title pricing-card-title">
{% if event.free %}
FREE!
{% else %}
${{ event.price|number_format }}
{% endif %}
</h1>
<p class="card-text">This {{ event.dates.over ? 'was' : 'is' }} a {{ event.dates.duration }} event that {{ event.dates.over ? 'started' : 'starts' }} on {{ event.startDate|date('l, F jS Y') }} at {{ event.startDate|date('h:i A T') }}{% if event.dates.numDays > 1 %} and ends on {{ event.endDate|date('l, F jS Y') }} at {{ event.endDate|date('h:i A T') }}{% endif %}.</p>
{% if event.registrationDates.now %}
{% if event.free %}
{% if event.joinUrl %}
<a href="{{ event.joinUrl }}" class="btn btn-primary" target="_blank" rel="noopener noreferrer">Join Event</a>
{% else %}
<small class="text-muted">A join URL does not exist yet for this event yet. Check back when it gets closer to {{ event.startDate|date('l, F jS Y') }}.</small>
{% endif %}
{% else %}
<button
class="btn btn-primary"
id="checkout-button"
role="link"
>
Purchase Ticket
</button>
{% endif %}
{% elseif event.registrationDates.over %}
<small class="text-muted">Registration is over for this event.</small>
{% else %}
<small class="text-muted">Registration will open on {{ event.registrationDates.start|date('l, F jS Y') }}.</small>
{% endif %}
{% if event.cfp.dates.now %}
<a href="{{ path('event_cfp', {id:event.id, slug:event.slug}) }}" class="btn btn-primary">Submit a Talk</a>
{% endif %}
</div>
<div class="col-4 d-none d-lg-block">
{% include "carbonad-standard.html.twig" %}
</div>
</div>
</div>
{% if event.speakers|length %}
<div class="card-footer text-muted">
{% for speaker in event.speakers %}
<a href="{{ path('event', {id:event.id, slug:event.slug}) }}#{{ speaker.topicSlug }}" title="{{ speaker.name }}"><img src="{{ speaker.avatarUrl }}" width="50" alt="{{ speaker.name }}" /></a>
{% endfor %}
</div>
{% endif %}
</div>
{% if event.conference %}
<h1>Location</h1>
<p class="lead">This event is hosted at <strong>{{ event.location.name }}</strong> which is located at <a href="https://www.google.com/maps?q={{ event.location.address.string }}" target="_blank" rel="noopener noreferrer">{{ event.location.address.string }}</a>.</p>
<div class="mapouter mb-4"><div class="gmap_canvas"><iframe width="400" height="300" id="gmap_canvas" src="https://www.google.com/maps?q={{ event.location.address.string }}&t=&z=13&ie=UTF8&iwloc=&output=embed" frameborder="0" scrolling="no" marginheight="0" marginwidth="0"></iframe><a href="https://www.crocothemes.net"></a></div><style>.mapouter{position:relative;text-align:right;height:300px;width:400px;}.gmap_canvas {overflow:hidden;background:none!important;height:300px;width:400px;}</style></div>
{% endif %}
{% if event.sponsors|length %}
<h2>Sponsors</h2>
<p class="lead">
Thanks to our sponsors! This event would not be possible without their support. If you are interested in becoming a sponsor, please contact us at <a href="mailto:sponsorship@doctrine-project.org?subject={{ event.name }} Sponsorship">sponsorship@doctrine-project.org</a>
</p>
<div class="card-deck mb-3">
{% for sponsor in event.sponsors %}
<div class="card mb-4 box-shadow">
<a href="{{ sponsor.urlWithUtmParameters }}" target="_blank"><img class="card-img-top p-2" height="100" src="{{ sponsor.logo }}" alt="{{ sponsor.name }}"></a>
</div>
{% endfor %}
</div>
{% endif %}
{% if event.schedule|length %}
<h2>Schedule</h2>
<table class="table table-striped">
{% for slot in event.schedule %}
<tr>
<td><a href="{{ path('event', {id:event.id, slug:event.slug}) }}#{{ slot.speaker.topicSlug }}"><strong>{{ slot.speaker.topic }}</strong></a> <span class="text-muted"><small>{{ slot.speaker.name }}</small></span></td>
<td>{{ slot.startDate|date('h:i A T') }}</td>
<td>{{ slot.endDate|date('h:i A T') }}</td>
<td>{% if slot.speaker.hasYouTubeVideo() %}<a href="{{ slot.speaker.youTubeUrl }}" target="_blank" rel="noopener noreferrer"><i class="fab fa-youtube text-danger"></i>{% endif %}</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% if event.speakers|length %}
<h2>Speakers</h2>
{% for speaker in event.speakers %}
<a name="{{ speaker.topicSlug }}">
<div class="card mb-4">
<div class="row no-gutters">
<div class="col-auto">
<img src="{{ speaker.avatarUrl }}" width="200" class="img-fluid" alt="{{ speaker.name }}" />
</div>
<div class="col">
<div class="card-block p-4">
<h4 class="card-title">
{{ speaker.topic }}
{% if speaker.hasYouTubeVideo() %}
<a href="{{ speaker.youTubeUrl }}" target="_blank" rel="noopener noreferrer"><i class="fab fa-youtube text-danger"></i></a>
{% endif %}
</h4>
<h5 class="card-subtitle mb-2 text-muted">{{ speaker.name }}</h5>
<p class="card-text">{{ speaker.description|nl2br }}</p>
</div>
</div>
</div>
</div>
{% endfor %}
{% endif %}
<div id="event-modal" class="modal fade bd-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="event-modal-label"></h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body" id="event-modal-body"></div>
</div>
</div>
</div>
<div id="stripe-error-message"></div>
<script type="text/javascript">
{% if not event.free %}
window.stripePublishableKey = '{{ get_stripe_publishable_key() }}';
{% endif %}
window.event = {
name: '{{ event.name }}',
sku: '{{ event.sku }}',
url: '{{ url('event', {id:event.id, slug:event.slug}) }}'
};
</script>
{% endblock %}

View File

@@ -137,27 +137,26 @@
<a class="dropdown-item" href="{{ path('contribute') }}">Contributor Workflow</a>
<a class="dropdown-item" href="{{ path('contribute_maintainer') }}">Maintainer Workflow</a>
<a class="dropdown-item" href="{{ path('contribute_website') }}">Contribute to Website</a>
<a class="dropdown-item" href="{{ path('team_maintainers') }}">Maintainers</a>
<a class="dropdown-item" href="{{ path('team_contributors') }}">Contributors</a>
<a class="dropdown-item" href="{{ path('policies') }}">Policies</a>
<a class="dropdown-item" href="https://github.com/doctrine" target="_blank" rel="noopener noreferrer">GitHub</a>
<a class="dropdown-item" href="{{ path('styleguide') }}">Styleguide</a>
</div>
</li>
<li class="nav-item{% if menuSlug == 'blog' %} active{% endif %}">
<a class="nav-link" href="{{ path('blog') }}">Blog</a>
<li class="nav-item{% if menuSlug == 'events' %} active{% endif %}">
<a class="nav-link" href="{{ path('events') }}">Events</a>
</li>
<li class="nav-item dropdown{% if menuSlug == 'team' %} active{% endif %}">
<a class="nav-link dropdown-toggle" href="{{ path('team_maintainers') }}" id="navbarTeamDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Team</a>
<div class="dropdown-menu" aria-labelledby="navbarTeamDropdown">
<a class="dropdown-item" href="{{ path('team_maintainers') }}">Maintainers</a>
<a class="dropdown-item" href="{{ path('team_contributors') }}">Contributors</a>
</div>
<li class="nav-item{% if menuSlug == 'consulting' %} active{% endif %}">
<a class="nav-link" href="{{ path('consulting') }}">Consulting</a>
</li>
<li class="nav-item{% if menuSlug == 'partners' %} active{% endif %}">
<a class="nav-link" href="{{ path('partners') }}">Partners</a>
</li>
<li class="nav-item{% if menuSlug == 'consulting' %} active{% endif %}">
<a class="nav-link" href="{{ path('consulting') }}">Consulting</a>
<li class="nav-item{% if menuSlug == 'blog' %} active{% endif %}">
<a class="nav-link" href="{{ path('blog') }}">Blog</a>
</li>
</ul>
@@ -211,6 +210,7 @@
<li class="list-inline-item"><a href="{{ path('sponsorship') }}">Sponsorship</a></li>
<li class="list-inline-item"><a href="{{ path('community') }}">Community</a></li>
<li class="list-inline-item"><a href="{{ path('blog') }}">Blog</a></li>
<li class="list-inline-item"><a href="{{ path('events') }}">Events</a></li>
<li class="list-inline-item"><a href="{{ path('consulting') }}">Consulting</a></li>
</ul>
</div>

View File

@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Doctrine\Website\Tests\Assets;
use Doctrine\Website\Assets\AssetIntegrityGenerator;
use PHPUnit\Framework\TestCase;
use Doctrine\Website\Tests\TestCase;
class AssetIntegrityGeneratorTest extends TestCase
{

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Tests;
require_once __DIR__ . '/../vendor/autoload.php';
use Doctrine\Website\Application;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use function strpos;
final class BuildAllBootstrap
{
private const COMMAND = 'build-all';
public function __invoke() : void
{
$container = Application::getContainer('test');
/** @var Application $application */
$application = $container->get(Application::class);
$consoleApplication = $application->getConsoleApplication();
$consoleApplication->setAutoExit(false);
$input = new ArrayInput([
'command' => self::COMMAND,
]);
$consoleApplication->find(self::COMMAND)
->run($input, new ConsoleOutput());
}
}
// only execute this for phpunit
if (strpos($_SERVER['PHP_SELF'], 'phpunit') !== false) {
(new BuildAllBootstrap())->__invoke();
}

View File

@@ -5,8 +5,8 @@ declare(strict_types=1);
namespace Doctrine\Website\Tests\Cache;
use Doctrine\Website\Cache\CacheClearer;
use Doctrine\Website\Tests\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Filesystem\Filesystem;
class CacheClearerTest extends TestCase

View File

@@ -10,8 +10,8 @@ use Doctrine\StaticWebsiteGenerator\SourceFile\SourceFileFilesystemReader;
use Doctrine\StaticWebsiteGenerator\SourceFile\SourceFileParameters;
use Doctrine\StaticWebsiteGenerator\SourceFile\SourceFiles;
use Doctrine\Website\DataBuilder\BlogPostDataBuilder;
use Doctrine\Website\Tests\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use function usort;
class BlogPostDataBuilderTest extends TestCase

View File

@@ -6,12 +6,10 @@ namespace Doctrine\Website\Tests\DataBuilder;
use Doctrine\SkeletonMapper\ObjectManagerInterface;
use Doctrine\Website\DataBuilder\ContributorDataBuilder;
use Doctrine\Website\Model\Project;
use Doctrine\Website\Model\ProjectContributor;
use Doctrine\Website\Model\TeamMember;
use Doctrine\Website\Repositories\ProjectContributorRepository;
use Doctrine\Website\Tests\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class ContributorDataBuilderTest extends TestCase
{
@@ -23,16 +21,15 @@ class ContributorDataBuilderTest extends TestCase
public function testBuild() : void
{
$project1 = new Project(['slug' => 'dbal']);
$project2 = new Project(['slug' => 'orm']);
$project1 = $this->createProject(['slug' => 'dbal']);
$project2 = $this->createProject(['slug' => 'orm']);
$jwageTeamMember = new TeamMember();
$ocramiusTeamMember = new TeamMember();
$objectManager = $this->createMock(ObjectManagerInterface::class);
$projectContributor1 = new ProjectContributor();
$projectContributor1->hydrate([
$projectContributor1 = $this->createProjectContributor([
'github' => 'jwage',
'teamMember' => $jwageTeamMember,
'avatarUrl' => 'https://avatars1.githubusercontent.com/u/97422?s=460&v=4',
@@ -40,10 +37,9 @@ class ContributorDataBuilderTest extends TestCase
'numAdditions' => 1,
'numDeletions' => 1,
'project' => $project1,
], $objectManager);
]);
$projectContributor2 = new ProjectContributor();
$projectContributor2->hydrate([
$projectContributor2 = $this->createProjectContributor([
'github' => 'jwage',
'teamMember' => $jwageTeamMember,
'avatarUrl' => 'https://avatars1.githubusercontent.com/u/97422?s=460&v=4',
@@ -51,10 +47,9 @@ class ContributorDataBuilderTest extends TestCase
'numAdditions' => 1,
'numDeletions' => 1,
'project' => $project2,
], $objectManager);
]);
$projectContributor3 = new ProjectContributor();
$projectContributor3->hydrate([
$projectContributor3 = $this->createProjectContributor([
'github' => 'ocramius',
'teamMember' => $ocramiusTeamMember,
'avatarUrl' => 'https://avatars0.githubusercontent.com/u/154256?s=460&v=4',
@@ -62,7 +57,7 @@ class ContributorDataBuilderTest extends TestCase
'numAdditions' => 1,
'numDeletions' => 1,
'project' => $project2,
], $objectManager);
]);
$projectContributors = [$projectContributor1, $projectContributor2, $projectContributor3];

View File

@@ -4,15 +4,13 @@ declare(strict_types=1);
namespace Doctrine\Website\Tests\DataBuilder;
use Doctrine\SkeletonMapper\ObjectManagerInterface;
use Doctrine\Website\DataBuilder\ProjectContributorDataBuilder;
use Doctrine\Website\Github\GithubProjectContributors;
use Doctrine\Website\Model\Project;
use Doctrine\Website\Model\TeamMember;
use Doctrine\Website\Repositories\ProjectRepository;
use Doctrine\Website\Repositories\TeamMemberRepository;
use Doctrine\Website\Tests\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class ProjectContributorDataBuilderTest extends TestCase
{
@@ -30,10 +28,8 @@ class ProjectContributorDataBuilderTest extends TestCase
public function testBuild() : void
{
$objectManager = $this->createMock(ObjectManagerInterface::class);
$project1 = new Project(['slug' => 'orm']);
$project2 = new Project(['slug' => 'dbal']);
$project1 = $this->createProject(['slug' => 'orm']);
$project2 = $this->createProject(['slug' => 'dbal']);
$jwageTeamMember = $this->createMock(TeamMember::class);

View File

@@ -14,8 +14,8 @@ use Doctrine\Website\Projects\ProjectDataReader;
use Doctrine\Website\Projects\ProjectDataRepository;
use Doctrine\Website\Projects\ProjectGitSyncer;
use Doctrine\Website\Projects\ProjectVersionsReader;
use Doctrine\Website\Tests\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class ProjectDataBuilderTest extends TestCase
{

View File

@@ -4,10 +4,10 @@ declare(strict_types=1);
namespace Doctrine\Website\Tests\DataSources;
use Doctrine\Website\DataSources\DoctrineUsers;
use PHPUnit\Framework\TestCase;
use Doctrine\Website\DataSources\ArrayDataSource;
use Doctrine\Website\Tests\TestCase;
class DoctrineUsersTest extends TestCase
class ArrayDataSourceTest extends TestCase
{
public function testGetSourceRows() : void
{
@@ -22,8 +22,8 @@ class DoctrineUsersTest extends TestCase
],
];
$doctrineUsers = new DoctrineUsers($rows);
$dataSource = new ArrayDataSource($rows);
self::assertEquals($rows, $doctrineUsers->getSourceRows());
self::assertEquals($rows, $dataSource->getSourceRows());
}
}

View File

@@ -12,8 +12,8 @@ use Doctrine\Website\Model\Project;
use Doctrine\Website\Model\TeamMember;
use Doctrine\Website\Repositories\ProjectRepository;
use Doctrine\Website\Repositories\TeamMemberRepository;
use Doctrine\Website\Tests\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class ContributorsTest extends TestCase
{

View File

@@ -12,8 +12,8 @@ use Doctrine\Website\Model\Project;
use Doctrine\Website\Model\TeamMember;
use Doctrine\Website\Repositories\ProjectRepository;
use Doctrine\Website\Repositories\TeamMemberRepository;
use Doctrine\Website\Tests\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class ProjectContributorsTest extends TestCase
{

View File

@@ -1,34 +0,0 @@
<?php
declare(strict_types=1);
namespace Doctrine\Website\Tests\DataSources;
use Doctrine\Website\DataSources\TeamMembers;
use Doctrine\Website\Tests\TestCase;
class TeamMembersTest extends TestCase
{
/** @var TeamMembers */
private $teamMembers;
protected function setUp() : void
{
$this->teamMembers = new TeamMembers([
['name' => 'ocramius'],
['name' => 'jwage'],
['name' => 'romanb'],
]);
}
public function testGetSourceRows() : void
{
$teamMemberRows = $this->teamMembers->getSourceRows();
self::assertSame([
['name' => 'ocramius'],
['name' => 'jwage'],
['name' => 'romanb'],
], $teamMemberRows);
}
}

View File

@@ -87,7 +87,7 @@ class DeployerTest extends TestCase
$this->processFactory->expects(self::at(2))
->method('run')
->with('cd /data/doctrine-website-staging && ./bin/console sync-repositories && ./bin/console build-website-data && ./bin/console build-docs && ./bin/console build-website /data/doctrine-website-build-staging --env=staging --publish')
->with('cd /data/doctrine-website-staging && ./bin/console migrations:migrate --no-interaction --env=staging && ./bin/console build-all /data/doctrine-website-build-staging --env=staging --publish')
->willReturn($process);
$deployer->deploy($output);

View File

@@ -12,7 +12,6 @@ use Doctrine\Website\Docs\RST\RSTCopier;
use Doctrine\Website\Docs\RST\RSTFileRepository;
use Doctrine\Website\Docs\RST\RSTLanguage;
use Doctrine\Website\Docs\RST\RSTPostBuildProcessor;
use Doctrine\Website\Model\Project;
use Doctrine\Website\Model\ProjectVersion;
use Doctrine\Website\Tests\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
@@ -67,7 +66,7 @@ class RSTBuilderTest extends TestCase
public function testBuildRSTDocs() : void
{
$project = new Project([
$project = $this->createProject([
'slug' => 'project-slug',
'docsSlug' => 'docs-slug',
]);

View File

@@ -11,7 +11,6 @@ use Doctrine\RST\Configuration;
use Doctrine\RST\Environment;
use Doctrine\RST\Kernel;
use Doctrine\Website\Docs\SearchIndexer;
use Doctrine\Website\Model\Project;
use Doctrine\Website\Model\ProjectVersion;
use Doctrine\Website\Tests\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
@@ -66,7 +65,7 @@ class SearchIndexerTest extends TestCase
public function testBuildSearchIndexes() : void
{
$project = new Project([
$project = $this->createProject([
'shortName' => 'ORM',
'docsSlug' => 'doctrine-orm',
'slug' => 'orm',

View File

@@ -8,8 +8,8 @@ use DateTimeImmutable;
use Doctrine\Website\Git\Tag;
use Doctrine\Website\Git\TagBranchGuesser;
use Doctrine\Website\ProcessFactory;
use Doctrine\Website\Tests\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Process\Process;
class TagBranchGuesserTest extends TestCase

View File

@@ -6,8 +6,8 @@ namespace Doctrine\Website\Tests\Git;
use Doctrine\Website\Git\TagReader;
use Doctrine\Website\ProcessFactory;
use Doctrine\Website\Tests\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Process\Process;
class TagReaderTest extends TestCase

View File

@@ -6,7 +6,7 @@ namespace Doctrine\Website\Tests\Git;
use DateTimeImmutable;
use Doctrine\Website\Git\Tag;
use PHPUnit\Framework\TestCase;
use Doctrine\Website\Tests\TestCase;
class TagTest extends TestCase
{

Some files were not shown because too many files have changed in this diff Show More