├── .dockerignore
├── skins
├── test
│ ├── templates
│ │ ├── Page_Footer.html.twig
│ │ ├── Page_Header.html.twig
│ │ ├── System_Message.html.twig
│ │ ├── Access_Denied.twig
│ │ ├── Piwik.twig
│ │ ├── Error_Page.html.twig
│ │ ├── Warning_Page.html.twig
│ │ ├── Page_Not_Found.html.twig
│ │ ├── Membership_Application_Confirmation.html.twig
│ │ ├── Form_Utils.twig
│ │ ├── CreditCardPaymentNotification.twig
│ │ ├── Donation_Cancellation_Confirmation.html.twig
│ │ ├── Membership_Application_Cancellation_Confirmation.html.twig
│ │ ├── Comment_List.html.twig
│ │ ├── Comment_List.rss.twig
│ │ └── Membership_Application.html.twig
│ └── README.md
└── laika
│ └── templates
│ ├── Error_Page.html.twig
│ ├── Comment_List.html.twig
│ ├── Contact_Form.html.twig
│ ├── Donation_Form.html.twig
│ ├── Frequent_Questions.html.twig
│ ├── Funds_Usage.html.twig
│ ├── Comment_Ticker.html.twig
│ ├── System_Message.html.twig
│ ├── Update_Address.html.twig
│ ├── Donation_Confirmation.html.twig
│ ├── Membership_Application.html.twig
│ ├── Membership_Fee_Change.html.twig
│ ├── AddressUpdateSuccess.html.twig
│ ├── page_layouts
│ ├── supporters.html.twig
│ └── base.html.twig
│ ├── Membership_Application_Confirmation.html.twig
│ ├── Subscription_Confirmation.twig
│ ├── Pattern_Library.html.twig
│ ├── matomo.html.twig
│ ├── Page_Not_Found.html.twig
│ ├── Access_Denied.twig
│ └── Comment_List.rss.twig
├── tests
├── Data
│ ├── files
│ │ ├── Banned_Cats.txt
│ │ ├── addSubscriptionResponse.js
│ │ └── emptyCommentList.rss
│ ├── GeneratedMailTemplates
│ │ ├── Contact_Confirm_to_User.txt
│ │ ├── Admin_Moderation.donation.txt
│ │ ├── Admin_Moderation.membership.txt
│ │ ├── Subscription_Request.txt
│ │ ├── Contact_Forward_to_Operator.txt
│ │ ├── Subscription_Confirmation.txt
│ │ ├── Donation_Cancellation_Confirmation.txt
│ │ ├── Membership_Application_Cancellation_Confirmation.txt
│ │ ├── Membership_Application_Confirmation.moderated_other_reason.txt
│ │ ├── Membership_Application_Confirmation.moderated_amount_too_high.txt
│ │ ├── Donation_Confirmation.direct_debit_unmoderated_recurring.txt
│ │ ├── Donation_Confirmation.direct_debit_unmoderated_non_recurring.txt
│ │ ├── Donation_Confirmation.deposit_unmoderated_recurring.txt
│ │ ├── Donation_Confirmation.deposit_unmoderated_non_recurring.txt
│ │ ├── Donation_Confirmation.paypal_unmoderated_non_recurring.txt
│ │ ├── Donation_Confirmation.paypal_unmoderated_recurring.txt
│ │ ├── Donation_Confirmation.sofort_unmoderated_non_recurring.txt
│ │ ├── Donation_Confirmation.credit_card_unmoderated_recurring.txt
│ │ ├── Donation_Confirmation.micropayment_unmoderated_recurring.txt
│ │ ├── Membership_Application_Confirmation.bank_transfer_active_yearly.txt
│ │ ├── Membership_Application_Confirmation.direct_debit_active_yearly.txt
│ │ ├── Membership_Application_Confirmation.paypal_sustaining_monthly.txt
│ │ ├── Membership_Application_Confirmation.direct_debit_sustaining_quarterly.txt
│ │ └── Membership_Application_Confirmation.direct_debit_active_yearly_receipt_optout.txt
│ └── ValidLocation.php
├── Fixtures
│ ├── FakeDonationEvent.php
│ ├── FakeMembershipEvent.php
│ ├── FailedValidationResult.php
│ ├── FeatureReaderStub.php
│ ├── LoggerSpy.php
│ ├── FakeEnvironmentSetup.php
│ ├── EntityManagerSpy.php
│ ├── FixedPaymentDelayCalculator.php
│ ├── SuccessfulMembershipAuthorizer.php
│ ├── MailContentProviderStub.php
│ ├── LogWriterSpy.php
│ ├── FixedTokenGenerator.php
│ ├── FailingPayPalVerificationService.php
│ ├── SucceedingEmailValidator.php
│ ├── UrlAuthenticatorStub.php
│ ├── ThrowingCampaignConfigurationLoader.php
│ ├── FakeBucketLoggingEvent.php
│ ├── FailingVerificationServiceFactory.php
│ ├── SucceedingVerificationServiceFactory.php
│ ├── FakeTranslator.php
│ ├── ErrorThrowingTemplateBasedMailer.php
│ ├── EventDispatcherSpy.php
│ ├── FakeUrlGenerator.php
│ ├── InMemoryTranslator.php
│ ├── BucketLoggerSpy.php
│ ├── DonationUrlAuthenticationLoaderStub.php
│ ├── BucketSelectionSpy.php
│ └── InMemoryTokenRepository.php
├── Unit
│ ├── Factories
│ │ ├── TwigFactoryTestImplementation.php
│ │ └── TwigFactoryTest.php
│ ├── Presentation
│ │ └── HonorificsTest.php
│ ├── Infrastructure
│ │ ├── EventHandling
│ │ │ ├── DonationEventEmitterTest.php
│ │ │ └── MembershipEventEmitterTest.php
│ │ └── Mail
│ │ │ └── BasicMailSubjectRendererTest.php
│ ├── Cli
│ │ └── FeatureToggleParserTest.php
│ └── BucketTesting
│ │ └── BucketTest.php
├── EdgeToEdge
│ ├── Routes
│ │ ├── GetApplicationVarsTrait.php
│ │ └── DefaultRouteTest.php
│ ├── TrackingDataTest.php
│ ├── BucketVariableTest.php
│ └── MatomoTest.php
├── TestEnvironmentBootstrapper.php
├── RebuildDatabaseSchemaTrait.php
├── SchemaCreator.php
└── Integration
│ └── MailTemplateFilenameTraversableTest.php
├── .docker
└── app
│ ├── mailhog.ini
│ └── msmtprc
├── web
├── skins
│ └── README.md
├── res
│ ├── img
│ │ └── banner
│ │ │ ├── banner1.gif
│ │ │ ├── banner2.gif
│ │ │ ├── banner3.gif
│ │ │ └── banner4.gif
│ ├── css
│ │ └── asset_state.css
│ └── js
│ │ └── matomo-loader.js
├── robots.txt
└── index.php
├── config
├── packages
│ ├── prod
│ │ ├── routing.yaml
│ │ └── monolog.yaml
│ ├── uat
│ │ ├── routing.yaml
│ │ └── monolog.yaml
│ ├── test
│ │ └── framework.yaml
│ ├── dev
│ │ └── monolog.yaml
│ ├── routing.yaml
│ ├── cache.yaml
│ └── framework.yaml
├── routes
│ └── dev
│ │ └── framework.yaml
├── preload.php
├── services_prod.yml
├── bundles.php
└── services_test.yaml
├── .github
└── CODEOWNERS
├── doc
├── images
│ ├── php_server.png
│ ├── phpunit_config.png
│ ├── php_interpreter.png
│ ├── remote_interpreter.png
│ └── remote_interpreter_config.png
├── adr
│ ├── 001_Use_ADRs.md
│ └── images
│ │ └── ADR27_ER_Diagram_Custom.mmd
├── Common_Practices_Typescript.md
├── HOWTO_Run_Drone_Locally.md
├── HOWTO_Import_Geodata.md
└── donation_state.dot
├── app
├── CookieNames.php
├── config
│ ├── campaigns.dev.yml
│ ├── campaigns.test.yml
│ └── migrations.php
├── MailTemplateFixtures
│ ├── TemplateVariant.php
│ ├── TemplateSettingsGenerator.php
│ ├── TemplateSettings.php
│ ├── SimpleSettingsGenerator.php
│ └── VariantSettingsGenerator.php
├── mail_templates
│ ├── Contact_Forward_to_Operator.txt.twig
│ ├── Contact_Confirm_to_User.txt.twig
│ ├── Admin_Moderation.txt.twig
│ ├── Subscription_Confirmation.txt.twig
│ └── Subscription_Request.txt.twig
├── Controllers
│ ├── PageNotFoundController.php
│ ├── Membership
│ │ ├── MembershipFeeUpgradeFrontendFlag.php
│ │ ├── ShowMembershipFeeChangeController.php
│ │ └── ShowMembershipConfirmationController.php
│ ├── StaticContent
│ │ ├── ShowUseOfFundsController.php
│ │ ├── ShowPatternLibraryController.php
│ │ ├── ShowFaqController.php
│ │ └── ShowContactFormController.php
│ ├── API
│ │ └── Donation
│ │ │ ├── AbstractApiController.php
│ │ │ └── FrameworkAddCommentRequest.php
│ ├── Donation
│ │ ├── CommentTickerController.php
│ │ └── ViewCommentController.php
│ ├── Subscription
│ │ └── ConfirmSubscriptionController.php
│ ├── Validation
│ │ ├── FindCitiesController.php
│ │ ├── FindStreetsController.php
│ │ ├── ValidationController.php
│ │ └── ValidateIbanController.php
│ ├── Payment
│ │ └── BankDataToIbanController.php
│ └── AddressChange
│ │ └── ShowAddressChangeSuccessController.php
├── AccessDeniedException.php
├── Migrations
│ ├── Version20210427124520.php
│ ├── Version20221118093850.php
│ └── Version20230823082138.php
├── EventHandlers
│ ├── PrettifyJsonResponse.php
│ └── StoreBucketSelection.php
└── UrlGeneratorAdapter.php
├── src
├── RefactoringException.php
├── Factories
│ ├── UnknownChoiceDefinition.php
│ ├── EnvironmentSetup
│ │ ├── EnvironmentSetup.php
│ │ └── EnvironmentSetupException.php
│ ├── ChoiceFactory.php
│ └── DoctrineFactory.php
├── Authentication
│ ├── TokenGenerator.php
│ ├── AuthenticationBoundedContext.php
│ ├── OldStyleTokens
│ │ ├── TokenRepository.php
│ │ └── LenientAuthorizationChecker.php
│ ├── RandomTokenGenerator.php
│ ├── MembershipUrlAuthenticationLoader.php
│ ├── DonationUrlAuthenticationLoader.php
│ ├── Token.php
│ └── AuthenticationContextFactory.php
├── BucketTesting
│ ├── Logging
│ │ ├── LoggingError.php
│ │ ├── LogWriter.php
│ │ ├── LoggingEvent.php
│ │ ├── BucketLogger.php
│ │ ├── NullBucketLogger.php
│ │ ├── Events
│ │ │ ├── DonationCreated.php
│ │ │ └── MembershipApplicationCreated.php
│ │ └── BestEffortBucketLogger.php
│ ├── FeatureToggle.php
│ ├── Domain
│ │ ├── BucketLoggingRepository.php
│ │ └── Model
│ │ │ ├── BucketLogBucket.php
│ │ │ ├── CampaignDate.php
│ │ │ └── Bucket.php
│ ├── CampaignConfigurationLoaderInterface.php
│ ├── BucketSelectionStrategy.php
│ ├── BucketTestingContextFactory.php
│ ├── Validation
│ │ ├── Rule
│ │ │ ├── CampaignValidationRuleInterface.php
│ │ │ ├── MinBucketCountRule.php
│ │ │ ├── StartAndEndTimeRule.php
│ │ │ ├── DefaultBucketRule.php
│ │ │ └── UniqueBucketRule.php
│ │ └── CampaignErrorCollection.php
│ ├── DoorkeeperFeatureToggle.php
│ ├── RandomBucketSelection.php
│ ├── DataAccess
│ │ └── DoctrineBucketLogRepository.php
│ ├── InactiveCampaignBucketSelection.php
│ ├── ParameterBucketSelection.php
│ └── SelectDefaultWhenParamsAreMissingSelection.php
├── Infrastructure
│ ├── EventHandling
│ │ ├── DomainEventEmitter.php
│ │ ├── DonationEventEmitter.php
│ │ ├── MembershipEventEmitter.php
│ │ └── EventDispatcher.php
│ ├── Mail
│ │ ├── MailSubjectRendererInterface.php
│ │ ├── MailerException.php
│ │ ├── Message.php
│ │ ├── NullMailer.php
│ │ ├── TemplateMailerInterface.php
│ │ ├── GetInTouchMailerInterface.php
│ │ ├── BasicMailSubjectRenderer.php
│ │ ├── OperatorMailer.php
│ │ ├── DonationConfirmationMailerAdapter.php
│ │ ├── AdminDonationModerationMailerAdapter.php
│ │ ├── MailTemplateFilenameTraversable.php
│ │ ├── MembershipMailerAdapter.php
│ │ ├── MailFormatter.php
│ │ ├── ErrorHandlingMailerDecorator.php
│ │ ├── DonationConfirmationMailSubjectRenderer.php
│ │ └── MembershipConfirmationMailSubjectRenderer.php
│ ├── Translation
│ │ ├── TranslatorInterface.php
│ │ └── JsonTranslator.php
│ ├── Validation
│ │ ├── NullDomainNameValidator.php
│ │ ├── InternetDomainNameValidator.php
│ │ └── ValidationErrorLogger.php
│ ├── UrlGenerator.php
│ ├── TrackingDataSelector.php
│ ├── UserDataKeyGenerator.php
│ ├── GetConfigCacheKey.php
│ ├── WordListFileReader.php
│ ├── TranslationsCollector.php
│ └── SubmissionRateLimit.php
├── FeatureToggle
│ ├── FeatureReader.php
│ └── Feature.php
├── Presentation
│ ├── ContentPage
│ │ ├── PageNotFoundException.php
│ │ ├── ContentNotFoundException.php
│ │ └── PageSelector.php
│ ├── Presenters
│ │ ├── ExceptionHtmlPresenterInterface.php
│ │ ├── PageNotFoundPresenter.php
│ │ ├── ErrorPageHtmlPresenter.php
│ │ ├── DonationFormPresenter
│ │ │ └── ImpressionCounts.php
│ │ ├── InternalErrorHtmlPresenter.php
│ │ ├── BankDataPresenter.php
│ │ ├── DevelopmentInternalErrorHtmlPresenter.php
│ │ ├── ConfirmSubscriptionHtmlPresenter.php
│ │ ├── CommentListJsonPresenter.php
│ │ ├── CommentListHtmlPresenter.php
│ │ └── AddSubscriptionHtmlPresenter.php
│ ├── CampaignPropertyExtractor.php
│ ├── ActiveFeatureRenderer.php
│ ├── BucketPropertyExtractor.php
│ ├── Honorifics.php
│ ├── TwigTemplate.php
│ └── Salutations.php
├── Autocomplete
│ ├── AutocompleteContextFactory.php
│ ├── Domain
│ │ └── LocationRepository.php
│ └── UseCases
│ │ ├── FindCitiesUseCase.php
│ │ └── FindStreetsUseCase.php
├── Validation
│ └── IsCustomAmountValidator.php
└── UseCases
│ └── GetInTouch
│ └── GetInTouchRequest.php
├── .env.test
├── cli
└── ApplicationConfigValidation
│ ├── ConfigValidationException.php
│ ├── ValidationErrorRenderer.php
│ └── SchemaLoader.php
├── .env.dev
├── phpcs.xml
├── phpstan.neon
├── bin
├── download_assets.sh
└── doctrine
└── .gitignore
/.dockerignore:
--------------------------------------------------------------------------------
1 | vendor
2 | skins/laika/node_modules
--------------------------------------------------------------------------------
/skins/test/templates/Page_Footer.html.twig:
--------------------------------------------------------------------------------
1 | page footer
--------------------------------------------------------------------------------
/skins/test/templates/Page_Header.html.twig:
--------------------------------------------------------------------------------
1 | page header
--------------------------------------------------------------------------------
/tests/Data/files/Banned_Cats.txt:
--------------------------------------------------------------------------------
1 | Nyan
2 | Garfield
3 | Felix da House
--------------------------------------------------------------------------------
/skins/test/templates/System_Message.html.twig:
--------------------------------------------------------------------------------
1 | {$ message | raw $}
2 |
--------------------------------------------------------------------------------
/.docker/app/mailhog.ini:
--------------------------------------------------------------------------------
1 | [mail function]
2 | sendmail_path = "/usr/bin/msmtp -t"
3 |
--------------------------------------------------------------------------------
/web/skins/README.md:
--------------------------------------------------------------------------------
1 | OK, content in here is copied from the build system --Gabriel
--------------------------------------------------------------------------------
/.docker/app/msmtprc:
--------------------------------------------------------------------------------
1 | account default
2 | host mailhog
3 | port 1025
4 | auto_from on
5 |
--------------------------------------------------------------------------------
/tests/Data/files/addSubscriptionResponse.js:
--------------------------------------------------------------------------------
1 | /**/test({
2 | "status": "OK"
3 | });
--------------------------------------------------------------------------------
/skins/test/templates/Access_Denied.twig:
--------------------------------------------------------------------------------
1 |
2 | {$ message|default( 'access_denied' ) $}
3 |
--------------------------------------------------------------------------------
/config/packages/prod/routing.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | router:
3 | strict_requirements: null
4 |
--------------------------------------------------------------------------------
/config/packages/uat/routing.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | router:
3 | strict_requirements: null
4 |
--------------------------------------------------------------------------------
/skins/test/templates/Piwik.twig:
--------------------------------------------------------------------------------
1 |
2 | {$ piwik.baseUrl $}
3 | idsite={$ piwik.siteId $}
4 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # This marks the default owner group of all files in this repository
2 | * @wmde/funtech-core
3 |
--------------------------------------------------------------------------------
/doc/images/php_server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wmde/fundraising-application/main/doc/images/php_server.png
--------------------------------------------------------------------------------
/skins/test/templates/Error_Page.html.twig:
--------------------------------------------------------------------------------
1 | {% if message is defined %}
2 | Internal Error: {$ message $}
3 | {% endif %}
--------------------------------------------------------------------------------
/skins/test/templates/Warning_Page.html.twig:
--------------------------------------------------------------------------------
1 | {% if message is defined %}
2 | Warning: {$ message $}
3 | {% endif %}
--------------------------------------------------------------------------------
/doc/images/phpunit_config.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wmde/fundraising-application/main/doc/images/phpunit_config.png
--------------------------------------------------------------------------------
/doc/images/php_interpreter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wmde/fundraising-application/main/doc/images/php_interpreter.png
--------------------------------------------------------------------------------
/web/res/img/banner/banner1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wmde/fundraising-application/main/web/res/img/banner/banner1.gif
--------------------------------------------------------------------------------
/web/res/img/banner/banner2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wmde/fundraising-application/main/web/res/img/banner/banner2.gif
--------------------------------------------------------------------------------
/web/res/img/banner/banner3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wmde/fundraising-application/main/web/res/img/banner/banner3.gif
--------------------------------------------------------------------------------
/web/res/img/banner/banner4.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wmde/fundraising-application/main/web/res/img/banner/banner4.gif
--------------------------------------------------------------------------------
/doc/images/remote_interpreter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wmde/fundraising-application/main/doc/images/remote_interpreter.png
--------------------------------------------------------------------------------
/config/routes/dev/framework.yaml:
--------------------------------------------------------------------------------
1 | _errors:
2 | resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
3 | prefix: /_error
4 |
--------------------------------------------------------------------------------
/doc/images/remote_interpreter_config.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wmde/fundraising-application/main/doc/images/remote_interpreter_config.png
--------------------------------------------------------------------------------
/config/packages/test/framework.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | test: true
3 | session:
4 | storage_factory_id: session.storage.factory.mock_file
5 |
--------------------------------------------------------------------------------
/skins/test/templates/Page_Not_Found.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'Base_Layout.html.twig' %}
2 |
3 | {% block main %}
4 | {$ message|default( site_metadata.page_not_found )|raw $}
5 | {% endblock %}
6 |
--------------------------------------------------------------------------------
/app/CookieNames.php:
--------------------------------------------------------------------------------
1 | {$ errors[field_name] $}
4 | {% endif %}
5 | {% endmacro %}
6 |
--------------------------------------------------------------------------------
/src/Authentication/TokenGenerator.php:
--------------------------------------------------------------------------------
1 | ['all' => true],
7 | Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
8 | ];
9 |
--------------------------------------------------------------------------------
/src/BucketTesting/FeatureToggle.php:
--------------------------------------------------------------------------------
1 |
8 | {% endblock %}
9 |
--------------------------------------------------------------------------------
/skins/test/templates/Donation_Cancellation_Confirmation.html.twig:
--------------------------------------------------------------------------------
1 |
2 | Donation ID: {$ donationId $}
3 | Cancellation status: {% if cancellationSuccessful %}successful{% else %}failed{% endif %}
4 |
5 | Mail delivery status: {% if mailDeliveryFailed %}failed{% else %}successful{% endif %}
6 |
--------------------------------------------------------------------------------
/tests/Data/GeneratedMailTemplates/Contact_Confirm_to_User.txt:
--------------------------------------------------------------------------------
1 | contact_confirm_to_user
2 |
3 | -------------------------------------
4 | address_of_organization
5 |
6 | phone
7 | www.wikimedia.de
8 |
9 | wikimedia_vision
10 |
11 |
12 | tax_id_organization
13 |
--------------------------------------------------------------------------------
/config/packages/dev/monolog.yaml:
--------------------------------------------------------------------------------
1 | monolog:
2 | handlers:
3 | main:
4 | type: error_log
5 | level: INFO
6 | channels: [ '!event', '!console' ]
7 | console:
8 | type: console
9 | process_psr_3_messages: false
10 | channels: [ '!event', '!doctrine', '!console' ]
--------------------------------------------------------------------------------
/config/packages/routing.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | router:
3 | utf8: true
4 |
5 | # Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
6 | # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
7 | #default_uri: http://localhost
8 |
--------------------------------------------------------------------------------
/skins/laika/templates/Comment_List.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'Base_Layout.html.twig' %}
2 |
3 | {% block title %}{$ page_title( 'comment_list_page_title' ) $}{% endblock %}
4 |
5 | {% block scripts %}
6 | {$ parent() $}
7 |
8 | {% endblock %}
9 |
--------------------------------------------------------------------------------
/skins/laika/templates/Contact_Form.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'Base_Layout.html.twig' %}
2 |
3 | {% block title %}{$ page_title( 'contact_form_page_title' ) $}{% endblock %}
4 |
5 | {% block scripts %}
6 | {$ parent() $}
7 |
8 | {% endblock %}
9 |
--------------------------------------------------------------------------------
/skins/laika/templates/Donation_Form.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'Base_Layout.html.twig' %}
2 |
3 | {% block title %}{$ page_title( 'donation_form_page_title' ) $}{% endblock %}
4 |
5 | {% block scripts %}
6 | {$ parent() $}
7 |
8 | {% endblock %}
9 |
--------------------------------------------------------------------------------
/skins/laika/templates/Frequent_Questions.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'Base_Layout.html.twig' %}
2 |
3 | {% block title %}{$ page_title( 'faq_page_title' ) $}{% endblock %}
4 |
5 | {% block scripts %}
6 | {$ parent() $}
7 |
8 | {% endblock %}
9 |
--------------------------------------------------------------------------------
/skins/laika/templates/Funds_Usage.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'Base_Layout.html.twig' %}
2 |
3 | {% block title %}{$ page_title( 'use_of_funds_page_title' ) $}{% endblock %}
4 |
5 | {% block scripts %}
6 | {$ parent() $}
7 |
8 | {% endblock %}
9 |
--------------------------------------------------------------------------------
/skins/laika/templates/Comment_Ticker.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'Base_Layout.html.twig' %}
2 |
3 | {% block title %}{$ page_title( 'comment_ticker_page_title' ) $}{% endblock %}
4 |
5 | {% block scripts %}
6 | {$ parent() $}
7 |
8 | {% endblock %}
9 |
--------------------------------------------------------------------------------
/skins/laika/templates/System_Message.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'Base_Layout.html.twig' %}
2 |
3 | {% block title %}{$ page_title( 'system_message_page_title' ) $}{% endblock %}
4 |
5 | {% block scripts %}
6 | {$ parent() $}
7 |
8 | {% endblock %}
9 |
--------------------------------------------------------------------------------
/skins/laika/templates/Update_Address.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'Base_Layout.html.twig' %}
2 |
3 | {% block title %}{$ page_title( 'update_address_page_title' ) $}{% endblock %}
4 |
5 | {% block scripts %}
6 | {$ parent() $}
7 |
8 | {% endblock %}
9 |
--------------------------------------------------------------------------------
/tests/Data/GeneratedMailTemplates/Admin_Moderation.donation.txt:
--------------------------------------------------------------------------------
1 | Hallo,
2 |
3 | eine Spende wurde automatisch moderiert, da der Betrag verdächtig hoch ist ( 7.777.777,77 Euro ).
4 |
5 | Zum Überprüfen der Moderation bitte https://backend.wikimedia.de/backend/donation/list aufrufen und dort nach ID 42 suchen.
6 |
7 |
--------------------------------------------------------------------------------
/tests/Data/GeneratedMailTemplates/Admin_Moderation.membership.txt:
--------------------------------------------------------------------------------
1 | Hallo,
2 |
3 | ein Mitgliedschaftsantrag wurde automatisch moderiert, da der Betrag verdächtig hoch ist ( 90.000 Euro ).
4 |
5 | Zum Überprüfen der Moderation bitte https://backend.wikimedia.de/backend/member/list aufrufen und dort nach ID 1 suchen.
6 |
7 |
--------------------------------------------------------------------------------
/tests/Data/GeneratedMailTemplates/Subscription_Request.txt:
--------------------------------------------------------------------------------
1 | subscription_request
2 |
3 | name_head_of_fundraising
4 |
5 | donation_hint
6 |
7 | -------------------------------------
8 | address_of_organization
9 |
10 | phone
11 | email
12 |
13 | bank_account
14 |
15 | tax_id_organization
16 |
17 | unsubscribe_hint
18 |
--------------------------------------------------------------------------------
/skins/laika/templates/Donation_Confirmation.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'Base_Layout.html.twig' %}
2 |
3 | {% block title %}{$ page_title( 'donation_confirmation_page_title' ) $}{% endblock %}
4 |
5 | {% block scripts %}
6 | {$ parent() $}
7 |
8 | {% endblock %}
9 |
--------------------------------------------------------------------------------
/skins/laika/templates/Membership_Application.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'Base_Layout.html.twig' %}
2 |
3 | {% block title %}{$ page_title( 'membership_application_page_title' ) $}{% endblock %}
4 |
5 | {% block scripts %}
6 | {$ parent() $}
7 |
8 | {% endblock %}
9 |
--------------------------------------------------------------------------------
/skins/laika/templates/Membership_Fee_Change.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'Base_Layout.html.twig' %}
2 |
3 | {% block title %}{$ page_title( 'membership_fee_upgrade_page_title' ) $}{% endblock %}
4 |
5 | {% block scripts %}
6 | {$ parent() $}
7 |
8 | {% endblock %}
9 |
--------------------------------------------------------------------------------
/skins/test/templates/Membership_Application_Cancellation_Confirmation.html.twig:
--------------------------------------------------------------------------------
1 | {% if cancellationSuccessful %}
2 | {$ 'membership-cancellation-header' $}
3 | {$ 'membership-cancellation-text'| raw $}
4 | {% else %}
5 | {$ 'membership-cancellation-failed-header' $}
6 | {$ 'membership-cancellation-failed-text' | raw $}
7 | {% endif %}
8 |
--------------------------------------------------------------------------------
/.env.dev:
--------------------------------------------------------------------------------
1 | KERNEL_CLASS='WMDE\Fundraising\Frontend\App\Kernel'
2 | APP_SECRET='$ecretf0rt3st'
3 | APP_DEBUG=0
4 | SYMFONY_DEPRECATIONS_HELPER=999999
5 | PANTHER_APP_ENV=panther
6 |
7 | PAYPAL_API_CLIENT_ID=test_paypal_client_id
8 | PAYPAL_API_CLIENT_SECRET=test_paypal_client_secret
9 | PAYPAL_API_URL=https://api-m.sandbox.paypal.com/
10 |
--------------------------------------------------------------------------------
/app/MailTemplateFixtures/TemplateVariant.php:
--------------------------------------------------------------------------------
1 |
8 | {% endblock %}
9 |
--------------------------------------------------------------------------------
/tests/Data/GeneratedMailTemplates/Contact_Forward_to_Operator.txt:
--------------------------------------------------------------------------------
1 | Über das Kontaktformular wurde eine Anfrage gesendet.
2 | Folgende Daten wurden übermittelt:
3 |
4 | Vorname: John
5 | Nachname: Doe
6 | E-Mail-Adresse: j.doe808@example.com
7 | Betreff: Missing Link
8 | Referenz: 123456
9 | Thema: Other
10 | Anfrage: Please advise
11 |
--------------------------------------------------------------------------------
/src/BucketTesting/Logging/LogWriter.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | public function getMetaData(): array;
13 |
14 | public function getName(): string;
15 | }
16 |
--------------------------------------------------------------------------------
/src/BucketTesting/Logging/BucketLogger.php:
--------------------------------------------------------------------------------
1 |
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/skins/test/templates/Comment_List.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
Die {$ comments |length |e $} neuesten Spenderkommentare
3 | {% for comment in comments %}
4 |
5 | {$ comment.amount |number_format(2, ',', '.') |e $}€ von {$ comment.author |e $} am {$ comment.publicationDate |e $}
6 | {$ comment.text |e $}
7 |
8 | {% endfor %}
9 |
--------------------------------------------------------------------------------
/app/mail_templates/Contact_Forward_to_Operator.txt.twig:
--------------------------------------------------------------------------------
1 | Über das Kontaktformular wurde eine Anfrage gesendet.
2 | Folgende Daten wurden übermittelt:
3 |
4 | Vorname: {$ firstName $}
5 | Nachname: {$ lastName $}
6 | E-Mail-Adresse: {$ emailAddress $}
7 | Betreff: {$ subject $}
8 | Referenz: {$ donationNumber $}
9 | Thema: {$ category $}
10 | Anfrage: {$ message $}
--------------------------------------------------------------------------------
/skins/laika/templates/Membership_Application_Confirmation.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'Base_Layout.html.twig' %}
2 |
3 | {% block title %}{$ page_title( 'membership_application_confirmation_page_title' ) $}{% endblock %}
4 |
5 | {% block scripts %}
6 | {$ parent() $}
7 |
8 | {% endblock %}
9 |
--------------------------------------------------------------------------------
/skins/laika/templates/Subscription_Confirmation.twig:
--------------------------------------------------------------------------------
1 | {% extends 'Base_Layout.html.twig' %}
2 |
3 | {% block title %}{$ page_title( 'subscription_confirmation_page_title' ) $}{% endblock %}
4 |
5 | {% block scripts %}
6 | {$ parent() $}
7 |
8 | {% endblock %}
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/BucketTesting/Domain/BucketLoggingRepository.php:
--------------------------------------------------------------------------------
1 | name}";
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Infrastructure/Mail/MailSubjectRendererInterface.php:
--------------------------------------------------------------------------------
1 | $templateArguments
11 | */
12 | public function render( array $templateArguments = [] ): string;
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | app
5 | src/
6 | tests/
7 | cli/
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/Infrastructure/Mail/MailerException.php:
--------------------------------------------------------------------------------
1 |
11 |
12 | {$ mail_content('tax_id_organization') $}
--------------------------------------------------------------------------------
/src/BucketTesting/CampaignConfigurationLoaderInterface.php:
--------------------------------------------------------------------------------
1 | $parameters
12 | */
13 | public function trans( string $messageKey, array $parameters = [] ): string;
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/BucketTesting/BucketSelectionStrategy.php:
--------------------------------------------------------------------------------
1 |
8 | {% endblock %}
9 |
10 | {% block styles %}
11 |
12 | {% endblock %}
13 |
--------------------------------------------------------------------------------
/tests/Data/files/emptyCommentList.rss:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Spenden an Wikimedia
5 | /list-comments.html
6 | Öffentliche Liste der über das Online-Formular der Wikimedia Fördergesellschaft getätigter Spenden
7 | de-de
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/web/res/css/asset_state.css:
--------------------------------------------------------------------------------
1 | body::after {
2 | position: fixed;
3 | bottom: 0;
4 | right: 0;
5 | display: block;
6 | padding: 0 16px;
7 | border-radius: 20px;
8 | margin: 5px;
9 | opacity: 0.5;
10 | }
11 |
12 | .external-assets::after {
13 | content: "External assets";
14 | background: orange;
15 | }
16 |
17 | .no-external-assets::after{
18 | content: "Builtin assets";
19 | background: green;
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Fixtures/FeatureReaderStub.php:
--------------------------------------------------------------------------------
1 | getLogCalls()->getFirstCall();
10 | if ( $logCall === null ) {
11 | throw new \RuntimeException( "Log call is null" );
12 | }
13 | return $logCall;
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/Infrastructure/Validation/NullDomainNameValidator.php:
--------------------------------------------------------------------------------
1 | template->render( [] );
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/Authentication/OldStyleTokens/TokenRepository.php:
--------------------------------------------------------------------------------
1 | $error
11 | */
12 | public static function render( array $error ): string {
13 | return sprintf(
14 | 'Error in JSON value "%s": %s',
15 | $error['pointer'],
16 | $error['message']
17 | );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/skins/laika/templates/matomo.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tests/Fixtures/FakeEnvironmentSetup.php:
--------------------------------------------------------------------------------
1 | entity = $object;
14 | }
15 |
16 | public function getEntity(): ?object {
17 | return $this->entity;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - vendor/phpstan/phpstan-phpunit/extension.neon
3 | - vendor/phpstan/phpstan-phpunit/rules.neon
4 | - vendor/phpstan/phpstan-doctrine/extension.neon
5 | - vendor/phpstan/phpstan-doctrine/rules.neon
6 | - phpstan-baseline.neon
7 |
8 | parameters:
9 | excludePaths:
10 | - src/Presentation/DonationMembershipApplicationAdapter.php
11 | - src/Presentation/Presenters/MembershipFormViolationPresenter.php
12 | - tests/EdgeToEdge/PayPalRequestLoggerTest.php
13 |
--------------------------------------------------------------------------------
/src/BucketTesting/Validation/Rule/CampaignValidationRuleInterface.php:
--------------------------------------------------------------------------------
1 | subject;
17 | }
18 |
19 | public function getMessageBody(): string {
20 | return $this->messageBody;
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/BucketTesting/DoorkeeperFeatureToggle.php:
--------------------------------------------------------------------------------
1 | doorkeeper->grantsAccessTo( $featureId );
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/tests/Fixtures/FixedPaymentDelayCalculator.php:
--------------------------------------------------------------------------------
1 | fixedDate;
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/app/AccessDeniedException.php:
--------------------------------------------------------------------------------
1 | $templateArguments
14 | */
15 | public function sendMail( EmailAddress $recipient, array $templateArguments = [] ): void {
16 | // Does nothing on purpose
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/MailTemplateFixtures/TemplateSettingsGenerator.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | public function getTemplateSettings(): iterable;
14 |
15 | public function getTemplateName(): string;
16 | }
17 |
--------------------------------------------------------------------------------
/src/Autocomplete/Domain/LocationRepository.php:
--------------------------------------------------------------------------------
1 |
12 | ---------------------------------------------------------------------------
13 |
14 | address_of_organization
15 |
16 | tax_id_organization_verbose
17 |
--------------------------------------------------------------------------------
/app/Controllers/StaticContent/ShowUseOfFundsController.php:
--------------------------------------------------------------------------------
1 | getUseOfFundsRenderer();
14 | return new Response( $renderUseOfFunds() );
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/BucketTesting/RandomBucketSelection.php:
--------------------------------------------------------------------------------
1 | getBuckets()[array_rand( $campaign->getBuckets() )];
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/Presentation/CampaignPropertyExtractor.php:
--------------------------------------------------------------------------------
1 | getUrlKey();
17 | },
18 | $campaigns
19 | );
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Fixtures/SuccessfulMembershipAuthorizer.php:
--------------------------------------------------------------------------------
1 | $message, 'errors' => $errors ], $responseCode );
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Infrastructure/Mail/TemplateMailerInterface.php:
--------------------------------------------------------------------------------
1 | $templateArguments
16 | */
17 | public function sendMail( EmailAddress $recipient, array $templateArguments = [] ): void;
18 | }
19 |
--------------------------------------------------------------------------------
/app/Controllers/Donation/CommentTickerController.php:
--------------------------------------------------------------------------------
1 | getLayoutTemplate(
13 | 'Comment_Ticker.html.twig'
14 | );
15 |
16 | return new Response( $template->render( [] ) );
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Presentation/ActiveFeatureRenderer.php:
--------------------------------------------------------------------------------
1 | active ) {
17 | $ids[] = $feature->getId();
18 | }
19 | }
20 | return $ids;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Infrastructure/EventHandling/DonationEventEmitter.php:
--------------------------------------------------------------------------------
1 | dispatcher->dispatch( $event );
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Validation/IsCustomAmountValidator.php:
--------------------------------------------------------------------------------
1 | getEuroCents() === 0 ) {
19 | return false;
20 | }
21 | return !in_array( $amount, $this->validAmounts );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Infrastructure/EventHandling/MembershipEventEmitter.php:
--------------------------------------------------------------------------------
1 | dispatcher->dispatch( $event );
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Infrastructure/Mail/GetInTouchMailerInterface.php:
--------------------------------------------------------------------------------
1 | $templateArguments Context parameters to use while rendering the template
14 | */
15 | public function sendMail( EmailAddress $recipient, array $templateArguments = [] ): void;
16 | }
17 | #
18 |
--------------------------------------------------------------------------------
/src/Presentation/Presenters/ErrorPageHtmlPresenter.php:
--------------------------------------------------------------------------------
1 | template->render( [ 'message' => $errorMessage ] );
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/bin/download_assets.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ASSET_BRANCH=${1:-main}
4 | TMP=$(mktemp -d)
5 | echo "Downloading assets ..."
6 | curl -sSL "https://gitlab.com/api/v4/projects/fun-tech%2ffundraising-app-frontend/jobs/artifacts/$ASSET_BRANCH/download?job=build-artifacts" > $TMP/assets.zip
7 |
8 | echo "... removing old assets ..."
9 | rm -rf web/skins/dist web/skins/laika
10 |
11 | echo "... extracting assets ..."
12 | unzip -q $TMP/assets.zip -d web/skins -x "*.html"
13 | mv web/skins/dist web/skins/laika
14 |
15 | echo "... cleanup ..."
16 | rm -rf $TMP
17 |
18 | echo "... finished."
19 |
--------------------------------------------------------------------------------
/tests/Fixtures/MailContentProviderStub.php:
--------------------------------------------------------------------------------
1 |
12 | ---------------------------------------------------------------------------
13 |
14 | address_of_organization
15 |
16 | phone
17 | www.wikimedia.de
18 |
19 | tax_id_organization
20 |
--------------------------------------------------------------------------------
/app/Controllers/StaticContent/ShowPatternLibraryController.php:
--------------------------------------------------------------------------------
1 | getLayoutTemplate( 'Pattern_Library.html.twig' );
13 | return new Response( $template->render( [ 'pattern' => $pattern, ] ) );
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tests/Fixtures/LogWriterSpy.php:
--------------------------------------------------------------------------------
1 | entries;
21 | }
22 |
23 | public function write( string $logEntry ): void {
24 | $this->entries[] = $logEntry;
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/Presentation/ContentPage/PageSelector.php:
--------------------------------------------------------------------------------
1 | $config pageId => slug
11 | */
12 | public function __construct( private readonly array $config ) {
13 | }
14 |
15 | public function getPageId( string $slug ): string {
16 | $pageId = array_search( $slug, $this->config );
17 |
18 | if ( $pageId === false ) {
19 | throw new PageNotFoundException;
20 | }
21 |
22 | return $pageId;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/doc/adr/001_Use_ADRs.md:
--------------------------------------------------------------------------------
1 | # Use Architectural Design Decision Records (ADR)
2 |
3 | Date: 2018-06-22
4 |
5 | ## Status
6 |
7 | Accepted
8 |
9 | ## Context
10 |
11 | We need to record the architectural decisions made on this project.
12 |
13 | ## Decision
14 |
15 | We will use Architecture Decision Records, as described by Michael Nygard in
16 | [this article](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions).
17 |
18 | We will keep the ADRs in Markdown files starting with three-digit number, counting up.
19 |
20 | ## Consequences
21 |
22 | See Michael Nygard's article, linked above.
--------------------------------------------------------------------------------
/src/Presentation/BucketPropertyExtractor.php:
--------------------------------------------------------------------------------
1 | getId();
21 | },
22 | $buckets
23 | );
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/Factories/ChoiceFactory.php:
--------------------------------------------------------------------------------
1 | getLayoutTemplate( 'Frequent_Questions.html.twig' );
14 | return new Response( $template->render(
15 | [
16 | 'faq_content' => $ffFactory->getFaqContent(),
17 | ]
18 | ) );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Unit/Presentation/HonorificsTest.php:
--------------------------------------------------------------------------------
1 | 'None', 'Dr.' => 'Dr.' ] );
16 | $this->assertSame( [ '', 'Dr.' ], $honorifics->getKeys() );
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/tests/Data/GeneratedMailTemplates/Membership_Application_Confirmation.moderated_other_reason.txt:
--------------------------------------------------------------------------------
1 | mail_introduction_male_lastname_informal
2 |
3 | membership_application_confirmation_sustaining/moderation_other_reason
4 |
5 | name_head_of_organization
6 | title_head_of_organization
7 | address_of_organization
8 |
9 | ---------------------------------------------------------------------------
10 | wikimedia_vision
11 |
12 | ---------------------------------------------------------------------------
13 |
14 | address_of_organization
15 |
16 | phone
17 | www.wikimedia.de
18 |
19 | tax_id_organization
20 |
--------------------------------------------------------------------------------
/app/mail_templates/Admin_Moderation.txt.twig:
--------------------------------------------------------------------------------
1 | {% if amount is defined %}
2 | {%- set formattedAmount = amount |format_number(locale=locale) ~ ' Euro' -%}
3 | {% elseif membershipFee is defined %}
4 | {%- set formattedAmount = membershipFee |format_number(locale=locale) ~ ' Euro' -%}
5 | {% else %}
6 | {% set formattedAmount = "-- ! Fehler im E-Mail-Template, bitte Templates überprüfen. ! --" %}
7 | {% endif %}
8 | Hallo,
9 |
10 | {$ itemType $} wurde automatisch moderiert, da der Betrag verdächtig hoch ist ( {$ formattedAmount $} ).
11 |
12 | Zum Überprüfen der Moderation bitte {$ focURL $} aufrufen und dort nach ID {$ id $} suchen.
13 |
--------------------------------------------------------------------------------
/tests/Data/GeneratedMailTemplates/Membership_Application_Confirmation.moderated_amount_too_high.txt:
--------------------------------------------------------------------------------
1 | mail_introduction_male_lastname_informal
2 |
3 | membership_application_confirmation_sustaining/moderation_amount_too_high
4 |
5 | name_head_of_organization
6 | title_head_of_organization
7 | address_of_organization
8 |
9 | ---------------------------------------------------------------------------
10 | wikimedia_vision
11 |
12 | ---------------------------------------------------------------------------
13 |
14 | address_of_organization
15 |
16 | phone
17 | www.wikimedia.de
18 |
19 | tax_id_organization
20 |
--------------------------------------------------------------------------------
/src/Presentation/Presenters/DonationFormPresenter/ImpressionCounts.php:
--------------------------------------------------------------------------------
1 | totalImpressionCount;
16 | }
17 |
18 | public function getSingleBannerImpressionCount(): int {
19 | return $this->singleBannerImpressionCount;
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Fixtures/FixedTokenGenerator.php:
--------------------------------------------------------------------------------
1 | token = $token ?? Token::fromHex( self::DEFAULT_TOKEN );
16 | }
17 |
18 | public function generateToken(): Token {
19 | return $this->token;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Infrastructure/UrlGenerator.php:
--------------------------------------------------------------------------------
1 | $parameters
15 | */
16 | public function generateAbsoluteUrl( string $routeName, array $parameters = [] ): string;
17 |
18 | /**
19 | * @param string $routeName
20 | * @param array $parameters
21 | */
22 | public function generateRelativeUrl( string $routeName, array $parameters = [] ): string;
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/Presentation/Presenters/InternalErrorHtmlPresenter.php:
--------------------------------------------------------------------------------
1 | template = $template;
18 | }
19 |
20 | public function present( \Throwable $exception ): string {
21 | return $this->template->render( [] );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tests/Fixtures/FailingPayPalVerificationService.php:
--------------------------------------------------------------------------------
1 | errorMessage );
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/Fixtures/SucceedingEmailValidator.php:
--------------------------------------------------------------------------------
1 | newConfirmSubscriptionUseCase();
14 | $result = $useCase->confirmSubscription( $confirmationCode );
15 | return new Response( $ffFactory->newConfirmSubscriptionHtmlPresenter()->present( $result ) );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/mail_templates/Subscription_Confirmation.txt.twig:
--------------------------------------------------------------------------------
1 | {$ mail_content('subscription_confirmation/body') $}
2 |
3 | {$ mail_content('name_head_of_fundraising') $}
4 |
5 | {$ mail_content('donation_hint', { 'piwiki_keyword': 'confirmed' }) $}
6 |
7 | -------------------------------------
8 | {$ mail_content('address_of_organization') $}
9 |
10 | {$ mail_content('phone') $}
11 | {$ mail_content('email') $}
12 |
13 | {$ mail_content('bank_account') $}
14 |
15 | {$ mail_content('tax_id_organization') $}
16 |
17 | {$ mail_content('unsubscribe_hint', {
18 | 'subscription_email': subscription.email,
19 | 'privacy_protection_link': url( 'page', { pageName: 'Datenschutz' } )
20 | }) $}
--------------------------------------------------------------------------------
/src/Autocomplete/UseCases/FindCitiesUseCase.php:
--------------------------------------------------------------------------------
1 | locationRepository->getCitiesForPostcode( $alphanumericPostCode );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/Controllers/Validation/FindCitiesController.php:
--------------------------------------------------------------------------------
1 | newFindCitiesUseCase()
16 | ->getCitiesForPostcode( $request->get( 'postcode', '' ) );
17 |
18 | return new JsonResponse( $cities );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Autocomplete/UseCases/FindStreetsUseCase.php:
--------------------------------------------------------------------------------
1 | locationRepository->getStreetsForPostcode( $alphanumericPostCode );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Presentation/Presenters/BankDataPresenter.php:
--------------------------------------------------------------------------------
1 | '', 'bic' => '', 'bankname' => '' ];
8 |
9 | /**
10 | * @param array $bankData
11 | * @return array{iban?:string,bic?:string,bankname?:string}
12 | */
13 | public static function getBankDataArray( array $bankData ): array {
14 | $filteredData = array_intersect_key( $bankData, self::BANK_DATA_KEYS );
15 | $stringData = array_map( 'strval', $filteredData );
16 | return array_merge( self::BANK_DATA_KEYS, $stringData );
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/Fixtures/UrlAuthenticatorStub.php:
--------------------------------------------------------------------------------
1 | newFindStreetsUseCase()
16 | ->getStreetsForPostcode( $request->get( 'postcode', '' ) );
17 |
18 | return new JsonResponse( $streets );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Fixtures/ThrowingCampaignConfigurationLoader.php:
--------------------------------------------------------------------------------
1 | exception;
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/Presentation/Honorifics.php:
--------------------------------------------------------------------------------
1 | $honorifics name => display name pairs
14 | */
15 | public function __construct( private readonly array $honorifics ) {
16 | }
17 |
18 | /**
19 | * @return string[]
20 | */
21 | public function getList(): array {
22 | return $this->honorifics;
23 | }
24 |
25 | /**
26 | * @return string[]
27 | */
28 | public function getKeys(): array {
29 | return array_keys( $this->honorifics );
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/BucketTesting/DataAccess/DoctrineBucketLogRepository.php:
--------------------------------------------------------------------------------
1 | entityManager->persist( $bucketLog );
18 | $this->entityManager->flush();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Infrastructure/Mail/BasicMailSubjectRenderer.php:
--------------------------------------------------------------------------------
1 | $templateArguments
19 | */
20 | public function render( array $templateArguments = [] ): string {
21 | return $this->translator->trans( $this->subjectKey );
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | !.*
3 |
4 | composer.phar
5 | npm-debug.log
6 | coverage.clover
7 | coverage.xml
8 |
9 | vendor/
10 | node_modules/
11 | var/
12 | coverage/
13 |
14 | .env
15 | .env.*.local
16 | .idea/
17 | .floo
18 | .flooignore
19 |
20 | .phpunit.result.cache
21 | .phpunit.cache
22 |
23 | app/config/
24 |
25 | app/fundraising-frontend-content/
26 |
27 | res/blz.lut2f
28 |
29 | web/res/js/wmde.js
30 | web/resources
31 | web/skins
32 |
33 | deployment/deployment.retry
34 |
35 | tmp/
36 |
37 | /.docker/database/*
38 | !/.docker/database/01.Database_Schema.sql
39 | !/.docker/database/02.Default_Data.sql
40 |
41 | # This is a file with lots of errors that we could copy 'ignore' rules from
42 | phpstan-baseline-source.neon
--------------------------------------------------------------------------------
/src/BucketTesting/Validation/Rule/MinBucketCountRule.php:
--------------------------------------------------------------------------------
1 | getBuckets() ) < 2 ) {
14 | $errorLogger->addError( 'Campaigns must have at least two buckets', $campaign );
15 | return false;
16 | }
17 | return true;
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/Infrastructure/TrackingDataSelector.php:
--------------------------------------------------------------------------------
1 | 0 ? array_shift( $nonEmptyValues ) : '';
15 | }
16 |
17 | public static function concatTrackingFromVarTuple( string $campaign, string $keyword ): string {
18 | if ( $campaign !== '' ) {
19 | return strtolower( implode( '/', array_filter( [ $campaign, $keyword ] ) ) );
20 | }
21 |
22 | return '';
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Presentation/Presenters/DevelopmentInternalErrorHtmlPresenter.php:
--------------------------------------------------------------------------------
1 | template = $template;
15 | }
16 |
17 | public function present( \Throwable $exception ): string {
18 | return $this->template->render( [
19 | 'message' => $exception->getMessage(),
20 | 'trace' => $exception->getTrace(),
21 | ] );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tests/Fixtures/FakeBucketLoggingEvent.php:
--------------------------------------------------------------------------------
1 | $metadata
13 | */
14 | public function __construct( private readonly array $metadata = [ 'id' => 123, 'some_fact' => 'water_is_wet' ] ) {
15 | }
16 |
17 | /**
18 | * @return array
19 | */
20 | public function getMetaData(): array {
21 | return $this->metadata;
22 | }
23 |
24 | public function getName(): string {
25 | return 'testEventLogged';
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/doc/Common_Practices_Typescript.md:
--------------------------------------------------------------------------------
1 | # Common practices for client-side code
2 |
3 | These guidelines are agreed-upon practices by the FUN team and reflect their current understanding of Vue.js and TypeScript.
4 |
5 | * We collect all new types in the `types.ts` file. When it becomes sufficiently large, we'll have enough code "examples" to refactor them into smaller cohesive units/modules.
6 | * When writing a Vuex store module, don't split mutations, actions, getters, etc in separate files. Doing this makes the code harder to navigate. If a Vuex module file becomes too big, ask the team members if splitting is ok.
7 | * In Vue.js files, use TypeScript
8 | * Don't use [vue-class-component decorator](https://github.com/vuejs/vue-class-component)
9 |
--------------------------------------------------------------------------------
/src/Authentication/RandomTokenGenerator.php:
--------------------------------------------------------------------------------
1 | length < 1 ) {
17 | throw new \InvalidArgumentException( 'Token length must be a positive integer' );
18 | }
19 |
20 | return new Token( random_bytes( $this->length ) );
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Fixtures/FailingVerificationServiceFactory.php:
--------------------------------------------------------------------------------
1 | errorMessage );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/Fixtures/SucceedingVerificationServiceFactory.php:
--------------------------------------------------------------------------------
1 |
11 | Show pink fluffy unicorns dancing on rainbows to select donors.
12 |
13 | This campaign is used for testing the parameter passing on redirects
14 | See AddDonationRouteTest.
15 | start: "2023-08-08"
16 | end: "2038-01-18"
17 | buckets:
18 | - "default"
19 | - "fluffy"
20 | - "pink"
21 | default_bucket: "default"
22 | url_key: pfu
23 | active: true
24 | param_only: true
25 |
--------------------------------------------------------------------------------
/src/BucketTesting/Validation/Rule/StartAndEndTimeRule.php:
--------------------------------------------------------------------------------
1 | getStartTimestamp() >= $campaign->getEndTimestamp() ) {
14 | $errorLogger->addError( 'Start date must be before end date.', $campaign );
15 | return false;
16 | }
17 | return true;
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/tests/EdgeToEdge/Routes/GetApplicationVarsTrait.php:
--------------------------------------------------------------------------------
1 | filter( '#appdata' )->getNode( 0 );
12 | $decodedApplicationVars = json_decode( $appElement->getAttribute( 'data-application-vars' ) );
13 | if ( !( $decodedApplicationVars instanceof \stdClass ) ) {
14 | throw new \RuntimeException( 'Could not decode application vars as an object' );
15 | }
16 | return $decodedApplicationVars;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/Data/GeneratedMailTemplates/Donation_Confirmation.direct_debit_unmoderated_recurring.txt:
--------------------------------------------------------------------------------
1 | mail_introduction_female_lastname_informal
2 |
3 | donation_confirmation/payment_introdonation_confirmation/paymenttype_directdebit
4 |
5 | donation_confirmation/receipt
6 |
7 | donation_confirmation/receipt_support
8 |
9 | donation_confirmation/greetings
10 |
11 | name_head_of_organization
12 | title_head_of_organization
13 |
14 | ---------------------------------------------------------------------------
15 | wikimedia_vision
16 |
17 | ---------------------------------------------------------------------------
18 |
19 | address_of_organization
20 |
21 | tax_id_organization_verbose
22 |
23 | donation_confirmation/number
24 |
--------------------------------------------------------------------------------
/app/Controllers/Validation/ValidationController.php:
--------------------------------------------------------------------------------
1 | getEmailValidator()->validate( $request->request->get( 'email', '' ) );
16 | return new JsonResponse( [
17 | 'status' => $validationResult->isSuccessful() ? 'OK' : 'ERR'
18 | ] );
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/BucketTesting/Validation/Rule/DefaultBucketRule.php:
--------------------------------------------------------------------------------
1 | getDefaultBucket();
15 | } catch ( \LogicException $e ) {
16 | $errorLogger->addError( 'Must have a valid default bucket.', $campaign );
17 | return false;
18 | }
19 | return true;
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/Presentation/TwigTemplate.php:
--------------------------------------------------------------------------------
1 | $context
15 | */
16 | public function __construct(
17 | private readonly Environment $twig,
18 | private readonly string $templatePath,
19 | private readonly array $context = []
20 | ) {
21 | }
22 |
23 | /**
24 | * @param array $arguments
25 | */
26 | public function render( array $arguments ): string {
27 | return $this->twig->render( $this->templatePath, $arguments + $this->context );
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/tests/Data/GeneratedMailTemplates/Donation_Confirmation.direct_debit_unmoderated_non_recurring.txt:
--------------------------------------------------------------------------------
1 | mail_introduction_female_lastname_informal
2 |
3 | donation_confirmation/payment_introdonation_confirmation/paymenttype_directdebit
4 |
5 | donation_confirmation/receipt_support donation_confirmation/receipt_opted_out
6 |
7 | donation_confirmation/greetings
8 |
9 | name_head_of_organization
10 | title_head_of_organization
11 |
12 | ---------------------------------------------------------------------------
13 | wikimedia_vision
14 |
15 | ---------------------------------------------------------------------------
16 |
17 | address_of_organization
18 |
19 | tax_id_organization_verbose
20 |
21 | donation_confirmation/number
22 |
--------------------------------------------------------------------------------
/tests/Data/ValidLocation.php:
--------------------------------------------------------------------------------
1 | getLayoutTemplate( 'Contact_Form.html.twig' );
14 | $templateContext = [
15 | 'contact_categories' => $ffFactory->getGetInTouchCategories(),
16 | 'contactFormValidationPatterns' => $ffFactory->getValidationRules()->contactForm,
17 | ];
18 | return new Response( $template->render( $templateContext ) );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/Controllers/Validation/ValidateIbanController.php:
--------------------------------------------------------------------------------
1 | newCheckIbanUseCase();
16 | $checkIbanResponse = $useCase->ibanIsValid( $request->query->get( 'iban', '' ) );
17 | return new JsonResponse( $ffFactory->newIbanPresenter()->present( $checkIbanResponse ) );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/BucketTesting/Logging/Events/DonationCreated.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | private array $metadata;
15 |
16 | public function __construct( int $donationId ) {
17 | $this->metadata = [
18 | 'id' => $donationId
19 | ];
20 | }
21 |
22 | /**
23 | * @return array
24 | */
25 | public function getMetaData(): array {
26 | return $this->metadata;
27 | }
28 |
29 | public function getName(): string {
30 | return 'donationCreated';
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/tests/Data/GeneratedMailTemplates/Donation_Confirmation.deposit_unmoderated_recurring.txt:
--------------------------------------------------------------------------------
1 | mail_introduction_female_lastname_informal
2 |
3 | donation_confirmation/paymenttype_banktransfer
4 |
5 | donation_confirmation/paymenttype_banktransfer_interval
6 | donation_confirmation/receipt_support donation_confirmation/receipt_opted_out
7 |
8 | donation_confirmation/greetings
9 |
10 | name_head_of_organization
11 | title_head_of_organization
12 |
13 | ---------------------------------------------------------------------------
14 | wikimedia_vision
15 |
16 | ---------------------------------------------------------------------------
17 |
18 | address_of_organization
19 |
20 | tax_id_organization_verbose
21 |
22 | donation_confirmation/number
23 |
--------------------------------------------------------------------------------
/tests/Data/GeneratedMailTemplates/Donation_Confirmation.deposit_unmoderated_non_recurring.txt:
--------------------------------------------------------------------------------
1 | mail_introduction_female_lastname_informal
2 |
3 | donation_confirmation/paymenttype_banktransfer
4 |
5 | donation_confirmation/paymenttype_banktransfer_once
6 |
7 | donation_confirmation/receipt
8 |
9 | donation_confirmation/receipt_support
10 |
11 | donation_confirmation/greetings
12 |
13 | name_head_of_organization
14 | title_head_of_organization
15 |
16 | ---------------------------------------------------------------------------
17 | wikimedia_vision
18 |
19 | ---------------------------------------------------------------------------
20 |
21 | address_of_organization
22 |
23 | tax_id_organization_verbose
24 |
25 | donation_confirmation/number
26 |
--------------------------------------------------------------------------------
/config/packages/cache.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | cache:
3 | # Unique name of your app: used to compute stable namespaces for cache keys.
4 | #prefix_seed: your_vendor_name/app_name
5 |
6 | # The "app" cache stores to the filesystem by default.
7 | # The data in this cache should persist between deploys.
8 | # Other options include:
9 |
10 | # Redis
11 | #app: cache.adapter.redis
12 | #default_redis_provider: redis://localhost
13 |
14 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
15 | #app: cache.adapter.apcu
16 |
17 | # Namespaced pools use the above "app" backend by default
18 | #pools:
19 | #my.dedicated.cache: null
20 |
--------------------------------------------------------------------------------
/config/packages/framework.yaml:
--------------------------------------------------------------------------------
1 | # see https://symfony.com/doc/current/reference/configuration/framework.html
2 | framework:
3 | secret: '%env(APP_SECRET)%'
4 | #csrf_protection: true
5 | http_method_override: false
6 |
7 | # Enables session support. Note that the session will ONLY be started if you read or write from it.
8 | # Remove or comment this section to explicitly disable session support.
9 | session:
10 | handler_id: null
11 | cookie_secure: auto
12 | cookie_samesite: lax
13 |
14 | #esi: true
15 | #fragments: true
16 | php_errors:
17 | log: true
18 |
19 | mailer:
20 | dsn: 'native://default'
21 |
22 | handle_all_throwables: true
23 |
24 | validation:
25 | email_validation_mode: html5
26 |
--------------------------------------------------------------------------------
/tests/Data/GeneratedMailTemplates/Donation_Confirmation.paypal_unmoderated_non_recurring.txt:
--------------------------------------------------------------------------------
1 | mail_introduction_female_lastname_informal
2 |
3 | donation_confirmation/payment_introdonation_confirmation/paymenttype_externaldonation_confirmation/payment_interval_once
4 |
5 | donation_confirmation/receipt_support donation_confirmation/receipt_opted_out
6 |
7 | donation_confirmation/greetings
8 |
9 | name_head_of_organization
10 | title_head_of_organization
11 |
12 | ---------------------------------------------------------------------------
13 | wikimedia_vision
14 |
15 | ---------------------------------------------------------------------------
16 |
17 | address_of_organization
18 |
19 | tax_id_organization_verbose
20 |
21 | donation_confirmation/number
22 |
--------------------------------------------------------------------------------
/tests/Data/GeneratedMailTemplates/Donation_Confirmation.paypal_unmoderated_recurring.txt:
--------------------------------------------------------------------------------
1 | mail_introduction_female_lastname_informal
2 |
3 | donation_confirmation/payment_introdonation_confirmation/paymenttype_externaldonation_confirmation/payment_interval_recurring
4 |
5 | donation_confirmation/receipt_support donation_confirmation/receipt_opted_out
6 |
7 | donation_confirmation/greetings
8 |
9 | name_head_of_organization
10 | title_head_of_organization
11 |
12 | ---------------------------------------------------------------------------
13 | wikimedia_vision
14 |
15 | ---------------------------------------------------------------------------
16 |
17 | address_of_organization
18 |
19 | tax_id_organization_verbose
20 |
21 | donation_confirmation/number
22 |
--------------------------------------------------------------------------------
/tests/Data/GeneratedMailTemplates/Donation_Confirmation.sofort_unmoderated_non_recurring.txt:
--------------------------------------------------------------------------------
1 | mail_introduction_female_lastname_informal
2 |
3 | donation_confirmation/payment_introdonation_confirmation/paymenttype_externaldonation_confirmation/payment_interval_once
4 |
5 | donation_confirmation/receipt_support donation_confirmation/receipt_opted_out
6 |
7 | donation_confirmation/greetings
8 |
9 | name_head_of_organization
10 | title_head_of_organization
11 |
12 | ---------------------------------------------------------------------------
13 | wikimedia_vision
14 |
15 | ---------------------------------------------------------------------------
16 |
17 | address_of_organization
18 |
19 | tax_id_organization_verbose
20 |
21 | donation_confirmation/number
22 |
--------------------------------------------------------------------------------
/src/BucketTesting/InactiveCampaignBucketSelection.php:
--------------------------------------------------------------------------------
1 | isActive() || $campaign->isExpired( $this->now ) ) {
18 | return $campaign->getDefaultBucket();
19 | }
20 | return null;
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/skins/laika/templates/Page_Not_Found.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'Base_Layout.html.twig' %}
2 |
3 | {% block title %}{$ page_title( 'page_not_found_page_title' ) $}{% endblock %}
4 |
5 | {% block main %}
6 |
7 |
11 |
12 |
17 |
18 | {% endblock %}
19 |
20 | {% block scripts %}
21 | {$ parent() $}
22 |
23 | {% endblock %}
24 |
--------------------------------------------------------------------------------
/src/Presentation/Presenters/ConfirmSubscriptionHtmlPresenter.php:
--------------------------------------------------------------------------------
1 | isSuccessful() ) {
18 | $contextVariables['error_message'] = $confirmationResponse->getValidationErrors()[0]->getMessageIdentifier();
19 | }
20 | return $this->template->render( $contextVariables );
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/MailTemplateFixtures/TemplateSettings.php:
--------------------------------------------------------------------------------
1 | $templateData Data passed to the template engine
14 | */
15 | public function __construct(
16 | public readonly string $templateName,
17 | public readonly string $id,
18 | public readonly array $templateData = []
19 | ) {
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/Fixtures/FakeTranslator.php:
--------------------------------------------------------------------------------
1 | messageWrapper, $messageKey );
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/app/MailTemplateFixtures/SimpleSettingsGenerator.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | public function getTemplateSettings(): iterable {
17 | yield new TemplateSettings( $this->templateName, $this->getId(), $this->templateData );
18 | }
19 |
20 | private function getId(): string {
21 | return basename( $this->templateName, '.txt.twig' );
22 | }
23 |
24 | public function getTemplateName(): string {
25 | return $this->templateName;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/doc/HOWTO_Run_Drone_Locally.md:
--------------------------------------------------------------------------------
1 | # How to run the DRONE CI pipeline on your local machine
2 |
3 | ## Prerequisites
4 |
5 | You need to have the `drone` command line interface installed on your
6 | local machine. See https://docs.drone.io/cli/install/ for download links
7 | and installation instructions.
8 |
9 | You need to have the `pass` password manager installed, configured and be
10 | able to access the Fundraising Tech password repository. You can try out
11 | your setup by running `pass wmde-fun/github-composer-ci-token`. After
12 | entering your GPG password you should then see the GitHub token.
13 |
14 | As the last item on the list, you should be able to use `docker` commands
15 | on your local machine.
16 |
17 | ## Running Drone CI pipeline on your local machine
18 |
19 |
20 | make drone-ci
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/BucketTesting/ParameterBucketSelection.php:
--------------------------------------------------------------------------------
1 | $parameters
14 | */
15 | public function __construct( private readonly array $parameters ) {
16 | }
17 |
18 | public function selectBucketForCampaign( Campaign $campaign ): ?Bucket {
19 | $urlKey = $campaign->getUrlKey();
20 | if ( !isset( $this->parameters[$urlKey] ) ) {
21 | return null;
22 | }
23 | return $campaign->getBucketByIndex( $this->parameters[$urlKey] );
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/BucketTesting/Validation/CampaignErrorCollection.php:
--------------------------------------------------------------------------------
1 | getName() . ': ' . $error;
19 | }
20 | $this->errors[] = $error;
21 | }
22 |
23 | /**
24 | * @return string[]
25 | */
26 | public function getErrors(): array {
27 | return $this->errors;
28 | }
29 |
30 | public function hasErrors(): bool {
31 | return !empty( $this->getErrors() );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Authentication/MembershipUrlAuthenticationLoader.php:
--------------------------------------------------------------------------------
1 | $parameters
18 | * @return array
19 | */
20 | public function addMembershipAuthorizationParameters( int $membershipId, array $parameters ): array;
21 | }
22 |
--------------------------------------------------------------------------------
/src/BucketTesting/SelectDefaultWhenParamsAreMissingSelection.php:
--------------------------------------------------------------------------------
1 | $params
14 | */
15 | public function __construct( private readonly array $params ) {
16 | }
17 |
18 | public function selectBucketForCampaign( Campaign $campaign ): ?Bucket {
19 | if ( $campaign->isOnlyActiveWithUrlKey() && !isset( $this->params[$campaign->getUrlKey()] ) ) {
20 | return $campaign->getDefaultBucket();
21 | }
22 | return null;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/web/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow: /list-comments.*
3 | Disallow: /spenden/list.*
4 | Disallow: /spenden/rss.*
5 | Disallow: /spenden/json.*
6 |
7 | Disallow: /donation/add
8 | Disallow: /donation/cancel
9 | Disallow: /donation/update
10 | Disallow: /validate-email
11 | Disallow: /validate-payment-data
12 | Disallow: /validate-address
13 | Disallow: /validate-fee
14 | Disallow: /check-iban
15 | Disallow: /generate-iban
16 | Disallow: /add-comment
17 | Disallow: /show-membership-confirmation
18 | Disallow: /change-membership-fee
19 | Disallow: /show-donation-confirmation
20 | Disallow: /cancel-membership-application
21 | Disallow: /sofort-payment-notification
22 | Disallow: /handle-paypal-payment-notification
23 | Disallow: /handle-paypal-membership-fee-payments
24 | Disallow: /handle-creditcard-payment-notification
25 |
--------------------------------------------------------------------------------
/doc/HOWTO_Import_Geodata.md:
--------------------------------------------------------------------------------
1 | # How to Import the Third Party Geodata
2 |
3 | The Geodata zip contains 2 sql files, a small one with community names, and a large one which includes streets.
4 |
5 | 1. Copy the smaller sql file into /.docker/database.
6 | 2. Rename it to `00.Geodata.sql`. This is needed because Docker runs the files in order, and the Geodata import needs to run before the Schema.
7 | 3. Remove the database volume with the command:
8 |
9 | ```$ docker volume rm APP_DIRECTORY_db-storage```
10 |
11 | APP_DIRECTORY is the directory name where you have checked out the `fundraising-application` code. You can check for the volume name by listing all docker volumes with the command `$ docker volume ls`.
12 | 4. Run `$ docker compose up`. When importing the Geotata this might take several minutes as it imports.
13 |
--------------------------------------------------------------------------------
/app/mail_templates/Subscription_Request.txt.twig:
--------------------------------------------------------------------------------
1 | {$ mail_content('subscription_request', {
2 | 'confirm_subscription_link': url( 'confirm-subscription', { confirmationCode: subscription.confirmationCode } ),
3 | 'day_of_the_week': day_of_the_week
4 | }) $}
5 |
6 | {$ mail_content('name_head_of_fundraising') $}
7 |
8 | {$ mail_content('donation_hint', { 'piwiki_keyword': 'requested' }) $}
9 |
10 | -------------------------------------
11 | {$ mail_content('address_of_organization') $}
12 |
13 | {$ mail_content('phone') $}
14 | {$ mail_content('email') $}
15 |
16 | {$ mail_content('bank_account') $}
17 |
18 | {$ mail_content('tax_id_organization') $}
19 |
20 | {$ mail_content('unsubscribe_hint', {
21 | 'subscription_email': subscription.email,
22 | 'privacy_protection_link': url( 'page', { pageName: 'Datenschutz' } )
23 | }) $}
--------------------------------------------------------------------------------
/doc/donation_state.dot:
--------------------------------------------------------------------------------
1 | digraph G {
2 | "New" -> "Moderation" [label="Add comment with bad words"]
3 | "New" -> "Cancelled" [label="Cancelled by user or in backend"]
4 | "Promise" -> "Moderation" [label="Add comment with bad words"]
5 | "External_Incomplete" -> "Moderation" [label="Add comment with bad words"]
6 | "External_Incomplete" -> "External_Booked" [label="Book donation"]
7 | "Moderation" -> "External_Incomplete" [label="Moderate OK in backend"]
8 | "Moderation" -> "External_Booked" [label="Moderate OK in backend"]
9 | "External_Booked" -> "Moderation" [label="Add comment with bad words"]
10 | "New" [shape=circle,peripheries=2]
11 | "Promise" [shape=circle,peripheries=2]
12 | "External_Incomplete" [shape=circle,peripheries=2]
13 | "External_Booked" [shape=circle]
14 | "Moderation" [shape=circle]
15 | }
16 |
--------------------------------------------------------------------------------
/src/BucketTesting/Logging/Events/MembershipApplicationCreated.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | private array $metadata;
15 |
16 | public function __construct( int $membershipApplicationId ) {
17 | $this->metadata = [
18 | 'id' => $membershipApplicationId
19 | ];
20 | }
21 |
22 | /**
23 | * @return array
24 | */
25 | public function getMetaData(): array {
26 | return $this->metadata;
27 | }
28 |
29 | public function getName(): string {
30 | return 'membershipApplicationCreated';
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/tests/Fixtures/ErrorThrowingTemplateBasedMailer.php:
--------------------------------------------------------------------------------
1 | $templateArguments
20 | */
21 | public function sendMail( EmailAddress $recipient, array $templateArguments = [] ): void {
22 | throw new \RuntimeException( self::ERROR_MESSAGE, 0, $this->previous );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/Controllers/Payment/BankDataToIbanController.php:
--------------------------------------------------------------------------------
1 | newGenerateBankDataFromGermanLegacyBankDataUseCase()->generateIban(
16 | $request->query->get( 'accountNumber', '' ),
17 | $request->query->get( 'bankCode', '' )
18 | );
19 | return new JsonResponse( $ffFactory->newIbanPresenter()->present( $generateIbanResponse ) );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Authentication/DonationUrlAuthenticationLoader.php:
--------------------------------------------------------------------------------
1 | $parameters
18 | * @return array
19 | */
20 | public function addDonationAuthorizationParameters( int $donationId, array $parameters ): array;
21 | }
22 |
--------------------------------------------------------------------------------
/tests/Fixtures/EventDispatcherSpy.php:
--------------------------------------------------------------------------------
1 | listeners );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/Controllers/API/Donation/FrameworkAddCommentRequest.php:
--------------------------------------------------------------------------------
1 | comment,
22 | isPublic: $this->isPublic,
23 | isAnonymous: !$this->withName,
24 | donationId: $this->donationId,
25 | );
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Data/GeneratedMailTemplates/Donation_Confirmation.credit_card_unmoderated_recurring.txt:
--------------------------------------------------------------------------------
1 | mail_introduction_female_lastname_informal
2 |
3 | donation_confirmation/payment_introdonation_confirmation/paymenttype_externaldonation_confirmation/payment_interval_recurring
4 |
5 | donation_confirmation/receipt_support donation_confirmation/receipt_opted_out
6 |
7 | donation_confirmation/greetings
8 |
9 | name_head_of_organization
10 | title_head_of_organization
11 |
12 | donation_confirmation/creditcard_interval_notice
13 |
14 | ---------------------------------------------------------------------------
15 | wikimedia_vision
16 |
17 | ---------------------------------------------------------------------------
18 |
19 | address_of_organization
20 |
21 | tax_id_organization_verbose
22 |
23 | donation_confirmation/number
24 |
--------------------------------------------------------------------------------
/tests/Data/GeneratedMailTemplates/Donation_Confirmation.micropayment_unmoderated_recurring.txt:
--------------------------------------------------------------------------------
1 | mail_introduction_female_lastname_informal
2 |
3 | donation_confirmation/payment_introdonation_confirmation/paymenttype_externaldonation_confirmation/payment_interval_recurring
4 |
5 | donation_confirmation/receipt_support donation_confirmation/receipt_opted_out
6 |
7 | donation_confirmation/greetings
8 |
9 | name_head_of_organization
10 | title_head_of_organization
11 |
12 | donation_confirmation/creditcard_interval_notice
13 |
14 | ---------------------------------------------------------------------------
15 | wikimedia_vision
16 |
17 | ---------------------------------------------------------------------------
18 |
19 | address_of_organization
20 |
21 | tax_id_organization_verbose
22 |
23 | donation_confirmation/number
24 |
--------------------------------------------------------------------------------
/tests/Data/GeneratedMailTemplates/Membership_Application_Confirmation.bank_transfer_active_yearly.txt:
--------------------------------------------------------------------------------
1 | mail_introduction_male_informal
2 |
3 | membership_application_confirmation_active/intro_start
4 |
5 | membership_application_confirmation_active/intro_ueb
6 |
7 | membership_application_confirmation_active/intro_end
8 |
9 | membership_application_confirmation_active/contact
10 |
11 | membership_application_confirmation_active/outro
12 |
13 | name_head_of_organization
14 | title_head_of_organization
15 | address_of_organization
16 |
17 | ---------------------------------------------------------------------------
18 | wikimedia_vision
19 |
20 | ---------------------------------------------------------------------------
21 |
22 | address_of_organization
23 |
24 | phone
25 | www.wikimedia.de
26 |
27 | tax_id_organization
28 |
--------------------------------------------------------------------------------
/tests/Data/GeneratedMailTemplates/Membership_Application_Confirmation.direct_debit_active_yearly.txt:
--------------------------------------------------------------------------------
1 | mail_introduction_male_informal
2 |
3 | membership_application_confirmation_active/intro_start
4 |
5 | membership_application_confirmation_active/intro_bez
6 |
7 | membership_application_confirmation_active/intro_end
8 |
9 | membership_application_confirmation_active/contact
10 |
11 | membership_application_confirmation_active/outro
12 |
13 | name_head_of_organization
14 | title_head_of_organization
15 | address_of_organization
16 |
17 | ---------------------------------------------------------------------------
18 | wikimedia_vision
19 |
20 | ---------------------------------------------------------------------------
21 |
22 | address_of_organization
23 |
24 | phone
25 | www.wikimedia.de
26 |
27 | tax_id_organization
28 |
--------------------------------------------------------------------------------
/tests/EdgeToEdge/Routes/DefaultRouteTest.php:
--------------------------------------------------------------------------------
1 | modifyConfiguration( [ 'skin' => 'laika' ] );
15 | $client = $this->createClient();
16 |
17 | $client->request( 'GET', '/' );
18 |
19 | $this->assertMatchesRegularExpression(
20 | '/
23 | {% endblock %}
24 |
--------------------------------------------------------------------------------
/skins/test/templates/Comment_List.rss.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Spenden an Wikimedia
5 | {$ basepath $}/list-comments.html
6 | Öffentliche Liste der über das Online-Formular der Wikimedia Fördergesellschaft getätigter Spenden
7 | de-de
8 | {$ rssPublicationData|e $}
9 |
10 | {% for comment in comments %} -
11 | {$ comment.amount |number_format(2, ',', '.') |e $} Euro von {$ comment.author |e $}
12 | {$ comment.text |e $}
13 | {$ comment.publicationDate |e $}
14 | {$ basepath $}/list-comments.html
15 |
{% endfor %}
16 |
17 |
--------------------------------------------------------------------------------
/src/BucketTesting/Validation/Rule/UniqueBucketRule.php:
--------------------------------------------------------------------------------
1 | getBuckets() as $bucket ) {
16 | if ( in_array( $bucket->getName(), $buckets ) ) {
17 | $valid = false;
18 | $errorLogger->addError( 'Duplicate bucket ' . $bucket->getName(), $campaign );
19 | }
20 | $buckets[] = $bucket->getName();
21 | }
22 | return $valid;
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/tests/Data/GeneratedMailTemplates/Membership_Application_Confirmation.paypal_sustaining_monthly.txt:
--------------------------------------------------------------------------------
1 | mail_introduction_male_lastname_informal
2 |
3 | membership_application_confirmation_sustaining/intro_start
4 |
5 | membership_application_confirmation_sustaining/intro_bez
6 |
7 | membership_application_confirmation_sustaining/intro_end
8 |
9 | membership_application_confirmation_sustaining/contact
10 |
11 | membership_application_confirmation_sustaining/outro
12 |
13 | name_head_of_organization
14 | title_head_of_organization
15 | address_of_organization
16 |
17 | ---------------------------------------------------------------------------
18 | wikimedia_vision
19 |
20 | ---------------------------------------------------------------------------
21 |
22 | address_of_organization
23 |
24 | phone
25 | www.wikimedia.de
26 |
27 | tax_id_organization
28 |
--------------------------------------------------------------------------------
/skins/laika/templates/Comment_List.rss.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Spenden an Wikimedia
5 | {$ basepath $}/list-comments.html
6 | Öffentliche Liste der über das Online-Formular der Wikimedia Fördergesellschaft getätigter Spenden
7 | de-de
8 | {$ rssPublicationData|e $}
9 |
10 | {% for comment in comments %} -
11 | {$ comment.amount |number_format(2, ',', '.') |e $} Euro von {$ comment.author |e $}
12 | {$ comment.text |e $}
13 | {$ comment.publicationDate |e $}
14 | {$ basepath $}/list-comments.html
15 |
{% endfor %}
16 |
17 |
--------------------------------------------------------------------------------
/src/Infrastructure/Validation/InternetDomainNameValidator.php:
--------------------------------------------------------------------------------
1 | isDomainBanned( $domain ) && $this->domainHasDnsEntry( $domain );
17 | }
18 |
19 | private function isDomainBanned( string $domain ): bool {
20 | return in_array( strtolower( trim( $domain ) ), self::BANNED_DOMAINS );
21 | }
22 |
23 | private function domainHasDnsEntry( string $domain ): bool {
24 | return checkdnsrr( $domain, 'MX' ) || checkdnsrr( $domain, 'A' ) || checkdnsrr( $domain, 'SOA' );
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Data/GeneratedMailTemplates/Membership_Application_Confirmation.direct_debit_sustaining_quarterly.txt:
--------------------------------------------------------------------------------
1 | mail_introduction_male_lastname_informal
2 |
3 | membership_application_confirmation_sustaining/intro_start
4 |
5 | membership_application_confirmation_sustaining/intro_bez
6 |
7 | membership_application_confirmation_sustaining/intro_end
8 |
9 | membership_application_confirmation_sustaining/contact
10 |
11 | membership_application_confirmation_sustaining/outro
12 |
13 | name_head_of_organization
14 | title_head_of_organization
15 | address_of_organization
16 |
17 | ---------------------------------------------------------------------------
18 | wikimedia_vision
19 |
20 | ---------------------------------------------------------------------------
21 |
22 | address_of_organization
23 |
24 | phone
25 | www.wikimedia.de
26 |
27 | tax_id_organization
28 |
--------------------------------------------------------------------------------
/src/Infrastructure/GetConfigCacheKey.php:
--------------------------------------------------------------------------------
1 | 0 ? md5( $fileStats ) : '';
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Presentation/Salutations.php:
--------------------------------------------------------------------------------
1 | >> $salutations
11 | */
12 | public function __construct( private readonly array $salutations ) {
13 | }
14 |
15 | /**
16 | * @return array>>
17 | */
18 | public function getList(): array {
19 | return $this->salutations;
20 | }
21 |
22 | /**
23 | * @return array>|null
24 | */
25 | public function getSalutation( string $value ): ?array {
26 | $data = array_filter( $this->salutations, static fn ( $salutation ) => $salutation['value'] == $value );
27 |
28 | if ( count( $data ) === 0 ) {
29 | return null;
30 | }
31 |
32 | return reset( $data );
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/Infrastructure/Mail/OperatorMailer.php:
--------------------------------------------------------------------------------
1 | $templateArguments
22 | */
23 | public function sendMailToOperator( EmailAddress $replyToAddress, string $subject, array $templateArguments = [] ): void {
24 | $this->messenger->sendMessageToOperator(
25 | new Message(
26 | $subject,
27 | $this->template->render( $templateArguments )
28 | ),
29 | $replyToAddress
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/Data/GeneratedMailTemplates/Membership_Application_Confirmation.direct_debit_active_yearly_receipt_optout.txt:
--------------------------------------------------------------------------------
1 | mail_introduction_male_informal
2 |
3 | membership_application_confirmation_active/intro_start
4 |
5 | membership_application_confirmation_active/intro_bez
6 |
7 | membership_application_confirmation_active/intro_end
8 |
9 | membership_application_confirmation_active/contact membership_application_confirmation_active/receipt
10 |
11 | membership_application_confirmation_active/outro
12 |
13 | name_head_of_organization
14 | title_head_of_organization
15 | address_of_organization
16 |
17 | ---------------------------------------------------------------------------
18 | wikimedia_vision
19 |
20 | ---------------------------------------------------------------------------
21 |
22 | address_of_organization
23 |
24 | phone
25 | www.wikimedia.de
26 |
27 | tax_id_organization
28 |
--------------------------------------------------------------------------------
/web/index.php:
--------------------------------------------------------------------------------
1 | bootEnv( __DIR__ . '/../.env' );
14 |
15 | $env = $_SERVER['APP_ENV'] ?? 'dev';
16 | $debug = $_SERVER['APP_DEBUG'] ? (bool)$_SERVER['APP_DEBUG'] : false;
17 |
18 | umask( 0002 );
19 | if ( $debug ) {
20 | umask( 0000 );
21 | Debug::enable();
22 | }
23 |
24 | $kernel = new Kernel( $env, $debug );
25 | $request = Request::createFromGlobals();
26 | $response = $kernel->handle( $request );
27 | $response->send();
28 | $kernel->terminate( $request, $response );
29 |
30 |
--------------------------------------------------------------------------------
/src/Infrastructure/Mail/DonationConfirmationMailerAdapter.php:
--------------------------------------------------------------------------------
1 | templateMailer->sendMail( $recipient, [
18 | 'recipient' => $templateArguments->recipient,
19 | 'donation' => get_object_vars( $templateArguments->donation )
20 | ] );
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/EventHandlers/PrettifyJsonResponse.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | public static function getSubscribedEvents(): array {
18 | return [
19 | KernelEvents::RESPONSE => 'onKernelResponse'
20 | ];
21 | }
22 |
23 | public function onKernelResponse( ResponseEvent $event ): void {
24 | $response = $event->getResponse();
25 | if ( $response instanceof JsonResponse ) {
26 | $response->setEncodingOptions( JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
27 | }
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/tests/TestEnvironmentBootstrapper.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | private array $configurationOverride = [];
16 |
17 | /**
18 | * @param array $config
19 | */
20 | public function overrideConfiguration( array $config ): void {
21 | $this->configurationOverride = $config;
22 | }
23 |
24 | /**
25 | * @return string[]
26 | */
27 | protected function getConfiguration(): array {
28 | $config = parent::getConfiguration();
29 | if ( $this->configurationOverride ) {
30 | $config = \array_replace_recursive( $config, $this->configurationOverride );
31 | }
32 | return $config;
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/tests/EdgeToEdge/TrackingDataTest.php:
--------------------------------------------------------------------------------
1 | modifyConfiguration( [ 'skin' => 'laika' ] );
18 | $client = $this->createClient();
19 | $client->request( 'get', '/', [
20 | self::PARAM_NAME_CAMPAIGN => 'campaign',
21 | self::PARAM_NAME_KEYWORD => 'keyword'
22 | ] );
23 |
24 | $this->assertEquals( 'campaign/keyword', $client->getRequest()->get( 'trackingCode' ) );
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/Authentication/Token.php:
--------------------------------------------------------------------------------
1 | tokenBytes );
23 | }
24 |
25 | public function getRawBytes(): string {
26 | return $this->tokenBytes;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/doc/adr/images/ADR27_ER_Diagram_Custom.mmd:
--------------------------------------------------------------------------------
1 | erDiagram
2 |
3 | SPENDEN ||--|| DONOR : has
4 |
5 | DONOR {
6 | string donor_type
7 | bool receipt
8 | bool mailing_list
9 | string email_address
10 | }
11 |
12 | COMPANY_DONOR {
13 | string company_name
14 | }
15 |
16 | SCRUBBED_DONOR {
17 | string original_donor_type
18 | string original_salutation
19 | }
20 |
21 | DONOR_NAME {
22 | string firstname
23 | string lastname
24 | string salutation
25 | string title
26 | }
27 |
28 | DONOR |o--|| COMPANY_DONOR : inherits_from
29 | DONOR |o--|| SCRUBBED_DONOR : inherits_from
30 |
31 | DONOR |o--|| DONOR_ADDRESS : has
32 | DONOR |o--|| DONOR_NAME : has
33 |
34 | DONOR_ADDRESS {
35 | string street_name
36 | string house_number
37 | string postcode
38 | string city
39 | string country_code
40 | }
--------------------------------------------------------------------------------
/skins/test/templates/Membership_Application.html.twig:
--------------------------------------------------------------------------------
1 | {% set showMembershipTypeOption = showMembershipTypeOption is defined ? showMembershipTypeOption : 'true' %}
2 | {% set formSlideOpen = false %}
3 | {% set initialPaymentType = paymentTypes|length == 1 ? paymentTypes|first : '' %}
4 |
5 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/Infrastructure/Mail/AdminDonationModerationMailerAdapter.php:
--------------------------------------------------------------------------------
1 | templateMailer->sendMail(
18 | $recipient,
19 | [
20 | 'id' => $templateArguments->donationId,
21 | 'moderationFlags' => $templateArguments->moderationFlags,
22 | 'amount' => $templateArguments->amount
23 | ]
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Infrastructure/Validation/ValidationErrorLogger.php:
--------------------------------------------------------------------------------
1 | > $validationErrors
18 | */
19 | public function logViolations( string $message, array $fields, array $validationErrors ): void {
20 | if ( $this->hasMoreErrorsThan( 4, $fields ) ) {
21 | return;
22 | }
23 |
24 | $this->logger->warning( $message, $validationErrors );
25 | }
26 |
27 | /**
28 | * @param int $number
29 | * @param string[] $violations
30 | */
31 | private function hasMoreErrorsThan( int $number, array $violations ): bool {
32 | return count( $violations ) > $number;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/RebuildDatabaseSchemaTrait.php:
--------------------------------------------------------------------------------
1 | getEntityManager();
14 | $schemaCreator = new SchemaCreator( $entityManager );
15 |
16 | try {
17 | $schemaCreator->dropSchema();
18 | } catch ( \Exception $ex ) {
19 | }
20 |
21 | $schemaCreator->createSchema();
22 | $entityManager->persist( new PaymentId() );
23 | $entityManager->persist( new DonationId() );
24 | $entityManager->persist( new MembershipId() );
25 | $entityManager->flush();
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/BucketTesting/Logging/BestEffortBucketLogger.php:
--------------------------------------------------------------------------------
1 | caughtException !== null ) {
22 | return;
23 | }
24 | try {
25 | $this->bucketLogger->writeEvent( $event, ...$buckets );
26 | } catch ( LoggingError $error ) {
27 | $this->caughtException = $error;
28 | $this->errorLogging->error( $error->getMessage(), [ 'exception' => $error ] );
29 | }
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/tests/SchemaCreator.php:
--------------------------------------------------------------------------------
1 | getSchemaTool()->createSchema( $this->getClassMetaData() );
18 | }
19 |
20 | public function dropSchema(): void {
21 | $this->getSchemaTool()->dropSchema( $this->getClassMetaData() );
22 | }
23 |
24 | private function getSchemaTool(): SchemaTool {
25 | return new SchemaTool( $this->entityManager );
26 | }
27 |
28 | /**
29 | * @return list
30 | * @phpstan-ignore-next-line
31 | */
32 | private function getClassMetaData(): array {
33 | return $this->entityManager->getMetadataFactory()->getAllMetadata();
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/tests/Unit/Infrastructure/EventHandling/DonationEventEmitterTest.php:
--------------------------------------------------------------------------------
1 | createMock( EventDispatcher::class );
19 | $dispatcher->expects( $this->once() )->method( 'dispatch' )->with( $event );
20 | $emitter = new DonationEventEmitter( $dispatcher );
21 |
22 | $emitter->emit( $event );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Authentication/OldStyleTokens/LenientAuthorizationChecker.php:
--------------------------------------------------------------------------------
1 | createMock( EventDispatcher::class );
19 | $dispatcher->expects( $this->once() )->method( 'dispatch' )->with( $event );
20 | $emitter = new MembershipEventEmitter( $dispatcher );
21 |
22 | $emitter->emit( $event );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/cli/ApplicationConfigValidation/SchemaLoader.php:
--------------------------------------------------------------------------------
1 | fileFetcher->fetchFile( $schema );
18 | } catch ( FileFetchingException $e ) {
19 | throw new ConfigValidationException( $e->getMessage(), $e->getCode(), $e );
20 | }
21 | $schema = json_decode( $schemaString );
22 | if ( $schema === null ) {
23 | throw new ConfigValidationException( 'Error parsing the schema file: ' . json_last_error_msg() );
24 | }
25 | if ( !is_object( $schema ) ) {
26 | throw new ConfigValidationException( 'Schema must be a JSON object.' );
27 | }
28 | return $schema;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/BucketTesting/Domain/Model/BucketLogBucket.php:
--------------------------------------------------------------------------------
1 | id;
26 | }
27 |
28 | public function getBucketLog(): BucketLog {
29 | return $this->bucketLog;
30 | }
31 |
32 | public function getName(): string {
33 | return $this->name;
34 | }
35 |
36 | public function getCampaign(): string {
37 | return $this->campaign;
38 | }
39 |
40 | public function setBucketLog( BucketLog $bucketLog ): BucketLogBucket {
41 | $this->bucketLog = $bucketLog;
42 | return $this;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/UrlGeneratorAdapter.php:
--------------------------------------------------------------------------------
1 | $parameters
17 | */
18 | public function generateAbsoluteUrl( string $routeName, array $parameters = [] ): string {
19 | return $this->urlGenerator->generate(
20 | $routeName,
21 | $parameters,
22 | UrlGeneratorInterface::ABSOLUTE_URL
23 | );
24 | }
25 |
26 | public function generateRelativeUrl( string $routeName, array $parameters = [] ): string {
27 | return $this->urlGenerator->generate(
28 | $routeName,
29 | $parameters,
30 | UrlGeneratorInterface::ABSOLUTE_PATH
31 | );
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/app/Migrations/Version20221118093850.php:
--------------------------------------------------------------------------------
1 | getTable( 'bucket_log' );
18 | $table->modifyColumn( 'event_name', [ 'length' => 64 ] );
19 | }
20 |
21 | public function postUp( Schema $schema ): void {
22 | $this->connection->executeStatement( 'UPDATE bucket_log SET event_name = "membershipApplicationCreated" WHERE event_name = "membershipApplicationCre"' );
23 | }
24 |
25 | public function down( Schema $schema ): void {
26 | $table = $schema->getTable( 'bucket_log' );
27 | $table->modifyColumn( 'event_name', [ 'length' => 24 ] );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Infrastructure/Mail/MailTemplateFilenameTraversable.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class MailTemplateFilenameTraversable implements \IteratorAggregate {
11 |
12 | public function __construct( private readonly string $mailTemplatePath ) {
13 | }
14 |
15 | /**
16 | * @return \Iterator
17 | */
18 | public function getIterator(): \Iterator {
19 | $glob = glob( $this->mailTemplatePath . '/*\.twig' );
20 |
21 | // we can't reliably trigger glob() returning `false` on Linux systems
22 | // @codeCoverageIgnoreStart
23 | if ( $glob === false ) {
24 | throw new \RuntimeException( sprintf( "Failed to find path name: %s",
25 | var_export( $this->mailTemplatePath . '/*\.twig', true ) ) );
26 | }
27 |
28 | // @codeCoverageIgnoreEnd
29 |
30 | foreach ( $glob as $fileName ) {
31 | yield basename( $fileName );
32 | }
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/Infrastructure/Mail/MembershipMailerAdapter.php:
--------------------------------------------------------------------------------
1 | templateMailer->sendMail( $recipient, get_object_vars( $templateArguments ) );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/config/packages/prod/monolog.yaml:
--------------------------------------------------------------------------------
1 | monolog:
2 | handlers:
3 | main:
4 | type: fingers_crossed
5 | action_level: notice
6 | handler: group
7 | excluded_http_codes: [ 404, 405 ]
8 | buffer_size: 50 # How many messages should be saved? Prevent memory leaks
9 | group:
10 | type: group
11 | members: ['logfile', 'errbit_wrapper']
12 | logfile:
13 | type: stream
14 | path: "%kernel.logs_dir%/application.log"
15 | level: info
16 | errbit_wrapper:
17 | type: fallbackgroup
18 | members: [ 'errbit', 'errbit_errors' ]
19 | errbit:
20 | type: service
21 | id: "app.errbit_logger"
22 | # Level must be configured on service until https://github.com/symfony/monolog-bundle/issues/322 is implemented
23 | errbit_errors:
24 | type: stream
25 | path: "%kernel.logs_dir%/errbit_errors.log"
26 |
27 | console:
28 | type: console
29 | process_psr_3_messages: false
30 | channels: [ '!event', '!doctrine', '!console' ]
--------------------------------------------------------------------------------
/config/packages/uat/monolog.yaml:
--------------------------------------------------------------------------------
1 | monolog:
2 | handlers:
3 | main:
4 | type: fingers_crossed
5 | action_level: notice
6 | handler: group
7 | excluded_http_codes: [ 404, 405 ]
8 | buffer_size: 50 # How many messages should be saved? Prevent memory leaks
9 | group:
10 | type: group
11 | members: ['logfile', 'errbit_wrapper']
12 | logfile:
13 | type: stream
14 | path: "%kernel.logs_dir%/application.log"
15 | level: info
16 | errbit_wrapper:
17 | type: fallbackgroup
18 | members: [ 'errbit', 'errbit_errors' ]
19 | errbit:
20 | type: service
21 | id: "app.errbit_logger"
22 | # Level must be configured on service until https://github.com/symfony/monolog-bundle/issues/322 is implemented
23 | errbit_errors:
24 | type: stream
25 | path: "%kernel.logs_dir%/errbit_errors.log"
26 |
27 | console:
28 | type: console
29 | process_psr_3_messages: false
30 | channels: [ '!event', '!doctrine', '!console' ]
--------------------------------------------------------------------------------
/skins/laika/templates/page_layouts/base.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'Base_Layout.html.twig' %}
2 | {% set page_works_without_js = true %}
3 | {% set title = site_metadata.page_titles[page_id] %}
4 | {% set content = web_content("pages/#{page_id}") %}
5 |
6 | {% block title %}{$ page_title( page_id ) $}{% endblock %}
7 |
8 | {% block main %}
9 |
10 |
14 |
15 |
23 |
24 |
25 | {% endblock %}
26 |
27 | {% block scripts %}
28 | {$ parent() $}
29 |
30 | {% endblock %}
31 |
--------------------------------------------------------------------------------
/web/res/js/matomo-loader.js:
--------------------------------------------------------------------------------
1 | var matomoSettings = document.getElementById('matomo-loader');
2 | var initialPaymentType = matomoSettings.dataset.initialPaymentType || '';
3 | var siteId = matomoSettings.dataset.siteId;
4 |
5 | var _paq = _paq || [];
6 | _paq.push(['requireCookieConsent']);
7 | _paq.push( ['setCustomDimension', 2, 'laika' ] );
8 | _paq.push( [ 'trackPageView' ] );
9 | _paq.push( [ 'enableLinkTracking' ] );
10 | _paq.push( [ 'trackVisibleContentImpressions' ] );
11 | if ( initialPaymentType ) {
12 | _paq.push( [ 'setCustomDimension', 1, initialPaymentType ] );
13 | }
14 |
15 | ( function () {
16 | var u = matomoSettings.dataset.matomoBaseUrl;
17 | _paq.push( [ 'setTrackerUrl', u + 'piwik.php' ] );
18 | _paq.push( [ 'setSiteId', siteId ] );
19 | var d = document,
20 | g = d.createElement( 'script' ),
21 | s = d.getElementsByTagName( 'script' )[ 0 ];
22 | g.type = 'text/javascript';
23 | g.async = true;
24 | g.defer = true;
25 | g.src = u + 'piwik.js';
26 | s.parentNode.insertBefore( g, s );
27 | } ) ();
28 |
--------------------------------------------------------------------------------
/app/Controllers/Membership/ShowMembershipFeeChangeController.php:
--------------------------------------------------------------------------------
1 | query->get( 'uuid', '' );
19 |
20 | $htmlPresenter = $ffFactory->newMembershipFeeChangeHTMLPresenter();
21 |
22 | $feeUpgradeUseCase = $ffFactory->newMembershipFeeUpgradeUseCase();
23 | $feeUpgradeUseCase->showFeeChange( $uuidFromRequest, $htmlPresenter );
24 |
25 | return $htmlPresenter->getHTMLResponse();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Infrastructure/WordListFileReader.php:
--------------------------------------------------------------------------------
1 | fileName === '' ) {
24 | return [];
25 | }
26 |
27 | try {
28 | $content = $this->fileFetcher->fetchFile( $this->fileName );
29 | } catch ( FileFetchingException $e ) {
30 | // The error will be logged and we don't want to bring down the application with missing files
31 | $content = '';
32 | }
33 |
34 | return array_values( array_filter( array_map( 'trim', explode( "\n", $content ) ) ) );
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/Infrastructure/Mail/MailFormatter.php:
--------------------------------------------------------------------------------
1 | getName() !== self::TIMEZONE ) {
18 | throw new \InvalidArgumentException( sprintf( 'CampaignDates must have time zone "%s".', self::TIMEZONE ) );
19 | }
20 | parent::__construct( $time, new DateTimeZone( self::TIMEZONE ) );
21 | }
22 |
23 | public static function createFromString( string $time, ?DateTimeZone $timezone = null ): self {
24 | if ( $timezone === null ) {
25 | return new self( $time );
26 | }
27 | $instance = new \DateTime( $time, $timezone );
28 | $instance->setTimezone( new DateTimeZone( self::TIMEZONE ) );
29 | return new self( $instance->format( \DateTime::ISO8601 ) );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/EdgeToEdge/BucketVariableTest.php:
--------------------------------------------------------------------------------
1 | modifyConfiguration( [ 'skin' => 'laika' ] );
21 | $client = $this->createClient();
22 |
23 | $client->request( 'GET', '/' );
24 |
25 | $applicationVars = $this->getDataApplicationVars( $client->getCrawler() );
26 | $this->assertContains( 'campaigns.show_unicorns.default', $applicationVars->selectedBuckets );
27 | $this->assertContains( 'pfu', $applicationVars->allowedCampaignParameters );
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/BucketTesting/Domain/Model/Bucket.php:
--------------------------------------------------------------------------------
1 | name;
21 | }
22 |
23 | public function getId(): string {
24 | return 'campaigns.' . $this->getCampaign()->getName() . '.' . $this->getName();
25 | }
26 |
27 | public function getCampaign(): Campaign {
28 | return $this->campaign;
29 | }
30 |
31 | public function isDefaultBucket(): bool {
32 | return $this->defaultBucket;
33 | }
34 |
35 | /**
36 | * @return array
37 | */
38 | public function getParameters(): array {
39 | return [ $this->campaign->getUrlKey() => $this->campaign->getIndexByBucket( $this ) ];
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/Presentation/Presenters/CommentListJsonPresenter.php:
--------------------------------------------------------------------------------
1 | >
14 | */
15 | public function present( CommentList $commentList ): array {
16 | // TODO Translate keys into English once old skins are phased out
17 | return array_map(
18 | static function ( Comment $comment ) {
19 | return [
20 | 'betrag' => $comment->donationAmount,
21 | 'spender' => $comment->authorName,
22 | 'kommentar' => $comment->commentText,
23 | 'datum' => $comment->donationTime->format( 'r' ),
24 | 'lokalisiertes_datum' => $comment->donationTime->format( 'd.m.Y \u\m H:i \U\h\r' ),
25 | 'id' => $comment->donationId,
26 | ];
27 | },
28 | $commentList->toArray()
29 | );
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/tests/Fixtures/BucketLoggerSpy.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | private array $events = [];
17 |
18 | public function writeEvent( LoggingEvent $event, Bucket ...$buckets ): void {
19 | $this->events[] = [
20 | 'event' => $event,
21 | 'buckets' => $buckets
22 | ];
23 | }
24 |
25 | public function getEventCount(): int {
26 | return count( $this->events );
27 | }
28 |
29 | public function getFirstEvent(): LoggingEvent {
30 | return $this->events[0]['event'];
31 | }
32 |
33 | /**
34 | * @return Bucket[]
35 | */
36 | public function getFirstBuckets(): array {
37 | return $this->events[0]['buckets'];
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/Fixtures/DonationUrlAuthenticationLoaderStub.php:
--------------------------------------------------------------------------------
1 | $authorizationParameters
13 | */
14 | public function __construct( private readonly array $authorizationParameters = [] ) {
15 | }
16 |
17 | public function getDonationUrlAuthenticator( int $donationId ): URLAuthenticator {
18 | return new UrlAuthenticatorStub();
19 | }
20 |
21 | /**
22 | * @param int $donationId
23 | * @param array $parameters
24 | *
25 | * @return array
26 | */
27 | public function addDonationAuthorizationParameters( int $donationId, array $parameters ): array {
28 | return [
29 | ...$parameters,
30 | ...$this->authorizationParameters
31 | ];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Infrastructure/Translation/JsonTranslator.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | private array $messages;
15 |
16 | public function __construct( private readonly FileFetcher $fileFetcher ) {
17 | $this->messages = [];
18 | }
19 |
20 | public function addFile( string $fileName ): self {
21 | $contents = $this->fileFetcher->fetchFile( $fileName );
22 | $this->messages = array_merge( $this->messages, json_decode( $contents, true ) );
23 | return $this;
24 | }
25 |
26 | public function trans( string $messageKey, array $parameters = [] ): string {
27 | if ( !isset( $this->messages[$messageKey] ) ) {
28 | throw new \InvalidArgumentException( "Unknown translation key: $messageKey" );
29 | }
30 | return str_replace( array_keys( $parameters ), array_values( $parameters ), $this->messages[$messageKey] );
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/Factories/DoctrineFactory.php:
--------------------------------------------------------------------------------
1 | entityManager = null;
22 | }
23 |
24 | public function getConnection(): Connection {
25 | return $this->connection;
26 | }
27 |
28 | public function getEntityManager(): EntityManager {
29 | if ( $this->entityManager === null ) {
30 | $connection = $this->getConnection();
31 | $this->entityManager = new EntityManager( $connection, $this->config, new EventManager() );
32 | $this->contextFactories->registerCustomTypes( $connection );
33 | }
34 | return $this->entityManager;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/Controllers/Donation/ViewCommentController.php:
--------------------------------------------------------------------------------
1 | getLayoutTemplate(
15 | 'Donation_Comment.html.twig'
16 | );
17 |
18 | return new Response(
19 | $template->render(
20 | [
21 | 'donationId' => (int)$request->query->get( 'donationId', '' ),
22 | 'updateToken' => $request->query->get( 'updateToken', '' ),
23 | 'cancelUrl' => $ffFactory->getUrlGenerator()->generateRelativeUrl(
24 | 'show-donation-confirmation',
25 | [
26 | 'id' => (int)$request->query->get( 'donationId', '' ),
27 | 'accessToken' => $request->query->get( 'accessToken', '' )
28 | ]
29 | )
30 | ]
31 | )
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Presentation/Presenters/CommentListHtmlPresenter.php:
--------------------------------------------------------------------------------
1 | template->render( [
18 | 'comments' => array_map(
19 | static function ( Comment $comment ) {
20 | return [
21 | 'amount' => $comment->donationAmount,
22 | 'author' => $comment->authorName,
23 | 'text' => $comment->commentText,
24 | 'publicationDate' => $comment->donationTime->format( 'r' ),
25 | ];
26 | },
27 | $commentList->toArray()
28 | ),
29 | 'page' => $pageNumber,
30 | 'max_pages' => 100
31 | ] );
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/app/EventHandlers/StoreBucketSelection.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | public static function getSubscribedEvents(): array {
23 | return [
24 | KernelEvents::REQUEST => [ 'setSelectedBuckets', self::PRIORITY ],
25 | ];
26 | }
27 |
28 | public function setSelectedBuckets( RequestEvent $event ): void {
29 | $request = $event->getRequest();
30 | $selector = $this->factory->getBucketSelector();
31 | $this->factory->setSelectedBuckets( $selector->selectBuckets( $request->query->all() ) );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Infrastructure/TranslationsCollector.php:
--------------------------------------------------------------------------------
1 | translationFiles[] = $transFile;
22 | }
23 |
24 | /**
25 | * @return array
26 | */
27 | public function collectTranslations(): array {
28 | $result = [];
29 |
30 | foreach ( $this->translationFiles as $transFile ) {
31 | $jsonFileContent = json_decode( $this->fileFetcher->fetchFile( $transFile ), true );
32 | if ( !is_array( $jsonFileContent ) ) {
33 | throw new RuntimeException( 'The file must contain a key,value data structure.' );
34 | }
35 | $result = array_merge( $result, $jsonFileContent );
36 | }
37 |
38 | return $result;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Unit/Factories/TwigFactoryTest.php:
--------------------------------------------------------------------------------
1 | true ], '/tmp' );
18 |
19 | $cache = $factory->getCache();
20 |
21 | $this->assertInstanceOf( FilesystemCache::class, $cache );
22 | $this->assertStringStartsWith( '/tmp/', $cache->generateKey( 'foo', '' ) );
23 | }
24 |
25 | public function testDisabledCacheReturnsNullCache(): void {
26 | $factory = new TwigFactoryTestImplementation( [ 'enable-cache' => false ], '/tmp' );
27 |
28 | $cache = $factory->getCache();
29 |
30 | $this->assertInstanceOf( NullCache::class, $cache );
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/tests/Integration/MailTemplateFilenameTraversableTest.php:
--------------------------------------------------------------------------------
1 | get( FunFunFactory::class );
20 | $mailTemplatePaths = $funFunFactory->newMailTemplateFilenameTraversable();
21 |
22 | $pathArray = iterator_to_array( $mailTemplatePaths );
23 |
24 | $this->assertContains( 'Contact_Confirm_to_User.txt.twig', $pathArray );
25 | $this->assertContains( 'Subscription_Request.txt.twig', $pathArray );
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/tests/Fixtures/BucketSelectionSpy.php:
--------------------------------------------------------------------------------
1 | bucketSelections = [];
19 | }
20 |
21 | public function selectBucketForCampaign( Campaign $campaign ): Bucket {
22 | $bucket = $this->bucketSelector->selectBucketForCampaign( $campaign );
23 | if ( $bucket === null ) {
24 | throw new \LogicException( 'Wrapped bucket selector must return a bucket' );
25 | }
26 | $this->bucketSelections[] = $bucket;
27 | return $bucket;
28 | }
29 |
30 | public function bucketWasSelected(): bool {
31 | return count( $this->bucketSelections ) > 0;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/MailTemplateFixtures/VariantSettingsGenerator.php:
--------------------------------------------------------------------------------
1 | variants = $variants;
15 | }
16 |
17 | /**
18 | * @return iterable
19 | */
20 | public function getTemplateSettings(): iterable {
21 | foreach ( $this->variants as $variant ) {
22 | $templateData = array_merge( $this->templateData, $variant->additionalTemplateData );
23 | yield new TemplateSettings( $this->templateName, $this->getId( $variant->name ), $templateData );
24 | }
25 | }
26 |
27 | public function getId( string $variantName ): string {
28 | return basename( $this->templateName, '.txt.twig' ) . '.' . $variantName;
29 | }
30 |
31 | public function getTemplateName(): string {
32 | return $this->templateName;
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/app/Controllers/Membership/ShowMembershipConfirmationController.php:
--------------------------------------------------------------------------------
1 | getTranslationCollector()->addTranslationFile( $ffFactory->getI18nDirectory() . '/messages/paymentTypes.json' );
16 | $presenter = $ffFactory->newMembershipApplicationConfirmationHtmlPresenter();
17 |
18 | $useCase = $ffFactory->newMembershipApplicationConfirmationUseCase(
19 | $presenter,
20 | $request->query->get( 'accessToken', '' )
21 | );
22 |
23 | $useCase->showConfirmation( new ShowAppConfirmationRequest( (int)$request->query->get( 'id', 0 ) ) );
24 | return new Response( $presenter->getHtml() );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Presentation/Presenters/AddSubscriptionHtmlPresenter.php:
--------------------------------------------------------------------------------
1 | $formData
22 | */
23 | public function present( ValidationResponse $subscriptionResponse, array $formData ): string {
24 | $errors = [];
25 | /** @var ConstraintViolation $constraintViolation */
26 | foreach ( $subscriptionResponse->getValidationErrors() as $constraintViolation ) {
27 | $errors[$constraintViolation->getSource()] = $constraintViolation->getMessageIdentifier();
28 | }
29 | return $this->template->render( array_merge( $formData, [ 'errors' => $errors ] ) );
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/tests/Unit/Infrastructure/Mail/BasicMailSubjectRendererTest.php:
--------------------------------------------------------------------------------
1 | value;
18 | $this->assertSame(
19 | 'mail_subject_getintouch',
20 | $this->newDonationConfirmationMailSubjectRenderer()->render( $templateArguments )
21 | );
22 | }
23 |
24 | public function newDonationConfirmationMailSubjectRenderer(): BasicMailSubjectRenderer {
25 | return new BasicMailSubjectRenderer(
26 | new FakeTranslator(),
27 | 'mail_subject_getintouch'
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/bin/doctrine:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | bootEnv( __DIR__ . '/../.env' );
21 |
22 | $kernel = new Kernel( $_SERVER['APP_ENV'], false );
23 | $kernel->boot();
24 | $factory = $kernel->getContainer()->get( FunFunFactory::class );
25 |
26 | $cli = ConsoleRunner::createApplication(
27 | new SingleManagerProvider( $factory->getEntityManager() ),
28 | $factory->newDoctrineMigrationCommands()
29 | );
30 | $cli->run();
31 |
--------------------------------------------------------------------------------
/src/Infrastructure/Mail/ErrorHandlingMailerDecorator.php:
--------------------------------------------------------------------------------
1 | $templateArguments
21 | */
22 | public function sendMail( EmailAddress $recipient, array $templateArguments = [] ): void {
23 | try {
24 | $this->templateBasedMailer->sendMail( $recipient, $templateArguments );
25 | } catch ( \RuntimeException $e ) {
26 | $message = $e->getMessage();
27 | $exception = $e;
28 | $previous = $e->getPrevious();
29 | if ( $previous !== null ) {
30 | $message .= ' ' . $previous->getMessage();
31 | $exception = $previous;
32 | }
33 | $this->logger->error( $message, [ 'exception' => $exception ] );
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Authentication/AuthenticationContextFactory.php:
--------------------------------------------------------------------------------
1 | registerAuthenticationDomain( $connection );
21 | }
22 |
23 | private function registerAuthenticationDomain( Connection $connection ): void {
24 | static $isRegistered = false;
25 | if ( $isRegistered ) {
26 | return;
27 | }
28 | Type::addType( 'AuthenticationBoundedContext', 'WMDE\Fundraising\Frontend\Authentication\DataAccess\AuthenticationBoundedContext' );
29 | $connection->getDatabasePlatform()->registerDoctrineTypeMapping( 'AuthenticationBoundedContext', 'AuthenticationBoundedContext' );
30 | $isRegistered = true;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Infrastructure/EventHandling/EventDispatcher.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | protected array $listeners = [];
16 |
17 | /**
18 | * @param class-string $eventClassName
19 | * @param callable $listener
20 | */
21 | public function addEventListener( string $eventClassName, callable $listener ): self {
22 | if ( empty( $this->listeners[$eventClassName] ) ) {
23 | $this->listeners[$eventClassName] = [];
24 | }
25 | $this->listeners[$eventClassName][] = $listener;
26 | return $this;
27 | }
28 |
29 | public function dispatch( DonationEvent|MembershipEvent $event ): void {
30 | $eventName = get_class( $event );
31 | if ( empty( $this->listeners[$eventName] ) ) {
32 | return;
33 | }
34 | array_map( static fn ( $handler ) => \call_user_func( $handler, $event ), $this->listeners[$eventName] );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/Unit/Cli/FeatureToggleParserTest.php:
--------------------------------------------------------------------------------
1 | assertSame(
18 | [
19 | 'campaigns.test_campaign.test_bucket_a',
20 | 'campaigns.test_campaign.test_bucket_b',
21 | 'campaigns.another_test_campaign.test_bucket_c',
22 | 'campaigns.another_test_campaign.test_bucket_d',
23 | 'campaigns.another_test_campaign.test_bucket_e',
24 | 'campaigns.another_test_campaign.test_bucket_c',
25 | 'campaigns.another_test_campaign.test_bucket_d',
26 | ],
27 | FeatureToggleParser::getFeatureToggleChecks( self::CHOICE_FACTORY_LOCATION )
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Infrastructure/Mail/DonationConfirmationMailSubjectRenderer.php:
--------------------------------------------------------------------------------
1 | $templateArguments
21 | */
22 | public function render( array $templateArguments = [] ): string {
23 | if (
24 | is_array( $templateArguments['donation'] ) &&
25 | isset( $templateArguments['donation']['paymentType'] ) &&
26 | $templateArguments['donation']['paymentType'] === PaymentType::BankTransfer->value
27 | ) {
28 | return $this->translator->trans( $this->bankTransferSubjectKey );
29 | }
30 | return $this->translator->trans( $this->defaultSubjectKey );
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/app/config/migrations.php:
--------------------------------------------------------------------------------
1 | [
6 | 'table_name' => 'doctrine_migration_versions',
7 | 'version_column_name' => 'version',
8 | 'version_column_length' => 250,
9 | 'executed_at_column_name' => 'executed_at',
10 | 'execution_time_column_name' => 'execution_time',
11 | ],
12 |
13 | 'migrations_paths' => [
14 | 'WMDE\Fundraising\Frontend\App\Migrations' => './app/Migrations',
15 | 'WMDE\Fundraising\AddressChangeContext\DataAccess\Migrations' => './vendor/wmde/fundraising-address-change/src/DataAccess/Migrations',
16 | 'WMDE\Fundraising\DonationContext\DataAccess\Migrations' => './vendor/wmde/fundraising-donations/src/DataAccess/Migrations',
17 | 'WMDE\Fundraising\MembershipContext\DataAccess\Migrations' => './vendor/wmde/fundraising-memberships/src/DataAccess/Migrations',
18 | 'WMDE\Fundraising\PaymentContext\DataAccess\Migrations' => './vendor/wmde/fundraising-payments/src/DataAccess/Migrations',
19 | ],
20 |
21 | 'all_or_nothing' => true,
22 | 'transactional' => false,
23 | 'check_database_platform' => true,
24 | 'organize_migrations' => 'none',
25 | 'connection' => null,
26 | 'em' => null,
27 | ];
28 |
--------------------------------------------------------------------------------
/src/Infrastructure/Mail/MembershipConfirmationMailSubjectRenderer.php:
--------------------------------------------------------------------------------
1 | activeMembershipSubject = $activeMembershipSubject;
20 | }
21 |
22 | /**
23 | * @param array $templateArguments
24 | */
25 | public function render( array $templateArguments = [] ): string {
26 | if ( $templateArguments['membershipType'] === MembershipApplication::ACTIVE_MEMBERSHIP ) {
27 | return $this->translator->trans( $this->activeMembershipSubject );
28 | }
29 | return $this->translator->trans( $this->sustainingMembershipSubject );
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/tests/EdgeToEdge/MatomoTest.php:
--------------------------------------------------------------------------------
1 | modifyConfiguration( [ 'skin' => 'laika' ] );
20 | }
21 |
22 | public function testMatomoScriptGetsEmbedded(): void {
23 | $client = $this->createClient();
24 | $client->request( 'GET', '/' );
25 | $this->assertStringContainsString( '', $client->getResponse()->getContent() ?: '' );
26 | }
27 |
28 | public function testConfigParametersAreUsed(): void {
29 | $client = $this->createClient();
30 | $client->request( 'GET', '/' );
31 |
32 | $this->assertStringContainsString( 'tracking.wikimedia.de', $client->getResponse()->getContent() ?: '' );
33 | $this->assertStringContainsString( 'idsite=1234', $client->getResponse()->getContent() ?: '' );
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/Infrastructure/SubmissionRateLimit.php:
--------------------------------------------------------------------------------
1 | get( $this->sessionKey, null );
20 | if ( !( $lastSubmission instanceof \DateTimeImmutable ) ) {
21 | return true;
22 | }
23 |
24 | $minNextTimestamp = $lastSubmission->add( $this->intervalBetweenSubmissions );
25 | if ( $minNextTimestamp > new \DateTime() ) {
26 | return false;
27 | }
28 |
29 | return true;
30 | }
31 |
32 | public function setRateLimitCookie( SessionInterface $session ): void {
33 | $lastSubmission = $session->get( $this->sessionKey, null );
34 | if ( !( $lastSubmission instanceof \DateTimeImmutable ) ) {
35 | $session->set( $this->sessionKey, new \DateTimeImmutable() );
36 | }
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/UseCases/GetInTouch/GetInTouchRequest.php:
--------------------------------------------------------------------------------
1 | firstName;
22 | }
23 |
24 | public function getLastName(): string {
25 | return $this->lastName;
26 | }
27 |
28 | public function getEmailAddress(): string {
29 | return $this->emailAddress;
30 | }
31 |
32 | public function getDonationNumber(): string {
33 | return $this->donationNumber;
34 | }
35 |
36 | public function getSubject(): string {
37 | return $this->subject;
38 | }
39 |
40 | public function getCategory(): string {
41 | return $this->category;
42 | }
43 |
44 | public function getMessageBody(): string {
45 | return $this->messageBody;
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/tests/Fixtures/InMemoryTokenRepository.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | private array $tokens = [];
16 |
17 | public function __construct( AuthenticationToken ...$tokens ) {
18 | foreach ( $tokens as $token ) {
19 | $this->storeToken( $token );
20 | }
21 | }
22 |
23 | public function storeToken( AuthenticationToken $token ): void {
24 | $this->tokens[$token->id . $token->authenticationBoundedContext->value] = $token;
25 | }
26 |
27 | public function getTokenById( int $id, AuthenticationBoundedContext $authenticationDomain ): AuthenticationToken {
28 | return $this->tokens[$id . $authenticationDomain->value] ?? new NullToken( $id, $authenticationDomain );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/Controllers/AddressChange/ShowAddressChangeSuccessController.php:
--------------------------------------------------------------------------------
1 | get( 'addressToken', '' );
16 | if ( $addressToken === '' ) {
17 | throw new AccessDeniedException( 'address_change_no_token_in_request' );
18 | }
19 |
20 | $addressChangeRepository = $ffFactory->getAddressChangeRepository();
21 | $addressChange = $addressChangeRepository->getAddressChangeByUuids( $addressToken, $addressToken );
22 |
23 | return new Response( $ffFactory->getLayoutTemplate( 'AddressUpdateSuccess.html.twig' )->render( [
24 | 'message' => $request->attributes->get( 'successMessage' ),
25 | 'addressToken' => $addressToken,
26 | 'receipt' => $addressChange->isOptedIntoDonationReceipt()
27 | ] ) );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/Unit/BucketTesting/BucketTest.php:
--------------------------------------------------------------------------------
1 | newCampaign();
28 | $bucketA = new Bucket( 'a', $campaign, Bucket::DEFAULT );
29 | $bucketB = new Bucket( 'b', $campaign, Bucket::NON_DEFAULT );
30 |
31 | $campaign->addBucket( $bucketA )->addBucket( $bucketB );
32 |
33 | $this->assertEquals( [ 't1' => 0 ], $bucketA->getParameters() );
34 | $this->assertEquals( [ 't1' => 1 ], $bucketB->getParameters() );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/Migrations/Version20230823082138.php:
--------------------------------------------------------------------------------
1 | createTable( 'legacy_auth_tokens' );
17 | $table->addColumn( 'id', 'integer', [ 'unsigned' => true ] );
18 | $table->addColumn( 'authentication_context', 'string', [ 'length' => 16 ] );
19 | $table->addColumn( 'access_token', 'string', [ 'length' => 64 ] );
20 | $table->addColumn( 'update_token', 'string', [ 'length' => 64 ] );
21 | $table->addColumn( 'update_token_expiry', 'datetime', [ 'notnull' => false ] );
22 | $table->setPrimaryKey( [ 'id', 'authentication_context' ] );
23 | $table->addIndex( [ 'access_token' ], 'access_token_idx' );
24 | $table->addIndex( [ 'update_token' ], 'update_token_idx' );
25 | }
26 |
27 | public function down( Schema $schema ): void {
28 | $schema->dropTable( 'legacy_auth_tokens' );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------