diff --git a/src/Campaigns/Actions/ConvertQueryDataToCampaign.php b/src/Campaigns/Actions/ConvertQueryDataToCampaign.php new file mode 100644 index 0000000000..405786561d --- /dev/null +++ b/src/Campaigns/Actions/ConvertQueryDataToCampaign.php @@ -0,0 +1,38 @@ + (int)$queryObject->id, + 'pageId' => (int)$queryObject->pageId, + 'type' => new CampaignType($queryObject->type), + 'title' => $queryObject->title, + 'shortDescription' => $queryObject->shortDescription, + 'longDescription' => $queryObject->longDescription, + 'logo' => $queryObject->logo, + 'image' => $queryObject->image, + 'primaryColor' => $queryObject->primaryColor, + 'secondaryColor' => $queryObject->secondaryColor, + 'goal' => (int)$queryObject->goal, + 'startDate' => Temporal::toDateTime($queryObject->startDate), + 'endDate' => Temporal::toDateTime($queryObject->endDate), + 'status' => new CampaignStatus($queryObject->status), + 'createdAt' => Temporal::toDateTime($queryObject->createdAt), + ]); + } +} diff --git a/src/Campaigns/Actions/DeleteCampaignPage.php b/src/Campaigns/Actions/DeleteCampaignPage.php new file mode 100644 index 0000000000..b807bdff63 --- /dev/null +++ b/src/Campaigns/Actions/DeleteCampaignPage.php @@ -0,0 +1,23 @@ + 1, + 'type' => CampaignType::CORE(), + 'title' => __('GiveWP Campaign', 'give'), + 'shortDescription' => __('Campaign short description', 'give'), + 'longDescription' => __('Campaign long description', 'give'), + 'goal' => 10000000, + 'status' => CampaignStatus::ACTIVE(), + 'logo' => '', + 'image' => '', + 'primaryColor' => '#28C77B', + 'secondaryColor' => '#FFA200', + 'createdAt' => Temporal::withoutMicroseconds($currentDate), + 'startDate' => Temporal::withoutMicroseconds($currentDate), + 'endDate' => Temporal::withoutMicroseconds($currentDate->modify('+1 day')), + ]; + } +} diff --git a/src/Campaigns/Migrations/P2P/SetCampaignType.php b/src/Campaigns/Migrations/P2P/SetCampaignType.php new file mode 100644 index 0000000000..66f9d3d292 --- /dev/null +++ b/src/Campaigns/Migrations/P2P/SetCampaignType.php @@ -0,0 +1,58 @@ +where('campaign_type', '') + ->update([ + 'campaign_type' => CampaignType::PEER_TO_PEER + ]); + } catch (DatabaseQueryException $exception) { + throw new DatabaseMigrationException('An error occurred while updating the campaign type', 0, $exception); + } + } +} diff --git a/src/Campaigns/Migrations/Tables/CreateCampaignFormsTable.php b/src/Campaigns/Migrations/Tables/CreateCampaignFormsTable.php index 1fa058c382..ac5df25ae5 100644 --- a/src/Campaigns/Migrations/Tables/CreateCampaignFormsTable.php +++ b/src/Campaigns/Migrations/Tables/CreateCampaignFormsTable.php @@ -45,15 +45,15 @@ public function run(): void { global $wpdb; - $table = $wpdb->prefix . 'give_campaign_forms'; + $table = $wpdb->give_campaign_forms; $charset = DB::get_charset_collate(); $sql = "CREATE TABLE $table ( - campaign_id INT UNSIGNED NOT NULL, - form_id INT UNSIGNED NOT NULL, - PRIMARY KEY (campaign_id), - KEY form_id (form_id) - ) $charset"; + campaign_id INT UNSIGNED NOT NULL, + form_id INT UNSIGNED NOT NULL, + PRIMARY KEY (campaign_id), + KEY form_id (form_id) + ) $charset"; try { DB::delta($sql); diff --git a/src/Campaigns/Migrations/Tables/CreateCampaignsTable.php b/src/Campaigns/Migrations/Tables/CreateCampaignsTable.php index 8bd6b2dec8..32d68e2b57 100644 --- a/src/Campaigns/Migrations/Tables/CreateCampaignsTable.php +++ b/src/Campaigns/Migrations/Tables/CreateCampaignsTable.php @@ -18,7 +18,7 @@ class CreateCampaignsTable extends Migration */ public static function id(): string { - return 'give-campaigns-create-give-core-campaigns-table'; + return 'give-campaigns-create-give-campaigns-table'; } /** @@ -45,28 +45,29 @@ public function run() { global $wpdb; - $table = $wpdb->prefix . 'give_campaigns'; + $table = $wpdb->give_campaigns; $charset = DB::get_charset_collate(); $sql = "CREATE TABLE $table ( - id INT UNSIGNED NOT NULL AUTO_INCREMENT, - form_id INT NOT NULL, - campaign_type VARCHAR(12) NOT NULL DEFAULT '', - campaign_title TEXT NOT NULL, - campaign_url TEXT NOT NULL, - short_desc TEXT NOT NULL, - long_desc TEXT NOT NULL, - campaign_logo TEXT NOT NULL, - campaign_image TEXT NOT NULL, - primary_color VARCHAR(7) NOT NULL, - secondary_color VARCHAR(7) NOT NULL, - campaign_goal INT UNSIGNED NOT NULL, - status VARCHAR(12) NOT NULL, - start_date DATETIME NULL, - end_date DATETIME NULL, - date_created DATETIME NOT NULL, - PRIMARY KEY (id) - ) $charset"; + id INT UNSIGNED NOT NULL AUTO_INCREMENT, + campaign_page_id INT UNSIGNED NULL, + form_id INT NOT NULL, + campaign_type VARCHAR(12) NOT NULL DEFAULT '', + campaign_title TEXT NOT NULL, + campaign_url TEXT NOT NULL, + short_desc TEXT NOT NULL, + long_desc TEXT NOT NULL, + campaign_logo TEXT NOT NULL, + campaign_image TEXT NOT NULL, + primary_color VARCHAR(7) NOT NULL, + secondary_color VARCHAR(7) NOT NULL, + campaign_goal INT UNSIGNED NOT NULL, + status VARCHAR(12) NOT NULL, + start_date DATETIME NULL, + end_date DATETIME NULL, + date_created DATETIME NOT NULL, + PRIMARY KEY (id) + ) $charset"; try { DB::delta($sql); diff --git a/src/Campaigns/Models/Campaign.php b/src/Campaigns/Models/Campaign.php new file mode 100644 index 0000000000..298656598d --- /dev/null +++ b/src/Campaigns/Models/Campaign.php @@ -0,0 +1,136 @@ + 'int', + 'pageId' => 'int', + 'type' => CampaignType::class, + 'title' => 'string', + 'shortDescription' => 'string', + 'longDescription' => 'string', + 'logo' => 'string', + 'image' => 'string', + 'primaryColor' => 'string', + 'secondaryColor' => 'string', + 'goal' => 'int', + 'status' => CampaignStatus::class, + 'startDate' => DateTime::class, + 'endDate' => DateTime::class, + 'createdAt' => DateTime::class, + ]; + + /** + * @unreleased + */ + public static function factory(): CampaignFactory + { + return new CampaignFactory(static::class); + } + + /** + * Find campaign by ID + * + * @unreleased + */ + public static function find($id): ?Campaign + { + return give(CampaignRepository::class)->getById($id); + } + + /** + * @unreleased + * + * @throws Exception + */ + public static function create(array $attributes): Campaign + { + $campaign = new static($attributes); + + give(CampaignRepository::class)->insert($campaign); + + return $campaign; + } + + /** + * @unreleased + * + * @throws Exception|InvalidArgumentException + */ + public function save(): void + { + if ( ! $this->id) { + give(CampaignRepository::class)->insert($this); + } else { + give(CampaignRepository::class)->update($this); + } + } + + /** + * @unreleased + * + * @throws Exception + */ + public function delete(): bool + { + return give(CampaignRepository::class)->delete($this); + } + + /** + * @unreleased + * + * @return ModelQueryBuilder + */ + public static function query(): ModelQueryBuilder + { + return give(CampaignRepository::class)->prepareQuery(); + } + + /** + * @unreleased + * + * @param object $object + */ + public static function fromQueryBuilderObject($object): Campaign + { + return (new ConvertQueryDataToCampaign())($object); + } +} diff --git a/src/Campaigns/Repositories/CampaignRepository.php b/src/Campaigns/Repositories/CampaignRepository.php new file mode 100644 index 0000000000..1f79b36c47 --- /dev/null +++ b/src/Campaigns/Repositories/CampaignRepository.php @@ -0,0 +1,211 @@ +prepareQuery() + ->where('id', $id) + ->get(); + } + + /** + * @unreleased + * + * @throws Exception|InvalidArgumentException + */ + public function insert(Campaign $campaign): void + { + $this->validateProperties($campaign); + + Hooks::doAction('givewp_campaign_creating', $campaign); + + $dateCreated = Temporal::withoutMicroseconds($campaign->createdAt ?: Temporal::getCurrentDateTime()); + $dateCreatedFormatted = Temporal::getFormattedDateTime($dateCreated); + $startDateFormatted = Temporal::getFormattedDateTime($campaign->startDate); + $endDateFormatted = Temporal::getFormattedDateTime($campaign->endDate); + + DB::query('START TRANSACTION'); + + try { + DB::table('give_campaigns') + ->insert([ + 'campaign_page_id' => $campaign->pageId, + 'campaign_type' => $campaign->type->getValue(), + 'campaign_title' => $campaign->title, + 'short_desc' => $campaign->shortDescription, + 'long_desc' => $campaign->longDescription, + 'campaign_logo' => $campaign->logo, + 'campaign_image' => $campaign->image, + 'primary_color' => $campaign->primaryColor, + 'secondary_color' => $campaign->secondaryColor, + 'campaign_goal' => $campaign->goal, + 'status' => $campaign->status->getValue(), + 'start_date' => $startDateFormatted, + 'end_date' => $endDateFormatted, + 'date_created' => $dateCreatedFormatted + ]); + + $campaignId = DB::last_insert_id(); + } catch (Exception $exception) { + DB::query('ROLLBACK'); + + Log::error('Failed creating a campaign', compact('campaign')); + + throw new $exception('Failed creating a campaign'); + } + + DB::query('COMMIT'); + + $campaign->id = $campaignId; + $campaign->createdAt = $dateCreated; + + Hooks::doAction('givewp_campaign_created', $campaign); + } + + /** + * @unreleased + * + * @throws Exception|InvalidArgumentException + */ + public function update(Campaign $campaign): void + { + $this->validateProperties($campaign); + + $startDateFormatted = Temporal::getFormattedDateTime($campaign->startDate); + $endDateFormatted = Temporal::getFormattedDateTime($campaign->endDate); + + Hooks::doAction('givewp_campaign_updating', $campaign); + + DB::query('START TRANSACTION'); + + try { + DB::table('give_campaigns') + ->where('id', $campaign->id) + ->update([ + 'campaign_type' => $campaign->type->getValue(), + 'campaign_page_id' => $campaign->pageId, + 'campaign_title' => $campaign->title, + 'short_desc' => $campaign->shortDescription, + 'long_desc' => $campaign->longDescription, + 'campaign_logo' => $campaign->logo, + 'campaign_image' => $campaign->image, + 'primary_color' => $campaign->primaryColor, + 'secondary_color' => $campaign->secondaryColor, + 'campaign_goal' => $campaign->goal, + 'status' => $campaign->status->getValue(), + 'start_date' => $startDateFormatted, + 'end_date' => $endDateFormatted, + ]); + } catch (Exception $exception) { + DB::query('ROLLBACK'); + + Log::error('Failed updating a campaign', compact('campaign')); + + throw new $exception('Failed updating a campaign'); + } + + DB::query('COMMIT'); + + Hooks::doAction('givewp_campaign_updated', $campaign); + } + + /** + * @unreleased + * + * @throws Exception + */ + public function delete(Campaign $campaign): bool + { + DB::query('START TRANSACTION'); + + Hooks::doAction('givewp_campaign_deleting', $campaign); + + try { + DB::table('give_campaigns') + ->where('id', $campaign->id) + ->delete(); + + } catch (Exception $exception) { + DB::query('ROLLBACK'); + + Log::error('Failed deleting a campaign', compact('campaign')); + + throw new $exception('Failed deleting a campaign'); + } + + DB::query('COMMIT'); + + Hooks::doAction('givewp_campaign_deleted', $campaign); + + return true; + } + + /** + * @unreleased + */ + private function validateProperties(Campaign $campaign): void + { + foreach ($this->requiredProperties as $key) { + if ( ! isset($campaign->$key)) { + throw new InvalidArgumentException("'$key' is required."); + } + } + } + + /** + * @unreleased + * + * @return ModelQueryBuilder + */ + public function prepareQuery(): ModelQueryBuilder + { + $builder = new ModelQueryBuilder(Campaign::class); + + return $builder->from('give_campaigns') + ->select( + 'id', + ['campaign_page_id', 'pageId'], + ['campaign_type', 'type'], + ['campaign_title', 'title'], + ['short_desc', 'shortDescription'], + ['long_desc', 'longDescription'], + ['campaign_logo', 'logo'], + ['campaign_image', 'image'], + ['primary_color', 'primaryColor'], + ['secondary_color', 'secondaryColor'], + ['campaign_goal', 'goal'], + 'status', + ['start_date', 'startDate'], + ['end_date', 'endDate'], + ['date_created', 'createdAt'] + ); + } +} diff --git a/src/Campaigns/ServiceProvider.php b/src/Campaigns/ServiceProvider.php index 8e8935f337..2c4a52216e 100644 --- a/src/Campaigns/ServiceProvider.php +++ b/src/Campaigns/ServiceProvider.php @@ -2,6 +2,8 @@ namespace Give\Campaigns; +use Give\Campaigns\Actions\DeleteCampaignPage; +use Give\Campaigns\Migrations\P2P\SetCampaignType; use Give\Campaigns\Migrations\Tables\CreateCampaignFormsTable; use Give\Campaigns\Migrations\Tables\CreateCampaignsTable; use Give\Framework\Migrations\MigrationsRegister; @@ -28,11 +30,9 @@ public function register(): void */ public function boot(): void { - // Hooks::addAction('init', Actions\MyAction::class); - // Hooks::addAction('rest_api_init', Controllers\MyEndpoint::class); - - $this->registerMigrations(); $this->registerMenus(); + $this->registerActions(); + $this->registerMigrations(); } @@ -44,6 +44,7 @@ private function registerMigrations(): void give(MigrationsRegister::class)->addMigrations( [ CreateCampaignsTable::class, + SetCampaignType::class, CreateCampaignFormsTable::class, ] ); @@ -61,6 +62,15 @@ private function registerTableNames(): void $wpdb->give_campaign_forms = $wpdb->prefix . 'give_campaign_forms'; } + + /** + * @unreleased + */ + private function registerActions(): void + { + Hooks::addAction('givewp_campaign_deleted', DeleteCampaignPage::class); + } + /** * @unreleased */ diff --git a/src/Campaigns/ValueObjects/CampaignStatus.php b/src/Campaigns/ValueObjects/CampaignStatus.php new file mode 100644 index 0000000000..f109fb01ab --- /dev/null +++ b/src/Campaigns/ValueObjects/CampaignStatus.php @@ -0,0 +1,33 @@ +create(); + $campaign = Campaign::find($mockCampaign->id); + + $this->assertInstanceOf(Campaign::class, $campaign); + $this->assertEquals($mockCampaign->toArray(), $campaign->toArray()); + } +} diff --git a/tests/Unit/Campaigns/Repositories/CampaignRepositoryTest.php b/tests/Unit/Campaigns/Repositories/CampaignRepositoryTest.php new file mode 100644 index 0000000000..adcb0308ae --- /dev/null +++ b/tests/Unit/Campaigns/Repositories/CampaignRepositoryTest.php @@ -0,0 +1,164 @@ +create(); + $repository = new CampaignRepository(); + + $campaign = $repository->getById($campaignFactory->id); + + $this->assertInstanceOf(Campaign::class, $campaignFactory); + $this->assertEquals($campaignFactory->id, $campaign->id); + } + + /** + * @unreleased + * + * @throws Exception + */ + public function testInsertShouldAddCampaignToDatabase() + { + $campaignFactory = new Campaign(Campaign::factory()->definition()); + $repository = new CampaignRepository(); + + $repository->insert($campaignFactory); + + $campaign = $repository->getById($campaignFactory->id); + + $this->assertEquals($campaign->getAttributes(), $campaignFactory->getAttributes()); + } + + /** + * @unreleased + * + * @throws Exception + */ + public function testInsertShouldFailValidationWhenMissingKeyAndThrowException() + { + $this->expectException(InvalidArgumentException::class); + + $currentDate = Temporal::getCurrentDateTime(); + + $campaignMissingStatus = new Campaign([ + 'pageId' => 1, + 'type' => CampaignType::CORE(), + 'title' => __('GiveWP Campaign', 'give'), + 'shortDescription' => __('Campaign short description', 'give'), + 'longDescription' => __('Campaign long description', 'give'), + 'goal' => 10000000, + 'logo' => '', + 'image' => '', + 'primaryColor' => '#28C77B', + 'secondaryColor' => '#FFA200', + 'createdAt' => Temporal::withoutMicroseconds($currentDate), + 'startDate' => Temporal::withoutMicroseconds($currentDate), + 'endDate' => Temporal::withoutMicroseconds($currentDate->modify('+1 day')), + ]); + + (new CampaignRepository())->insert($campaignMissingStatus); + } + + + /** + * @unreleased + * + * @throws Exception + */ + public function testUpdateShouldFailValidationWhenMissingKeyAndThrowException() + { + $this->expectException(InvalidArgumentException::class); + + $currentDate = Temporal::getCurrentDateTime(); + + $campaignMissingStatus = new Campaign([ + 'pageId' => 1, + 'type' => CampaignType::CORE(), + 'title' => __('GiveWP Campaign', 'give'), + 'shortDescription' => __('Campaign short description', 'give'), + 'longDescription' => __('Campaign long description', 'give'), + 'goal' => 10000000, + 'logo' => '', + 'image' => '', + 'primaryColor' => '#28C77B', + 'secondaryColor' => '#FFA200', + 'createdAt' => Temporal::withoutMicroseconds($currentDate), + 'startDate' => Temporal::withoutMicroseconds($currentDate), + 'endDate' => Temporal::withoutMicroseconds($currentDate->modify('+1 day')), + ]); + + (new CampaignRepository())->update($campaignMissingStatus); + } + + /** + * @unreleased + * + * @throws Exception + */ + public function testUpdateShouldUpdateCampaignValuesInTheDatabase() + { + $repository = new CampaignRepository(); + $campaignFactory = Campaign::factory()->create(); + + // update campaign + $campaignFactory->title = 'Updated campaign title'; + $campaignFactory->shortDescription = 'Updated short description'; + $campaignFactory->type = CampaignType::PEER_TO_PEER(); + $campaignFactory->status = CampaignStatus::INACTIVE(); + + $repository->update($campaignFactory); + + $campaign = $repository->prepareQuery() + ->where('id', $campaignFactory->id) + ->get(); + + $this->assertNotEquals(CampaignType::CORE()->getValue(), $campaign->type->getValue()); + $this->assertNotEquals(CampaignStatus::ACTIVE()->getValue(), $campaign->status->getValue()); + $this->assertEquals('Updated campaign title', $campaign->title); + $this->assertEquals('Updated short description', $campaign->shortDescription); + } + + /** + * @unreleased + * + * @throws Exception + */ + public function testDeleteShouldRemoveCampaignFromTheDatabase() + { + $repository = new CampaignRepository(); + $campaignFactory = Campaign::factory()->create(); + + $repository->delete($campaignFactory); + + $campaign = $repository->prepareQuery() + ->where('id', $campaignFactory->id) + ->get(); + + $this->assertNull($campaign); + } +}