diff --git a/plugins/Referrers/API.php b/plugins/Referrers/API.php index 445398b7601..e3c1047dc1a 100644 --- a/plugins/Referrers/API.php +++ b/plugins/Referrers/API.php @@ -14,7 +14,9 @@ use Piwik\Archive; use Piwik\Common; use Piwik\DataTable; +use Piwik\Db; use Piwik\Metrics; +use Piwik\Option; use Piwik\Piwik; use Piwik\Plugins\Actions\ArchivingHelper; use Piwik\Plugins\Referrers\Columns\Metrics\VisitorsFromReferrerPercent; @@ -749,6 +751,93 @@ public function getNumberOfDistinctWebsitesUrls($idSite, string $period, string return $this->getNumeric(Archiver::METRIC_DISTINCT_URLS_RECORD_NAME, $idSite, $period, $date, $segment); } + /** + * @unsanitized + */ + public function getCampaignUrlPresets($idSite, string $search = '') + { + Piwik::checkUserHasViewAccess($idSite); + + if (!empty($search)) { + $optionName = $this->getCampaignUrlPresetsOptionName($idSite); + $sql = sprintf( + "SELECT option_value FROM `%s` WHERE option_name = '%s' AND option_value LIKE '%%%s%%'", + Common::prefixTable('option'), + $optionName, + $search + ); + $optionValue = Db::fetchOne($sql); + } else { + $optionValue = Option::get($this->getCampaignUrlPresetsOptionName($idSite)); + } + + $presets = json_decode($optionValue, true); + if (!is_array($presets)) { + return []; + } + + return Common::unsanitizeInputValues(array_slice(array_reverse($presets), 0, 5)); + } + + public function saveCampaignUrlPreset( + $idSite, + string $websiteUrl = '', + string $generatedUrl = '', + string $campaignName = '', + string $campaignKeyword = '', + string $campaignSource = '', + string $campaignMedium = '', + string $campaignId = '', + string $campaignContent = '', + string $campaignGroup = '', + string $campaignPlacement = '' + ) { + Piwik::checkUserHasViewAccess($idSite); + + $optionName = $this->getCampaignUrlPresetsOptionName($idSite); + $presets = json_decode(Option::get($optionName), true); + if (!is_array($presets)) { + $presets = []; + } + + $presets[] = [ + 'websiteUrl' => $websiteUrl, + 'generatedUrl' => $generatedUrl, + 'campaignName' => $campaignName, + 'campaignKeyword' => $campaignKeyword, + 'campaignSource' => $campaignSource, + 'campaignMedium' => $campaignMedium, + 'campaignId' => $campaignId, + 'campaignContent' => $campaignContent, + 'campaignGroup' => $campaignGroup, + 'campaignPlacement' => $campaignPlacement, + 'login' => Piwik::getCurrentUserLogin(), + 'savedAt' => date('Y-m-d H:i:s'), + ]; + + Option::set($optionName, json_encode($presets)); + + return Common::unsanitizeInputValues(array_slice(array_reverse($presets), 0, 5)); + } + + public function deleteCampaignUrlPreset($idSite, int $index) + { + Piwik::checkUserHasViewAccess($idSite); + + $optionName = $this->getCampaignUrlPresetsOptionName($idSite); + $presets = json_decode(Option::get($optionName), true); + if (!is_array($presets)) { + return []; + } + + unset($presets[$index]); + $presets = array_values($presets); + + Option::set($optionName, json_encode($presets)); + + return Common::unsanitizeInputValues(array_slice(array_reverse($presets), 0, 5)); + } + private function getNumeric(string $name, $idSite, string $period, string $date, ?string $segment) { Piwik::checkUserHasViewAccess($idSite); @@ -756,6 +845,11 @@ private function getNumeric(string $name, $idSite, string $period, string $date, return $archive->getDataTableFromNumeric($name); } + private function getCampaignUrlPresetsOptionName($idSite): string + { + return 'Referrers.campaignUrlPresets.' . (int) $idSite; + } + /** * Removes idsubdatatable_in_db metadata from a DataTable. Used by Social tables since * they use fake subtable IDs. diff --git a/plugins/Referrers/tests/Integration/APITest.php b/plugins/Referrers/tests/Integration/APITest.php new file mode 100644 index 00000000000..76bee79a003 --- /dev/null +++ b/plugins/Referrers/tests/Integration/APITest.php @@ -0,0 +1,84 @@ +siteId = Fixture::createWebsite('2014-01-01 01:02:03'); + $this->api = API::getInstance(); + } + + public function tearDown(): void + { + Option::delete($this->getPresetOptionName()); + + parent::tearDown(); + } + + public function testSaveCampaignUrlPresetReturnsSavedPreset(): void + { + $result = $this->api->saveCampaignUrlPreset( + $this->siteId, + 'https://example.com/landing-page', + 'https://example.com/landing-page?mtm_campaign=Spring+sale', + 'Spring sale', + 'shoes', + 'newsletter', + 'email', + 'spring-2026', + 'hero-banner', + 'vip', + 'top-slot' + ); + + $this->assertIsArray($result); + $this->assertCount(1, $result); + $this->assertSame('Spring sale', $result[0]['campaignName']); + $this->assertSame('newsletter', $result[0]['campaignSource']); + } + + public function provideContainerConfig() + { + return [ + 'Piwik\Access' => new FakeAccess(true), + ]; + } + + private function getPresetOptionName(): string + { + return 'Referrers.campaignUrlPresets.' . $this->siteId; + } +} diff --git a/plugins/Referrers/vue/src/CampaignBuilder/CampaignBuilder.vue b/plugins/Referrers/vue/src/CampaignBuilder/CampaignBuilder.vue index 0dcaea150b8..75484a9c645 100644 --- a/plugins/Referrers/vue/src/CampaignBuilder/CampaignBuilder.vue +++ b/plugins/Referrers/vue/src/CampaignBuilder/CampaignBuilder.vue @@ -135,15 +135,55 @@ > +
+

Recent campaigns

+
+ + +
+ +