diff --git a/.travis.yml b/.travis.yml index 01cbde6..d473ac2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/composer.json b/composer.json index 0a75d65..6243c92 100644 --- a/composer.json +++ b/composer.json @@ -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 diff --git a/composer.lock b/composer.lock index 2805796..cf52a57 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "823e65210c110d30b41f903f7954dbfd", + "content-hash": "9d6143b0c9edf1056e827a8110065460", "packages": [ { "name": "algolia/algoliasearch-client-php", @@ -599,6 +599,88 @@ ], "time": "2018-11-21T01:24:55+00:00" }, + { + "name": "doctrine/dbal", + "version": "v2.9.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9", + "reference": "22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9", + "shasum": "" + }, + "require": { + "doctrine/cache": "^1.0", + "doctrine/event-manager": "^1.0", + "ext-pdo": "*", + "php": "^7.1" + }, + "require-dev": { + "doctrine/coding-standard": "^5.0", + "jetbrains/phpstorm-stubs": "^2018.1.2", + "phpstan/phpstan": "^0.10.1", + "phpunit/phpunit": "^7.4", + "symfony/console": "^2.0.5|^3.0|^4.0", + "symfony/phpunit-bridge": "^3.4.5|^4.0.5" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.9.x-dev", + "dev-develop": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\DBAL\\": "lib/Doctrine/DBAL" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", + "homepage": "https://www.doctrine-project.org/projects/dbal.html", + "keywords": [ + "abstraction", + "database", + "dbal", + "mysql", + "persistence", + "pgsql", + "php", + "queryobject" + ], + "time": "2018-12-31T03:27:51+00:00" + }, { "name": "doctrine/event-manager", "version": "v1.0.0", @@ -850,6 +932,170 @@ ], "time": "2014-09-09T13:34:57+00:00" }, + { + "name": "doctrine/migrations", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/migrations.git", + "reference": "43280c14b696a7896a9c70a5e0e4a312ff003187" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/43280c14b696a7896a9c70a5e0e4a312ff003187", + "reference": "43280c14b696a7896a9c70a5e0e4a312ff003187", + "shasum": "" + }, + "require": { + "doctrine/dbal": "^2.6", + "ocramius/package-versions": "^1.3", + "ocramius/proxy-manager": "^2.0.2", + "php": "^7.1", + "symfony/console": "^3.4||^4.0", + "symfony/stopwatch": "^3.4||^4.0" + }, + "require-dev": { + "doctrine/coding-standard": "^5.0", + "doctrine/orm": "^2.6", + "ext-pdo_sqlite": "*", + "jdorn/sql-formatter": "^1.1", + "mikey179/vfsstream": "^1.6", + "phpstan/phpstan": "^0.10", + "phpstan/phpstan-deprecation-rules": "^0.10", + "phpstan/phpstan-phpunit": "^0.10", + "phpstan/phpstan-strict-rules": "^0.10", + "phpunit/phpunit": "^7.0", + "symfony/process": "^3.4||^4.0", + "symfony/yaml": "^3.4||^4.0" + }, + "suggest": { + "jdorn/sql-formatter": "Allows to generate formatted SQL with the diff command.", + "symfony/yaml": "Allows the use of yaml for migration configuration files." + }, + "bin": [ + "bin/doctrine-migrations" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Migrations\\": "lib/Doctrine/Migrations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Michael Simonson", + "email": "contact@mikesimonson.com" + } + ], + "description": "PHP Doctrine Migrations project offer additional functionality on top of the database abstraction layer (DBAL) for versioning your database schema and easily deploying changes to it. It is a very easy to use and a powerful tool.", + "homepage": "https://www.doctrine-project.org/projects/migrations.html", + "keywords": [ + "database", + "dbal", + "migrations", + "php" + ], + "time": "2019-04-25T22:14:55+00:00" + }, + { + "name": "doctrine/orm", + "version": "v2.6.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/orm.git", + "reference": "434820973cadf2da2d66e7184be370084cc32ca8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/orm/zipball/434820973cadf2da2d66e7184be370084cc32ca8", + "reference": "434820973cadf2da2d66e7184be370084cc32ca8", + "shasum": "" + }, + "require": { + "doctrine/annotations": "~1.5", + "doctrine/cache": "~1.6", + "doctrine/collections": "^1.4", + "doctrine/common": "^2.7.1", + "doctrine/dbal": "^2.6", + "doctrine/instantiator": "~1.1", + "ext-pdo": "*", + "php": "^7.1", + "symfony/console": "~3.0|~4.0" + }, + "require-dev": { + "doctrine/coding-standard": "^1.0", + "phpunit/phpunit": "^6.5", + "squizlabs/php_codesniffer": "^3.2", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" + }, + "bin": [ + "bin/doctrine" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\ORM\\": "lib/Doctrine/ORM" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "Object-Relational-Mapper for PHP", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "database", + "orm" + ], + "time": "2018-11-20T23:46:46+00:00" + }, { "name": "doctrine/persistence", "version": "v1.1.0", @@ -1480,6 +1726,200 @@ ], "time": "2019-01-28T19:31:35+00:00" }, + { + "name": "ocramius/package-versions", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/Ocramius/PackageVersions.git", + "reference": "a4d4b60d0e60da2487bd21a2c6ac089f85570dbb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/a4d4b60d0e60da2487bd21a2c6ac089f85570dbb", + "reference": "a4d4b60d0e60da2487bd21a2c6ac089f85570dbb", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0.0", + "php": "^7.1.0" + }, + "require-dev": { + "composer/composer": "^1.6.3", + "doctrine/coding-standard": "^5.0.1", + "ext-zip": "*", + "infection/infection": "^0.7.1", + "phpunit/phpunit": "^7.0.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "time": "2019-02-21T12:16:21+00:00" + }, + { + "name": "ocramius/proxy-manager", + "version": "2.2.2", + "source": { + "type": "git", + "url": "https://github.com/Ocramius/ProxyManager.git", + "reference": "14b137b06b0f911944132df9d51e445a35920ab1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Ocramius/ProxyManager/zipball/14b137b06b0f911944132df9d51e445a35920ab1", + "reference": "14b137b06b0f911944132df9d51e445a35920ab1", + "shasum": "" + }, + "require": { + "ocramius/package-versions": "^1.1.3", + "php": "^7.2.0", + "zendframework/zend-code": "^3.3.0" + }, + "require-dev": { + "couscous/couscous": "^1.6.1", + "ext-phar": "*", + "humbug/humbug": "1.0.0-RC.0@RC", + "nikic/php-parser": "^3.1.1", + "padraic/phpunit-accelerator": "dev-master@DEV", + "phpbench/phpbench": "^0.12.2", + "phpstan/phpstan": "dev-master#856eb10a81c1d27c701a83f167dc870fd8f4236a as 0.9.999", + "phpstan/phpstan-phpunit": "dev-master#5629c0a1f4a9c417cb1077cf6693ad9753895761", + "phpunit/phpunit": "^6.4.3", + "squizlabs/php_codesniffer": "^2.9.1" + }, + "suggest": { + "ocramius/generated-hydrator": "To have very fast object to array to object conversion for ghost objects", + "zendframework/zend-json": "To have the JsonRpc adapter (Remote Object feature)", + "zendframework/zend-soap": "To have the Soap adapter (Remote Object feature)", + "zendframework/zend-xmlrpc": "To have the XmlRpc adapter (Remote Object feature)" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "ProxyManager\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.io/" + } + ], + "description": "A library providing utilities to generate, instantiate and generally operate with Object Proxies", + "homepage": "https://github.com/Ocramius/ProxyManager", + "keywords": [ + "aop", + "lazy loading", + "proxy", + "proxy pattern", + "service proxies" + ], + "time": "2018-09-27T13:45:01+00:00" + }, + { + "name": "pelago/emogrifier", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/MyIntervals/emogrifier.git", + "reference": "8ee7fb5ad772915451ed3415c1992bd3697d4983" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/8ee7fb5ad772915451ed3415c1992bd3697d4983", + "reference": "8ee7fb5ad772915451ed3415c1992bd3697d4983", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "php": "^5.5.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0", + "symfony/css-selector": "^3.4.0 || ^4.0.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.2.0", + "phpmd/phpmd": "^2.6.0", + "phpunit/phpunit": "^4.8.0", + "squizlabs/php_codesniffer": "^3.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Pelago\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Reeve", + "email": "jreeve@pelagodesign.com" + }, + { + "name": "Cameron Brooks" + }, + { + "name": "Jaime Prado" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Zoli Szabó", + "email": "zoli.szabo+github@gmail.com" + }, + { + "name": "Jake Hotson", + "email": "jake@qzdesign.co.uk" + } + ], + "description": "Converts CSS styles into inline style attributes in your HTML code", + "homepage": "https://www.myintervals.com/emogrifier.php", + "keywords": [ + "css", + "email", + "pre-processing" + ], + "time": "2018-12-10T10:36:30+00:00" + }, { "name": "php-http/cache-plugin", "version": "1.6.0", @@ -2295,6 +2735,171 @@ ], "time": "2019-03-31T21:09:11+00:00" }, + { + "name": "sendgrid/php-http-client", + "version": "3.9.6", + "source": { + "type": "git", + "url": "https://github.com/sendgrid/php-http-client.git", + "reference": "e9a04d949ee2d19938ab83dc100933a3b41a8ec7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sendgrid/php-http-client/zipball/e9a04d949ee2d19938ab83dc100933a3b41a8ec7", + "reference": "e9a04d949ee2d19938ab83dc100933a3b41a8ec7", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "~4.4", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "SendGrid\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Bernier", + "email": "dx@sendgrid.com" + }, + { + "name": "Elmer Thomas", + "email": "elmer@thinkingserious.com" + } + ], + "description": "HTTP REST client, simplified for PHP", + "homepage": "http://github.com/sendgrid/php-http-client", + "keywords": [ + "api", + "fluent", + "http", + "rest", + "sendgrid" + ], + "time": "2018-04-10T18:06:08+00:00" + }, + { + "name": "sendgrid/sendgrid", + "version": "7.3.0", + "source": { + "type": "git", + "url": "https://github.com/sendgrid/sendgrid-php.git", + "reference": "37fa19d3ae019842f07a2a43e92ed0f566ad927d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sendgrid/sendgrid-php/zipball/37fa19d3ae019842f07a2a43e92ed0f566ad927d", + "reference": "37fa19d3ae019842f07a2a43e92ed0f566ad927d", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "php": ">=5.6", + "sendgrid/php-http-client": "~3.9" + }, + "replace": { + "sendgrid/sendgrid-php": "*" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.9 || ^6.4.3", + "squizlabs/php_codesniffer": "3.*", + "swaggest/json-diff": "^3.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "SendGrid\\": "lib/", + "SendGrid\\Mail\\": "lib/mail/", + "SendGrid\\Contacts\\": "lib/contacts/", + "SendGrid\\Stats\\": "lib/stats/" + }, + "files": [ + "lib/SendGrid.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "This library allows you to quickly and easily send emails through Twilio SendGrid using PHP.", + "homepage": "http://github.com/sendgrid/sendgrid-php", + "keywords": [ + "email", + "grid", + "send", + "sendgrid", + "twilio sendgrid" + ], + "time": "2019-04-15T17:27:21+00:00" + }, + { + "name": "stripe/stripe-php", + "version": "v6.34.5", + "source": { + "type": "git", + "url": "https://github.com/stripe/stripe-php.git", + "reference": "124fafb57c287cf6e13830bcf329cac160a6afe8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/124fafb57c287cf6e13830bcf329cac160a6afe8", + "reference": "124fafb57c287cf6e13830bcf329cac160a6afe8", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "1.*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0", + "symfony/process": "~2.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Stripe\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Stripe and contributors", + "homepage": "https://github.com/stripe/stripe-php/contributors" + } + ], + "description": "Stripe PHP Library", + "homepage": "https://stripe.com/", + "keywords": [ + "api", + "payment processing", + "stripe" + ], + "time": "2019-05-07T00:56:15+00:00" + }, { "name": "symfony/config", "version": "v4.2.6", @@ -2498,6 +3103,59 @@ ], "time": "2018-12-05T08:06:11+00:00" }, + { + "name": "symfony/css-selector", + "version": "v4.2.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "48eddf66950fa57996e1be4a55916d65c10c604a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/48eddf66950fa57996e1be4a55916d65c10c604a", + "reference": "48eddf66950fa57996e1be4a55916d65c10c604a", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "time": "2019-01-16T20:31:39+00:00" + }, { "name": "symfony/debug", "version": "v4.2.6", @@ -3229,6 +3887,56 @@ ], "time": "2019-04-03T13:26:22+00:00" }, + { + "name": "symfony/stopwatch", + "version": "v4.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "b1a5f646d56a3290230dbc8edf2a0d62cda23f67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/b1a5f646d56a3290230dbc8edf2a0d62cda23f67", + "reference": "b1a5f646d56a3290230dbc8edf2a0d62cda23f67", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/contracts": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Stopwatch Component", + "homepage": "https://symfony.com", + "time": "2019-01-16T20:31:39+00:00" + }, { "name": "symfony/yaml", "version": "v4.2.6", @@ -3354,6 +4062,113 @@ "templating" ], "time": "2019-04-16T12:36:25+00:00" + }, + { + "name": "zendframework/zend-code", + "version": "3.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-code.git", + "reference": "c21db169075c6ec4b342149f446e7b7b724f95eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-code/zipball/c21db169075c6ec4b342149f446e7b7b724f95eb", + "reference": "c21db169075c6ec4b342149f446e7b7b724f95eb", + "shasum": "" + }, + "require": { + "php": "^7.1", + "zendframework/zend-eventmanager": "^2.6 || ^3.0" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "ext-phar": "*", + "phpunit/phpunit": "^6.2.3", + "zendframework/zend-coding-standard": "^1.0.0", + "zendframework/zend-stdlib": "^2.7 || ^3.0" + }, + "suggest": { + "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", + "zendframework/zend-stdlib": "Zend\\Stdlib component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3.x-dev", + "dev-develop": "3.4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Code\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides facilities to generate arbitrary code using an object oriented interface", + "homepage": "https://github.com/zendframework/zend-code", + "keywords": [ + "code", + "zf2" + ], + "time": "2018-08-13T20:36:59+00:00" + }, + { + "name": "zendframework/zend-eventmanager", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-eventmanager.git", + "reference": "a5e2583a211f73604691586b8406ff7296a946dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/a5e2583a211f73604691586b8406ff7296a946dd", + "reference": "a5e2583a211f73604691586b8406ff7296a946dd", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "athletic/athletic": "^0.1", + "container-interop/container-interop": "^1.1.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-stdlib": "^2.7.3 || ^3.0" + }, + "suggest": { + "container-interop/container-interop": "^1.1.0, to use the lazy listeners feature", + "zendframework/zend-stdlib": "^2.7.3 || ^3.0, to use the FilterChain feature" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev", + "dev-develop": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\EventManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Trigger and listen to events within a PHP application", + "homepage": "https://github.com/zendframework/zend-eventmanager", + "keywords": [ + "event", + "eventmanager", + "events", + "zf2" + ], + "time": "2018-04-25T15:33:34+00:00" } ], "packages-dev": [ @@ -4201,56 +5016,6 @@ ], "time": "2019-02-16T20:54:15+00:00" }, - { - "name": "ocramius/package-versions", - "version": "1.4.0", - "source": { - "type": "git", - "url": "https://github.com/Ocramius/PackageVersions.git", - "reference": "a4d4b60d0e60da2487bd21a2c6ac089f85570dbb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/a4d4b60d0e60da2487bd21a2c6ac089f85570dbb", - "reference": "a4d4b60d0e60da2487bd21a2c6ac089f85570dbb", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0.0", - "php": "^7.1.0" - }, - "require-dev": { - "composer/composer": "^1.6.3", - "doctrine/coding-standard": "^5.0.1", - "ext-zip": "*", - "infection/infection": "^0.7.1", - "phpunit/phpunit": "^7.0.0" - }, - "type": "composer-plugin", - "extra": { - "class": "PackageVersions\\Installer", - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "PackageVersions\\": "src/PackageVersions" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", - "time": "2019-02-21T12:16:21+00:00" - }, { "name": "phar-io/manifest", "version": "1.0.3", @@ -5817,59 +6582,6 @@ ], "time": "2019-04-10T23:49:02+00:00" }, - { - "name": "symfony/css-selector", - "version": "v4.2.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/css-selector.git", - "reference": "48eddf66950fa57996e1be4a55916d65c10c604a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/48eddf66950fa57996e1be4a55916d65c10c604a", - "reference": "48eddf66950fa57996e1be4a55916d65c10c604a", - "shasum": "" - }, - "require": { - "php": "^7.1.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.2-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\CssSelector\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jean-François Simon", - "email": "jeanfrancois.simon@sensiolabs.com" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony CssSelector Component", - "homepage": "https://symfony.com", - "time": "2019-01-16T20:31:39+00:00" - }, { "name": "symfony/dom-crawler", "version": "v4.2.6", @@ -5927,6 +6639,137 @@ "homepage": "https://symfony.com", "time": "2019-02-23T15:17:42+00:00" }, + { + "name": "symfony/polyfill-php72", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/ab50dcf166d5f577978419edd37aa2bb8eabce0c", + "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v4.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "3c4084cb1537c0e2ad41aad622bbf55a44a5c9ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3c4084cb1537c0e2ad41aad622bbf55a44a5c9ce", + "reference": "3c4084cb1537c0e2ad41aad622bbf55a44a5c9ce", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php72": "~1.5" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/console": "<3.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0", + "twig/twig": "~1.34|~2.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony mechanism for exploring and dumping PHP variables", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "time": "2019-05-01T12:55:36+00:00" + }, { "name": "theseer/tokenizer", "version": "1.1.2", diff --git a/config/config.yml b/config/config.yml index cce7bc4..5c2235d 100644 --- a/config/config.yml +++ b/config/config.yml @@ -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 diff --git a/config/config_prod.yml b/config/config_prod.yml index 5057dd9..34da107 100644 --- a/config/config_prod.yml +++ b/config/config_prod.yml @@ -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' diff --git a/config/config_test.yml b/config/config_test.yml index b6279be..58de21a 100644 --- a/config/config_test.yml +++ b/config/config_test.yml @@ -1,6 +1,9 @@ imports: - { resource: config.yml } +parameters: + doctrine.website.mysql.password: 'VdtLtifRh4gt37T' + services: Doctrine\Website\Github\GithubProjectContributors: alias: Doctrine\Website\Github\TestGithubProjectContributors diff --git a/config/routes.yml b/config/routes.yml index d2c2129..846dbc3 100644 --- a/config/routes.yml +++ b/config/routes.yml @@ -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 diff --git a/config/services.xml b/config/services.xml index 829a926..50fba50 100644 --- a/config/services.xml +++ b/config/services.xml @@ -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"> + + root + + + %doctrine.website.root_dir% @@ -13,13 +18,10 @@ %doctrine.website.source_dir% %doctrine.website.projects_data% %doctrine.website.webpack_build_dir% - %doctrine.website.team_members% %doctrine.website.templates_dir% - %doctrine.website.doctrine_users% - %doctrine.website.sponsors% - %doctrine.website.partners% %doctrine.website.project_integration.types% %doctrine.website.routes% + %doctrine.website.stripe.publishable_key% @@ -55,6 +57,20 @@ + + + + + + + %doctrine.website.templates_dir% + + + + + + + @@ -121,6 +137,9 @@ %doctrine.website.cache_dir%/fscache + + %doctrine.website.send_grid.api_key% + @@ -128,6 +147,7 @@ + @@ -140,6 +160,7 @@ + @@ -151,7 +172,7 @@ %doctrine.website.algolia.admin_api_key% - + %doctrine.website.title% %doctrine.website.subtitle% %doctrine.website.url% @@ -159,8 +180,11 @@ %doctrine.website.description% %doctrine.website.env% %doctrine.website.google_analytics_tracking_id% + %doctrine.website.assets_url% + + @@ -208,7 +232,7 @@ - + @@ -216,7 +240,7 @@ Doctrine\Website\Model\BlogPost - + @@ -227,7 +251,7 @@ - + @@ -240,13 +264,17 @@ - + + + %doctrine.website.doctrine_users% + + Doctrine\Website\Model\DoctrineUser - + @@ -259,13 +287,17 @@ - + + + %doctrine.website.sponsors% + + Doctrine\Website\Model\Sponsor - + @@ -278,13 +310,17 @@ - + + + %doctrine.website.partners% + + Doctrine\Website\Model\Partner - + @@ -303,7 +339,7 @@ - + @@ -322,7 +358,7 @@ - + @@ -341,7 +377,7 @@ - + @@ -354,13 +390,17 @@ - + + + %doctrine.website.team_members% + + Doctrine\Website\Model\TeamMember - + @@ -368,6 +408,31 @@ Doctrine\Website\Model\TeamMember + + + + + + + + %doctrine.website.events% + + + Doctrine\Website\Model\Event + + + + + + + + %doctrine.website.env% + + + + Doctrine\Website\Model\Event + + Doctrine\Website\Model\BlogPost @@ -409,6 +474,11 @@ + + Doctrine\Website\Model\Event + + + Doctrine\Website\Model\SitemapPage @@ -422,5 +492,50 @@ + + + + + %doctrine.website.root_dir%/lib/Model/Entity + + %doctrine.website.debug% + + + + + + + + + + + Doctrine\Website\Model\Entity\EventParticipant + + + + + + doctrine_website_%doctrine.website.env% + %doctrine.website.mysql.user% + %doctrine.website.mysql.password% + localhost + pdo_mysql + + + + + + Doctrine Website Migrations + + + Doctrine\Website\Migrations + + + true + + + %doctrine.website.root_dir%/lib/Migrations + + diff --git a/data/events.yml b/data/events.yml new file mode 100644 index 0000000..699e08c --- /dev/null +++ b/data/events.yml @@ -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' diff --git a/config/partners.yml b/data/partners.yml similarity index 100% rename from config/partners.yml rename to data/partners.yml diff --git a/config/projects.yml b/data/projects.yml similarity index 100% rename from config/projects.yml rename to data/projects.yml diff --git a/config/sponsors.yml b/data/sponsors.yml similarity index 100% rename from config/sponsors.yml rename to data/sponsors.yml diff --git a/config/team_members.yml b/data/team_members.yml similarity index 100% rename from config/team_members.yml rename to data/team_members.yml diff --git a/lib/Application.php b/lib/Application.php index f48cba9..c75a8a0 100644 --- a/lib/Application.php +++ b/lib/Application.php @@ -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; } } diff --git a/lib/Commands/BuildAllCommand.php b/lib/Commands/BuildAllCommand.php new file mode 100644 index 0000000..b177e97 --- /dev/null +++ b/lib/Commands/BuildAllCommand.php @@ -0,0 +1,109 @@ +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 ./doctrine %s', $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()); + } +} diff --git a/lib/Commands/BuildWebsiteCommand.php b/lib/Commands/BuildWebsiteCommand.php index 7e4a56b..9711afa 100644 --- a/lib/Commands/BuildWebsiteCommand.php +++ b/lib/Commands/BuildWebsiteCommand.php @@ -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); + }); + } } diff --git a/lib/Commands/EventParticipantsCommand.php b/lib/Commands/EventParticipantsCommand.php new file mode 100644 index 0000000..bfade65 --- /dev/null +++ b/lib/Commands/EventParticipantsCommand.php @@ -0,0 +1,183 @@ +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 %d participants.', count($eventParticipants))); + } +} diff --git a/lib/Controllers/EventsController.php b/lib/Controllers/EventsController.php new file mode 100644 index 0000000..75460b1 --- /dev/null +++ b/lib/Controllers/EventsController.php @@ -0,0 +1,49 @@ +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([]); + } +} diff --git a/lib/DataSources/TeamMembers.php b/lib/DataSources/ArrayDataSource.php similarity index 54% rename from lib/DataSources/TeamMembers.php rename to lib/DataSources/ArrayDataSource.php index 96ffc93..81a52c8 100644 --- a/lib/DataSources/TeamMembers.php +++ b/lib/DataSources/ArrayDataSource.php @@ -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; } } diff --git a/lib/DataSources/DoctrineUsers.php b/lib/DataSources/DoctrineUsers.php deleted file mode 100644 index 8848c9f..0000000 --- a/lib/DataSources/DoctrineUsers.php +++ /dev/null @@ -1,29 +0,0 @@ -doctrineUsers = $doctrineUsers; - } - - /** - * @return mixed[][] - */ - public function getSourceRows() : array - { - return $this->doctrineUsers; - } -} diff --git a/lib/DataSources/Partners.php b/lib/DataSources/Partners.php deleted file mode 100644 index c11629e..0000000 --- a/lib/DataSources/Partners.php +++ /dev/null @@ -1,29 +0,0 @@ -partners = $partners; - } - - /** - * @return mixed[][] - */ - public function getSourceRows() : array - { - return $this->partners; - } -} diff --git a/lib/DataSources/Sponsors.php b/lib/DataSources/Sponsors.php deleted file mode 100644 index c38dbf7..0000000 --- a/lib/DataSources/Sponsors.php +++ /dev/null @@ -1,29 +0,0 @@ -sponsors = $sponsors; - } - - /** - * @return mixed[][] - */ - public function getSourceRows() : array - { - return $this->sponsors; - } -} diff --git a/lib/Deployer.php b/lib/Deployer.php index da51c15..456303d 100644 --- a/lib/Deployer.php +++ b/lib/Deployer.php @@ -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 diff --git a/lib/Email/RenderEmail.php b/lib/Email/RenderEmail.php new file mode 100644 index 0000000..1da5a63 --- /dev/null +++ b/lib/Email/RenderEmail.php @@ -0,0 +1,80 @@ +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); + } +} diff --git a/lib/Email/RenderedEmail.php b/lib/Email/RenderedEmail.php new file mode 100644 index 0000000..4acd6bc --- /dev/null +++ b/lib/Email/RenderedEmail.php @@ -0,0 +1,39 @@ +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; + } +} diff --git a/lib/Email/SendEmail.php b/lib/Email/SendEmail.php new file mode 100644 index 0000000..677fe05 --- /dev/null +++ b/lib/Email/SendEmail.php @@ -0,0 +1,49 @@ +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); + } +} diff --git a/lib/Event/EmailParticipants.php b/lib/Event/EmailParticipants.php new file mode 100644 index 0000000..91021eb --- /dev/null +++ b/lib/Event/EmailParticipants.php @@ -0,0 +1,37 @@ +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, + ] + ); + } + } +} diff --git a/lib/Event/GetStripeEventParticipants.php b/lib/Event/GetStripeEventParticipants.php new file mode 100644 index 0000000..f8e6c39 --- /dev/null +++ b/lib/Event/GetStripeEventParticipants.php @@ -0,0 +1,109 @@ +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); + } +} diff --git a/lib/Hydrators/BlogPostHydrator.php b/lib/Hydrators/BlogPostHydrator.php new file mode 100644 index 0000000..c6cb971 --- /dev/null +++ b/lib/Hydrators/BlogPostHydrator.php @@ -0,0 +1,39 @@ +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(); + } +} diff --git a/lib/Hydrators/ContributorHydrator.php b/lib/Hydrators/ContributorHydrator.php new file mode 100644 index 0000000..8d13383 --- /dev/null +++ b/lib/Hydrators/ContributorHydrator.php @@ -0,0 +1,38 @@ +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'] ?? []; + } +} diff --git a/lib/Hydrators/DoctrineUserHydrator.php b/lib/Hydrators/DoctrineUserHydrator.php new file mode 100644 index 0000000..92a2179 --- /dev/null +++ b/lib/Hydrators/DoctrineUserHydrator.php @@ -0,0 +1,28 @@ +name = (string) $data['name']; + $this->url = (string) $data['url']; + } +} diff --git a/lib/Hydrators/EventHydrator.php b/lib/Hydrators/EventHydrator.php new file mode 100644 index 0000000..191c91d --- /dev/null +++ b/lib/Hydrators/EventHydrator.php @@ -0,0 +1,169 @@ + '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 + ); + } +} diff --git a/lib/Hydrators/ModelHydrator.php b/lib/Hydrators/ModelHydrator.php new file mode 100644 index 0000000..18c16a8 --- /dev/null +++ b/lib/Hydrators/ModelHydrator.php @@ -0,0 +1,81 @@ +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]; + } +} diff --git a/lib/Hydrators/PartnerHydrator.php b/lib/Hydrators/PartnerHydrator.php new file mode 100644 index 0000000..7eaac10 --- /dev/null +++ b/lib/Hydrators/PartnerHydrator.php @@ -0,0 +1,57 @@ +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'] ?? [] + ) + ); + } +} diff --git a/lib/Hydrators/ProjectContributorHydrator.php b/lib/Hydrators/ProjectContributorHydrator.php new file mode 100644 index 0000000..a85fc13 --- /dev/null +++ b/lib/Hydrators/ProjectContributorHydrator.php @@ -0,0 +1,41 @@ +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); + } +} diff --git a/lib/Hydrators/ProjectHydrator.php b/lib/Hydrators/ProjectHydrator.php new file mode 100644 index 0000000..abbca27 --- /dev/null +++ b/lib/Hydrators/ProjectHydrator.php @@ -0,0 +1,90 @@ +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) + ); + } +} diff --git a/lib/Hydrators/SitemapPageHydrator.php b/lib/Hydrators/SitemapPageHydrator.php new file mode 100644 index 0000000..eb20b2a --- /dev/null +++ b/lib/Hydrators/SitemapPageHydrator.php @@ -0,0 +1,29 @@ +url = (string) ($sitemapPage['url'] ?? ''); + $this->date = $sitemapPage['date'] ?? new DateTimeImmutable(); + } +} diff --git a/lib/Hydrators/SponsorHydrator.php b/lib/Hydrators/SponsorHydrator.php new file mode 100644 index 0000000..1e5dd48 --- /dev/null +++ b/lib/Hydrators/SponsorHydrator.php @@ -0,0 +1,45 @@ +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'] ?? ''); + } +} diff --git a/lib/Hydrators/TeamMemberHydrator.php b/lib/Hydrators/TeamMemberHydrator.php new file mode 100644 index 0000000..5677fdb --- /dev/null +++ b/lib/Hydrators/TeamMemberHydrator.php @@ -0,0 +1,57 @@ +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); + }; + } +} diff --git a/lib/Migrations/Version20190515005142.php b/lib/Migrations/Version20190515005142.php new file mode 100644 index 0000000..a740449 --- /dev/null +++ b/lib/Migrations/Version20190515005142.php @@ -0,0 +1,29 @@ +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'); + } +} diff --git a/lib/Model/Address.php b/lib/Model/Address.php new file mode 100644 index 0000000..27069ab --- /dev/null +++ b/lib/Model/Address.php @@ -0,0 +1,87 @@ +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 + ); + } +} diff --git a/lib/Model/BlogPost.php b/lib/Model/BlogPost.php index 3437435..75d8052 100644 --- a/lib/Model/BlogPost.php +++ b/lib/Model/BlogPost.php @@ -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; diff --git a/lib/Model/Contributor.php b/lib/Model/Contributor.php index d1cc43d..06170c2 100644 --- a/lib/Model/Contributor.php +++ b/lib/Model/Contributor.php @@ -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; diff --git a/lib/Model/DateTimeRange.php b/lib/Model/DateTimeRange.php new file mode 100644 index 0000000..302cee7 --- /dev/null +++ b/lib/Model/DateTimeRange.php @@ -0,0 +1,109 @@ +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'; + } +} diff --git a/lib/Model/DoctrineUser.php b/lib/Model/DoctrineUser.php index 4cf1da8..435293a 100644 --- a/lib/Model/DoctrineUser.php +++ b/lib/Model/DoctrineUser.php @@ -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; diff --git a/lib/Model/Entity/EventParticipant.php b/lib/Model/Entity/EventParticipant.php new file mode 100644 index 0000000..86da264 --- /dev/null +++ b/lib/Model/Entity/EventParticipant.php @@ -0,0 +1,65 @@ +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; + } +} diff --git a/lib/Model/Entity/EventParticipantRepository.php b/lib/Model/Entity/EventParticipantRepository.php new file mode 100644 index 0000000..4607721 --- /dev/null +++ b/lib/Model/Entity/EventParticipantRepository.php @@ -0,0 +1,29 @@ +findOneBy(['email' => $email]); + + return $eventParticipant; + } + + /** + * @return EventParticipant[] + */ + public function findByEventId(int $eventId) : array + { + /** @var EventParticipant[] $eventParticipants */ + $eventParticipants = $this->findBy(['eventId' => $eventId]); + + return $eventParticipants; + } +} diff --git a/lib/Model/Event.php b/lib/Model/Event.php new file mode 100644 index 0000000..c5c334b --- /dev/null +++ b/lib/Model/Event.php @@ -0,0 +1,165 @@ +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; + } +} diff --git a/lib/Model/EventCfp.php b/lib/Model/EventCfp.php new file mode 100644 index 0000000..374b696 --- /dev/null +++ b/lib/Model/EventCfp.php @@ -0,0 +1,47 @@ +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; + } +} diff --git a/lib/Model/EventLocation.php b/lib/Model/EventLocation.php new file mode 100644 index 0000000..2a32613 --- /dev/null +++ b/lib/Model/EventLocation.php @@ -0,0 +1,30 @@ +name = $name; + $this->address = $address; + } + + public function getName() : string + { + return $this->name; + } + + public function getAddress() : Address + { + return $this->address; + } +} diff --git a/lib/Model/EventParticipants.php b/lib/Model/EventParticipants.php new file mode 100644 index 0000000..9a2ea7a --- /dev/null +++ b/lib/Model/EventParticipants.php @@ -0,0 +1,32 @@ +eventId = $eventId; + $this->eventParticipantRepository = $eventParticipantRepository; + } + + protected function doInitialize() : void + { + $eventParticipants = $this->eventParticipantRepository + ->findByEventId($this->eventId); + + $this->collection = new ArrayCollection($eventParticipants); + } +} diff --git a/lib/Model/EventSchedule.php b/lib/Model/EventSchedule.php new file mode 100644 index 0000000..9097989 --- /dev/null +++ b/lib/Model/EventSchedule.php @@ -0,0 +1,53 @@ +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); + } +} diff --git a/lib/Model/EventScheduleSlot.php b/lib/Model/EventScheduleSlot.php new file mode 100644 index 0000000..03368e8 --- /dev/null +++ b/lib/Model/EventScheduleSlot.php @@ -0,0 +1,44 @@ +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; + } +} diff --git a/lib/Model/EventSpeaker.php b/lib/Model/EventSpeaker.php new file mode 100644 index 0000000..ef5777d --- /dev/null +++ b/lib/Model/EventSpeaker.php @@ -0,0 +1,84 @@ +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); + } +} diff --git a/lib/Model/EventSpeakers.php b/lib/Model/EventSpeakers.php new file mode 100644 index 0000000..02bd02c --- /dev/null +++ b/lib/Model/EventSpeakers.php @@ -0,0 +1,58 @@ +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); + } +} diff --git a/lib/Model/EventSponsor.php b/lib/Model/EventSponsor.php new file mode 100644 index 0000000..c94cd17 --- /dev/null +++ b/lib/Model/EventSponsor.php @@ -0,0 +1,51 @@ +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; + } +} diff --git a/lib/Model/EventSponsors.php b/lib/Model/EventSponsors.php new file mode 100644 index 0000000..fc1159a --- /dev/null +++ b/lib/Model/EventSponsors.php @@ -0,0 +1,48 @@ +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); + } +} diff --git a/lib/Model/EventType.php b/lib/Model/EventType.php new file mode 100644 index 0000000..f3b1d76 --- /dev/null +++ b/lib/Model/EventType.php @@ -0,0 +1,15 @@ +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; diff --git a/lib/Model/Project.php b/lib/Model/Project.php index 82dd7f6..de0b860 100644 --- a/lib/Model/Project.php +++ b/lib/Model/Project.php @@ -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; diff --git a/lib/Model/ProjectContributor.php b/lib/Model/ProjectContributor.php index 6add572..e106ff7 100644 --- a/lib/Model/ProjectContributor.php +++ b/lib/Model/ProjectContributor.php @@ -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; diff --git a/lib/Model/SitemapPage.php b/lib/Model/SitemapPage.php index ce13f00..dbd3255 100644 --- a/lib/Model/SitemapPage.php +++ b/lib/Model/SitemapPage.php @@ -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; diff --git a/lib/Model/Sponsor.php b/lib/Model/Sponsor.php index 2a15515..9874147 100644 --- a/lib/Model/Sponsor.php +++ b/lib/Model/Sponsor.php @@ -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; diff --git a/lib/Model/TeamMember.php b/lib/Model/TeamMember.php index 082b54c..51ca737 100644 --- a/lib/Model/TeamMember.php +++ b/lib/Model/TeamMember.php @@ -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; diff --git a/lib/Repositories/EventRepository.php b/lib/Repositories/EventRepository.php new file mode 100644 index 0000000..293b3b9 --- /dev/null +++ b/lib/Repositories/EventRepository.php @@ -0,0 +1,55 @@ +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(); + } +} diff --git a/lib/Requests/EventRequests.php b/lib/Requests/EventRequests.php new file mode 100644 index 0000000..0822f29 --- /dev/null +++ b/lib/Requests/EventRequests.php @@ -0,0 +1,38 @@ +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); + } +} diff --git a/lib/Site.php b/lib/Site.php new file mode 100644 index 0000000..ed2e7cd --- /dev/null +++ b/lib/Site.php @@ -0,0 +1,44 @@ +assetsUrl = $assetsUrl; + } + + public function getAssetsUrl() : string + { + return $this->assetsUrl; + } +} diff --git a/lib/Twig/MainExtension.php b/lib/Twig/MainExtension.php index 34dc925..a93d67f 100644 --- a/lib/Twig/MainExtension.php +++ b/lib/Twig/MainExtension.php @@ -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); diff --git a/lib/WebsiteBuilder.php b/lib/WebsiteBuilder.php index 64d56db..00411e8 100644 --- a/lib/WebsiteBuilder.php +++ b/lib/WebsiteBuilder.php @@ -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 %s', $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 %s', $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 npm run %s ', $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 %s to %s.', + $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); } } diff --git a/phpunit b/phpunit new file mode 100755 index 0000000..607b3a5 --- /dev/null +++ b/phpunit @@ -0,0 +1,16 @@ +#!/usr/bin/env php +Events + + {% if upcomingEvents|length %} + + +

Don't see something that you are interested in? Suggest an event topic you would like to see and we will see what we can do!

+ {% else %} +

No upcoming events are currently scheduled. Come back soon to check for new events! You may also suggest an event topic you would like to see.

+ {% endif %} + + {% if pastEvents|length %} +

Past Events

+ + + {% endif %} + + {% include "carbonad-standard.html.twig" %} +{% endblock %} diff --git a/source/events/suggest.html b/source/events/suggest.html new file mode 100644 index 0000000..c9fa4c6 --- /dev/null +++ b/source/events/suggest.html @@ -0,0 +1,12 @@ +{% block title %}Suggest a Doctrine Event{% endblock %} + +{% block content %} + + + +{% endblock %} diff --git a/source/js/event.js b/source/js/event.js new file mode 100644 index 0000000..e14918c --- /dev/null +++ b/source/js/event.js @@ -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 ' + window.event.name + ' 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 ' + window.event.name + ' was not successful. Please give it another try.' + ); + + window.location.hash = ''; + } + + if (window.location.hash === '#thanks') { + openEventModal( + 'Event Finished', + 'Thanks for attending ' + window.event.name + '! 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; + } + }); + }); + }); +}; diff --git a/source/js/index.js b/source/js/index.js index c2aa041..6e95c86 100644 --- a/source/js/index.js +++ b/source/js/index.js @@ -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'); \ No newline at end of file + +$.getScript('https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit'); diff --git a/source/styles/index.scss b/source/styles/index.scss index c4dd5fc..5a2de78 100644 --- a/source/styles/index.scss +++ b/source/styles/index.scss @@ -464,6 +464,10 @@ div.jsactive pre { height: 14px; } +.modal-backdrop.in { + opacity: 0.7; +} + @media (max-width: 900px) { .sidebar { display: none; diff --git a/templates/emails/css/framework.css b/templates/emails/css/framework.css new file mode 100644 index 0000000..19b55fc --- /dev/null +++ b/templates/emails/css/framework.css @@ -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; + } +} diff --git a/templates/emails/css/layout.css b/templates/emails/css/layout.css new file mode 100644 index 0000000..0a3d15f --- /dev/null +++ b/templates/emails/css/layout.css @@ -0,0 +1,8 @@ +.header { + text-align: center; +} + +.header img { + height: 60px; + margin-top: 20px; +} diff --git a/templates/emails/email.html.twig b/templates/emails/email.html.twig new file mode 100644 index 0000000..134cca2 --- /dev/null +++ b/templates/emails/email.html.twig @@ -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 %} diff --git a/templates/emails/events/participant-ticket.html.twig b/templates/emails/events/participant-ticket.html.twig new file mode 100644 index 0000000..166fef3 --- /dev/null +++ b/templates/emails/events/participant-ticket.html.twig @@ -0,0 +1,21 @@ +{% extends 'emails/layout.html.twig' %} + +{% block subject %} + [Doctrine Event] {{ event.name }} +{% endblock %} + +{% block body_html %} +

Hi {{ participant.email }},

+ +

You are receiving this e-mail because you registered for the Doctrine Event named {{ event.name }}.

+ + {% if event.conference %} +

This event is hosted at {{ event.location.name }} which is located at {{ event.location.address.string }}.

+ {% else %} +

You can join the event at the following url: {{ event.joinUrl }}

+ {% endif %} + +

If you need help, e-mail us at events@doctrine-project.org.

+ +

Thanks, Doctrine Team!

+{% endblock %} diff --git a/templates/emails/layout.html.twig b/templates/emails/layout.html.twig new file mode 100644 index 0000000..543b441 --- /dev/null +++ b/templates/emails/layout.html.twig @@ -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 %} + + + + + + Doctrine + + + + + {% block preheader '' %} + + + + + + + + + + + + + + +{% endblock %} diff --git a/templates/event-cfp.html.twig b/templates/event-cfp.html.twig new file mode 100644 index 0000000..742694d --- /dev/null +++ b/templates/event-cfp.html.twig @@ -0,0 +1,27 @@ +{% block title %}{{ event.name }}{% endblock %} + +{% block meta_description event.description %} + +{% block content %} + + + {% if event.cfp.exists %} + {% if event.cfp.dates.now %} +

Call for Papers started on {{ event.cfp.dates.start|date('l, F jS Y') }} and ends on {{ event.cfp.dates.end|date('l, F jS Y') }}.

+ + + {% elseif event.cfp.dates.upcoming %} +

Call for Papers starts on {{ event.cfp.dates.start|date('l, F jS Y') }} and ends on {{ event.cfp.dates.end|date('l, F jS Y') }}.

+ {% 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 %} diff --git a/templates/event.html.twig b/templates/event.html.twig new file mode 100644 index 0000000..c5fe7cd --- /dev/null +++ b/templates/event.html.twig @@ -0,0 +1,173 @@ +{% block title %}{{ event.name }}{% endblock %} + +{% block meta_description event.description %} + +{% block content %} + + + {% if event.dates.over %} + {% set alertMessage %} + This event has ended. Watch for additional events in the future. + {% endset %} + + {% include "alert.html.twig" with {alertMessage:alertMessage} %} + {% endif %} + +

{{ event.description }}

+ +
+
+
+
+

+ {% if event.free %} + FREE! + {% else %} + ${{ event.price|number_format }} + {% endif %} +

+ +

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 %}.

+ + {% if event.registrationDates.now %} + {% if event.free %} + {% if event.joinUrl %} + Join Event + {% else %} + 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') }}. + {% endif %} + {% else %} + + {% endif %} + {% elseif event.registrationDates.over %} + Registration is over for this event. + {% else %} + Registration will open on {{ event.registrationDates.start|date('l, F jS Y') }}. + {% endif %} + + {% if event.cfp.dates.now %} + Submit a Talk + {% endif %} +
+ +
+ {% include "carbonad-standard.html.twig" %} +
+
+
+ + {% if event.speakers|length %} + + {% endif %} +
+ + {% if event.conference %} +

Location

+ +

This event is hosted at {{ event.location.name }} which is located at {{ event.location.address.string }}.

+ +
+ {% endif %} + + {% if event.sponsors|length %} +

Sponsors

+ +

+ 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 sponsorship@doctrine-project.org +

+ +
+ {% for sponsor in event.sponsors %} +
+ {{ sponsor.name }} +
+ {% endfor %} +
+ {% endif %} + + {% if event.schedule|length %} +

Schedule

+ + + {% for slot in event.schedule %} + + + + + + + {% endfor %} +
{{ slot.speaker.topic }} {{ slot.speaker.name }}{{ slot.startDate|date('h:i A T') }}{{ slot.endDate|date('h:i A T') }}{% if slot.speaker.hasYouTubeVideo() %}{% endif %}
+ {% endif %} + + {% if event.speakers|length %} +

Speakers

+ + {% for speaker in event.speakers %} + +
+
+
+ {{ speaker.name }} +
+
+
+

+ {{ speaker.topic }} + + {% if speaker.hasYouTubeVideo() %} + + {% endif %} +

+
{{ speaker.name }}
+

{{ speaker.description|nl2br }}

+
+
+
+
+ {% endfor %} + {% endif %} + + + +
+ + +{% endblock %} diff --git a/templates/layouts/layout.html.twig b/templates/layouts/layout.html.twig index 7fcf6dc..6515ec5 100644 --- a/templates/layouts/layout.html.twig +++ b/templates/layouts/layout.html.twig @@ -137,27 +137,26 @@ Contributor Workflow Maintainer Workflow Contribute to Website + Maintainers + Contributors Policies GitHub Styleguide - - + - @@ -211,6 +210,7 @@
  • Sponsorship
  • Community
  • Blog
  • +
  • Events
  • Consulting
  • diff --git a/tests/Assets/AssetIntegrityGeneratorTest.php b/tests/Assets/AssetIntegrityGeneratorTest.php index a213dc4..9597e8c 100644 --- a/tests/Assets/AssetIntegrityGeneratorTest.php +++ b/tests/Assets/AssetIntegrityGeneratorTest.php @@ -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 { diff --git a/tests/BuildAllBootstrap.php b/tests/BuildAllBootstrap.php new file mode 100644 index 0000000..d4fd987 --- /dev/null +++ b/tests/BuildAllBootstrap.php @@ -0,0 +1,40 @@ +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(); +} diff --git a/tests/Cache/CacheClearerTest.php b/tests/Cache/CacheClearerTest.php index 4892aa5..5a10003 100644 --- a/tests/Cache/CacheClearerTest.php +++ b/tests/Cache/CacheClearerTest.php @@ -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 diff --git a/tests/DataBuilder/BlogPostDataBuilderTest.php b/tests/DataBuilder/BlogPostDataBuilderTest.php index 112096a..3509fa1 100644 --- a/tests/DataBuilder/BlogPostDataBuilderTest.php +++ b/tests/DataBuilder/BlogPostDataBuilderTest.php @@ -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 diff --git a/tests/DataBuilder/ContributorDataBuilderTest.php b/tests/DataBuilder/ContributorDataBuilderTest.php index 4e92346..0c94612 100644 --- a/tests/DataBuilder/ContributorDataBuilderTest.php +++ b/tests/DataBuilder/ContributorDataBuilderTest.php @@ -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]; diff --git a/tests/DataBuilder/ProjectContributorDataBuilderTest.php b/tests/DataBuilder/ProjectContributorDataBuilderTest.php index 096b4e1..c8ecade 100644 --- a/tests/DataBuilder/ProjectContributorDataBuilderTest.php +++ b/tests/DataBuilder/ProjectContributorDataBuilderTest.php @@ -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); diff --git a/tests/DataBuilder/ProjectDataBuilderTest.php b/tests/DataBuilder/ProjectDataBuilderTest.php index 828212d..a0399e4 100644 --- a/tests/DataBuilder/ProjectDataBuilderTest.php +++ b/tests/DataBuilder/ProjectDataBuilderTest.php @@ -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 { diff --git a/tests/DataSources/DoctrineUsersTest.php b/tests/DataSources/ArrayDataSourceTest.php similarity index 62% rename from tests/DataSources/DoctrineUsersTest.php rename to tests/DataSources/ArrayDataSourceTest.php index 4efe9d2..501c8bb 100644 --- a/tests/DataSources/DoctrineUsersTest.php +++ b/tests/DataSources/ArrayDataSourceTest.php @@ -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()); } } diff --git a/tests/DataSources/ContributorsTest.php b/tests/DataSources/ContributorsTest.php index df2caad..1e438f4 100644 --- a/tests/DataSources/ContributorsTest.php +++ b/tests/DataSources/ContributorsTest.php @@ -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 { diff --git a/tests/DataSources/ProjectContributorsTest.php b/tests/DataSources/ProjectContributorsTest.php index b365606..7ae6fac 100644 --- a/tests/DataSources/ProjectContributorsTest.php +++ b/tests/DataSources/ProjectContributorsTest.php @@ -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 { diff --git a/tests/DataSources/TeamMembersTest.php b/tests/DataSources/TeamMembersTest.php deleted file mode 100644 index c7dfb47..0000000 --- a/tests/DataSources/TeamMembersTest.php +++ /dev/null @@ -1,34 +0,0 @@ -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); - } -} diff --git a/tests/DeployerTest.php b/tests/DeployerTest.php index 7e1b8a1..401c02b 100644 --- a/tests/DeployerTest.php +++ b/tests/DeployerTest.php @@ -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); diff --git a/tests/Docs/RST/RSTBuilderTest.php b/tests/Docs/RST/RSTBuilderTest.php index aa6af7a..881d16d 100644 --- a/tests/Docs/RST/RSTBuilderTest.php +++ b/tests/Docs/RST/RSTBuilderTest.php @@ -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', ]); diff --git a/tests/Docs/SearchIndexerTest.php b/tests/Docs/SearchIndexerTest.php index 0a19799..406a3c2 100644 --- a/tests/Docs/SearchIndexerTest.php +++ b/tests/Docs/SearchIndexerTest.php @@ -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', diff --git a/tests/Git/TagBranchGuesserTest.php b/tests/Git/TagBranchGuesserTest.php index 09e7572..b40e480 100644 --- a/tests/Git/TagBranchGuesserTest.php +++ b/tests/Git/TagBranchGuesserTest.php @@ -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 diff --git a/tests/Git/TagReaderTest.php b/tests/Git/TagReaderTest.php index c7d3cde..4968e82 100644 --- a/tests/Git/TagReaderTest.php +++ b/tests/Git/TagReaderTest.php @@ -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 diff --git a/tests/Git/TagTest.php b/tests/Git/TagTest.php index f798d82..7131d73 100644 --- a/tests/Git/TagTest.php +++ b/tests/Git/TagTest.php @@ -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 { diff --git a/tests/Github/ProdGithubProjectContributorsTest.php b/tests/Github/ProdGithubProjectContributorsTest.php index 2c98aba..001eb78 100644 --- a/tests/Github/ProdGithubProjectContributorsTest.php +++ b/tests/Github/ProdGithubProjectContributorsTest.php @@ -7,10 +7,10 @@ namespace Doctrine\Website\Tests\Github; use Doctrine\Common\Cache\FilesystemCache; use Doctrine\Website\Github\ProdGithubProjectContributors; use Doctrine\Website\Model\Project; +use Doctrine\Website\Tests\TestCase; use Github\Api\Repo; use Github\Client; use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\Framework\TestCase; use RuntimeException; class ProdGithubProjectContributorsTest extends TestCase diff --git a/tests/Model/EventTest.php b/tests/Model/EventTest.php new file mode 100644 index 0000000..e7bd6b6 --- /dev/null +++ b/tests/Model/EventTest.php @@ -0,0 +1,274 @@ +createTestEvent()->isWebinar()); + self::assertFalse($this->createTestEvent([ + 'type' => EventType::CONFERENCE, + 'location' => [], + ])->isWebinar()); + } + + public function testIsConference() : void + { + self::assertFalse($this->createTestEvent()->isConference()); + self::assertTrue($this->createTestEvent([ + 'type' => EventType::CONFERENCE, + 'location' => [], + ])->isConference()); + } + + public function testGetSkuProd() : void + { + self::assertSame('prod_123', $this->createTestEvent(['env' => 'prod'])->getSku()); + } + + public function testGetSkuTest() : void + { + self::assertSame('test_123', $this->createTestEvent()->getSku()); + } + + public function testGetName() : void + { + self::assertSame('Doctrine for Beginners', $this->createTestEvent()->getName()); + } + + public function testGetSlug() : void + { + self::assertSame('doctrine-for-beginners', $this->createTestEvent()->getSlug()); + } + + public function testGetJoinUrl() : void + { + self::assertSame('https://www.joinurl.com', $this->createTestEvent()->getJoinUrl()); + } + + public function testGetDates() : void + { + self::assertEquals( + new DateTimeImmutable('2019-05-28 11:00:00'), + $this->createTestEvent()->getDates()->getStart() + ); + + self::assertEquals( + new DateTimeImmutable('2019-05-28 11:00:00'), + $this->createTestEvent()->getStartDate() + ); + + self::assertEquals( + new DateTimeImmutable('2019-05-28 11:45:00'), + $this->createTestEvent()->getDates()->getEnd() + ); + + self::assertEquals( + new DateTimeImmutable('2019-05-28 11:45:00'), + $this->createTestEvent()->getEndDate() + ); + + self::assertSame(0, $this->createTestEvent()->getDates()->getNumDays()); + self::assertSame(0, $this->createTestEvent()->getDates()->getNumHours()); + self::assertSame(45, $this->createTestEvent()->getDates()->getNumMinutes()); + self::assertSame('45-minute', $this->createTestEvent()->getDates()->getDuration()); + + self::assertSame(3, $this->createTestEvent([ + 'schedule' => [], + 'startDate' => '2019-05-28', + 'endDate' => '2019-05-30', + ])->getDates()->getNumDays()); + + self::assertSame('3-day', $this->createTestEvent([ + 'schedule' => [], + 'startDate' => '2019-05-28', + 'endDate' => '2019-05-30', + ])->getDates()->getDuration()); + + self::assertSame(71, $this->createTestEvent([ + 'schedule' => [], + 'startDate' => '2019-05-28 11:00:00', + 'endDate' => '2019-05-30 10:00:00', + ])->getDates()->getNumHours()); + } + + public function testGetRegistrationDates() : void + { + self::assertEquals( + new DateTimeImmutable('2019-05-01'), + $this->createTestEvent()->getRegistrationDates()->getStart() + ); + + self::assertEquals( + new DateTimeImmutable('2019-05-27'), + $this->createTestEvent()->getRegistrationDates()->getEnd() + ); + } + + public function testGetCfp() : void + { + self::assertTrue($this->createTestEvent()->getCfp()->exists()); + + self::assertSame( + 'https://docs.google.com/forms/d/e/123/viewform', + $this->createTestEvent()->getCfp()->getGoogleFormUrl() + ); + + self::assertSame( + 'https://docs.google.com/forms/d/e/123/viewform?embedded=true', + $this->createTestEvent()->getCfp()->getEmbeddedGoogleFormUrl() + ); + + self::assertEquals( + new DateTimeImmutable('2019-05-01'), + $this->createTestEvent()->getCfp()->getDates()->getStart() + ); + + self::assertEquals( + new DateTimeImmutable('2019-05-02'), + $this->createTestEvent()->getCfp()->getDates()->getEnd() + ); + } + + public function testGetLocation() : void + { + self::assertNull($this->createTestEvent()->getLocation()); + + $event = $this->createTestEvent([ + 'type' => EventType::CONFERENCE, + 'location' => [ + 'name' => 'Awesome Hotel', + 'address' => [ + 'line1' => 'Line 1', + 'line2' => 'Line 2', + 'city' => 'City', + 'state' => 'State', + 'zipCode' => 'Zip Code', + 'countryCode' => 'Country Code', + ], + ], + ]); + + /** @var EventLocation $location */ + $location = $event->getLocation(); + + self::assertSame('Awesome Hotel', $location->getName()); + self::assertSame('Line 1', $location->getAddress()->getLine1()); + self::assertSame('Line 2', $location->getAddress()->getLine2()); + self::assertSame('City', $location->getAddress()->getCity()); + self::assertSame('State', $location->getAddress()->getState()); + self::assertSame('Zip Code', $location->getAddress()->getZipCode()); + self::assertSame('Country Code', $location->getAddress()->getCountryCode()); + } + + public function testGetSponsors() : void + { + $sponsor = $this->createTestEvent()->getSponsors()->first(); + + self::assertSame('Blackfire.io', $sponsor->getName()); + self::assertSame('https://blackfire.io/', $sponsor->getUrl()); + self::assertSame( + 'https://blackfire.io/?utm_source=doctrine&utm_medium=website&utm_campaign=doctrine-for-beginners', + $sponsor->getUrlWithUtmParameters() + ); + self::assertSame('/images/blackfire.svg', $sponsor->getLogo()); + } + + public function testGetSpeakers() : void + { + $speaker = $this->createTestEvent()->getSpeakers()->first(); + + self::assertSame('Jonathan H. Wage', $speaker->getName()); + self::assertSame('Doctrine for Beginners', $speaker->getTopic()); + self::assertSame('doctrine-for-beginners', $speaker->getTopicSlug()); + self::assertSame('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.', $speaker->getDescription()); + } + + public function testGetSchedule() : void + { + $event = $this->createTestEvent(); + + $speaker = $event->getSpeakers()->first(); + $slot = $event->getSchedule()->first(); + + self::assertSame($speaker, $slot->getSpeaker()); + } + + public function testGetDescription() : void + { + self::assertSame('Test Description', $this->createTestEvent()->getDescription()); + } + + public function testGetPrice() : void + { + self::assertSame(0.00, $this->createTestEvent()->getPrice()); + self::assertSame(5.00, $this->createTestEvent(['price' => 5.00])->getPrice()); + } + + public function testIsFree() : void + { + self::assertTrue($this->createTestEvent()->isFree()); + self::assertFalse($this->createTestEvent(['price' => 5.00])->isFree()); + } + + /** + * @param mixed[] $data + */ + private function createTestEvent(array $data = []) : Event + { + return $this->createEvent(array_merge([ + 'env' => 'dev', + 'sku' => [ + 'test' => 'test_123', + 'prod' => 'prod_123', + ], + 'cfp' => [ + 'googleFormId' => '123', + 'startDate' => '2019-05-01', + 'endDate' => '2019-05-02', + ], + 'name' => 'Doctrine for Beginners', + 'slug' => 'doctrine-for-beginners', + 'description' => 'Test Description', + 'price' => 0.0, + 'joinUrl' => 'https://www.joinurl.com', + 'startDate' => '2019-05-28', + 'endDate' => '2019-05-28', + 'registrationStartDate' => '2019-05-01', + 'registrationEndDate' => '2019-05-27', + 'sponsors' => [ + [ + 'name' => 'Blackfire.io', + 'url' => 'https://blackfire.io/', + 'logo' => '/images/blackfire.svg', + 'utmParameters' => ['utm_source' => 'doctrine'], + ], + ], + '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 11:45', + ], + ], + ], $data)); + } +} diff --git a/tests/Projects/ProjectTest.php b/tests/Projects/ProjectTest.php index fd52111..21e40de 100644 --- a/tests/Projects/ProjectTest.php +++ b/tests/Projects/ProjectTest.php @@ -19,7 +19,7 @@ class ProjectTest extends TestCase protected function setUp() : void { - $this->project = new Project([ + $this->project = $this->createProject([ 'name' => 'Test Project', 'shortName' => 'Test Project', 'slug' => 'test-project', diff --git a/tests/Projects/ProjectVersionsReaderTest.php b/tests/Projects/ProjectVersionsReaderTest.php index 1970d6b..121034f 100644 --- a/tests/Projects/ProjectVersionsReaderTest.php +++ b/tests/Projects/ProjectVersionsReaderTest.php @@ -9,8 +9,8 @@ use Doctrine\Website\Git\Tag; use Doctrine\Website\Git\TagBranchGuesser; use Doctrine\Website\Git\TagReader; use Doctrine\Website\Projects\ProjectVersionsReader; +use Doctrine\Website\Tests\TestCase; use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\Framework\TestCase; class ProjectVersionsReaderTest extends TestCase { diff --git a/tests/Requests/ContributorRequestsTest.php b/tests/Requests/ContributorRequestsTest.php index b382503..3e2a61d 100644 --- a/tests/Requests/ContributorRequestsTest.php +++ b/tests/Requests/ContributorRequestsTest.php @@ -4,13 +4,11 @@ declare(strict_types=1); namespace Doctrine\Website\Tests\Requests; -use Doctrine\SkeletonMapper\ObjectManagerInterface; use Doctrine\StaticWebsiteGenerator\Request\ArrayRequestCollection; -use Doctrine\Website\Model\Contributor; use Doctrine\Website\Repositories\ContributorRepository; use Doctrine\Website\Requests\ContributorRequests; +use Doctrine\Website\Tests\TestCase; use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\Framework\TestCase; class ContributorRequestsTest extends TestCase { @@ -22,13 +20,9 @@ class ContributorRequestsTest extends TestCase public function testGetContributors() : void { - $objectManager = $this->createMock(ObjectManagerInterface::class); + $contributor1 = $this->createContributor(['github' => 'github1']); - $contributor1 = new Contributor(); - $contributor1->hydrate(['github' => 'github1'], $objectManager); - - $contributor2 = new Contributor(); - $contributor2->hydrate(['github' => 'github2'], $objectManager); + $contributor2 = $this->createContributor(['github' => 'github2']); $contributors = [$contributor1, $contributor2]; diff --git a/tests/Requests/ProjectRequestsTest.php b/tests/Requests/ProjectRequestsTest.php index d45dfd5..7d96207 100644 --- a/tests/Requests/ProjectRequestsTest.php +++ b/tests/Requests/ProjectRequestsTest.php @@ -5,11 +5,10 @@ declare(strict_types=1); namespace Doctrine\Website\Tests\Requests; use Doctrine\StaticWebsiteGenerator\Request\ArrayRequestCollection; -use Doctrine\Website\Model\Project; use Doctrine\Website\Repositories\ProjectRepository; use Doctrine\Website\Requests\ProjectRequests; +use Doctrine\Website\Tests\TestCase; use PHPUnit\Framework\MockObject\MockObject; -use PHPUnit\Framework\TestCase; class ProjectRequestsTest extends TestCase { @@ -21,8 +20,8 @@ class ProjectRequestsTest extends TestCase public function testGetProjects() : void { - $project1 = new Project(['slug' => 'project1']); - $project2 = new Project(['slug' => 'project2']); + $project1 = $this->createProject(['slug' => 'project1']); + $project2 = $this->createProject(['slug' => 'project2']); $projects = [$project1, $project2]; diff --git a/tests/TestCase.php b/tests/TestCase.php index fcd9031..ce19de7 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -4,21 +4,94 @@ declare(strict_types=1); namespace Doctrine\Website\Tests; +use Doctrine\SkeletonMapper\ObjectRepository\ObjectRepositoryInterface; use Doctrine\Website\Application; +use Doctrine\Website\Model\Contributor; +use Doctrine\Website\Model\Event; +use Doctrine\Website\Model\Project; +use Doctrine\Website\Model\ProjectContributor; +use Doctrine\Website\Repositories\ContributorRepository; +use Doctrine\Website\Repositories\EventRepository; +use Doctrine\Website\Repositories\ProjectContributorRepository; +use Doctrine\Website\Repositories\ProjectRepository; use PHPUnit\Framework\TestCase as BaseTestCase; use Symfony\Component\DependencyInjection\ContainerBuilder; +use function rand; abstract class TestCase extends BaseTestCase { /** @var ContainerBuilder */ - private $container; + private static $container; protected function getContainer() : ContainerBuilder { - if ($this->container === null) { - $this->container = Application::getContainer('test'); + if (self::$container === null) { + self::$container = Application::getContainer('test'); } - return $this->container; + return self::$container; + } + + /** + * @param mixed[] $data + */ + protected function createModel(string $repositoryClassName, array $data) : object + { + /** @var ObjectRepositoryInterface $repository */ + $repository = $this->getContainer()->get($repositoryClassName); + + $object = $repository->create($repository->getClassName()); + + $repository->hydrate($object, $data); + + return $object; + } + + + /** + * @param mixed[] $data + */ + protected function createEvent(array $data) : Event + { + $data['id'] = rand(); + + /** @var Event $event */ + $event = $this->createModel(EventRepository::class, $data); + + return $event; + } + + + /** + * @param mixed[] $data + */ + protected function createProject(array $data) : Project + { + /** @var Project $project */ + $project = $this->createModel(ProjectRepository::class, $data); + + return $project; + } + + /** + * @param mixed[] $data + */ + protected function createContributor(array $data) : Contributor + { + /** @var Contributor $contributor */ + $contributor = $this->createModel(ContributorRepository::class, $data); + + return $contributor; + } + + /** + * @param mixed[] $data + */ + protected function createProjectContributor(array $data) : ProjectContributor + { + /** @var ProjectContributor $projectContributor */ + $projectContributor = $this->createModel(ProjectContributorRepository::class, $data); + + return $projectContributor; } } diff --git a/tests/Twig/MainExtensionTest.php b/tests/Twig/MainExtensionTest.php index 8457f7d..74b2060 100644 --- a/tests/Twig/MainExtensionTest.php +++ b/tests/Twig/MainExtensionTest.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace Doctrine\Website\Tests\Twig; use Doctrine\Website\Assets\AssetIntegrityGenerator; -use Doctrine\Website\Model\Project; use Doctrine\Website\Model\ProjectVersion; use Doctrine\Website\Tests\TestCase; use Doctrine\Website\Twig\MainExtension; @@ -39,7 +38,8 @@ class MainExtensionTest extends TestCase $this->parsedown, $this->assetIntegrityGenerator, $this->sourceDir, - $this->webpackBuildDir + $this->webpackBuildDir, + 'stripe-publishable-key' ); } @@ -49,13 +49,13 @@ class MainExtensionTest extends TestCase self::assertSame('Search', $placeholder); - $project = new Project(['shortName' => 'ORM']); + $project = $this->createProject(['shortName' => 'ORM']); $placeholder = $this->mainExtension->getSearchBoxPlaceholder($project); self::assertSame('Search ORM', $placeholder); - $project = new Project([ + $project = $this->createProject([ 'shortName' => 'ORM', 'versions' => [ [ diff --git a/tests/phpunit-build-all.xml b/tests/phpunit-build-all.xml new file mode 100644 index 0000000..1e5132a --- /dev/null +++ b/tests/phpunit-build-all.xml @@ -0,0 +1,21 @@ + + + + + + ./tests + + + + + + ./lib + + + diff --git a/webpack.config.js b/webpack.config.js index 37671b5..f8e8eb0 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -25,7 +25,7 @@ const plugins = () => { if (!isWatching) { plugins.push( new PurgecssPlugin({ - whitelistPatterns: [/^ais/, /^carbon/, /^badge/], + whitelistPatterns: [/^ais/, /^carbon/, /^badge/, /^modal-backdrop/], paths: [] .concat(glob.sync(__dirname + '/templates/**/*.twig')) .concat(glob.sync(__dirname + '/source/**/*.html'))