├── Dockerfile
├── migrations.yml
├── local-db-config.php
├── .gitignore
├── tests
├── uploadCoverage.sh
├── integration
│ ├── AutoloaderTest.php
│ ├── FactoryTest.php
│ ├── MembershipApplicationInsertionTest.php
│ ├── DatabaseInstallationTest.php
│ └── SofortPaymentTest.php
├── bootstrap.php
├── unit
│ ├── DonationPayments
│ │ └── SofortPaymentTest.php
│ ├── AddressChangeTest.php
│ ├── SubscriptionTest.php
│ ├── DonationTest.php
│ └── MembershipApplicationTest.php
└── TestEnvironment.php
├── Makefile
├── docker-compose.yml
├── .scrutinizer.yml
├── .travis.yml
├── src
├── Entities
│ ├── DonationPayments
│ │ └── SofortPayment.php
│ ├── DonationPayment.php
│ ├── AddressChange.php
│ ├── Address.php
│ ├── Subscription.php
│ ├── Donation.php
│ └── MembershipApplication.php
└── Store
│ ├── Installer.php
│ ├── MembershipApplicationData.php
│ ├── DonationData.php
│ └── Factory.php
├── phpunit.xml.dist
├── cli-config.php
├── composer.json
├── migrations
├── Version20190524000000.php
├── Version20200317000001.php
├── Version20200317000002.php
├── Version20190109000000.php
└── Version20180612000000.php
├── phpcs.xml
├── README.md
└── COPYING
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM php:7.2-cli
2 |
3 | RUN apt-get update \
4 | && docker-php-ext-install -j$(nproc) pdo pdo_mysql
5 |
--------------------------------------------------------------------------------
/migrations.yml:
--------------------------------------------------------------------------------
1 | name: Doctrine Migration Scripts
2 | migrations_namespace: DoctrineMigrations
3 | table_name: doctrine_migration_versions
4 | migrations_directory: migrations
5 |
--------------------------------------------------------------------------------
/local-db-config.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class AutoloaderTest extends TestCase {
15 |
16 | public function testCanLoadEntities() {
17 | $this->assertInternalType( 'object', new Address() );
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | addPsr4( 'WMDE\\Fundraising\\Store\\Tests\\', __DIR__ );
18 | unset( $autoLoader );
19 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: trusty
2 | sudo: required
3 |
4 | language: php
5 |
6 | matrix:
7 | include:
8 | - php: 7.2
9 | env: DB=sqlite; TYPE=coverage
10 | - php: 7.2
11 | env: DB=mysql
12 |
13 | install:
14 | - if [ "$DB" == "mysql" ]; then mysql -e 'create database spenden;'; fi
15 | - travis_retry composer install
16 |
17 | script:
18 | - composer validate --no-interaction
19 | - make ci
20 |
21 | after_success: bash tests/uploadCoverage.sh
22 |
23 | notifications:
24 | email:
25 | on_success: change
26 | on_failure: always
27 |
28 | cache:
29 | directories:
30 | - $HOME/.composer/cache
31 |
--------------------------------------------------------------------------------
/src/Entities/DonationPayments/SofortPayment.php:
--------------------------------------------------------------------------------
1 | confirmedAt;
28 | }
29 |
30 | public function setConfirmedAt( ?DateTime $confirmedAt ): void {
31 | $this->confirmedAt = $confirmedAt;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 | tests
17 |
18 |
19 |
20 |
21 | src
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/cli-config.php:
--------------------------------------------------------------------------------
1 | 'pdo_mysql',
18 | 'host' => DB_HOST,
19 | 'dbname' => DB_NAME,
20 | 'user' => DB_USER,
21 | 'password' => DB_PASS
22 | ] ) );
23 |
24 | return ConsoleRunner::createHelperSet( $factory->getEntityManager() );
25 |
--------------------------------------------------------------------------------
/src/Entities/DonationPayment.php:
--------------------------------------------------------------------------------
1 | id;
30 | }
31 |
32 | public function setId( int $id ): void {
33 | $this->id = $id;
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/tests/integration/FactoryTest.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class FactoryTest extends TestCase {
16 |
17 | private const PROXY_PATH = '/path/to/proxy/classes/';
18 |
19 | public function testGivenCustomProxyDir_itIsPassedToProxyGenerator() {
20 | $factory = new Factory( $this->newConnection(), self::PROXY_PATH );
21 | $this->assertSame( self::PROXY_PATH, $factory->getEntityManager()->getConfiguration()->getProxyDir() );
22 | }
23 |
24 | private function newConnection() {
25 | return DriverManager::getConnection( [ 'driver' => 'pdo_sqlite', 'memory' => true ] );
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/tests/integration/MembershipApplicationInsertionTest.php:
--------------------------------------------------------------------------------
1 | getFactory()->getEntityManager();
17 | $entityManager->persist( new MembershipApplication() );
18 | $entityManager->flush();
19 |
20 | $count = $entityManager->createQueryBuilder()
21 | ->select( 'COUNT(r.id)' )
22 | ->from( MembershipApplication::class, 'r' )
23 | ->getQuery()
24 | ->getSingleScalarResult();
25 |
26 | $this->assertSame( '1', $count );
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/tests/unit/DonationPayments/SofortPaymentTest.php:
--------------------------------------------------------------------------------
1 | assertNull( $payment->getConfirmedAt() );
19 | }
20 |
21 | public function testAccessors(): void {
22 | $payment = new SofortPayment();
23 | $payment->setId( 5 );
24 | $payment->setConfirmedAt( new DateTime( '2008-11-03T15:30:00Z' ) );
25 |
26 | $this->assertSame( 5, $payment->getId() );
27 | $this->assertEquals( new DateTime( '2008-11-03T15:30:00Z' ), $payment->getConfirmedAt() );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Store/Installer.php:
--------------------------------------------------------------------------------
1 |
15 | * @author Jonas Kress
16 | */
17 | class Installer {
18 |
19 | private $entityManager;
20 |
21 | public function __construct( EntityManager $entityManager ) {
22 | $this->entityManager = $entityManager;
23 | }
24 |
25 | public function install() {
26 | $this->getSchemaTool()->createSchema( $this->getClassMetaData() );
27 | }
28 |
29 | public function uninstall() {
30 | $this->getSchemaTool()->dropSchema( $this->getClassMetaData() );
31 | }
32 |
33 | private function getSchemaTool() {
34 | return new SchemaTool( $this->entityManager );
35 | }
36 |
37 | private function getClassMetaData() {
38 | return $this->entityManager->getMetadataFactory()->getAllMetadata();
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wmde/fundraising-store",
3 | "description": "Persistence services around the fundraising database",
4 | "license": "GPL-2.0-or-later",
5 | "require": {
6 | "php": ">=7.1",
7 | "doctrine/dbal": "^2.5",
8 | "doctrine/orm": "^2.5",
9 | "doctrine/migrations": "~1.8",
10 | "gedmo/doctrine-extensions": "^2.4",
11 | "ramsey/uuid": "^3.7",
12 | "symfony/yaml": "~4.1"
13 | },
14 | "require-dev": {
15 | "squizlabs/php_codesniffer": "~3.0",
16 | "phpunit/phpunit": "~6.2",
17 | "ockcyp/covers-validator": "~0.6.1"
18 | },
19 | "autoload": {
20 | "psr-4": {
21 | "WMDE\\Fundraising\\": "src/"
22 | }
23 | },
24 | "extra": {
25 | "branch-alias": {
26 | "dev-master": "10.0.x-dev"
27 | }
28 | },
29 | "scripts": {
30 | "test": [
31 | "composer validate --no-interaction",
32 | "vendor/bin/phpunit",
33 | "vendor/bin/covers-validator"
34 | ],
35 | "cs": [
36 | "@phpcs"
37 | ],
38 | "ci": [
39 | "@test",
40 | "@cs"
41 | ],
42 | "phpcs": [
43 | "vendor/bin/phpcs"
44 | ]
45 | },
46 | "bin": [
47 | "cli-config.php"
48 | ]
49 | }
50 |
--------------------------------------------------------------------------------
/migrations/Version20190524000000.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
21 |
22 | $this->addSql('ALTER TABLE address_change ADD donation_receipt TINYINT(1) NOT NULL');
23 | }
24 |
25 | public function down(Schema $schema) : void
26 | {
27 | $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
28 |
29 | $this->addSql('ALTER TABLE address_change DROP donation_receipt');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/integration/DatabaseInstallationTest.php:
--------------------------------------------------------------------------------
1 |
12 | * @author Jonas Kress
13 | */
14 | class DatabaseInstallationTest extends TestCase {
15 |
16 | private $databaseName;
17 | private $tableNames;
18 |
19 | public function setUp() {
20 | $environment = TestEnvironment::newDefault();
21 | $this->databaseName = $environment->getDatabaseName();
22 | $this->tableNames = $environment->getFactory()->getConnection()->getSchemaManager()->createSchema()->getTableNames();
23 | }
24 |
25 | /**
26 | * @dataProvider expectedTableNameProvider
27 | */
28 | public function testTablesAreCreated( string $tableName ) {
29 | $this->assertContains(
30 | $this->databaseName . '.' . $tableName,
31 | $this->tableNames
32 | );
33 | }
34 |
35 | public function expectedTableNameProvider() {
36 | yield [ 'address' ];
37 | yield [ 'request' ];
38 | yield [ 'spenden' ];
39 | yield [ 'subscription' ];
40 | yield [ 'donation_payment' ];
41 | yield [ 'donation_payment_sofort' ];
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/Store/MembershipApplicationData.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class MembershipApplicationData {
14 |
15 | private $accessToken;
16 | private $updateToken;
17 | private $preservedStatus;
18 |
19 | /**
20 | * @return string|null
21 | */
22 | public function getAccessToken() {
23 | return $this->accessToken;
24 | }
25 |
26 | /**
27 | * @param string|null $token
28 | */
29 | public function setAccessToken( $token ) {
30 | $this->accessToken = $token;
31 | }
32 |
33 | /**
34 | * @return string|null
35 | */
36 | public function getUpdateToken() {
37 | return $this->updateToken;
38 | }
39 |
40 | /**
41 | * @param string|null $updateToken
42 | */
43 | public function setUpdateToken( $updateToken ) {
44 | $this->updateToken = $updateToken;
45 | }
46 |
47 | /**
48 | * @since 2.1
49 | * @return int|null
50 | */
51 | public function getPreservedStatus() {
52 | return $this->preservedStatus;
53 | }
54 |
55 | /**
56 | * @since 2.1
57 | * @param int|null $status
58 | */
59 | public function setPreservedStatus( $status ) {
60 | $this->preservedStatus = $status;
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/Store/DonationData.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class DonationData {
14 |
15 | private $accessToken;
16 | private $updateToken;
17 | private $updateTokenExpiry;
18 |
19 | /**
20 | * @return string|null
21 | */
22 | public function getAccessToken() {
23 | return $this->accessToken;
24 | }
25 |
26 | /**
27 | * @param string|null $token
28 | */
29 | public function setAccessToken( $token ) {
30 | $this->accessToken = $token;
31 | }
32 |
33 | /**
34 | * @return string|null
35 | */
36 | public function getUpdateToken() {
37 | return $this->updateToken;
38 | }
39 |
40 | /**
41 | * @param string|null $updateToken
42 | */
43 | public function setUpdateToken( $updateToken ) {
44 | $this->updateToken = $updateToken;
45 | }
46 |
47 | /**
48 | * @return string|null Time in 'Y-m-d H:i:s' format
49 | */
50 | public function getUpdateTokenExpiry() {
51 | return $this->updateTokenExpiry;
52 | }
53 |
54 | /**
55 | * @param string|null $updateTokenExpiry Time in 'Y-m-d H:i:s' format
56 | */
57 | public function setUpdateTokenExpiry( $updateTokenExpiry ) {
58 | $this->updateTokenExpiry = $updateTokenExpiry;
59 | }
60 |
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/tests/TestEnvironment.php:
--------------------------------------------------------------------------------
1 |
13 | * @author Jonas Kress
14 | */
15 | class TestEnvironment {
16 |
17 | public static function newDefault() {
18 | return new self();
19 | }
20 |
21 | private $factory;
22 |
23 | public function __construct() {
24 | $this->factory = new Factory( DriverManager::getConnection(
25 | $this->newConnectionDetails()
26 | ) );
27 |
28 | try {
29 | $this->factory->newInstaller()->uninstall();
30 | }
31 | catch ( \Exception $ex ) {
32 | }
33 |
34 | $this->factory->newInstaller()->install();
35 | }
36 |
37 | public function getFactory() {
38 | return $this->factory;
39 | }
40 |
41 | private function newConnectionDetails() {
42 | if ( getenv( 'DB' ) === 'mysql' ) {
43 | return [
44 | 'driver' => 'pdo_mysql',
45 | 'user' => 'root',
46 | 'password' => '',
47 | 'dbname' => 'spenden',
48 | 'host' => 'localhost',
49 | ];
50 | }
51 |
52 | return [
53 | 'driver' => 'pdo_sqlite',
54 | 'memory' => true,
55 | ];
56 | }
57 |
58 | public function getDatabaseName() {
59 | if ( getenv( 'DB' ) === 'mysql' ) {
60 | return 'spenden';
61 | }
62 |
63 | return 'public';
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/tests/unit/AddressChangeTest.php:
--------------------------------------------------------------------------------
1 | entityManager = TestEnvironment::newDefault()->getFactory()->getEntityManager();
23 | }
24 |
25 | public function testWhenNewAddressChangeIsPersisted_uuidIsGeneratedAndStored() {
26 | $addressChange = AddressChange::newDonationAddressChange( AddressChange::ADDRESS_TYPE_PERSON, 1 );
27 | $this->entityManager->persist( $addressChange );
28 | $this->entityManager->flush();
29 |
30 | /** @var AddressChange $retrievedAddressChange */
31 | $retrievedAddressChange = $this->entityManager->getRepository( AddressChange::class )->findOneBy( [] );
32 |
33 | $this->assertSame( $addressChange->getCurrentIdentifier(), $retrievedAddressChange->getCurrentIdentifier() );
34 | }
35 |
36 | public function testWhenAddressIdentifierIsUpdated_dataIsProperlyAssigned() {
37 | $addressChange = AddressChange::newMembershipAddressChange( AddressChange::ADDRESS_TYPE_PERSON, 2 );
38 | $initialIdentifier = $addressChange->getCurrentIdentifier();
39 | $addressChange->updateAddressIdentifier();
40 |
41 | $this->assertSame( $initialIdentifier, $addressChange->getPreviousIdentifier() );
42 | $this->assertNotSame( $initialIdentifier, $addressChange->getCurrentIdentifier() );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/migrations/Version20200317000001.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
19 |
20 | $this->addSql('ALTER TABLE address_change ADD external_id INT NOT NULL, ADD external_id_type VARCHAR(10) NOT NULL');
21 | $this->addSql('CREATE INDEX ac_ext_id ON address_change (external_id_type, external_id)');
22 | }
23 |
24 | public function postUp( Schema $schema ) {
25 | $this->connection->exec( 'UPDATE address_change a INNER JOIN spenden d ON d.address_change_id=a.id SET a.external_id_type="donation", a.external_id=d.address_change_id' );
26 | $this->connection->exec( 'UPDATE address_change a INNER JOIN request r ON r.address_change_id=a.id SET a.external_id_type="membership", a.external_id=r.address_change_id' );
27 | }
28 |
29 | public function down(Schema $schema) : void
30 | {
31 | $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
32 |
33 | $this->addSql('DROP INDEX ac_ext_id ON address_change');
34 | $this->addSql('ALTER TABLE address_change DROP external_id, DROP external_id_type');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/unit/SubscriptionTest.php:
--------------------------------------------------------------------------------
1 |
15 | * @author Jeroen De Dauw < jeroendedauw@gmail.com >
16 | */
17 | class SubscriptionTest extends TestCase {
18 |
19 | public function testSetAndGetSource() {
20 | $subscription = new Subscription();
21 | $subscription->setSource( 'foobar' );
22 | $this->assertSame( 'foobar', $subscription->getSource() );
23 | }
24 |
25 | public function testWhenSubscriptionIsNew_isUnconfirmedReturnsTrue() {
26 | $this->assertTrue( ( new Subscription() )->isUnconfirmed() );
27 | }
28 |
29 | public function testWhenConfirmed_isUnconfirmedReturnsFalse() {
30 | $subscription = new Subscription();
31 | $subscription->markAsConfirmed();
32 |
33 | $this->assertFalse( $subscription->isUnconfirmed() );
34 | }
35 |
36 | public function testWhenPendingModeration_isUnconfirmedReturnsTrue() {
37 | $subscription = new Subscription();
38 | $subscription->markForModeration();
39 |
40 | $this->assertTrue( $subscription->isUnconfirmed() );
41 | }
42 |
43 | public function testWhenSubscriptionIsNew_needsModerationReturnsFalse() {
44 | $subscription = new Subscription();
45 |
46 | $this->assertFalse( $subscription->needsModeration() );
47 | }
48 |
49 | public function testWhenPendingModeration_needsModerationReturnsTrue() {
50 | $subscription = new Subscription();
51 | $subscription->markForModeration();
52 |
53 | $this->assertTrue( $subscription->needsModeration() );
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/tests/integration/SofortPaymentTest.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | class SofortPaymentTest extends TestCase {
19 |
20 | public function testPaymentWithOnlyId_isPersisted(): void {
21 | $donation = new Donation();
22 | $payment = new SofortPayment();
23 | $donation->setPayment( $payment );
24 |
25 | $entityManager = TestEnvironment::newDefault()->getFactory()->getEntityManager();
26 | $entityManager->persist( $donation );
27 | $entityManager->flush();
28 |
29 | /**
30 | * @var $retrievedPayment SofortPayment
31 | */
32 | $retrievedPayment = $entityManager->getRepository( SofortPayment::class )
33 | ->findOneBy( [] );
34 |
35 | $this->assertSame( $payment->getId(), $retrievedPayment->getId() );
36 | $this->assertNull( $retrievedPayment->getConfirmedAt() );
37 | }
38 |
39 | public function testPaymentWithIdAndConfirmedAt_isPersisted(): void {
40 | $donation = new Donation();
41 | $payment = new SofortPayment();
42 | $payment->setConfirmedAt( new DateTime( '2017-07-14T22:00:01Z' ) );
43 | $donation->setPayment( $payment );
44 |
45 | $entityManager = TestEnvironment::newDefault()->getFactory()->getEntityManager();
46 | $entityManager->persist( $donation );
47 | $entityManager->flush();
48 |
49 | /**
50 | * @var $retrievedPayment SofortPayment
51 | */
52 | $retrievedPayment = $entityManager->getRepository( SofortPayment::class )
53 | ->findOneBy( [] );
54 |
55 | $this->assertSame( $payment->getId(), $retrievedPayment->getId() );
56 | $this->assertEquals( new DateTime( '2017-07-14T22:00:01Z' ), $retrievedPayment->getConfirmedAt() );
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/migrations/Version20200317000002.php:
--------------------------------------------------------------------------------
1 | AddressChange and Memberships->AddressChange.
12 | * The relationship was inverted in the previous migration.
13 | */
14 | final class Version20200317000002 extends AbstractMigration
15 | {
16 | public function up(Schema $schema) : void
17 | {
18 | $this->addSql('ALTER TABLE request DROP FOREIGN KEY FK_3B978F9FBB7DB7BC');
19 | $this->addSql('DROP INDEX UNIQ_3B978F9FBB7DB7BC ON request');
20 | $this->addSql('ALTER TABLE request DROP address_change_id');
21 | $this->addSql('ALTER TABLE spenden DROP FOREIGN KEY FK_3CBBD045BB7DB7BC');
22 | $this->addSql('DROP INDEX UNIQ_3CBBD045BB7DB7BC ON spenden');
23 | $this->addSql('ALTER TABLE spenden DROP address_change_id');
24 |
25 | }
26 |
27 | public function down(Schema $schema) : void
28 | {
29 | $this->addSql('ALTER TABLE request ADD address_change_id INT DEFAULT NULL');
30 | $this->addSql('ALTER TABLE request ADD CONSTRAINT FK_3B978F9FBB7DB7BC FOREIGN KEY (address_change_id) REFERENCES address_change (id)');
31 | $this->addSql('CREATE UNIQUE INDEX UNIQ_3B978F9FBB7DB7BC ON request (address_change_id)');
32 | $this->addSql('ALTER TABLE spenden ADD address_change_id INT DEFAULT NULL');
33 | $this->addSql('ALTER TABLE spenden ADD CONSTRAINT FK_3CBBD045BB7DB7BC FOREIGN KEY (address_change_id) REFERENCES address_change (id)');
34 | $this->addSql('CREATE UNIQUE INDEX UNIQ_3CBBD045BB7DB7BC ON spenden (address_change_id)');
35 | }
36 |
37 | public function postDown( Schema $schema ) {
38 | $this->connection->exec( 'UPDATE spenden d INNER JOIN address_change a ON a.external_id=d.id AND a.external_id_type="donation" SET d.address_change_id=a.id' );
39 | $this->connection->exec( 'UPDATE request r INNER JOIN address_change a ON a.external_id=r.id AND a.external_id_type="membership" SET r.address_change_id=a.id' );
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/Store/Factory.php:
--------------------------------------------------------------------------------
1 | driver
37 | */
38 | public function __construct( Connection $connection, $proxyDir = '/tmp', array $doctrineEntityPaths = [],
39 | $additionalMetadataDrivers = [] ) {
40 | $this->connection = $connection;
41 | $this->proxyDir = $proxyDir;
42 | $this->doctrineEntityPaths = $doctrineEntityPaths;
43 | $this->additionalMetadataDrivers = $additionalMetadataDrivers;
44 | }
45 |
46 | /**
47 | * @since 0.1
48 | * @return Installer
49 | */
50 | public function newInstaller() {
51 | return new Installer( $this->getEntityManager() );
52 | }
53 |
54 | /**
55 | * @since 0.1
56 | * @return EntityManager
57 | * @throws \Doctrine\DBAL\DBALException
58 | * @throws \Doctrine\ORM\ORMException
59 | */
60 | public function getEntityManager() {
61 | if ( !$this->entityManager ) {
62 | $this->entityManager = $this->setupEntityManager();
63 | }
64 |
65 | return $this->entityManager;
66 | }
67 |
68 | private function setupEntityManager() {
69 | $config = Setup::createConfiguration();
70 | $paths = array_merge( self::DEFAULT_DOCTRINE_ENTITY_PATHS, $this->doctrineEntityPaths );
71 |
72 | $annotationReader = new AnnotationReader();
73 | $annotationDriver = new AnnotationDriver( $annotationReader, $paths );
74 | AnnotationRegistry::registerLoader( 'class_exists' );
75 | $driver = new MappingDriverChain();
76 | $driver->addDriver( $annotationDriver, 'WMDE\Fundraising\Entities' );
77 | foreach ( $this->additionalMetadataDrivers as $namespace => $additionalMetadataDriver ) {
78 | $driver->addDriver( $additionalMetadataDriver, $namespace );
79 | }
80 | $config->setMetadataDriverImpl( $driver );
81 | $config->setProxyDir( $this->proxyDir );
82 |
83 | $eventManager = $this->connection->getEventManager();
84 | $timestampableListener = new TimestampableListener;
85 | $timestampableListener->setAnnotationReader( $annotationReader );
86 | $eventManager->addEventSubscriber( $timestampableListener );
87 |
88 | $entityManager = EntityManager::create( $this->connection, $config, $eventManager );
89 |
90 | $platform = $entityManager->getConnection()->getDatabasePlatform();
91 | $platform->registerDoctrineTypeMapping( 'enum', 'string' );
92 |
93 | return $entityManager;
94 | }
95 |
96 | /**
97 | * @since 0.1
98 | * @return Connection
99 | */
100 | public function getConnection() {
101 | return $this->connection;
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 | src/
8 | tests/
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | 0
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | 0
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | 0
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | 0
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/src/Entities/AddressChange.php:
--------------------------------------------------------------------------------
1 | read access is in AddressChange repo and update in exporter code
86 | *
87 | * @var \DateTime
88 | *
89 | * @ORM\Column(name="export_date", type="datetime", nullable=true)
90 | */
91 | private $exportDate;
92 |
93 | /**
94 | * When this was created
95 | *
96 | * No getter / setter needed, modification is in AddressChange repo
97 | *
98 | * @var \DateTime
99 | * @ORM\Column(name="created_at", type="datetime")
100 | */
101 | private $createdAt;
102 |
103 | /**
104 | * When this was last modified
105 | *
106 | * No getter / setter needed, modification is in AddressChange repo
107 | *
108 | * @var \DateTime
109 | * @ORM\Column(name="modified_at", type="datetime")
110 | */
111 | private $modifiedAt;
112 |
113 | /**
114 | * @var boolean
115 | *
116 | * @ORM\Column(name="donation_receipt", type="boolean", nullable=false)
117 | */
118 | private $donationReceipt = true;
119 |
120 |
121 | private function __construct( string $addressType, string $externalIdType, int $externalId ) {
122 | $this->createdAt = new \DateTime();
123 | $this->modifiedAt = new \DateTime();
124 | $this->addressType = $addressType;
125 | $this->externalIdType = $externalIdType;
126 | $this->externalId = $externalId;
127 | if ($this->identifier === null) {
128 | $this->generateUuid();
129 | }
130 | }
131 |
132 | public static function newDonationAddressChange( string $addressType, int $donationId ): self {
133 | return new self( $addressType, self::EXTERNAL_ID_TYPE_DONATION, $donationId );
134 | }
135 |
136 | public static function newMembershipAddressChange( string $addressType, int $membershipId ): self {
137 | return new self( $addressType, self::EXTERNAL_ID_TYPE_MEMBERSHIP, $membershipId );
138 | }
139 |
140 | private function generateUuid(): void {
141 | $this->identifier = Uuid::uuid4()->toString();
142 | }
143 |
144 | public function updateAddressIdentifier(): void {
145 | $this->previousIdentifier = $this->getCurrentIdentifier();
146 | $this->generateUuid();
147 | }
148 |
149 | public function getCurrentIdentifier(): string {
150 | if ($this->identifier === null) {
151 | $this->generateUuid();
152 | }
153 | return $this->identifier;
154 | }
155 |
156 | public function getPreviousIdentifier(): ?string {
157 | return $this->previousIdentifier;
158 | }
159 |
160 | public function isOptedIntoDonationReceipt() {
161 | return $this->donationReceipt;
162 | }
163 |
164 | public function setDonationReceipt( $donationReceipt ) {
165 | $this->donationReceipt = $donationReceipt;
166 | }
167 |
168 | public function getAddress(): ?Address {
169 | return $this->address;
170 | }
171 |
172 | public function setAddress( Address $address ) {
173 | $this->address = $address;
174 | }
175 |
176 | public function getAddressType(): string {
177 | return $this->addressType;
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/tests/unit/DonationTest.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | class DonationTest extends TestCase {
19 |
20 | public function testDataEncodingAndDecodingRoundtrips() {
21 | $donation = new Donation();
22 |
23 | $someData = [
24 | 'nyan' => 'cat',
25 | 'foo' => null,
26 | 'bar' => 9000.01,
27 | 'baz' => [ true ]
28 | ];
29 |
30 | $donation->encodeAndSetData( $someData );
31 |
32 | $this->assertSame( $someData, $donation->getDecodedData() );
33 | }
34 |
35 | public function testGivenNoData_getDecodedDataReturnsEmptyArray() {
36 | $donation = new Donation();
37 |
38 | $this->assertSame( [], $donation->getDecodedData() );
39 | }
40 |
41 | public function testWhenSettingIdToAnInteger_getIdReturnsIt() {
42 | $donation = new Donation();
43 | $donation->setId( 1337 );
44 |
45 | $this->assertSame( 1337, $donation->getId() );
46 | }
47 |
48 | public function testWhenSettingIdToNull_getIdReturnsNull() {
49 | $donation = new Donation();
50 | $donation->setId( 1337 );
51 | $donation->setId( null );
52 |
53 | $this->assertNull( $donation->getId() );
54 | }
55 |
56 | public function testWhenIdIsNotSet_getIdReturnsNull() {
57 | $donation = new Donation();
58 |
59 | $this->assertNull( $donation->getId() );
60 | }
61 |
62 | public function testGivenNoData_getDataObjectReturnsObjectWithNullValues() {
63 | $donation = new Donation();
64 |
65 | $this->assertNull( $donation->getDataObject()->getAccessToken() );
66 | $this->assertNull( $donation->getDataObject()->getUpdateToken() );
67 | $this->assertNull( $donation->getDataObject()->getUpdateTokenExpiry() );
68 | }
69 |
70 | public function testGivenData_getDataObjectReturnsTheValues() {
71 | $donation = new Donation();
72 | $donation->encodeAndSetData( [
73 | 'token' => 'foo',
74 | 'utoken' => 'bar',
75 | 'uexpiry' => 'baz',
76 | ] );
77 |
78 | $this->assertSame( 'foo', $donation->getDataObject()->getAccessToken() );
79 | $this->assertSame( 'bar', $donation->getDataObject()->getUpdateToken() );
80 | $this->assertSame( 'baz', $donation->getDataObject()->getUpdateTokenExpiry() );
81 | }
82 |
83 | public function testWhenProvidingData_setDataObjectSetsData() {
84 | $data = new DonationData();
85 | $data->setAccessToken( 'foo' );
86 | $data->setUpdateToken( 'bar' );
87 | $data->setUpdateTokenExpiry( 'baz' );
88 |
89 | $donation = new Donation();
90 | $donation->setDataObject( $data );
91 |
92 | $this->assertSame(
93 | [
94 | 'token' => 'foo',
95 | 'utoken' => 'bar',
96 | 'uexpiry' => 'baz',
97 | ],
98 | $donation->getDecodedData()
99 | );
100 | }
101 |
102 | public function testWhenProvidingNullData_setObjectDoesNotSetFields() {
103 | $donation = new Donation();
104 | $donation->setDataObject( new DonationData() );
105 |
106 | $this->assertSame(
107 | [],
108 | $donation->getDecodedData()
109 | );
110 | }
111 |
112 | public function testWhenDataAlreadyExists_setDataObjectRetainsAndUpdatesData() {
113 | $donation = new Donation();
114 | $donation->encodeAndSetData( [
115 | 'nyan' => 'cat',
116 | 'token' => 'wee',
117 | 'pink' => 'fluffy',
118 | ] );
119 |
120 | $data = new DonationData();
121 | $data->setAccessToken( 'foo' );
122 | $data->setUpdateToken( 'bar' );
123 |
124 | $donation->setDataObject( $data );
125 |
126 | $this->assertSame(
127 | [
128 | 'nyan' => 'cat',
129 | 'token' => 'foo',
130 | 'pink' => 'fluffy',
131 | 'utoken' => 'bar',
132 | ],
133 | $donation->getDecodedData()
134 | );
135 | }
136 |
137 | public function testWhenModifyingTheDataObject_modificationsAreReflected() {
138 | $donation = new Donation();
139 | $donation->encodeAndSetData( [
140 | 'nyan' => 'cat',
141 | 'token' => 'wee',
142 | 'pink' => 'fluffy',
143 | ] );
144 |
145 | $donation->modifyDataObject( function( DonationData $data ) {
146 | $data->setAccessToken( 'foo' );
147 | $data->setUpdateToken( 'bar' );
148 | } );
149 |
150 | $this->assertSame(
151 | [
152 | 'nyan' => 'cat',
153 | 'token' => 'foo',
154 | 'pink' => 'fluffy',
155 | 'utoken' => 'bar',
156 | ],
157 | $donation->getDecodedData()
158 | );
159 | }
160 |
161 | public function testStatusConstantsExist() {
162 | $this->assertNotNull( Donation::STATUS_NEW );
163 | $this->assertNotNull( Donation::STATUS_CANCELLED );
164 | $this->assertNotNull( Donation::STATUS_EXTERNAL_BOOKED );
165 | $this->assertNotNull( Donation::STATUS_EXTERNAL_INCOMPLETE );
166 | $this->assertNotNull( Donation::STATUS_MODERATION );
167 | $this->assertNotNull( Donation::STATUS_PROMISE );
168 | $this->assertNotNull( Donation::STATUS_EXPORTED );
169 | }
170 |
171 | }
172 |
--------------------------------------------------------------------------------
/migrations/Version20190109000000.php:
--------------------------------------------------------------------------------
1 | abortIf(
27 | $this->connection->getDatabasePlatform()->getName() !== 'mysql',
28 | 'Migration can only be executed safely on \'mysql\'.'
29 | );
30 | $this->addSql(
31 | 'ALTER TABLE address_change ADD address_type VARCHAR(10) NOT NULL, ' .
32 | 'ADD export_date DATETIME, ' .
33 | 'ADD created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, ' .
34 | 'ADD modified_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP'
35 | );
36 | $this->addSql( 'CREATE INDEX ac_export_date ON address_change (export_date)' );
37 | }
38 |
39 | public function postUp( Schema $schema ) {
40 | $entityCount = $this->getTotalUpdateRowCount();
41 | $pageCount = (int)ceil( $entityCount / self::DB_QUERY_PAGE_SIZE );
42 | $this->write( $entityCount . ' entries to be updated.' );
43 | // As the migration progresses, this low-cardinality temporary index speeds up getCurrentPage query
44 | $this->connection->exec( 'CREATE INDEX tmp_address_change ON address_change (address_type)' );
45 | $this->connection->setAutoCommit( false );
46 | $this->connection->connect(); // with autoCommit set to false, this implicitly begins a new Transaction. beginTransaction leads to wonky behavior
47 | for ( $page = 0; $page < $pageCount; $page++ ) {
48 | $startTime = microtime( true );
49 | $query = '';
50 | foreach ( $this->getCurrentPage() as $addressEntry ) {
51 | $data = $addressEntry['donation_data'] ?? $addressEntry['membership_data'];
52 | if ( $data === null ) {
53 | // Entries with no data are deleted from the AddressChange table and the foreign key reference is removed
54 | $query .= $this->getAddressChangeDeleteQueries( (int) $addressEntry['id'] );
55 | continue;
56 | }
57 | $type = $this->getAddressType( $data );
58 | $query .= 'UPDATE address_change SET address_type = "' . $type . '" WHERE id = "' . $addressEntry['id'] . '";';
59 | }
60 | if ( $query !== '' ) {
61 | $this->connection->exec( $query );
62 | $this->connection->commit();
63 | $this->write( sprintf( 'Updated page %d / %d - %.2f seconds', $page + 1, $pageCount, microtime( true ) - $startTime ) );
64 | }
65 | }
66 | $this->connection->exec( 'DROP INDEX tmp_address_change ON address_change' );
67 | }
68 |
69 | private function getAddressType( string $data ): string {
70 | if ( isset( $data['adresstyp'] ) && $data['adresstyp'] === 'firma'
71 | || ( isset( $data['firma'] ) && $data['firma'] !== '' ) ) {
72 | return AddressChange::ADDRESS_TYPE_COMPANY;
73 | }
74 | return AddressChange::ADDRESS_TYPE_PERSON;
75 | }
76 |
77 | private function getAddressChangeDeleteQueries( int $id ): string {
78 | $query = 'UPDATE ' . self::DB_TABLE_DONATIONS . ' SET address_change_id = NULL WHERE address_change_id = ' . $id . ';';
79 | $query .= 'UPDATE ' . self::DB_TABLE_MEMBERSHIP_APPLICATIONS . ' SET address_change_id = NULL WHERE address_change_id = ' . $id . ';';
80 | $query .= 'DELETE FROM ' . self::DB_TABLE_ADDRESS_CHANGE . ' WHERE id = ' . $id . ';';
81 | return $query;
82 | }
83 |
84 | private function getCurrentPage(): PDOStatement {
85 | $queryBuilder = $this->connection->createQueryBuilder();
86 | $query = $queryBuilder->select( 'address_change.id, s.data as donation_data, r.data as membership_data' )
87 | ->from( 'address_change' )
88 | ->leftJoin(
89 | 'address_change',
90 | 'spenden',
91 | 's',
92 | 's.address_change_id = address_change.id'
93 | )->leftJoin(
94 | 'address_change',
95 | 'request',
96 | 'r',
97 | 'r.address_change_id = address_change.id'
98 | )
99 | ->where( 'address_type = ""' )
100 | ->setMaxResults( self::DB_QUERY_PAGE_SIZE );
101 | return $query->execute();
102 | }
103 |
104 | private function getTotalUpdateRowCount(): int {
105 | $queryBuilder = $this->connection->createQueryBuilder();
106 | $queryBuilder->select( 'COUNT(id)' )
107 | ->from( self::DB_TABLE_ADDRESS_CHANGE, self::DB_TABLE_ADDRESS_CHANGE )
108 | ->where( 'address_type = ""' );
109 |
110 | return (int)$queryBuilder->execute()->fetchColumn( 0 );
111 | }
112 |
113 | public function down( Schema $schema ): void {
114 | $this->abortIf(
115 | $this->connection->getDatabasePlatform()->getName() !== 'mysql',
116 | 'Migration can only be executed safely on \'mysql\'.'
117 | );
118 | $this->addSql( 'DROP INDEX ac_export_date ON address_change' );
119 | $this->addSql( 'ALTER TABLE address_change DROP address_type, DROP export_date, DROP created_at, DROP modified_at' );
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/Entities/Address.php:
--------------------------------------------------------------------------------
1 | salutation = $salutation;
97 |
98 | return $this;
99 | }
100 |
101 | /**
102 | * Get salutation
103 | *
104 | * @return string
105 | */
106 | public function getSalutation() {
107 | return $this->salutation;
108 | }
109 |
110 | /**
111 | * Set company name
112 | *
113 | * @param string $company
114 | * @return self
115 | */
116 | public function setCompany( $company ) {
117 | $this->company = $company;
118 |
119 | return $this;
120 | }
121 |
122 | /**
123 | * Get company name
124 | *
125 | * @return string
126 | */
127 | public function getCompany() {
128 | return $this->company;
129 | }
130 |
131 | /**
132 | * Set title
133 | *
134 | * @param string $title
135 | * @return self
136 | */
137 | public function setTitle( $title ) {
138 | $this->title = $title;
139 |
140 | return $this;
141 | }
142 |
143 | /**
144 | * Get title
145 | *
146 | * @return string
147 | */
148 | public function getTitle() {
149 | return $this->title;
150 | }
151 |
152 | /**
153 | * Set first name
154 | *
155 | * @param string $firstName
156 | * @return self
157 | */
158 | public function setFirstName( $firstName ) {
159 | $this->firstName = $firstName;
160 |
161 | return $this;
162 | }
163 |
164 | /**
165 | * Get first name
166 | *
167 | * @return string
168 | */
169 | public function getFirstName() {
170 | return $this->firstName;
171 | }
172 |
173 | /**
174 | * Set last name
175 | *
176 | * @param string $lastName
177 | * @return self
178 | */
179 | public function setLastName( $lastName ) {
180 | $this->lastName = $lastName;
181 |
182 | return $this;
183 | }
184 |
185 | /**
186 | * Get last name
187 | *
188 | * @return string
189 | */
190 | public function getLastName() {
191 | return $this->lastName;
192 | }
193 |
194 | /**
195 | * Set address (street, etc)
196 | *
197 | * @param string $address
198 | * @return self
199 | */
200 | public function setAddress( $address ) {
201 | $this->address = $address;
202 |
203 | return $this;
204 | }
205 |
206 | /**
207 | * Get address (street, etc)
208 | *
209 | * @return string
210 | */
211 | public function getAddress() {
212 | return $this->address;
213 | }
214 |
215 | /**
216 | * Set postcode
217 | *
218 | * @param string $postcode
219 | * @return self
220 | */
221 | public function setPostcode( $postcode ) {
222 | $this->postcode = $postcode;
223 |
224 | return $this;
225 | }
226 |
227 | /**
228 | * Get postcode
229 | *
230 | * @return string
231 | */
232 | public function getPostcode() {
233 | return $this->postcode;
234 | }
235 |
236 | /**
237 | * Set city
238 | *
239 | * @param string $city
240 | * @return self
241 | */
242 | public function setCity( $city ) {
243 | $this->city = $city;
244 |
245 | return $this;
246 | }
247 |
248 | /**
249 | * Get city
250 | *
251 | * @return string
252 | */
253 | public function getCity() {
254 | return $this->city;
255 | }
256 |
257 | /**
258 | * Set country
259 | *
260 | * @param string $country
261 | * @return self
262 | */
263 | public function setCountry( $country ) {
264 | $this->country = $country;
265 |
266 | return $this;
267 | }
268 |
269 | /**
270 | * Get country
271 | *
272 | * @return string
273 | */
274 | public function getCountry() {
275 | return $this->country;
276 | }
277 |
278 | /**
279 | * @return int
280 | */
281 | public function getId() {
282 | return $this->id;
283 | }
284 |
285 | /**
286 | * @param int $id
287 | */
288 | public function setId( $id ) {
289 | $this->id = $id;
290 | }
291 | }
292 |
--------------------------------------------------------------------------------
/src/Entities/Subscription.php:
--------------------------------------------------------------------------------
1 | fullName = $fullName;
102 |
103 | return $this;
104 | }
105 |
106 | public function getFullName(): string {
107 | return $this->fullName;
108 | }
109 |
110 | public function setEmail( string $email ): self {
111 | $this->email = $email;
112 |
113 | return $this;
114 | }
115 |
116 | public function getEmail(): string {
117 | return $this->email;
118 | }
119 |
120 | public function setExport( \DateTime $export = null ): self {
121 | $this->export = $export;
122 |
123 | return $this;
124 | }
125 |
126 | /**
127 | * @return \DateTime|null
128 | */
129 | public function getExport() {
130 | return $this->export;
131 | }
132 |
133 | public function setBackup( \DateTime $backup = null ): self {
134 | $this->backup = $backup;
135 |
136 | return $this;
137 | }
138 |
139 | /**
140 | * @return \DateTime|null
141 | */
142 | public function getBackup() {
143 | return $this->backup;
144 | }
145 |
146 | /**
147 | * Usage of this method is discouraged, it's only for initialization with Doctrine.
148 | *
149 | * @see Subscription::markAsConfirmed()
150 | * @see Subscription::markForModeration()
151 | * @param int $status
152 | * @return Subscription
153 | */
154 | public function setStatus( int $status ): self {
155 | $this->status = $status;
156 |
157 | return $this;
158 | }
159 |
160 | /**
161 | * Usage of this method is discouraged. Try using something like @see isUnconfirmed
162 | *
163 | * @return int
164 | */
165 | public function getStatus(): int {
166 | return $this->status;
167 | }
168 |
169 | public function setConfirmationCode( string $confirmationCode ): self {
170 | $this->confirmationCode = $confirmationCode;
171 |
172 | return $this;
173 | }
174 |
175 | public function getConfirmationCode(): string {
176 | return $this->confirmationCode;
177 | }
178 |
179 | public function getId(): int {
180 | return $this->id;
181 | }
182 |
183 | public function getAddress(): Address {
184 | return $this->address;
185 | }
186 |
187 | public function setAddress( Address $address ) {
188 | $this->address = $address;
189 | }
190 |
191 | public function getCreatedAt(): \DateTime {
192 | return $this->createdAt;
193 | }
194 |
195 | public function setCreatedAt( \DateTime $createdAt ): self {
196 | $this->createdAt = $createdAt;
197 | return $this;
198 | }
199 |
200 | public function getTracking(): string {
201 | return $this->tracking;
202 | }
203 |
204 | public function setTracking( string $tracking ) {
205 | $this->tracking = $tracking;
206 | }
207 |
208 | /**
209 | * @since 3.0
210 | */
211 | public function setSource( string $source ): self {
212 | $this->source = $source;
213 |
214 | return $this;
215 | }
216 |
217 | /**
218 | * @since 3.0
219 | */
220 | public function getSource(): string {
221 | return $this->source;
222 | }
223 |
224 | /**
225 | * @since 3.0
226 | */
227 | public function markAsConfirmed() {
228 | $this->status = self::STATUS_CONFIRMED;
229 | }
230 |
231 | /**
232 | * @since 3.0
233 | */
234 | public function markForModeration() {
235 | $this->status = self::STATUS_MODERATION;
236 | }
237 |
238 | public function isUnconfirmed(): bool {
239 | return $this->status !== self::STATUS_CONFIRMED;
240 | }
241 |
242 | /**
243 | * @since 3.0
244 | */
245 | public function needsModeration(): bool {
246 | return $this->status === self::STATUS_MODERATION;
247 | }
248 |
249 | }
250 |
--------------------------------------------------------------------------------
/migrations/Version20180612000000.php:
--------------------------------------------------------------------------------
1 | abortIf(
31 | $this->connection->getDatabasePlatform()->getName() !== 'mysql',
32 | 'Migration can only be executed safely on \'mysql\'.'
33 | );
34 | $this->skipIf( $schema->hasTable( 'address_change' ), 'AddressChange table already exists' );
35 |
36 | $this->addSql( 'CREATE TABLE address_change (id INT AUTO_INCREMENT NOT NULL, address_id INT DEFAULT NULL, current_identifier VARCHAR(36) DEFAULT NULL, previous_identifier VARCHAR(36) DEFAULT NULL, UNIQUE INDEX UNIQ_7B0E7B9FA8954A18 (current_identifier), UNIQUE INDEX UNIQ_7B0E7B9F2EC1D3 (previous_identifier), INDEX IDX_7B0E7B9FF5B7AF75 (address_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB' );
37 | $this->addSql( 'ALTER TABLE address_change ADD CONSTRAINT FK_7B0E7B9FF5B7AF75 FOREIGN KEY (address_id) REFERENCES address (id)' );
38 | $this->addSql( 'ALTER TABLE spenden ADD address_change_id INT DEFAULT NULL' );
39 | $this->addSql( 'ALTER TABLE spenden ADD CONSTRAINT FK_3CBBD045BB7DB7BC FOREIGN KEY (address_change_id) REFERENCES address_change (id)' );
40 | $this->addSql( 'CREATE UNIQUE INDEX UNIQ_3CBBD045BB7DB7BC ON spenden (address_change_id)' );
41 | $this->addSql( 'ALTER TABLE request ADD address_change_id INT DEFAULT NULL' );
42 | $this->addSql( 'ALTER TABLE request ADD CONSTRAINT FK_3B978F9FBB7DB7BC FOREIGN KEY (address_change_id) REFERENCES address_change (id)' );
43 | $this->addSql( 'CREATE UNIQUE INDEX UNIQ_3B978F9FBB7DB7BC ON request (address_change_id)' );
44 | }
45 |
46 | /**
47 | * @param Schema $schema
48 | *
49 | * @throws \Doctrine\DBAL\DBALException
50 | */
51 | public function postUp( Schema $schema ) {
52 | $this->updateAddressChangeForeignKeys( self::DB_TABLE_DONATIONS );
53 | $this->updateAddressChangeForeignKeys( self::DB_TABLE_MEMBERSHIP_APPLICATIONS );
54 | }
55 |
56 | /**
57 | * @param $table
58 | *
59 | * @throws \Doctrine\DBAL\DBALException
60 | */
61 | private function updateAddressChangeForeignKeys( $table ): void {
62 | $entityCount = $this->getAddressChangeNullColumnCount( $table );
63 | $pageCount = (int) ceil( $entityCount / self::DB_QUERY_PAGE_SIZE );
64 | $currentAddressChangeId = $this->getHighestAddressChangeId();
65 | $this->connection->beginTransaction();
66 | $query = '';
67 |
68 | for ( $currentEntity = 0; $currentEntity < $entityCount; $currentEntity++ ) {
69 | $query .= 'INSERT INTO address_change ( current_identifier ) VALUES ( "' . Uuid::uuid4()->toString() . '" );';
70 | if ( $currentEntity % self::DB_QUERY_PAGE_SIZE === 0 || $entityCount - 1 === $currentEntity ) {
71 | $stmt = $this->connection->prepare( $query );
72 | $stmt->execute();
73 | $stmt->closeCursor();
74 | $this->connection->commit();
75 |
76 | if ( $entityCount - 1 !== $currentEntity ) {
77 | $this->connection->beginTransaction();
78 | }
79 | $this->write( $currentEntity . " / $entityCount\n" );
80 | $query = '';
81 | }
82 | }
83 |
84 | for ( $page = 0; $page < $pageCount; $page++ ) {
85 | $entities = $this->getPageForTable( $table );
86 | $this->write( 'Page: ' . $page . ' / ' . $pageCount . "\n" );
87 | foreach ( $entities as $entity ) {
88 | $currentAddressChangeId++;
89 | $this->write(
90 | 'UPDATE ' . $table . ' SET address_change_id = ' . $currentAddressChangeId . ' WHERE id = ' . (int)$entity['id'] . "\n"
91 | );
92 | $this->connection->executeUpdate(
93 | 'UPDATE ' . $table . ' SET address_change_id = ? WHERE id = ?',
94 | [ $currentAddressChangeId, (int)$entity['id'] ]
95 | );
96 | }
97 | }
98 | }
99 |
100 | private function getAddressChangeNullColumnCount( $table ): int {
101 | $queryBuilder = $this->connection->createQueryBuilder();
102 | $queryBuilder->select( 'COUNT(id)' )
103 | ->from( $table, $table )
104 | ->where( 'address_change_id is NULL' );
105 |
106 | return $queryBuilder->execute()->fetchColumn( 0 );
107 | }
108 |
109 | private function getPageForTable( string $table ): Statement {
110 | $queryBuilder = $this->connection->createQueryBuilder();
111 | $queryBuilder->select( 'id' )
112 | ->from( $table )
113 | ->where( 'address_change_id is NULL' )
114 | ->orderBy( 'id', 'ASC' )
115 | ->setMaxResults( self::DB_QUERY_PAGE_SIZE );
116 | return $queryBuilder->execute();
117 | }
118 |
119 | private function getHighestAddressChangeId(): int {
120 | $dbResult = $this->connection->createQueryBuilder()
121 | ->select( 'id' )
122 | ->from( 'address_change' )
123 | ->orderBy( 'id', 'DESC' )
124 | ->setMaxResults( 1 )
125 | ->execute()->fetchColumn( 0 );
126 | return (int)$dbResult;
127 | }
128 |
129 | /**
130 | * @param Schema $schema
131 | *
132 | * @throws \Doctrine\DBAL\DBALException
133 | * @throws \Doctrine\DBAL\Migrations\AbortMigrationException
134 | */
135 | public function down( Schema $schema ): void {
136 | $this->abortIf(
137 | $this->connection->getDatabasePlatform()->getName() !== 'mysql',
138 | 'Migration can only be executed safely on \'mysql\'.'
139 | );
140 |
141 | $this->addSql( 'ALTER TABLE spenden DROP FOREIGN KEY FK_3CBBD045BB7DB7BC' );
142 | $this->addSql( 'ALTER TABLE request DROP FOREIGN KEY FK_3B978F9FBB7DB7BC' );
143 | $this->addSql( 'DROP TABLE address_change' );
144 | $this->addSql( 'DROP INDEX UNIQ_3B978F9FBB7DB7BC ON request' );
145 | $this->addSql( 'ALTER TABLE request DROP address_change_id' );
146 | $this->addSql( 'DROP INDEX UNIQ_3CBBD045BB7DB7BC ON spenden' );
147 | $this->addSql( 'ALTER TABLE spenden DROP address_change_id' );
148 | }
149 | }
--------------------------------------------------------------------------------
/tests/unit/MembershipApplicationTest.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | class MembershipApplicationTest extends TestCase {
19 |
20 | public function testWhenSettingIdToAnInteger_getIdReturnsIt() {
21 | $application = new MembershipApplication();
22 | $application->setId( 1337 );
23 |
24 | $this->assertSame( 1337, $application->getId() );
25 | }
26 |
27 | public function testWhenSettingIdToNull_getIdReturnsNull() {
28 | $application = new MembershipApplication();
29 | $application->setId( 1337 );
30 | $application->setId( null );
31 |
32 | $this->assertNull( $application->getId() );
33 | }
34 |
35 | public function testWhenIdIsNotSet_getIdReturnsNull() {
36 | $application = new MembershipApplication();
37 |
38 | $this->assertNull( $application->getId() );
39 | }
40 |
41 | public function testGivenNoData_getDataObjectReturnsObjectWithNullValues() {
42 | $application = new MembershipApplication();
43 |
44 | $this->assertNull( $application->getDataObject()->getAccessToken() );
45 | $this->assertNull( $application->getDataObject()->getUpdateToken() );
46 | $this->assertNull( $application->getDataObject()->getPreservedStatus() );
47 | }
48 |
49 | public function testWhenProvidingData_setDataObjectSetsData() {
50 | $data = new MembershipApplicationData();
51 | $data->setAccessToken( 'foo' );
52 | $data->setUpdateToken( 'bar' );
53 | $data->setPreservedStatus( 1337 );
54 |
55 | $application = new MembershipApplication();
56 | $application->setDataObject( $data );
57 |
58 | $this->assertSame(
59 | [
60 | 'token' => 'foo',
61 | 'utoken' => 'bar',
62 | 'old_status' => 1337,
63 | ],
64 | $application->getDecodedData()
65 | );
66 | }
67 |
68 | public function testWhenProvidingNullData_setObjectDoesNotSetFields() {
69 | $application = new MembershipApplication();
70 | $application->setDataObject( new MembershipApplicationData() );
71 |
72 | $this->assertSame(
73 | [],
74 | $application->getDecodedData()
75 | );
76 | }
77 |
78 | public function testWhenDataAlreadyExists_setDataObjectRetainsAndUpdatesData() {
79 | $application = new MembershipApplication();
80 | $application->encodeAndSetData( [
81 | 'nyan' => 'cat',
82 | 'token' => 'wee',
83 | 'pink' => 'fluffy',
84 | ] );
85 |
86 | $data = new MembershipApplicationData();
87 | $data->setAccessToken( 'foo' );
88 | $data->setUpdateToken( 'bar' );
89 |
90 | $application->setDataObject( $data );
91 |
92 | $this->assertSame(
93 | [
94 | 'nyan' => 'cat',
95 | 'token' => 'foo',
96 | 'pink' => 'fluffy',
97 | 'utoken' => 'bar',
98 | ],
99 | $application->getDecodedData()
100 | );
101 | }
102 |
103 | public function testWhenModifyingTheDataObject_modificationsAreReflected() {
104 | $application = new MembershipApplication();
105 | $application->encodeAndSetData( [
106 | 'nyan' => 'cat',
107 | 'token' => 'wee',
108 | 'pink' => 'fluffy',
109 | ] );
110 |
111 | $application->modifyDataObject( function( MembershipApplicationData $data ) {
112 | $data->setAccessToken( 'foo' );
113 | $data->setUpdateToken( 'bar' );
114 | } );
115 |
116 | $this->assertSame(
117 | [
118 | 'nyan' => 'cat',
119 | 'token' => 'foo',
120 | 'pink' => 'fluffy',
121 | 'utoken' => 'bar',
122 | ],
123 | $application->getDecodedData()
124 | );
125 | }
126 |
127 | public function testStatusConstantsExist() {
128 | $this->assertNotNull( MembershipApplication::STATUS_MODERATION );
129 | $this->assertNotNull( MembershipApplication::STATUS_ABORTED );
130 | $this->assertNotNull( MembershipApplication::STATUS_CANCELED );
131 | $this->assertNotNull( MembershipApplication::STATUS_CONFIRMED );
132 | $this->assertNotNull( MembershipApplication::STATUS_DELETED );
133 | $this->assertNotNull( MembershipApplication::STATUS_NEUTRAL );
134 | }
135 |
136 | public function testGivenModerationStatus_needsModerationReturnsTrue() {
137 | $application = new MembershipApplication();
138 | $application->setStatus( MembershipApplication::STATUS_MODERATION );
139 |
140 | $this->assertTrue( $application->needsModeration() );
141 | }
142 |
143 | public function testGivenDefaultStatus_needsModerationReturnsFalse() {
144 | $application = new MembershipApplication();
145 |
146 | $this->assertFalse( $application->needsModeration() );
147 | }
148 |
149 | public function testGivenModerationAndCancelledStatus_needsModerationReturnsTrue() {
150 | $application = new MembershipApplication();
151 | $application->setStatus(
152 | MembershipApplication::STATUS_MODERATION + MembershipApplication::STATUS_CANCELED
153 | );
154 |
155 | $this->assertTrue( $application->needsModeration() );
156 | }
157 |
158 | public function testGivenCancelledStatus_isCancelledReturnsTrue() {
159 | $application = new MembershipApplication();
160 | $application->setStatus( MembershipApplication::STATUS_CANCELED );
161 |
162 | $this->assertTrue( $application->isCancelled() );
163 | }
164 |
165 | public function testGivenDefaultStatus_isCancelledReturnsFalse() {
166 | $application = new MembershipApplication();
167 |
168 | $this->assertFalse( $application->isCancelled() );
169 | }
170 |
171 | public function testGivenModerationAndCancelledStatus_isCancelledReturnsTrue() {
172 | $application = new MembershipApplication();
173 | $application->setStatus(
174 | MembershipApplication::STATUS_MODERATION + MembershipApplication::STATUS_CANCELED
175 | );
176 |
177 | $this->assertTrue( $application->isCancelled() );
178 | }
179 |
180 | public function testGivenDeletedStatus_isDeletedReturnsTrue() {
181 | $application = new MembershipApplication();
182 | $application->setStatus( MembershipApplication::STATUS_DELETED );
183 |
184 | $this->assertTrue( $application->isDeleted() );
185 | }
186 |
187 | public function testGivenDefaultStatus_isDeletedReturnsFalse() {
188 | $application = new MembershipApplication();
189 |
190 | $this->assertFalse( $application->isCancelled() );
191 | }
192 |
193 | public function testGivenModerationAndDeletedStatus_isDeletedReturnsTrue() {
194 | $application = new MembershipApplication();
195 | $application->setStatus(
196 | MembershipApplication::STATUS_MODERATION + MembershipApplication::STATUS_DELETED
197 | );
198 |
199 | $this->assertTrue( $application->isDeleted() );
200 | }
201 |
202 | public function testDefaultDonationReceiptValue_isNull(): void {
203 | $application = new MembershipApplication();
204 |
205 | $this->assertNull( $application->getDonationReceipt() );
206 | }
207 |
208 | public function testSetDonationReceiptValue_canBeRetrieved(): void {
209 | $application = new MembershipApplication();
210 | $application->setDonationReceipt( false );
211 |
212 | $this->assertFalse( $application->getDonationReceipt() );
213 | }
214 |
215 | }
216 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Persistence Services for WMDE Fundraising
2 |
3 | [](http://travis-ci.org/wmde/FundraisingStore)
4 | [](https://scrutinizer-ci.com/g/wmde/FundraisingStore/?branch=master)
5 | [](https://scrutinizer-ci.com/g/wmde/FundraisingStore/?branch=master)
6 | [](https://packagist.org/packages/wmde/fundraising-store)
7 | [](https://packagist.org/packages/wmde/fundraising-store)
8 |
9 | [](https://packagist.org/packages/wmde/fundraising-store)
10 | [](//packagist.org/packages/wmde/fundraising-store)
11 |
12 | **Fundraising Store** contains persistence services for the WMDE fundraising codebase.
13 |
14 | ## Installation
15 |
16 | You can use [Composer](http://getcomposer.org/) to download and install
17 | this package as well as its dependencies.
18 |
19 | To add this package as a local, per-project dependency to your project, run:
20 |
21 | composer require wmde/fundraising-store
22 |
23 | This package provides `vendor/bin/cli-config.php` file required by the Doctrine Console.
24 | A default database configuration, compatible with the container used for integration tests (see `docker-compose.yml`),
25 | can be found in `local-db-config.php`.
26 | Applications wanting to use this package in conjunction with the Doctrine Console should provide their own `cli-config.php`.
27 |
28 | ## Architecture and Future
29 |
30 | This library and the tables it creates is used by the fundraising Bounded Contexts and by the Fundaising Operation Center application. The idea is to migrate this library away by moving all code specific to a Bounded Context into that Bounded Context and all code specific to an application to that application. This way the Bounded Contexts can have truely private peristance and follow the [Clean Architecture + Bounded Contexts architecture rules](https://www.entropywins.wtf/blog/2018/08/14/clean-architecture-bounded-contexts/).
31 |
32 | For a short history of we ended up with this library, see [Bounded Contexts in the Wikimedia Fundraising Software](https://www.entropywins.wtf/blog/2018/08/14/bounded-contexts-in-the-wikimedia-fundraising-software/)
33 |
34 | ## Development
35 |
36 | In order to create a level playing field for development this project uses
37 | docker &
38 | docker-compose,
39 | as well as composer for dependency management.
40 |
41 | ### Provision the system
42 |
43 | docker-compose build
44 |
45 | ### Install dependencies
46 |
47 | docker run -it --rm --user $(id -u):$(id -g) -v "$PWD":/code -v ~/.composer:/composer -w /code composer composer install
48 |
49 | ### Tests
50 |
51 | docker-compose run --rm app vendor/bin/phpunit
52 |
53 | ### Running test and code-style checks with composer
54 | All committed code is checked by our CI against the unit tests and the coding styleguide.
55 | The configuration for these tools can be found in the root directory.
56 | After installation through composer you can either call the tools directly from their location in `vendor/bin`
57 | or use the provided `composer` tasks:
58 |
59 | composer ci # Run all checks
60 | composer cs # Run style checks
61 | composer test # Run unit tests
62 |
63 | Be aware that the concepts of composer as a script runner and containerization might not work together out-of-the-box.
64 |
65 | ## Release notes
66 |
67 | ### Scheduled for next release (11.x)
68 | * Invert relationship of Donation and Membership to AddressChange.
69 | WARNING: This gets rid of the "cascade" feature (automatic creation of AddressChange records
70 | when creating Donations/memberships). The FundraisingFrontend would have to implement an event
71 | handler that does the creation.
72 |
73 | ### Version 10.5.0 (2020-01-31)
74 |
75 | * Add `$additionalMetadataDrivers` parameter to `Factory` to support XML-mapped entities (e.g. in the AddressChange
76 | bounded context).
77 |
78 | ### Version 10.4.1 (2020-01-30)
79 |
80 | * Change `donationReceipt` default to `true`
81 |
82 | ### Version 10.4.0 (2019-05-29)
83 |
84 | * Add `donationReceipt` to `AddressChange`
85 |
86 | ### Version 10.3.3 (2019-02-04)
87 |
88 | * Fix address change migration
89 |
90 | ### Version 10.3.2 (2019-01-31)
91 |
92 | * Allow `getAddress` to return null in `AddressChange`
93 |
94 | ### Version 10.3.1 (2019-01-30)
95 |
96 | * Fix column names in database for `createdAt` and `modifiedAt` to `AddressChange`
97 |
98 | ### Version 10.3.0 (2019-01-29)
99 |
100 | * Add `createdAt` and `modifiedAt` to `AddressChange`
101 |
102 | ### Version 10.2.0 (2019-01-18)
103 |
104 | * Add `exportDate` to `AddressChange`
105 |
106 | ### Version 10.1.0 (2019-01-16)
107 |
108 | * Remove `thirdPartyIdentifier` from `AddressChange`
109 |
110 | ### Version 10.0.0 (2019-01-11)
111 |
112 | * Add `addressType` and `thirdPartyIdentifier` to `AddressChange`
113 |
114 | ### Version 9.0.0 (2018-08-16)
115 |
116 | * Remove `User`, `ActionLog`, `BackendBanner`, `BackendImpression` doctrine entity and move them to `fundraising-backend` repository
117 |
118 | ### Version 8.0.0 (2018-06-06)
119 |
120 | * Add `AddressChange` entity for address export functionality
121 |
122 | ### Version 7.0.1 (2017-12-05)
123 |
124 | * Changed default timestamp value from "0" to "current time", prevening errors on newer versions of MySQL
125 |
126 | ### Version 7.0.0 (2017-11-01)
127 |
128 | * Introduced `MembershipApplication::setDonationReceipt()` et al.
129 |
130 | ### Version 6.1.0 (2017-08-16)
131 |
132 | * Introduced constant `Donation::STATUS_EXPORTED` for historical reasons.
133 |
134 | ### Version 6.0.1 (2017-08-02)
135 |
136 | * Updated Doctrine DBAL and ORM min version.
137 |
138 | ### Version 6.0.0 (2017-07-18)
139 |
140 | * Added `DonationPayments\SofortPayment` entity
141 |
142 | ### Version 5.0.0 (2017-03-30)
143 |
144 | #### Breaking changes
145 |
146 | * Added full text indices for donations and memberships. This breaks backward compatibility for MySQL versions < 5.6.
147 | * Prefixed all indices of donations with `d_`.
148 | * Mark `donation_id` of `MembershipApplication` as deprecated. It's no longer written by the fundraising frontend, but
149 | still referenced in some places by the backend, so for now we just mark it as deprecated instead of removing it.
150 |
151 | ### Version 4.2.0 (2017-01-04)
152 |
153 | * Added `MembershipApplication::isDeleted()`
154 |
155 | ### Version 4.1.0 (2016-12-06)
156 |
157 | * Add parameter for setting proxy dir to Factory. For backwards compatibility it's optional and defaults to `/tmp`.
158 |
159 | ### Version 4.0.0 (2016-11-25)
160 |
161 | #### Breaking changes
162 |
163 | * The subscription confirmation code is now a plain string instead of a binary (blob). This makes it easier to read and test.
164 |
165 | ### Version 3.0.0 (2016-11-16)
166 |
167 | #### Breaking changes
168 |
169 | * The Subscription status flags have been removed and usage of setStatus and getStatus is now discouraged
170 | * Changed the minimum PHP version to 7.0
171 |
172 | #### New features
173 |
174 | * Added `source` field to `subscription` table. This field indicates what led to the subscription,
175 | for instance the "remind me later" feature.
176 | * Added `Subscription::getSource` and `Subscription::setSource`
177 | * Added `Subscription::markAsConfirmed`
178 | * Added `Subscription::markForModeration`
179 | * Added `Subscription::needsModeration`
180 |
181 | #### Bug fixes
182 |
183 | * `Subscription::isUnconfirmed` now correctly returns true when a subscription has been marked for moderation
184 |
185 | ### Version 2.1.0 (2016-10-10)
186 |
187 | * Schema change: Added `payment_type` field column to the `request` table
188 | * Added `MembershipApplication::setPaymentType` and `MembershipApplication::getPaymentType`
189 | * Ability ro re-ruse the file `cli-config.php` when including `FundraisingStore` in an application is removed.
190 |
191 | ### Version 2.0.1 (2016-09-28)
192 |
193 | By the rules of semantic versioning, this version should have been 2.1 but was tagged wrongly.
194 |
195 | * Added `MembershipApplicationData::setPreservedStatus` and `MembershipApplicationData::getPreservedStatus`.
196 | This is used to store the previous status when the status changes from a positive to a negative value.
197 |
198 | ### Version 2.0.0 (2016-08-03)
199 |
200 | #### Breaking changes
201 |
202 | * Renamed several entities. Database table names where not changed to remain backwards compatible.
203 | * `Spenden` => `Donation`.
204 | * `Users` => `User`
205 | * `BackendImpressions` => `BackendImpression`
206 | * `Request` was split into `MembershipApplication` and `Subscription` (the type field was removed)
207 | * The public PHP interfaces of `Donation` and `MembershipApplication` were changed to English.
208 | * Creation timestamps are now added automatically to donations, membership requests and subscriptions. Donation creation
209 | timestamp `dt_new` is now mandatory (not nullable).
210 | * The `guid` field of `MembershipApplication` was removed.
211 | * Changed the minimum PHP version to 5.6
212 |
213 | #### New features
214 |
215 | * Added `Address` entity
216 | * Added `DonationData` class to provide nicer access to the data field
217 | * Added new methods to `Donation`
218 | * `setId`
219 | * `getDecodedData`
220 | * `encodeAndSetData`
221 | * `getDataObject`
222 | * `setDataObject`
223 | * `modifyDataObject`
224 | * Added `MembershipApplicationData` class to provide nicer access to the data field
225 | * Added new methods to `MembershipApplication`
226 | * `setId`
227 | * `getDecodedData`
228 | * `encodeAndSetData`
229 | * `getDataObject`
230 | * `setDataObject`
231 | * `modifyDataObject`
232 |
233 | ### Version 1.0 (2016-01-11)
234 |
235 | * Added CLI configuration for Doctrine ORM shell commands
236 | * Added request type and status constants
237 | * Automatically set the full name in Request when first or last name is set.
238 | * Changed the minimum PHP version to 5.5
239 |
240 | ### Version 0.1 (2015-07-10)
241 |
242 | Initial release with `Store\Factory`, `Store\Installer` and these entities:
243 |
244 | * ActionLog
245 | * BackendBanner
246 | * BackendImpressions
247 | * Request
248 | * Spenden
249 | * Users
250 |
251 | ## Future Plans
252 | * Migrate the payment metadata from `Donation::data` to specific subclasses of the `DonationPayment` abstract class.
253 |
254 | ## Links
255 |
256 | * [Fundraising Store on Packagist](https://packagist.org/packages/wmde/fundraising-store)
257 | * [Fundraising Store on TravisCI](https://travis-ci.org/wmde/FundraisingStore)
258 | * [Fundraising Store on ScrutinizerCI](https://scrutinizer-ci.com/g/wmde/FundraisingStore)
259 |
--------------------------------------------------------------------------------
/src/Entities/Donation.php:
--------------------------------------------------------------------------------
1 | donorFullName = $donorFullName;
226 |
227 | return $this;
228 | }
229 |
230 | /**
231 | * @return string
232 | */
233 | public function getDonorFullName() {
234 | return $this->donorFullName;
235 | }
236 |
237 | /**
238 | * @param string $donorCity
239 | *
240 | * @return self
241 | */
242 | public function setDonorCity( $donorCity ) {
243 | $this->donorCity = $donorCity;
244 |
245 | return $this;
246 | }
247 |
248 | /**
249 | * @return string
250 | */
251 | public function getDonorCity() {
252 | return $this->donorCity;
253 | }
254 |
255 | /**
256 | * @param string $donorEmail
257 | *
258 | * @return self
259 | */
260 | public function setDonorEmail( $donorEmail ) {
261 | $this->donorEmail = $donorEmail;
262 |
263 | return $this;
264 | }
265 |
266 | /**
267 | * @return string
268 | */
269 | public function getDonorEmail() {
270 | return $this->donorEmail;
271 | }
272 |
273 | /**
274 | * @param boolean $donorOptsIntoNewsletter
275 | *
276 | * @return self
277 | */
278 | public function setDonorOptsIntoNewsletter( $donorOptsIntoNewsletter ) {
279 | $this->donorOptsIntoNewsletter = $donorOptsIntoNewsletter;
280 |
281 | return $this;
282 | }
283 |
284 | /**
285 | * @return boolean
286 | */
287 | public function getDonorOptsIntoNewsletter() {
288 | return $this->donorOptsIntoNewsletter;
289 | }
290 |
291 | /**
292 | * Set donation receipt state
293 | *
294 | * @param boolean $donationReceipt
295 | * @return self
296 | */
297 | public function setDonationReceipt( $donationReceipt ) {
298 | $this->donationReceipt = $donationReceipt;
299 |
300 | return $this;
301 | }
302 |
303 | /**
304 | * Get donation receipt state
305 | *
306 | * @return boolean
307 | */
308 | public function getDonationReceipt() {
309 | return $this->donationReceipt;
310 | }
311 |
312 | /**
313 | * Set publicly displayed donation record
314 | *
315 | * @param string $publicRecord
316 | * @return self
317 | */
318 | public function setPublicRecord( $publicRecord ) {
319 | $this->publicRecord = $publicRecord;
320 |
321 | return $this;
322 | }
323 |
324 | /**
325 | * Get publicly displayed donation record
326 | *
327 | * @return string
328 | */
329 | public function getPublicRecord() {
330 | return $this->publicRecord;
331 | }
332 |
333 | /**
334 | * @param string $amount
335 | * @return self
336 | */
337 | public function setAmount( $amount ) {
338 | $this->amount = $amount;
339 |
340 | return $this;
341 | }
342 |
343 | /**
344 | * @return string
345 | */
346 | public function getAmount() {
347 | return $this->amount;
348 | }
349 |
350 | /**
351 | * @param integer $paymentIntervalInMonths
352 | *
353 | * @return self
354 | */
355 | public function setPaymentIntervalInMonths( $paymentIntervalInMonths ) {
356 | $this->paymentIntervalInMonths = $paymentIntervalInMonths;
357 |
358 | return $this;
359 | }
360 |
361 | /**
362 | * @return integer
363 | */
364 | public function getPaymentIntervalInMonths() {
365 | return $this->paymentIntervalInMonths;
366 | }
367 |
368 | /**
369 | * Set payment type short code
370 | *
371 | * @param string $paymentType
372 | * @return self
373 | */
374 | public function setPaymentType( $paymentType ) {
375 | $this->paymentType = $paymentType;
376 |
377 | return $this;
378 | }
379 |
380 | /**
381 | * Get payment type short code
382 | *
383 | * @return string
384 | */
385 | public function getPaymentType() {
386 | return $this->paymentType;
387 | }
388 |
389 | /**
390 | * @param string $comment
391 | * @return self
392 | */
393 | public function setComment( $comment ) {
394 | $this->comment = $comment;
395 |
396 | return $this;
397 | }
398 |
399 | /**
400 | * @return string
401 | */
402 | public function getComment() {
403 | return $this->comment;
404 | }
405 |
406 | /**
407 | * Set bank transfer reference code
408 | *
409 | * @param string $bankTransferCode
410 | *
411 | * @return self
412 | */
413 | public function setBankTransferCode( $bankTransferCode ) {
414 | $this->bankTransferCode = $bankTransferCode;
415 |
416 | return $this;
417 | }
418 |
419 | /**
420 | * Get bank transfer reference code
421 | *
422 | * @return string
423 | */
424 | public function getBankTransferCode() {
425 | return $this->bankTransferCode;
426 | }
427 |
428 | /**
429 | * @param string $source
430 | * @return self
431 | */
432 | public function setSource( $source ) {
433 | $this->source = $source;
434 |
435 | return $this;
436 | }
437 |
438 | /**
439 | * @return string
440 | */
441 | public function getSource() {
442 | return $this->source;
443 | }
444 |
445 | /**
446 | * @param string $remoteAddr
447 | * @return self
448 | */
449 | public function setRemoteAddr( $remoteAddr ) {
450 | $this->remoteAddr = $remoteAddr;
451 |
452 | return $this;
453 | }
454 |
455 | /**
456 | * @return string
457 | */
458 | public function getRemoteAddr() {
459 | return $this->remoteAddr;
460 | }
461 |
462 | /**
463 | * @param string $hash
464 | * @return self
465 | */
466 | public function setHash( $hash ) {
467 | $this->hash = $hash;
468 |
469 | return $this;
470 | }
471 |
472 | /**
473 | * @return string
474 | */
475 | public function getHash() {
476 | return $this->hash;
477 | }
478 |
479 | /**
480 | * Sets if the donations comment should be public or private.
481 | * @param boolean $isPublic
482 | * @return self
483 | */
484 | public function setIsPublic( $isPublic ) {
485 | $this->isPublic = $isPublic;
486 |
487 | return $this;
488 | }
489 |
490 | /**
491 | * Gets if the donations comment is public or private.
492 | * @return boolean
493 | */
494 | public function getIsPublic() {
495 | return $this->isPublic;
496 | }
497 |
498 | /**
499 | * @param \DateTime $creationTime
500 | *
501 | * @return self
502 | */
503 | public function setCreationTime( $creationTime ) {
504 | $this->creationTime = $creationTime;
505 |
506 | return $this;
507 | }
508 |
509 | /**
510 | * @return \DateTime
511 | */
512 | public function getCreationTime() {
513 | return $this->creationTime;
514 | }
515 |
516 | /**
517 | * @param \DateTime|null $deletionTime
518 | *
519 | * @return self
520 | */
521 | public function setDeletionTime( $deletionTime ) {
522 | $this->deletionTime = $deletionTime;
523 |
524 | return $this;
525 | }
526 |
527 | /**
528 | * @return \DateTime|null
529 | */
530 | public function getDeletionTime() {
531 | return $this->deletionTime;
532 | }
533 |
534 | /**
535 | * @param \DateTime $dtExp
536 | * @return self
537 | */
538 | public function setDtExp( $dtExp ) {
539 | $this->dtExp = $dtExp;
540 |
541 | return $this;
542 | }
543 |
544 | /**
545 | * @return \DateTime
546 | */
547 | public function getDtExp() {
548 | return $this->dtExp;
549 | }
550 |
551 | /**
552 | * @param string $status
553 | * @return self
554 | */
555 | public function setStatus( $status ) {
556 | $this->status = $status;
557 |
558 | return $this;
559 | }
560 |
561 | /**
562 | * @return string
563 | */
564 | public function getStatus() {
565 | return $this->status;
566 | }
567 |
568 | /**
569 | * @param \DateTime $dtGruen
570 | * @return self
571 | */
572 | public function setDtGruen( $dtGruen ) {
573 | $this->dtGruen = $dtGruen;
574 |
575 | return $this;
576 | }
577 |
578 | /**
579 | * @return \DateTime
580 | */
581 | public function getDtGruen() {
582 | return $this->dtGruen;
583 | }
584 |
585 | /**
586 | * @param \DateTime $dtBackup
587 | * @return self
588 | */
589 | public function setDtBackup( $dtBackup ) {
590 | $this->dtBackup = $dtBackup;
591 |
592 | return $this;
593 | }
594 |
595 | /**
596 | * @return \DateTime
597 | */
598 | public function getDtBackup() {
599 | return $this->dtBackup;
600 | }
601 |
602 | /**
603 | * @return integer|null
604 | */
605 | public function getId() {
606 | return $this->id;
607 | }
608 |
609 | public function getPayment(): ?DonationPayment {
610 | return $this->payment;
611 | }
612 |
613 | public function setPayment( DonationPayment $payment ) {
614 | $this->payment = $payment;
615 | }
616 |
617 |
618 | /**
619 | * @since 2.0
620 | *
621 | * @param integer|null $id
622 | */
623 | public function setId( $id ) {
624 | $this->id = $id;
625 | }
626 |
627 | public function getUExpiry() {
628 | return $this->getDecodedData()['uexpiry'];
629 | }
630 |
631 | public function uTokenIsExpired() {
632 | return time() > strtotime( $this->getUExpiry() );
633 | }
634 |
635 | public function validateToken( $tokenToCheck, $serverSecret ) {
636 | $checkToken = preg_replace( '/\$.*$/', '', $tokenToCheck );
637 |
638 | $checkToken = $checkToken . '$' .
639 | sha1( sha1( "$checkToken+$serverSecret" ) . '|' .
640 | sha1( "{$this->id}+$serverSecret" ) . '|' .
641 | sha1( "{$this->creationTime->format( 'Y-m-d H:i:s' )}+$serverSecret" ) );
642 | return $checkToken === $tokenToCheck;
643 | }
644 |
645 | public function getEntryType( $mode = null ) {
646 | $data = $this->getDecodedData();
647 |
648 | if ( $mode === null ) {
649 | $mode = $this->publicRecord;
650 | if ( !is_int( $mode ) ) {
651 | return $this->publicRecord;
652 | }
653 | }
654 |
655 | if ( $mode == 1 || $mode == 2 ) {
656 | $eintrag = $this->donorFullName;
657 | } else {
658 | $eintrag = 'anonym';
659 | }
660 |
661 | if ( ( $mode == 1 || $mode == 3 ) && !empty( $data['ort'] ) ) {
662 | $eintrag .= ', ' . $data['ort'];
663 | }
664 |
665 | return $eintrag;
666 | }
667 |
668 | /**
669 | * @deprecated since 2.0, use encodeAndSetData or setDataObject instead
670 | *
671 | * @param string $data Base 64 encoded, serialized PHP array
672 | * @return self
673 | */
674 | public function setData( $data ) {
675 | $this->data = $data;
676 |
677 | return $this;
678 | }
679 |
680 | /**
681 | * @deprecated since 2.0, use @see getDecodedData or @see getDataObject instead
682 | *
683 | * @return string Base 64 encoded, serialized PHP array
684 | */
685 | public function getData() {
686 | return $this->data;
687 | }
688 |
689 | /**
690 | * NOTE: if possible, use @see getDataObject instead, as it provides a nicer API.
691 | *
692 | * @since 2.0
693 | * @return array
694 | */
695 | public function getDecodedData() {
696 | if ( $this->data === null ) {
697 | return [];
698 | }
699 |
700 | $data = unserialize( base64_decode( $this->data ) );
701 |
702 | return is_array( $data ) ? $data : [];
703 | }
704 |
705 | /**
706 | * NOTE: if possible, use @see modifyDataObject instead, as it provides a nicer API.
707 | *
708 | * @since 2.0
709 | * @param array $data
710 | */
711 | public function encodeAndSetData( array $data ) {
712 | $this->data = base64_encode( serialize( $data ) );
713 | }
714 |
715 | /**
716 | * WARNING: updates made to the return value will not be reflected in the Donation state.
717 | * Similarly, updates to the Donation state will not propagate to the returned object.
718 | * To update the Donation state, explicitly call @see setDataObject.
719 | *
720 | * @since 2.0
721 | * @return DonationData
722 | */
723 | public function getDataObject() {
724 | $dataArray = $this->getDecodedData();
725 |
726 | $data = new DonationData();
727 |
728 | $data->setAccessToken( array_key_exists( 'token', $dataArray ) ? $dataArray['token'] : null );
729 | $data->setUpdateToken( array_key_exists( 'utoken', $dataArray ) ? $dataArray['utoken'] : null );
730 | $data->setUpdateTokenExpiry( array_key_exists( 'uexpiry', $dataArray ) ? $dataArray['uexpiry'] : null );
731 |
732 | return $data;
733 | }
734 |
735 | /**
736 | * @since 2.0
737 | * @param DonationData $data
738 | */
739 | public function setDataObject( DonationData $data ) {
740 | $dataArray = array_merge(
741 | $this->getDecodedData(),
742 | [
743 | 'token' => $data->getAccessToken(),
744 | 'utoken' => $data->getUpdateToken(),
745 | 'uexpiry' => $data->getUpdateTokenExpiry(),
746 | ]
747 | );
748 |
749 | foreach ( [ 'token', 'utoken', 'uexpiry' ] as $keyName ) {
750 | if ( is_null( $dataArray[$keyName] ) ) {
751 | unset( $dataArray[$keyName] );
752 | }
753 | }
754 |
755 | $this->encodeAndSetData( $dataArray );
756 | }
757 |
758 | /**
759 | * @since 2.0
760 | * @param callable $modificationFunction Takes a modifiable DonationData parameter
761 | */
762 | public function modifyDataObject( callable $modificationFunction ) {
763 | $dataObject = $this->getDataObject();
764 | $modificationFunction( $dataObject );
765 | $this->setDataObject( $dataObject );
766 | }
767 | }
768 |
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
1 | The license text below "----" applies to all files within this distribution, other
2 | than those that are in a directory which contains files named "LICENSE" or
3 | "COPYING", or a subdirectory thereof. For those files, the license text contained in
4 | said file overrides any license information contained in directories of smaller depth.
5 | Alternative licenses are typically used for software that is provided by external
6 | parties, and merely packaged with this software for convenience.
7 | ----
8 |
9 | GNU GENERAL PUBLIC LICENSE
10 | Version 2, June 1991
11 |
12 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
13 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
14 | Everyone is permitted to copy and distribute verbatim copies
15 | of this license document, but changing it is not allowed.
16 |
17 | Preamble
18 |
19 | The licenses for most software are designed to take away your
20 | freedom to share and change it. By contrast, the GNU General Public
21 | License is intended to guarantee your freedom to share and change free
22 | software--to make sure the software is free for all its users. This
23 | General Public License applies to most of the Free Software
24 | Foundation's software and to any other program whose authors commit to
25 | using it. (Some other Free Software Foundation software is covered by
26 | the GNU Lesser General Public License instead.) You can apply it to
27 | your programs, too.
28 |
29 | When we speak of free software, we are referring to freedom, not
30 | price. Our General Public Licenses are designed to make sure that you
31 | have the freedom to distribute copies of free software (and charge for
32 | this service if you wish), that you receive source code or can get it
33 | if you want it, that you can change the software or use pieces of it
34 | in new free programs; and that you know you can do these things.
35 |
36 | To protect your rights, we need to make restrictions that forbid
37 | anyone to deny you these rights or to ask you to surrender the rights.
38 | These restrictions translate to certain responsibilities for you if you
39 | distribute copies of the software, or if you modify it.
40 |
41 | For example, if you distribute copies of such a program, whether
42 | gratis or for a fee, you must give the recipients all the rights that
43 | you have. You must make sure that they, too, receive or can get the
44 | source code. And you must show them these terms so they know their
45 | rights.
46 |
47 | We protect your rights with two steps: (1) copyright the software, and
48 | (2) offer you this license which gives you legal permission to copy,
49 | distribute and/or modify the software.
50 |
51 | Also, for each author's protection and ours, we want to make certain
52 | that everyone understands that there is no warranty for this free
53 | software. If the software is modified by someone else and passed on, we
54 | want its recipients to know that what they have is not the original, so
55 | that any problems introduced by others will not reflect on the original
56 | authors' reputations.
57 |
58 | Finally, any free program is threatened constantly by software
59 | patents. We wish to avoid the danger that redistributors of a free
60 | program will individually obtain patent licenses, in effect making the
61 | program proprietary. To prevent this, we have made it clear that any
62 | patent must be licensed for everyone's free use or not licensed at all.
63 |
64 | The precise terms and conditions for copying, distribution and
65 | modification follow.
66 |
67 | GNU GENERAL PUBLIC LICENSE
68 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
69 |
70 | 0. This License applies to any program or other work which contains
71 | a notice placed by the copyright holder saying it may be distributed
72 | under the terms of this General Public License. The "Program", below,
73 | refers to any such program or work, and a "work based on the Program"
74 | means either the Program or any derivative work under copyright law:
75 | that is to say, a work containing the Program or a portion of it,
76 | either verbatim or with modifications and/or translated into another
77 | language. (Hereinafter, translation is included without limitation in
78 | the term "modification".) Each licensee is addressed as "you".
79 |
80 | Activities other than copying, distribution and modification are not
81 | covered by this License; they are outside its scope. The act of
82 | running the Program is not restricted, and the output from the Program
83 | is covered only if its contents constitute a work based on the
84 | Program (independent of having been made by running the Program).
85 | Whether that is true depends on what the Program does.
86 |
87 | 1. You may copy and distribute verbatim copies of the Program's
88 | source code as you receive it, in any medium, provided that you
89 | conspicuously and appropriately publish on each copy an appropriate
90 | copyright notice and disclaimer of warranty; keep intact all the
91 | notices that refer to this License and to the absence of any warranty;
92 | and give any other recipients of the Program a copy of this License
93 | along with the Program.
94 |
95 | You may charge a fee for the physical act of transferring a copy, and
96 | you may at your option offer warranty protection in exchange for a fee.
97 |
98 | 2. You may modify your copy or copies of the Program or any portion
99 | of it, thus forming a work based on the Program, and copy and
100 | distribute such modifications or work under the terms of Section 1
101 | above, provided that you also meet all of these conditions:
102 |
103 | a) You must cause the modified files to carry prominent notices
104 | stating that you changed the files and the date of any change.
105 |
106 | b) You must cause any work that you distribute or publish, that in
107 | whole or in part contains or is derived from the Program or any
108 | part thereof, to be licensed as a whole at no charge to all third
109 | parties under the terms of this License.
110 |
111 | c) If the modified program normally reads commands interactively
112 | when run, you must cause it, when started running for such
113 | interactive use in the most ordinary way, to print or display an
114 | announcement including an appropriate copyright notice and a
115 | notice that there is no warranty (or else, saying that you provide
116 | a warranty) and that users may redistribute the program under
117 | these conditions, and telling the user how to view a copy of this
118 | License. (Exception: if the Program itself is interactive but
119 | does not normally print such an announcement, your work based on
120 | the Program is not required to print an announcement.)
121 |
122 | These requirements apply to the modified work as a whole. If
123 | identifiable sections of that work are not derived from the Program,
124 | and can be reasonably considered independent and separate works in
125 | themselves, then this License, and its terms, do not apply to those
126 | sections when you distribute them as separate works. But when you
127 | distribute the same sections as part of a whole which is a work based
128 | on the Program, the distribution of the whole must be on the terms of
129 | this License, whose permissions for other licensees extend to the
130 | entire whole, and thus to each and every part regardless of who wrote it.
131 |
132 | Thus, it is not the intent of this section to claim rights or contest
133 | your rights to work written entirely by you; rather, the intent is to
134 | exercise the right to control the distribution of derivative or
135 | collective works based on the Program.
136 |
137 | In addition, mere aggregation of another work not based on the Program
138 | with the Program (or with a work based on the Program) on a volume of
139 | a storage or distribution medium does not bring the other work under
140 | the scope of this License.
141 |
142 | 3. You may copy and distribute the Program (or a work based on it,
143 | under Section 2) in object code or executable form under the terms of
144 | Sections 1 and 2 above provided that you also do one of the following:
145 |
146 | a) Accompany it with the complete corresponding machine-readable
147 | source code, which must be distributed under the terms of Sections
148 | 1 and 2 above on a medium customarily used for software interchange; or,
149 |
150 | b) Accompany it with a written offer, valid for at least three
151 | years, to give any third party, for a charge no more than your
152 | cost of physically performing source distribution, a complete
153 | machine-readable copy of the corresponding source code, to be
154 | distributed under the terms of Sections 1 and 2 above on a medium
155 | customarily used for software interchange; or,
156 |
157 | c) Accompany it with the information you received as to the offer
158 | to distribute corresponding source code. (This alternative is
159 | allowed only for noncommercial distribution and only if you
160 | received the program in object code or executable form with such
161 | an offer, in accord with Subsection b above.)
162 |
163 | The source code for a work means the preferred form of the work for
164 | making modifications to it. For an executable work, complete source
165 | code means all the source code for all modules it contains, plus any
166 | associated interface definition files, plus the scripts used to
167 | control compilation and installation of the executable. However, as a
168 | special exception, the source code distributed need not include
169 | anything that is normally distributed (in either source or binary
170 | form) with the major components (compiler, kernel, and so on) of the
171 | operating system on which the executable runs, unless that component
172 | itself accompanies the executable.
173 |
174 | If distribution of executable or object code is made by offering
175 | access to copy from a designated place, then offering equivalent
176 | access to copy the source code from the same place counts as
177 | distribution of the source code, even though third parties are not
178 | compelled to copy the source along with the object code.
179 |
180 | 4. You may not copy, modify, sublicense, or distribute the Program
181 | except as expressly provided under this License. Any attempt
182 | otherwise to copy, modify, sublicense or distribute the Program is
183 | void, and will automatically terminate your rights under this License.
184 | However, parties who have received copies, or rights, from you under
185 | this License will not have their licenses terminated so long as such
186 | parties remain in full compliance.
187 |
188 | 5. You are not required to accept this License, since you have not
189 | signed it. However, nothing else grants you permission to modify or
190 | distribute the Program or its derivative works. These actions are
191 | prohibited by law if you do not accept this License. Therefore, by
192 | modifying or distributing the Program (or any work based on the
193 | Program), you indicate your acceptance of this License to do so, and
194 | all its terms and conditions for copying, distributing or modifying
195 | the Program or works based on it.
196 |
197 | 6. Each time you redistribute the Program (or any work based on the
198 | Program), the recipient automatically receives a license from the
199 | original licensor to copy, distribute or modify the Program subject to
200 | these terms and conditions. You may not impose any further
201 | restrictions on the recipients' exercise of the rights granted herein.
202 | You are not responsible for enforcing compliance by third parties to
203 | this License.
204 |
205 | 7. If, as a consequence of a court judgment or allegation of patent
206 | infringement or for any other reason (not limited to patent issues),
207 | conditions are imposed on you (whether by court order, agreement or
208 | otherwise) that contradict the conditions of this License, they do not
209 | excuse you from the conditions of this License. If you cannot
210 | distribute so as to satisfy simultaneously your obligations under this
211 | License and any other pertinent obligations, then as a consequence you
212 | may not distribute the Program at all. For example, if a patent
213 | license would not permit royalty-free redistribution of the Program by
214 | all those who receive copies directly or indirectly through you, then
215 | the only way you could satisfy both it and this License would be to
216 | refrain entirely from distribution of the Program.
217 |
218 | If any portion of this section is held invalid or unenforceable under
219 | any particular circumstance, the balance of the section is intended to
220 | apply and the section as a whole is intended to apply in other
221 | circumstances.
222 |
223 | It is not the purpose of this section to induce you to infringe any
224 | patents or other property right claims or to contest validity of any
225 | such claims; this section has the sole purpose of protecting the
226 | integrity of the free software distribution system, which is
227 | implemented by public license practices. Many people have made
228 | generous contributions to the wide range of software distributed
229 | through that system in reliance on consistent application of that
230 | system; it is up to the author/donor to decide if he or she is willing
231 | to distribute software through any other system and a licensee cannot
232 | impose that choice.
233 |
234 | This section is intended to make thoroughly clear what is believed to
235 | be a consequence of the rest of this License.
236 |
237 | 8. If the distribution and/or use of the Program is restricted in
238 | certain countries either by patents or by copyrighted interfaces, the
239 | original copyright holder who places the Program under this License
240 | may add an explicit geographical distribution limitation excluding
241 | those countries, so that distribution is permitted only in or among
242 | countries not thus excluded. In such case, this License incorporates
243 | the limitation as if written in the body of this License.
244 |
245 | 9. The Free Software Foundation may publish revised and/or new versions
246 | of the General Public License from time to time. Such new versions will
247 | be similar in spirit to the present version, but may differ in detail to
248 | address new problems or concerns.
249 |
250 | Each version is given a distinguishing version number. If the Program
251 | specifies a version number of this License which applies to it and "any
252 | later version", you have the option of following the terms and conditions
253 | either of that version or of any later version published by the Free
254 | Software Foundation. If the Program does not specify a version number of
255 | this License, you may choose any version ever published by the Free Software
256 | Foundation.
257 |
258 | 10. If you wish to incorporate parts of the Program into other free
259 | programs whose distribution conditions are different, write to the author
260 | to ask for permission. For software which is copyrighted by the Free
261 | Software Foundation, write to the Free Software Foundation; we sometimes
262 | make exceptions for this. Our decision will be guided by the two goals
263 | of preserving the free status of all derivatives of our free software and
264 | of promoting the sharing and reuse of software generally.
265 |
266 | NO WARRANTY
267 |
268 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
269 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
270 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
271 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
272 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
273 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
274 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
275 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
276 | REPAIR OR CORRECTION.
277 |
278 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
279 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
280 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
281 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
282 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
283 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
284 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
285 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
286 | POSSIBILITY OF SUCH DAMAGES.
287 |
288 | END OF TERMS AND CONDITIONS
289 |
290 | How to Apply These Terms to Your New Programs
291 |
292 | If you develop a new program, and you want it to be of the greatest
293 | possible use to the public, the best way to achieve this is to make it
294 | free software which everyone can redistribute and change under these terms.
295 |
296 | To do so, attach the following notices to the program. It is safest
297 | to attach them to the start of each source file to most effectively
298 | convey the exclusion of warranty; and each file should have at least
299 | the "copyright" line and a pointer to where the full notice is found.
300 |
301 |
302 | Copyright (C)
303 |
304 | This program is free software; you can redistribute it and/or modify
305 | it under the terms of the GNU General Public License as published by
306 | the Free Software Foundation; either version 2 of the License, or
307 | (at your option) any later version.
308 |
309 | This program is distributed in the hope that it will be useful,
310 | but WITHOUT ANY WARRANTY; without even the implied warranty of
311 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
312 | GNU General Public License for more details.
313 |
314 | You should have received a copy of the GNU General Public License along
315 | with this program; if not, write to the Free Software Foundation, Inc.,
316 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
317 |
318 | Also add information on how to contact you by electronic and paper mail.
319 |
320 | If the program is interactive, make it output a short notice like this
321 | when it starts in an interactive mode:
322 |
323 | Gnomovision version 69, Copyright (C) year name of author
324 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
325 | This is free software, and you are welcome to redistribute it
326 | under certain conditions; type `show c' for details.
327 |
328 | The hypothetical commands `show w' and `show c' should show the appropriate
329 | parts of the General Public License. Of course, the commands you use may
330 | be called something other than `show w' and `show c'; they could even be
331 | mouse-clicks or menu items--whatever suits your program.
332 |
333 | You should also get your employer (if you work as a programmer) or your
334 | school, if any, to sign a "copyright disclaimer" for the program, if
335 | necessary. Here is a sample; alter the names:
336 |
337 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
338 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
339 |
340 | , 1 April 1989
341 | Ty Coon, President of Vice
342 |
343 | This General Public License does not permit incorporating your program into
344 | proprietary programs. If your program is a subroutine library, you may
345 | consider it more useful to permit linking proprietary applications with the
346 | library. If this is what you want to do, use the GNU Lesser General
347 | Public License instead of this License.
348 |
--------------------------------------------------------------------------------
/src/Entities/MembershipApplication.php:
--------------------------------------------------------------------------------
1 | id;
297 | }
298 |
299 | /**
300 | * @since 2.0
301 | *
302 | * @param integer|null $id
303 | */
304 | public function setId( $id ) {
305 | $this->id = $id;
306 | }
307 |
308 | /**
309 | * @param integer|null $donationId
310 | *
311 | * @return self
312 | */
313 | public function setDonationId( $donationId ) {
314 | $this->donationId = $donationId;
315 |
316 | return $this;
317 | }
318 |
319 | /**
320 | * Returns the id of the donation that led to the membership application,
321 | * or null when the application is not linked to any donation.
322 | *
323 | * @return integer|null
324 | */
325 | public function getDonationId() {
326 | return $this->donationId;
327 | }
328 |
329 | /**
330 | * @param \DateTime|null $creationTime
331 | *
332 | * @return self
333 | */
334 | public function setCreationTime( $creationTime ) {
335 | $this->creationTime = $creationTime;
336 |
337 | return $this;
338 | }
339 |
340 | /**
341 | * @return \DateTime|null
342 | */
343 | public function getCreationTime() {
344 | return $this->creationTime;
345 | }
346 |
347 | /**
348 | * @param string|null $applicantSalutation
349 | *
350 | * @return self
351 | */
352 | public function setApplicantSalutation( $applicantSalutation ) {
353 | $this->applicantSalutation = $applicantSalutation;
354 |
355 | return $this;
356 | }
357 |
358 | /**
359 | * @return string|null
360 | */
361 | public function getApplicantSalutation() {
362 | return $this->applicantSalutation;
363 | }
364 |
365 | /**
366 | * @param string|null $company
367 | *
368 | * @return self
369 | */
370 | public function setCompany( $company ) {
371 | $this->company = $company;
372 |
373 | return $this;
374 | }
375 |
376 | /**
377 | * @return string|null
378 | */
379 | public function getCompany() {
380 | return $this->company;
381 | }
382 |
383 | /**
384 | * @param string $applicantTitle
385 | *
386 | * @return self
387 | */
388 | public function setApplicantTitle( $applicantTitle ) {
389 | $this->applicantTitle = $applicantTitle;
390 |
391 | return $this;
392 | }
393 |
394 | /**
395 | * @return string
396 | */
397 | public function getApplicantTitle() {
398 | return $this->applicantTitle;
399 | }
400 |
401 | /**
402 | * @param string $probablyUnusedNameField
403 | *
404 | * @return self
405 | */
406 | public function setProbablyUnusedNameField( $probablyUnusedNameField ) {
407 | $this->probablyUnusedNameField = $probablyUnusedNameField;
408 |
409 | return $this;
410 | }
411 |
412 | /**
413 | * @return string
414 | */
415 | public function getProbablyUnusedNameField() {
416 | return $this->probablyUnusedNameField;
417 | }
418 |
419 | /**
420 | * @param string $applicantFirstName
421 | *
422 | * @return self
423 | */
424 | public function setApplicantFirstName( $applicantFirstName ) {
425 | $this->applicantFirstName = $applicantFirstName;
426 | $this->setNameFromParts( $applicantFirstName, $this->getApplicantLastName() );
427 |
428 | return $this;
429 | }
430 |
431 | /**
432 | * @return string
433 | */
434 | public function getApplicantFirstName() {
435 | return $this->applicantFirstName;
436 | }
437 |
438 | /**
439 | * @param string $applicantLastName
440 | *
441 | * @return self
442 | */
443 | public function setApplicantLastName( $applicantLastName ) {
444 | $this->applicantLastName = $applicantLastName;
445 | $this->setNameFromParts( $this->getApplicantFirstName(), $applicantLastName );
446 |
447 | return $this;
448 | }
449 |
450 | /**
451 | * @return string
452 | */
453 | public function getApplicantLastName() {
454 | return $this->applicantLastName;
455 | }
456 |
457 | /**
458 | * Sets the full name
459 | *
460 | * @param string|null $firstName
461 | * @param string|null $lastName
462 | *
463 | * @return self
464 | */
465 | private function setNameFromParts( $firstName, $lastName ) {
466 | $this->setProbablyUnusedNameField( implode(
467 | ' ',
468 | array_filter( [ $firstName, $lastName ] )
469 | ) );
470 |
471 | return $this;
472 | }
473 |
474 | /**
475 | * Set address (street, etc)
476 | *
477 | * @param string|null $address
478 | *
479 | * @return self
480 | */
481 | public function setAddress( $address ) {
482 | $this->address = $address;
483 |
484 | return $this;
485 | }
486 |
487 | /**
488 | * Get address (street, etc)
489 | *
490 | * @return string|null
491 | */
492 | public function getAddress() {
493 | return $this->address;
494 | }
495 |
496 | /**
497 | * @param string|null $postcode
498 | *
499 | * @return self
500 | */
501 | public function setPostcode( $postcode ) {
502 | $this->postcode = $postcode;
503 |
504 | return $this;
505 | }
506 |
507 | /**
508 | * @return string|null
509 | */
510 | public function getPostcode() {
511 | return $this->postcode;
512 | }
513 |
514 | /**
515 | * @param string|null $city
516 | *
517 | * @return self
518 | */
519 | public function setCity( $city ) {
520 | $this->city = $city;
521 |
522 | return $this;
523 | }
524 |
525 | /**
526 | * @return string|null
527 | */
528 | public function getCity() {
529 | return $this->city;
530 | }
531 |
532 | /**
533 | * Set email
534 | *
535 | * @param string $applicantEmailAddress
536 | *
537 | * @return self
538 | */
539 | public function setApplicantEmailAddress( $applicantEmailAddress ) {
540 | $this->applicantEmailAddress = $applicantEmailAddress;
541 |
542 | return $this;
543 | }
544 |
545 | /**
546 | * Get email
547 | *
548 | * @return string
549 | */
550 | public function getApplicantEmailAddress() {
551 | return $this->applicantEmailAddress;
552 | }
553 |
554 | /**
555 | * Set phone
556 | *
557 | * @param string $applicantPhoneNumber
558 | *
559 | * @return self
560 | */
561 | public function setApplicantPhoneNumber( $applicantPhoneNumber ) {
562 | $this->applicantPhoneNumber = $applicantPhoneNumber;
563 |
564 | return $this;
565 | }
566 |
567 | /**
568 | * Get phone
569 | *
570 | * @return string
571 | */
572 | public function getApplicantPhoneNumber() {
573 | return $this->applicantPhoneNumber;
574 | }
575 |
576 | /**
577 | * @param \DateTime|null $dateOfBirth
578 | *
579 | * @return self
580 | */
581 | public function setApplicantDateOfBirth( $dateOfBirth ) {
582 | $this->applicantDateOfBirth = $dateOfBirth;
583 |
584 | return $this;
585 | }
586 |
587 | /**
588 | * @return \DateTime|null
589 | */
590 | public function getApplicantDateOfBirth() {
591 | return $this->applicantDateOfBirth;
592 | }
593 |
594 | /**
595 | * @param string $wikimediumShipping
596 | *
597 | * @return self
598 | */
599 | public function setWikimediumShipping( $wikimediumShipping ) {
600 | $this->wikimediumShipping = $wikimediumShipping;
601 |
602 | return $this;
603 | }
604 |
605 | /**
606 | * @return string
607 | */
608 | public function getWikimediumShipping() {
609 | return $this->wikimediumShipping;
610 | }
611 |
612 | /**
613 | * @param string $membershipType
614 | *
615 | * @return self
616 | */
617 | public function setMembershipType( $membershipType ) {
618 | $this->membershipType = $membershipType;
619 |
620 | return $this;
621 | }
622 |
623 | /**
624 | * @return string
625 | */
626 | public function getMembershipType() {
627 | return $this->membershipType;
628 | }
629 |
630 | /**
631 | * @since 2.1
632 | *
633 | * @param string $paymentType
634 | *
635 | * @return self
636 | */
637 | public function setPaymentType( $paymentType ) {
638 | $this->paymentType = $paymentType;
639 |
640 | return $this;
641 | }
642 |
643 | /**
644 | * @since 2.1
645 | *
646 | * @return string
647 | */
648 | public function getPaymentType() {
649 | return $this->paymentType;
650 | }
651 |
652 | /**
653 | * @param integer $paymentAmountInEuro
654 | *
655 | * @return self
656 | */
657 | public function setPaymentAmount( $paymentAmountInEuro ) {
658 | $this->paymentAmountInEuro = $paymentAmountInEuro;
659 |
660 | return $this;
661 | }
662 |
663 | /**
664 | * @return integer
665 | */
666 | public function getPaymentAmount() {
667 | return $this->paymentAmountInEuro;
668 | }
669 |
670 | /**
671 | * @param integer $paymentIntervalInMonths
672 | *
673 | * @return self
674 | */
675 | public function setPaymentIntervalInMonths($paymentIntervalInMonths) {
676 | $this->paymentIntervalInMonths = $paymentIntervalInMonths;
677 |
678 | return $this;
679 | }
680 |
681 | /**
682 | * @return integer
683 | */
684 | public function getPaymentIntervalInMonths() {
685 | return $this->paymentIntervalInMonths;
686 | }
687 |
688 |
689 | /**
690 | * @param string $paymentBankAccount
691 | *
692 | * @return self
693 | */
694 | public function setPaymentBankAccount( $paymentBankAccount ) {
695 | $this->paymentBankAccount = $paymentBankAccount;
696 |
697 | return $this;
698 | }
699 |
700 | /**
701 | * @return string
702 | */
703 | public function getPaymentBankAccount() {
704 | return $this->paymentBankAccount;
705 | }
706 |
707 | /**
708 | * @param string $paymentBankName
709 | *
710 | * @return self
711 | */
712 | public function setPaymentBankName( $paymentBankName ) {
713 | $this->paymentBankName = $paymentBankName;
714 |
715 | return $this;
716 | }
717 |
718 | /**
719 | * @return string
720 | */
721 | public function getPaymentBankName() {
722 | return $this->paymentBankName;
723 | }
724 |
725 | /**
726 | * @param string $paymentBankCode
727 | *
728 | * @return self
729 | */
730 | public function setPaymentBankCode( $paymentBankCode ) {
731 | $this->paymentBankCode = $paymentBankCode;
732 |
733 | return $this;
734 | }
735 |
736 | /**
737 | * @return string
738 | */
739 | public function getPaymentBankCode() {
740 | return $this->paymentBankCode;
741 | }
742 |
743 | /**
744 | * @param string|null $paymentIban
745 | *
746 | * @return self
747 | */
748 | public function setPaymentIban( $paymentIban ) {
749 | $this->paymentIban = $paymentIban;
750 |
751 | return $this;
752 | }
753 |
754 | /**
755 | * @return string|null
756 | */
757 | public function getPaymentIban() {
758 | return $this->paymentIban;
759 | }
760 |
761 | /**
762 | * @param string|null $paymentBic
763 | *
764 | * @return self
765 | */
766 | public function setPaymentBic( $paymentBic ) {
767 | $this->paymentBic = $paymentBic;
768 |
769 | return $this;
770 | }
771 |
772 | /**
773 | * @return string|null
774 | */
775 | public function getPaymentBic() {
776 | return $this->paymentBic;
777 | }
778 |
779 | /**
780 | * @param string $paymentBankAccountHolder
781 | *
782 | * @return self
783 | */
784 | public function setPaymentBankAccountHolder( $paymentBankAccountHolder ) {
785 | $this->paymentBankAccountHolder = $paymentBankAccountHolder;
786 |
787 | return $this;
788 | }
789 |
790 | /**
791 | * @return string
792 | */
793 | public function getPaymentBankAccountHolder() {
794 | return $this->paymentBankAccountHolder;
795 | }
796 |
797 | /**
798 | * @param string $comment
799 | *
800 | * @return self
801 | */
802 | public function setComment( $comment ) {
803 | $this->comment = $comment;
804 |
805 | return $this;
806 | }
807 |
808 | /**
809 | * @return string
810 | */
811 | public function getComment() {
812 | return $this->comment;
813 | }
814 |
815 | /**
816 | * Sets the time of export.
817 | *
818 | * @param \DateTime|null $export
819 | *
820 | * @return self
821 | */
822 | public function setExport( $export ) {
823 | $this->export = $export;
824 |
825 | return $this;
826 | }
827 |
828 | /**
829 | * Returns the time of export.
830 | *
831 | * @return \DateTime|null
832 | */
833 | public function getExport() {
834 | return $this->export;
835 | }
836 |
837 | /**
838 | * Sets the time of backup.
839 | *
840 | * @param \DateTime|null $backup
841 | *
842 | * @return self
843 | */
844 | public function setBackup( $backup ) {
845 | $this->backup = $backup;
846 |
847 | return $this;
848 | }
849 |
850 | /**
851 | * Returns the time of backup.
852 | *
853 | * @return \DateTime|null
854 | */
855 | public function getBackup() {
856 | return $this->backup;
857 | }
858 |
859 | /**
860 | * @param boolean $wikilogin
861 | *
862 | * @return self
863 | */
864 | public function setWikilogin( $wikilogin ) {
865 | $this->wikilogin = $wikilogin;
866 |
867 | return $this;
868 | }
869 |
870 | /**
871 | * @return boolean
872 | */
873 | public function getWikilogin() {
874 | return $this->wikilogin;
875 | }
876 |
877 | /**
878 | * @param string|null $tracking
879 | *
880 | * @return self
881 | */
882 | public function setTracking( $tracking ) {
883 | $this->tracking = $tracking;
884 |
885 | return $this;
886 | }
887 |
888 | /**
889 | * @return string|null
890 | */
891 | public function getTracking() {
892 | return $this->tracking;
893 | }
894 |
895 | /**
896 | * Sets the status of the membership request.
897 | * The allowed values are the STATUS_ constants in this class.
898 | *
899 | * @param integer $status
900 | *
901 | * @return self
902 | */
903 | public function setStatus( $status ) {
904 | $this->status = $status;
905 |
906 | return $this;
907 | }
908 |
909 | /**
910 | * Returns the status of the membership request.
911 | * The possible values are the STATUS_ constants in this class.
912 | *
913 | * @return integer
914 | */
915 | public function getStatus() {
916 | return $this->status;
917 | }
918 |
919 | /**
920 | * @param string|null $country
921 | *
922 | * @return self
923 | */
924 | public function setCountry( $country ) {
925 | $this->country = $country;
926 |
927 | return $this;
928 | }
929 |
930 | /**
931 | * @return string|null
932 | */
933 | public function getCountry() {
934 | return $this->country;
935 | }
936 |
937 | /**
938 | * @param string|null $data
939 | * @return self
940 | */
941 | public function setData( $data ) {
942 | $this->data = $data;
943 |
944 | return $this;
945 | }
946 |
947 | /**
948 | * @return string|null
949 | */
950 | public function getData() {
951 | return $this->data;
952 | }
953 |
954 | /**
955 | * @return bool
956 | */
957 | public function isUnconfirmed() {
958 | return $this->status === self::STATUS_NEUTRAL;
959 | }
960 |
961 | /**
962 | * @return bool
963 | */
964 | public function needsModeration() {
965 | return $this->status < 0 && abs( $this->status ) & abs( self::STATUS_MODERATION );
966 | }
967 |
968 | /**
969 | * @return bool
970 | */
971 | public function isCancelled() {
972 | return $this->status < 0 && abs( $this->status ) & abs( self::STATUS_CANCELED );
973 | }
974 |
975 | /**
976 | * @since 4.2
977 | * @return bool
978 | */
979 | public function isDeleted() {
980 | return $this->status < 0 && abs( $this->status ) & abs( self::STATUS_DELETED );
981 | }
982 |
983 | public function log( $message ) {
984 | $dataArray = $this->getDecodedData();
985 | $dataArray['log'][date( 'Y-m-d H:i:s' )] = $message;
986 | $this->encodeAndSetData( $dataArray );
987 |
988 | return $this;
989 | }
990 |
991 | /**
992 | * NOTE: if possible, use @see getDataObject instead, as it provides a nicer API.
993 | *
994 | * @since 2.0
995 | * @return array
996 | */
997 | public function getDecodedData() {
998 | if ( $this->data === null ) {
999 | return [];
1000 | }
1001 |
1002 | $data = unserialize( base64_decode( $this->data ) );
1003 |
1004 | return is_array( $data ) ? $data : [];
1005 | }
1006 |
1007 | /**
1008 | * NOTE: if possible, use @see modifyDataObject instead, as it provides a nicer API.
1009 | *
1010 | * @since 2.0
1011 | * @param array $dataArray
1012 | */
1013 | public function encodeAndSetData( array $dataArray ) {
1014 | $this->data = base64_encode( serialize( $dataArray ) );
1015 | }
1016 |
1017 | /**
1018 | * WARNING: updates made to the return value will not be reflected in the Donation state.
1019 | * Similarly, updates to the Donation state will not propagate to the returned object.
1020 | * To update the Donation state, explicitly call @see setDataObject.
1021 | *
1022 | * @since 2.0
1023 | * @return MembershipApplicationData
1024 | */
1025 | public function getDataObject() {
1026 | $dataArray = $this->getDecodedData();
1027 |
1028 | $data = new MembershipApplicationData();
1029 |
1030 | $data->setAccessToken( array_key_exists( 'token', $dataArray ) ? $dataArray['token'] : null );
1031 | $data->setUpdateToken( array_key_exists( 'utoken', $dataArray ) ? $dataArray['utoken'] : null );
1032 | $data->setPreservedStatus( array_key_exists( 'old_status', $dataArray ) ? $dataArray['old_status'] : null );
1033 |
1034 | return $data;
1035 | }
1036 |
1037 | /**
1038 | * @since 2.0
1039 | * @param MembershipApplicationData $data
1040 | */
1041 | public function setDataObject( MembershipApplicationData $data ) {
1042 | $dataArray = array_merge(
1043 | $this->getDecodedData(),
1044 | [
1045 | 'token' => $data->getAccessToken(),
1046 | 'utoken' => $data->getUpdateToken(),
1047 | 'old_status' => $data->getPreservedStatus(),
1048 | ]
1049 | );
1050 |
1051 | foreach ( [ 'token', 'utoken', 'old_status' ] as $keyName ) {
1052 | if ( is_null( $dataArray[$keyName] ) ) {
1053 | unset( $dataArray[$keyName] );
1054 | }
1055 | }
1056 |
1057 | $this->encodeAndSetData( $dataArray );
1058 | }
1059 |
1060 | /**
1061 | * @since 2.0
1062 | * @param callable $modificationFunction Takes a modifiable MembershipApplicationData parameter
1063 | */
1064 | public function modifyDataObject( callable $modificationFunction ) {
1065 | $dataObject = $this->getDataObject();
1066 | $modificationFunction( $dataObject );
1067 | $this->setDataObject( $dataObject );
1068 | }
1069 |
1070 | /**
1071 | * Set donation receipt state
1072 | *
1073 | * @since 7.0
1074 | *
1075 | * @param boolean|null $donationReceipt
1076 | * @return self
1077 | */
1078 | public function setDonationReceipt( ?bool $donationReceipt ): self {
1079 | $this->donationReceipt = $donationReceipt;
1080 |
1081 | return $this;
1082 | }
1083 |
1084 | /**
1085 | * Get donation receipt state
1086 | *
1087 | * @since 7.0
1088 | *
1089 | * @return boolean|null
1090 | */
1091 | public function getDonationReceipt(): ?bool {
1092 | return $this->donationReceipt;
1093 | }
1094 | }
1095 |
--------------------------------------------------------------------------------