diff --git a/.github/workflows/phpcs_activity.yml b/.github/workflows/phpcs_activity.yml new file mode 100644 index 0000000..5e9a7d0 --- /dev/null +++ b/.github/workflows/phpcs_activity.yml @@ -0,0 +1,43 @@ +name: PHP_CodeSniffer - civiremote_activity + +on: + push: ~ + pull_request: + branches: [ main ] + +jobs: + phpcs: + runs-on: ubuntu-latest + name: PHP_CodeSniffer + defaults: + run: + working-directory: modules/civiremote_activity + + steps: + - uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.2 + coverage: none + tools: cs2pr + env: + fail-fast: true + + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache dependencies + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('modules/civiremote_activity/tools/phpcs/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer update --no-progress --prefer-dist + + - name: Run PHP_CodeSniffer + run: composer phpcs -- -q --report=checkstyle | cs2pr diff --git a/.github/workflows/phpstan_activity.yml b/.github/workflows/phpstan_activity.yml new file mode 100644 index 0000000..eb0d116 --- /dev/null +++ b/.github/workflows/phpstan_activity.yml @@ -0,0 +1,49 @@ +name: PHPStan - civiremote_activity + +on: + push: ~ + pull_request: + branches: [ main ] + +jobs: + phpstan: + runs-on: ubuntu-latest + strategy: + matrix: + php-versions: ['7.4', '8.0', '8.2'] + prefer: ['prefer-stable', 'prefer-lowest'] + name: PHPStan with PHP ${{ matrix.php-versions }} ${{ matrix.prefer }} + defaults: + run: + working-directory: modules/civiremote_activity + + steps: + - uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: none + env: + fail-fast: true + + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache dependencies + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ matrix.prefer }}-${{ hashFiles('modules/civiremote_activity/**/composer.json') }} + restore-keys: ${{ runner.os }}-composer-${{ matrix.prefer }}- + + - name: Install dependencies + run: | + composer update --no-progress --prefer-dist --${{ matrix.prefer }} --optimize-autoloader && + composer composer-phpstan -- update --no-progress --prefer-dist --optimize-autoloader && + composer --working-dir=ci update --no-progress --prefer-dist --${{ matrix.prefer }} --optimize-autoloader + + - name: Run PHPStan + run: composer phpstan -- analyse -c phpstan.ci.neon diff --git a/modules/civiremote_activity/ci/README.md b/modules/civiremote_activity/ci/README.md new file mode 100644 index 0000000..0bfc584 --- /dev/null +++ b/modules/civiremote_activity/ci/README.md @@ -0,0 +1,2 @@ +The dependencies specified in composer.json of this directory are required to +run phpstan in CI. diff --git a/modules/civiremote_activity/ci/composer.json b/modules/civiremote_activity/ci/composer.json new file mode 100644 index 0000000..2e52df8 --- /dev/null +++ b/modules/civiremote_activity/ci/composer.json @@ -0,0 +1,29 @@ +{ + "minimum-stability": "dev", + "prefer-stable": true, + "config": { + "sort-packages": true + }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/systopia/drupal-json_forms.git" + }, + { + "type": "vcs", + "url": "https://github.com/systopia/opis-json-schema-ext.git" + }, + { + "type": "vcs", + "url": "https://github.com/systopia/expression-language-ext.git" + }, + { + "type": "composer", + "url": "https://packages.drupal.org/8" + } + ], + "require": { + "drupal/core": "^9.5 || ^10", + "drupal/json_forms": "~0.1" + } +} diff --git a/modules/civiremote_activity/civiremote_activity.info.yml b/modules/civiremote_activity/civiremote_activity.info.yml new file mode 100644 index 0000000..93ae7be --- /dev/null +++ b/modules/civiremote_activity/civiremote_activity.info.yml @@ -0,0 +1,10 @@ +name: CiviRemote Activity +type: module +description: 'CiviRemote Activity' +package: CiviCRM +core_version_requirement: ^9.5 || ^10 +dependencies: + - civiremote_entity + +project: civiremote +version: 0.1-dev diff --git a/modules/civiremote_activity/civiremote_activity.routing.yml b/modules/civiremote_activity/civiremote_activity.routing.yml new file mode 100644 index 0000000..f102ef7 --- /dev/null +++ b/modules/civiremote_activity/civiremote_activity.routing.yml @@ -0,0 +1,25 @@ +civiremote_activity.create_form: + path: '/civiremote/activity/{profile}/add' + defaults: + _controller: 'Drupal\civiremote_activity\Controller\ActivityCreateController::form' + options: + no_cache: TRUE + parameters: + profile: + type: string + requirements: + _user_is_logged_in: 'TRUE' + +civiremote_activity.update_form: + path: '/civiremote/activity/{profile}/{id}' + defaults: + _controller: 'Drupal\civiremote_activity\Controller\ActivityUpdateController::form' + options: + no_cache: TRUE + parameters: + profile: + type: string + id: + type: int + requirements: + _user_is_logged_in: 'TRUE' diff --git a/modules/civiremote_activity/civiremote_activity.services.yml b/modules/civiremote_activity/civiremote_activity.services.yml new file mode 100644 index 0000000..09c90eb --- /dev/null +++ b/modules/civiremote_activity/civiremote_activity.services.yml @@ -0,0 +1,23 @@ +services: + _defaults: + autowire: true + public: false # Controller classes and services directly fetched from container need to be public + + Drupal\civiremote_activity\Api\ActivityApi: + class: Drupal\civiremote_activity\Api\ActivityApi + + Drupal\civiremote_activity\Controller\ActivityCreateController: + class: Drupal\civiremote_activity\Controller\ActivityUpdateController + public: true + + Drupal\civiremote_activity\Controller\ActivityUpdateController: + class: Drupal\civiremote_activity\Controller\ActivityUpdateController + public: true + + Drupal\civiremote_activity\Form\RequestHandler\ActivityCreateFormRequestHandler: + class: Drupal\civiremote_activity\Form\RequestHandler\ActivityCreateFormRequestHandler + public: true + + Drupal\civiremote_activity\Form\RequestHandler\ActivityUpdateFormRequestHandler: + class: Drupal\civiremote_activity\Form\RequestHandler\ActivityUpdateFormRequestHandler + public: true diff --git a/modules/civiremote_activity/composer.json b/modules/civiremote_activity/composer.json new file mode 100644 index 0000000..f701f6b --- /dev/null +++ b/modules/civiremote_activity/composer.json @@ -0,0 +1,68 @@ +{ + "name": "custom/civiremote_activity", + "description": "Drupal frontend to access CiviCRM Remote Activity.", + "type": "drupal-custom-module", + "license": "AGPL-3.0-only", + "authors": [ + { + "name": "SYSTOPIA GmbH", + "email": "info@systopia.de" + } + ], + "autoload": { + "psr-4": { + "Drupal\\civiremote_activity\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Drupal\\Tests\\civiremote_activity\\": "tests/src/" + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "config": { + "sort-packages": true, + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true + } + }, + "repositories": [ + { + "type": "composer", + "url": "https://packages.drupal.org/8" + } + ], + "require": { + "php": "^7.4 || ^8" + }, + "require-dev": { + "drupal/core-dev": "^9.5 || ^10" + }, + "scripts": { + "composer-phpstan": [ + "@composer --working-dir=tools/phpstan" + ], + "composer-tools": [ + "@composer-phpstan" + ], + "phpcs": [ + "@php vendor/bin/phpcs" + ], + "phpcbf": [ + "@php vendor/bin/phpcbf" + ], + "phpstan": [ + "@php tools/phpstan/vendor/bin/phpstan" + ], + "phpunit": [ + "@php vendor/bin/phpunit --coverage-text" + ], + "test": [ + "@phpcs", + "@phpstan", + "@phpunit" + ] + } +} diff --git a/modules/civiremote_activity/phpcs.xml.dist b/modules/civiremote_activity/phpcs.xml.dist new file mode 100644 index 0000000..64633e5 --- /dev/null +++ b/modules/civiremote_activity/phpcs.xml.dist @@ -0,0 +1,82 @@ + + + + src + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/civiremote_activity/phpstan.ci.neon b/modules/civiremote_activity/phpstan.ci.neon new file mode 100644 index 0000000..d051f46 --- /dev/null +++ b/modules/civiremote_activity/phpstan.ci.neon @@ -0,0 +1,8 @@ +includes: + - phpstan.neon.dist + +parameters: + bootstrapFiles: + - ci/vendor/autoload.php + scanFiles: + - ci/vendor/drupal/core/tests/Drupal/Tests/UnitTestCase.php diff --git a/modules/civiremote_activity/phpstan.neon.dist b/modules/civiremote_activity/phpstan.neon.dist new file mode 100644 index 0000000..4344a63 --- /dev/null +++ b/modules/civiremote_activity/phpstan.neon.dist @@ -0,0 +1,28 @@ +parameters: + paths: + - src + #- tests + #- civiremote_activity.module + bootstrapFiles: + - vendor/autoload.php + scanDirectories: + - ../civiremote_entity/src + level: 9 + checkTooWideReturnTypesInProtectedAndPublicMethods: true + checkUninitializedProperties: true + checkMissingCallableSignature: true + treatPhpDocTypesAsCertain: false + exceptions: + check: + missingCheckedExceptionInThrows: true + tooWideThrowType: true + checkedExceptionClasses: + - \Assert\AssertionFailedException + implicitThrows: false + ignoreErrors: + # Note paths are prefixed with ""*/" to work with inspections in PHPStorm because of: + # https://youtrack.jetbrains.com/issue/WI-63891/PHPStan-ignoreErrors-configuration-isnt-working-with-inspections + # Happens in classes implementing ContainerInjectionInterface::create() + - '/ constructor expects [^\s]+, object(\|null)? given.$/' + + tmpDir: .phpstan diff --git a/modules/civiremote_activity/phpunit.xml.dist b/modules/civiremote_activity/phpunit.xml.dist new file mode 100644 index 0000000..6545ac2 --- /dev/null +++ b/modules/civiremote_activity/phpunit.xml.dist @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + tests/src/Unit + + + + + + + + + + + + src + + + + diff --git a/modules/civiremote_activity/src/Api/ActivityApi.php b/modules/civiremote_activity/src/Api/ActivityApi.php new file mode 100644 index 0000000..d4b663a --- /dev/null +++ b/modules/civiremote_activity/src/Api/ActivityApi.php @@ -0,0 +1,31 @@ +. + */ + +declare(strict_types = 1); + +namespace Drupal\civiremote_activity\Api; + +use Drupal\civiremote_entity\Api\AbstractEntityApi; + +final class ActivityApi extends AbstractEntityApi { + + protected function getRemoteEntityName(): string { + return 'RemoteActivity'; + } + +} diff --git a/modules/civiremote_activity/src/Controller/ActivityCreateController.php b/modules/civiremote_activity/src/Controller/ActivityCreateController.php new file mode 100644 index 0000000..141cca2 --- /dev/null +++ b/modules/civiremote_activity/src/Controller/ActivityCreateController.php @@ -0,0 +1,38 @@ +. + */ + +declare(strict_types = 1); + +namespace Drupal\civiremote_activity\Controller; + +use Drupal\civiremote_activity\Form\ActivityCreateForm; +use Drupal\Core\Controller\ControllerBase; + +final class ActivityCreateController extends ControllerBase { + + /** + * @phpstan-return array JSON serializable. + */ + public function form(string $profile): array { + $form = $this->formBuilder()->getForm(ActivityCreateForm::class); + $form['#title'] ??= $this->t('CiviRemote Activity Create'); + + return $form; + } + +} diff --git a/modules/civiremote_activity/src/Controller/ActivityUpdateController.php b/modules/civiremote_activity/src/Controller/ActivityUpdateController.php new file mode 100644 index 0000000..410274d --- /dev/null +++ b/modules/civiremote_activity/src/Controller/ActivityUpdateController.php @@ -0,0 +1,38 @@ +. + */ + +declare(strict_types = 1); + +namespace Drupal\civiremote_activity\Controller; + +use Drupal\civiremote_activity\Form\ActivityUpdateForm; +use Drupal\Core\Controller\ControllerBase; + +final class ActivityUpdateController extends ControllerBase { + + /** + * @phpstan-return array JSON serializable. + */ + public function form(string $profile, int $id): array { + $form = $this->formBuilder()->getForm(ActivityUpdateForm::class); + $form['#title'] ??= $this->t('CiviRemote Activity Update'); + + return $form; + } + +} diff --git a/modules/civiremote_activity/src/Form/ActivityCreateForm.php b/modules/civiremote_activity/src/Form/ActivityCreateForm.php new file mode 100644 index 0000000..c928060 --- /dev/null +++ b/modules/civiremote_activity/src/Form/ActivityCreateForm.php @@ -0,0 +1,52 @@ +. + */ + +declare(strict_types=1); + +namespace Drupal\civiremote_activity\Form; + +use Drupal\civiremote_activity\Form\RequestHandler\ActivityCreateFormRequestHandler; +use Drupal\civiremote_entity\Form\AbstractEntityForm; +use Drupal\civiremote_entity\Form\ResponseHandler\FormResponseHandlerInterface; +use Drupal\json_forms\Form\FormArrayFactoryInterface; +use Drupal\json_forms\Form\Validation\FormValidationMapperInterface; +use Drupal\json_forms\Form\Validation\FormValidatorInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +final class ActivityCreateForm extends AbstractEntityForm { + + /** + * {@inheritDoc} + * + * @return static + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get(FormArrayFactoryInterface::class), + $container->get(FormValidatorInterface::class), + $container->get(FormValidationMapperInterface::class), + $container->get(ActivityCreateFormRequestHandler::class), + $container->get(FormResponseHandlerInterface::class), + ); + } + + public function getFormId(): string { + return 'civiremote_activity_create_form'; + } + +} diff --git a/modules/civiremote_activity/src/Form/ActivityUpdateForm.php b/modules/civiremote_activity/src/Form/ActivityUpdateForm.php new file mode 100644 index 0000000..3974ca7 --- /dev/null +++ b/modules/civiremote_activity/src/Form/ActivityUpdateForm.php @@ -0,0 +1,52 @@ +. + */ + +declare(strict_types=1); + +namespace Drupal\civiremote_activity\Form; + +use Drupal\civiremote_activity\Form\RequestHandler\ActivityUpdateFormRequestHandler; +use Drupal\civiremote_entity\Form\AbstractEntityForm; +use Drupal\civiremote_entity\Form\ResponseHandler\FormResponseHandlerInterface; +use Drupal\json_forms\Form\FormArrayFactoryInterface; +use Drupal\json_forms\Form\Validation\FormValidationMapperInterface; +use Drupal\json_forms\Form\Validation\FormValidatorInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +final class ActivityUpdateForm extends AbstractEntityForm { + + /** + * {@inheritDoc} + * + * @return static + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get(FormArrayFactoryInterface::class), + $container->get(FormValidatorInterface::class), + $container->get(FormValidationMapperInterface::class), + $container->get(ActivityUpdateFormRequestHandler::class), + $container->get(FormResponseHandlerInterface::class), + ); + } + + public function getFormId(): string { + return 'civiremote_activity_update_form'; + } + +} diff --git a/modules/civiremote_activity/src/Form/RequestHandler/ActivityCreateFormRequestHandler.php b/modules/civiremote_activity/src/Form/RequestHandler/ActivityCreateFormRequestHandler.php new file mode 100644 index 0000000..82b2f3b --- /dev/null +++ b/modules/civiremote_activity/src/Form/RequestHandler/ActivityCreateFormRequestHandler.php @@ -0,0 +1,35 @@ +. + */ + +declare(strict_types=1); + +namespace Drupal\civiremote_activity\Form\RequestHandler; + +use Drupal\civiremote_activity\Api\ActivityApi; +use Drupal\civiremote_entity\Form\RequestHandler\EntityCreateFormRequestHandler; + +final class ActivityCreateFormRequestHandler extends EntityCreateFormRequestHandler { + + // For autowiring: + // phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod.Found + public function __construct(ActivityApi $activityApi) { + // phpcs:enable + parent::__construct($activityApi); + } + +} diff --git a/modules/civiremote_activity/src/Form/RequestHandler/ActivityUpdateFormRequestHandler.php b/modules/civiremote_activity/src/Form/RequestHandler/ActivityUpdateFormRequestHandler.php new file mode 100644 index 0000000..25418fb --- /dev/null +++ b/modules/civiremote_activity/src/Form/RequestHandler/ActivityUpdateFormRequestHandler.php @@ -0,0 +1,35 @@ +. + */ + +declare(strict_types=1); + +namespace Drupal\civiremote_activity\Form\RequestHandler; + +use Drupal\civiremote_activity\Api\ActivityApi; +use Drupal\civiremote_entity\Form\RequestHandler\EntityUpdateFormRequestHandler; + +final class ActivityUpdateFormRequestHandler extends EntityUpdateFormRequestHandler { + + // For autowiring: + // phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod.Found + public function __construct(ActivityApi $activityApi) { + // phpcs:enable + parent::__construct($activityApi); + } + +} diff --git a/modules/civiremote_activity/tools/phpstan/composer.json b/modules/civiremote_activity/tools/phpstan/composer.json new file mode 100644 index 0000000..0a77baa --- /dev/null +++ b/modules/civiremote_activity/tools/phpstan/composer.json @@ -0,0 +1,18 @@ +{ + "require": { + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.7", + "phpstan/phpstan-beberlei-assert": "^1.0", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "thecodingmachine/phpstan-strict-rules": "^1.0", + "voku/phpstan-rules": "^3" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + }, + "sort-packages": true + } +}