Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/Archive/ArchivePurger.php17
-rw-r--r--core/DataAccess/Model.php66
-rw-r--r--plugins/CoreAdminHome/Tasks.php33
-rw-r--r--plugins/CoreAdminHome/tests/Integration/TasksTest.php127
-rw-r--r--plugins/SegmentEditor/Model.php81
-rw-r--r--plugins/SegmentEditor/tests/Integration/ModelTest.php186
-rw-r--r--tests/PHPUnit/Fixtures/RawArchiveDataWithTempAndInvalidated.php22
-rw-r--r--tests/PHPUnit/Integration/Archive/ArchivePurgerTest.php38
8 files changed, 362 insertions, 208 deletions
diff --git a/core/Archive/ArchivePurger.php b/core/Archive/ArchivePurger.php
index c69bf3ecb9..cd330ea37c 100644
--- a/core/Archive/ArchivePurger.php
+++ b/core/Archive/ArchivePurger.php
@@ -166,14 +166,15 @@ class ArchivePurger
/**
* @param Date $dateStart
- * @param array $segmentHashesByIdSite List of valid segment hashes, indexed by site ID
+ * @param array $deletedSegments List of segments whose archives should be purged
* @return int
*/
- public function purgeDeletedSegmentArchives(Date $dateStart, array $segmentHashesByIdSite)
+ public function purgeDeletedSegmentArchives(Date $dateStart, array $deletedSegments)
{
- $idArchivesToDelete = $this->getDeletedSegmentArchiveIds($dateStart, $segmentHashesByIdSite);
-
- return $this->purge($idArchivesToDelete, $dateStart, 'deleted segments');
+ if (count($deletedSegments)) {
+ $idArchivesToDelete = $this->getDeletedSegmentArchiveIds($dateStart, $deletedSegments);
+ return $this->purge($idArchivesToDelete, $dateStart, 'deleted segments');
+ }
}
/**
@@ -211,11 +212,11 @@ class ArchivePurger
return $deletedRowCount;
}
- protected function getDeletedSegmentArchiveIds(Date $date, array $segmentHashesByIdSite)
+ protected function getDeletedSegmentArchiveIds(Date $date, array $deletedSegments)
{
$archiveTable = ArchiveTableCreator::getNumericTable($date);
- return $this->model->getArchiveIdsForDeletedSegments(
- $archiveTable, $segmentHashesByIdSite, $this->getOldestTemporaryArchiveToKeepThreshold()
+ return $this->model->getArchiveIdsForSegments(
+ $archiveTable, $deletedSegments, $this->getOldestTemporaryArchiveToKeepThreshold()
);
}
diff --git a/core/DataAccess/Model.php b/core/DataAccess/Model.php
index 85294b8bcc..3f80753835 100644
--- a/core/DataAccess/Model.php
+++ b/core/DataAccess/Model.php
@@ -354,50 +354,54 @@ class Model
* Get a list of IDs of archives with segments that no longer exist in the DB. Excludes temporary archives that
* may still be in use, as specified by the $oldestToKeep passed in.
* @param string $archiveTableName
- * @param array $segmentHashesById Whitelist of existing segments, indexed by site ID
+ * @param array $segments List of segments to match against
* @param string $oldestToKeep Datetime string
* @return array With keys idarchive, name, idsite
*/
- public function getArchiveIdsForDeletedSegments($archiveTableName, array $segmentHashesById, $oldestToKeep)
+ public function getArchiveIdsForSegments($archiveTableName, array $segments, $oldestToKeep)
{
- $validSegmentClauses = [];
-
- foreach ($segmentHashesById as $idSite => $segments) {
- // segments are md5 hashes and such not a problem re sql injection. for performance etc we don't want to use
- // bound parameters for the query
- foreach ($segments as $segment) {
- if (!preg_match('/^[a-z0-9A-Z]+$/', $segment)) {
- throw new Exception($segment . ' expected to be an md5 hash');
- }
- }
-
- // Special case as idsite=0 means the segments are not site-specific
- if ($idSite === 0) {
- foreach ($segments as $segmentHash) {
- $validSegmentClauses[] = '(name LIKE "done' . $segmentHash . '%")';
- }
- continue;
+ $segmentClauses = [];
+ foreach ($segments as $segment) {
+ if (!empty($segment['definition'])) {
+ $segmentClauses[] = $this->getDeletedSegmentWhereClause($segment);
}
+ }
- $idSite = (int)$idSite;
-
- // Vanilla case - segments that are valid for a single site only
- $sql = '(idsite = ' . $idSite . ' AND (';
- $sql .= 'name LIKE "done' . implode('%" OR name LIKE "done', $segments) . '%"';
- $sql .= '))';
- $validSegmentClauses[] = $sql;
+ if (empty($segmentClauses)) {
+ return array();
}
- $isValidSegmentSql = implode(' OR ', $validSegmentClauses);
+ $segmentClauses = implode(' OR ', $segmentClauses);
$sql = 'SELECT idarchive FROM ' . $archiveTableName
- . ' WHERE name LIKE "done%" AND name != "done"'
- . ' AND ts_archived < ?'
- . ' AND NOT (' . $isValidSegmentSql . ')';
+ . ' WHERE ts_archived < ?'
+ . ' AND (' . $segmentClauses . ')';
$rows = Db::fetchAll($sql, array($oldestToKeep));
- return array_map(function($row) { return $row['idarchive']; }, $rows);
+ return array_column($rows, 'idarchive');
+ }
+
+ private function getDeletedSegmentWhereClause(array $segment)
+ {
+ $idSite = (int)$segment['enable_only_idsite'];
+ $segmentHash = Segment::getSegmentHash($segment['definition']);
+ // Valid segment hashes are md5 strings - just confirm that it is so it's safe for SQL injection
+ if (!ctype_xdigit($segmentHash)) {
+ throw new Exception($segment . ' expected to be an md5 hash');
+ }
+
+ $nameClause = 'name LIKE "done' . $segmentHash . '%"';
+ $idSiteClause = '';
+ if ($idSite > 0) {
+ $idSiteClause = ' AND idsite = ' . $idSite;
+ } elseif (! empty($segment['idsites_to_preserve'])) {
+ // A segment for all sites was deleted, but there are segments for a single site with the same definition
+ $idSitesToPreserve = array_map('intval', $segment['idsites_to_preserve']);
+ $idSiteClause = ' AND idsite NOT IN (' . implode(',', $idSitesToPreserve) . ')';
+ }
+
+ return "($nameClause $idSiteClause)";
}
/**
diff --git a/plugins/CoreAdminHome/Tasks.php b/plugins/CoreAdminHome/Tasks.php
index ef9b0bd215..d94979c9c3 100644
--- a/plugins/CoreAdminHome/Tasks.php
+++ b/plugins/CoreAdminHome/Tasks.php
@@ -23,9 +23,9 @@ use Piwik\Piwik;
use Piwik\Plugins\CoreAdminHome\Emails\JsTrackingCodeMissingEmail;
use Piwik\Plugins\CoreAdminHome\Emails\TrackingFailuresEmail;
use Piwik\Plugins\CoreAdminHome\Tasks\ArchivesToPurgeDistributedList;
+use Piwik\Plugins\SegmentEditor\Model;
use Piwik\Plugins\SitesManager\SitesManager;
use Piwik\Scheduler\Schedule\SpecificTime;
-use Piwik\Segment;
use Piwik\Settings\Storage\Backend\MeasurableSettingsTable;
use Piwik\Tracker\Failures;
use Piwik\Site;
@@ -275,7 +275,10 @@ class Tasks extends \Piwik\Plugin\Tasks
*/
public function purgeOrphanedArchives()
{
- $segmentHashesByIdSite = $this->getSegmentHashesByIdSite();
+ $eightDaysAgo = Date::factory('now')->subDay(8);
+ $model = new Model();
+ $deletedSegments = $model->getSegmentsDeletedSince($eightDaysAgo);
+
$archiveTables = ArchiveTableCreator::getTablesArchivesInstalled('numeric');
$datesPurged = array();
@@ -286,35 +289,15 @@ class Tasks extends \Piwik\Plugin\Tasks
$dateObj = Date::factory("$year-$month-15");
$this->archivePurger->purgeDeletedSiteArchives($dateObj);
- $this->archivePurger->purgeDeletedSegmentArchives($dateObj, $segmentHashesByIdSite);
+ if (count($deletedSegments)) {
+ $this->archivePurger->purgeDeletedSegmentArchives($dateObj, $deletedSegments);
+ }
$datesPurged[$date] = true;
}
}
/**
- * Get a list of all segment hashes that currently exist, indexed by idSite.
- * @return array
- */
- public function getSegmentHashesByIdSite()
- {
- //Get a list of hashes of all segments that exist now
- $sql = "SELECT DISTINCT definition, enable_only_idsite FROM " . Common::prefixTable('segment')
- . " WHERE deleted = 0";
- $rows = Db::fetchAll($sql);
- $segmentHashes = array();
- foreach ($rows as $row) {
- $idSite = (int)$row['enable_only_idsite'];
- if (! isset($segmentHashes[$idSite])) {
- $segmentHashes[$idSite] = array();
- }
- $segmentHashes[$idSite][] = Segment::getSegmentHash($row['definition']);
- }
-
- return $segmentHashes;
- }
-
- /**
* we should only purge outdated & custom range archives if we know cron archiving has just run,
* or if browser triggered archiving is enabled. if cron archiving has run, then we know the latest
* archives are in the database, and we can remove temporary ones. if browser triggered archiving is
diff --git a/plugins/CoreAdminHome/tests/Integration/TasksTest.php b/plugins/CoreAdminHome/tests/Integration/TasksTest.php
index 830da7ef97..a23d368de4 100644
--- a/plugins/CoreAdminHome/tests/Integration/TasksTest.php
+++ b/plugins/CoreAdminHome/tests/Integration/TasksTest.php
@@ -219,133 +219,6 @@ class TasksTest extends IntegrationTestCase
$this->assertEquals(2, $mail->getNumFailures());
}
- public function test_getSegmentHashesByIdSite_emptyWhenNoSegments()
- {
- $segmentsByIdSite = $this->tasks->getSegmentHashesByIdSite();
- $this->assertEquals(array(), $segmentsByIdSite);
- }
-
- public function test_getSegmentHashesByIdSite_allWebsiteAndSiteSpecificSegments()
- {
- $model = new Model();
- $model->createSegment(array(
- 'name' => 'Test Segment 1',
- 'definition' => 'continentCode==eur',
- 'enable_only_idsite' => 0,
- 'deleted' => 0
- ));
- $model->createSegment(array(
- 'name' => 'Test Segment 2',
- 'definition' => 'countryCode==nz',
- 'enable_only_idsite' => 0,
- 'deleted' => 0
- ));
- $model->createSegment(array(
- 'name' => 'Test Segment 3',
- 'definition' => 'countryCode==au',
- 'enable_only_idsite' => 2,
- 'deleted' => 0
- ));
-
- $segmentsByIdSite = $this->tasks->getSegmentHashesByIdSite();
- $expected = array(
- 0 => array('be90051048558489e1d62f4245a6dc65', 'b92fbb3009b32cf632965802de2fb760'),
- 2 => array('cffd4336c22c6782211f853495076b1a')
- );
- $this->assertEquals($expected, $segmentsByIdSite);
- }
-
- public function test_getSegmentHashesByIdSite_deletedSegment()
- {
- $model = new Model();
- $model->createSegment(array(
- 'name' => 'Test Segment 1',
- 'definition' => 'continentCode==eur',
- 'enable_only_idsite' => 0,
- 'deleted' => 0
- ));
- $model->createSegment(array(
- 'name' => 'Test Segment 2',
- 'definition' => 'countryCode==nz',
- 'enable_only_idsite' => 0,
- 'deleted' => 1
- ));
- $model->createSegment(array(
- 'name' => 'Test Segment 3',
- 'definition' => 'countryCode==au',
- 'enable_only_idsite' => 2,
- 'deleted' => 0
- ));
-
- $segmentsByIdSite = $this->tasks->getSegmentHashesByIdSite();
- $expected = array(
- 0 => array('be90051048558489e1d62f4245a6dc65'),
- 2 => array('cffd4336c22c6782211f853495076b1a')
- );
- $this->assertEquals($expected, $segmentsByIdSite);
- }
-
- public function test_getSegmentHashesByIdSite_invalidSegment()
- {
- $model = new Model();
- $model->createSegment(array(
- 'name' => 'Test Segment 4',
- 'definition' => 'countryCode=nz', //The single "=" is invalid - we should generate a hash anyway
- 'enable_only_idsite' => 0,
- 'deleted' => 0
- ));
- $model->createSegment(array(
- 'name' => 'Test Segment 5',
- 'definition' => 'countryCode==au',
- 'enable_only_idsite' => 0,
- 'deleted' => 0
- ));
-
- $expected = array(
- 0 => array('5ffe7e116fae7576c047b1fb811584a5', 'cffd4336c22c6782211f853495076b1a'),
- );
-
- $segmentsByIdSite = $this->tasks->getSegmentHashesByIdSite();
- $this->assertEquals($expected, $segmentsByIdSite);
- }
-
- public function test_getSegmentHashesByIdSite_siteSpecificCustomDimension()
- {
- // Insert a custom dimension for idsite = 1
- $configuration = new Configuration();
- $configuration->configureNewDimension(
- 1,
- 'mydimension',
- CustomDimensions::SCOPE_VISIT,
- 1,
- 1,
- array(),
- true
- );
-
- $model = new Model();
- $model->createSegment(array(
- 'name' => 'Test Segment 6',
- 'definition' => 'mydimension==red',
- 'enable_only_idsite' => 1,
- 'deleted' => 0
- ));
- $model->createSegment(array(
- 'name' => 'Test Segment 7',
- 'definition' => 'countryCode==au',
- 'enable_only_idsite' => 2,
- 'deleted' => 0
- ));
-
- $expected = array(
- 1 => array('240d2a84a309debd26bdbaa8eb3d363c'),
- 2 => array('cffd4336c22c6782211f853495076b1a')
- );
-
- $segmentsByIdSite = $this->tasks->getSegmentHashesByIdSite();
- $this->assertEquals($expected, $segmentsByIdSite);
- }
-
/**
* @param Date[] $dates
*/
diff --git a/plugins/SegmentEditor/Model.php b/plugins/SegmentEditor/Model.php
index 0025c7c15c..c505f7a4f2 100644
--- a/plugins/SegmentEditor/Model.php
+++ b/plugins/SegmentEditor/Model.php
@@ -129,6 +129,87 @@ class Model
return $segment;
}
+ /**
+ * Gets a list of segments that have been deleted in the last week and therefore may have orphaned archives.
+ * @param Date $date Segments deleted on or after this date will be returned.
+ * @return array of segments. The segments are only populated with the fields needed for archive invalidation
+ * (e.g. definition, enable_only_idsite).
+ * @throws \Exception
+ */
+ public function getSegmentsDeletedSince(Date $date)
+ {
+ $dateStr = $date->getDatetime();
+ $sql = "SELECT DISTINCT definition, enable_only_idsite FROM " . Common::prefixTable('segment')
+ . " WHERE deleted = 1 AND ts_last_edit >= ?";
+ $deletedSegments = Db::fetchAll($sql, array($dateStr));
+
+ if (empty($deletedSegments)) {
+ return array();
+ }
+
+ $existingSegments = $this->getExistingSegmentsLike($deletedSegments);
+
+ foreach ($deletedSegments as $i => $deleted) {
+ $deletedSegments[$i]['idsites_to_preserve'] = array();
+ foreach ($existingSegments as $existing) {
+ if ($existing['definition'] != $deleted['definition'] &&
+ $existing['definition'] != urlencode($deleted['definition']) &&
+ $existing['definition'] != urldecode($deleted['definition'])
+ ) {
+ continue;
+ }
+
+ if (
+ $existing['enable_only_idsite'] == $deleted['enable_only_idsite']
+ || $existing['enable_only_idsite'] == 0
+ ) {
+ // There is an identical segment (for either the specific site or for all sites) that is active
+ // The archives for this segment will therefore still be needed
+ unset($deletedSegments[$i]);
+ break;
+ } elseif ($deleted['enable_only_idsite'] == 0) {
+ // It is an all-sites segment that got deleted, but there is a single-site segment that is active
+ // Need to make sure we don't erase the segment's archives for that particular site
+ $deletedSegments[$i]['idsites_to_preserve'][] = $existing['enable_only_idsite'];
+ }
+ }
+ }
+
+ return $deletedSegments;
+ }
+
+ private function getExistingSegmentsLike(array $segments)
+ {
+ if (empty($segments)) {
+ return array();
+ }
+
+ $whereClauses = array();
+ $bind = array();
+ $definitionWhereClauseTemplate = '(definition = ? OR definition = ? OR definition = ?)';
+ foreach ($segments as $segment) {
+ // Sometimes they are stored encoded and sometimes they aren't
+ $bind[] = $segment['definition'];
+ $bind[] = urlencode($segment['definition']);
+ $bind[] = urldecode($segment['definition']);
+
+ if ($segment['enable_only_idsite'] == 0) {
+ // They deleted an all-sites segment, but there is a single-site segment with same definition?
+ // Need to handle this carefully so that the archives for the single-site segment are preserved
+ $whereClauses[] = "$definitionWhereClauseTemplate";
+ } else {
+ $whereClauses[] = "($definitionWhereClauseTemplate AND (enable_only_idsite = ? OR enable_only_idsite = 0))";
+ $bind[] = $segment['enable_only_idsite'];
+ }
+ }
+ $whereClauses = implode(' OR ', $whereClauses);
+
+ // Check for any non-deleted segments with the same definition
+ $sql = "SELECT DISTINCT definition, enable_only_idsite FROM " . Common::prefixTable('segment')
+ . " WHERE deleted = 0 AND (" . $whereClauses . ")";
+ return Db::fetchAll($sql, $bind);
+ }
+
public function deleteSegment($idSegment)
{
$fieldsToSet = array(
diff --git a/plugins/SegmentEditor/tests/Integration/ModelTest.php b/plugins/SegmentEditor/tests/Integration/ModelTest.php
index 92034f3efc..f309221c8f 100644
--- a/plugins/SegmentEditor/tests/Integration/ModelTest.php
+++ b/plugins/SegmentEditor/tests/Integration/ModelTest.php
@@ -25,6 +25,8 @@ class ModelTest extends IntegrationTestCase
private $idSegment3;
+ private $idSegment4;
+
public function setUp()
{
parent::setUp();
@@ -55,7 +57,11 @@ class ModelTest extends IntegrationTestCase
{
parent::tearDown();
// Force a hard delete of segment
- $idsToDelete = $this->idSegment1 . ', ' . $this->idSegment2 . ', ' . $this->idSegment3;
+ $idsToDelete = array($this->idSegment1, $this->idSegment2, $this->idSegment3);
+ if ($this->idSegment4) {
+ $idsToDelete[] = $this->idSegment4;
+ }
+ $idsToDelete = implode(',', $idsToDelete);
Db::query(
"DELETE FROM " . Common::prefixTable('segment') . " WHERE idsegment IN ($idsToDelete)"
);
@@ -146,6 +152,184 @@ class ModelTest extends IntegrationTestCase
$this->assertEmpty($segment);
}
+ public function test_getSegmentsDeletedSince_noDeletedSegments()
+ {
+ $date = Date::factory('now');
+ $segments = $this->model->getSegmentsDeletedSince($date);
+ $this->assertEmpty($segments);
+ }
+
+ public function test_getSegmentsDeletedSince_oneDeletedSegment()
+ {
+ $this->model->deleteSegment($this->idSegment3);
+
+ $date = Date::factory('now')->subDay(1);
+ $segments = $this->model->getSegmentsDeletedSince($date);
+
+ $this->assertCount(1, $segments);
+ $this->assertEquals('country==Hobbiton', $segments[0]['definition']);
+ }
+
+ public function test_getSegmentsDeletedSince_segmentDeletedTooLongAgo()
+ {
+ // Manually delete it to set timestamp 9 days in past
+ $deletedAt = Date::factory('now')->subDay(9)->toString('Y-m-d H:i:s');
+ $this->model->updateSegment($this->idSegment1, array(
+ 'deleted' => 1,
+ 'ts_last_edit' => $deletedAt
+ ));
+
+ // The segment deleted above should not be included as it was more than 8 days ago
+ $date = Date::factory('now')->subDay(8);
+ $segments = $this->model->getSegmentsDeletedSince($date);
+
+ $this->assertEmpty($segments);
+ }
+
+ public function test_getSegmentsDeletedSince_duplicateSegment()
+ {
+ // Turn segment1 into a duplicate of segment2, except it's also deleted
+ $this->model->updateSegment($this->idSegment1, array(
+ 'definition' => 'country==Genovia',
+ 'deleted' => 1,
+ 'ts_last_edit' => Date::factory('now')->toString('Y-m-d H:i:s')
+ ));
+
+ $date = Date::factory('now')->subDay(8);
+ $segments = $this->model->getSegmentsDeletedSince($date);
+
+ $this->assertEmpty($segments);
+ }
+
+ public function test_getSegmentsDeletedSince_duplicateSegmentDifferentIdSite()
+ {
+ // Turn segment2 into a duplicate of segment3, except for a different idsite and also deleted
+ $this->model->updateSegment($this->idSegment2, array(
+ 'definition' => 'country==Hobbiton',
+ 'enable_only_idsite' => 2,
+ 'deleted' => 1,
+ 'ts_last_edit' => Date::factory('now')->toString('Y-m-d H:i:s')
+ ));
+
+ $date = Date::factory('now')->subDay(8);
+ $segments = $this->model->getSegmentsDeletedSince($date);
+
+ $this->assertCount(1, $segments);
+ $this->assertEquals('country==Hobbiton', $segments[0]['definition']);
+ $this->assertEquals(2, $segments[0]['enable_only_idsite']);
+ }
+
+ public function test_getSegmentsDeletedSince_duplicateSegmentAllSitesAndSingleSite()
+ {
+ // Turn segment2 into a duplicate of segment3, except for all sites and also deleted
+ $this->model->updateSegment($this->idSegment2, array(
+ 'definition' => 'country==Hobbiton',
+ 'enable_only_idsite' => 0,
+ 'deleted' => 1,
+ 'ts_last_edit' => Date::factory('now')->toString('Y-m-d H:i:s')
+ ));
+
+ $date = Date::factory('now')->subDay(8);
+ $segments = $this->model->getSegmentsDeletedSince($date);
+
+ $this->assertCount(1, $segments);
+ $this->assertEquals('country==Hobbiton', $segments[0]['definition']);
+ $this->assertEquals(0, $segments[0]['enable_only_idsite']);
+ $this->assertEquals(array(1), $segments[0]['idsites_to_preserve']);
+ }
+
+ public function test_getSegmentsDeletedSince_duplicateSegmentSingleSiteAndAllSites()
+ {
+ // Turn segment3 into a duplicate of segment1, except for a single site and deleted
+ $this->model->updateSegment($this->idSegment3, array(
+ 'definition' => 'country==Narnia',
+ 'enable_only_idsite' => 1,
+ 'deleted' => 1,
+ 'ts_last_edit' => Date::factory('now')->toString('Y-m-d H:i:s')
+ ));
+
+ $date = Date::factory('now')->subDay(8);
+ $segments = $this->model->getSegmentsDeletedSince($date);
+
+ // There is still a live segment for all sites, so the deleted site-specific one is ignored
+ $this->assertEmpty($segments);
+ }
+
+ public function test_getSegmentsDeletedSince_ExistingSiteSpecificAndAllSitesMatch()
+ {
+ // A deleted all-sites segment, with both an all-sites and a site-specific segment still present
+ $this->model->updateSegment($this->idSegment1, array(
+ 'definition' => 'actions >= 1',
+ 'enable_only_idsite' => 0,
+ 'deleted' => 0,
+ 'ts_last_edit' => Date::factory('now')->toString('Y-m-d H:i:s')
+ ));
+ $this->model->updateSegment($this->idSegment2, array(
+ 'definition' => 'actions >= 1',
+ 'enable_only_idsite' => 0,
+ 'deleted' => 1,
+ 'ts_last_edit' => Date::factory('now')->toString('Y-m-d H:i:s')
+ ));
+ $this->model->updateSegment($this->idSegment3, array(
+ 'definition' => 'actions >= 1',
+ 'enable_only_idsite' => 3,
+ 'deleted' => 0,
+ 'ts_last_edit' => Date::factory('now')->toString('Y-m-d H:i:s')
+ ));
+ $this->idSegment4 = $this->model->createSegment(array(
+ 'definition' => 'actions >= 1',
+ 'enable_only_idsite' => 1,
+ 'deleted' => 1,
+ 'ts_last_edit' => Date::factory('now')->toString('Y-m-d H:i:s')
+ ));
+
+ $date = Date::factory('now')->subDay(8);
+ $segments = $this->model->getSegmentsDeletedSince($date);
+
+ $this->assertEmpty($segments);
+ }
+
+ public function test_getSegmentsDeletedSince_urlDecodedVersionOfSegment()
+ {
+ // Turn segment2 into a duplicate of segment3, except a urlencoded version
+ $this->model->updateSegment($this->idSegment2, array(
+ 'definition' => 'country%3D%3DHobbiton',
+ 'enable_only_idsite' => 1,
+ 'deleted' => 1,
+ 'ts_last_edit' => Date::factory('now')->toString('Y-m-d H:i:s')
+ ));
+
+ $date = Date::factory('now')->subDay(8);
+ $segments = $this->model->getSegmentsDeletedSince($date);
+
+ // The two encoded and decoded version of the segments should be treated as duplicates
+ // This means there segment has a non-deleted version so it's not returned
+ $this->assertEmpty($segments);
+ }
+
+ public function test_getSegmentsDeletedSince_urlEncodedVersionOfSegment()
+ {
+ // segment1 => url decoded version, deleted
+ $this->model->updateSegment($this->idSegment1, array(
+ 'definition' => 'country==Narnia',
+ 'deleted' => 1,
+ 'ts_last_edit' => Date::factory('now')->toString('Y-m-d H:i:s')
+ ));
+ // segment2 => url encoded version, not deleted
+ $this->model->updateSegment($this->idSegment2, array(
+ 'definition' => 'country%3D%3DNarnia',
+ 'deleted' => 0,
+ 'ts_last_edit' => Date::factory('now')->toString('Y-m-d H:i:s')
+ ));
+
+ $date = Date::factory('now')->subDay(8);
+ $segments = $this->model->getSegmentsDeletedSince($date);
+
+ // The two encoded and decoded version of the segments should be treated as duplicates
+ // This means there segment has a non-deleted version so it's not returned
+ $this->assertEmpty($segments);
+ }
+
private function assertReturnedIdsMatch(array $expectedIds, array $resultSet)
{
$this->assertEquals(count($expectedIds), count($resultSet));
diff --git a/tests/PHPUnit/Fixtures/RawArchiveDataWithTempAndInvalidated.php b/tests/PHPUnit/Fixtures/RawArchiveDataWithTempAndInvalidated.php
index cc7da149c9..b4f597b97a 100644
--- a/tests/PHPUnit/Fixtures/RawArchiveDataWithTempAndInvalidated.php
+++ b/tests/PHPUnit/Fixtures/RawArchiveDataWithTempAndInvalidated.php
@@ -246,7 +246,7 @@ class RawArchiveDataWithTempAndInvalidated extends Fixture
array(
'idarchive' => 20,
'idsite' => 1,
- 'name' => 'doneabcd1234abcd5678',
+ 'name' => 'doneeb5d2797aedd15d819b1a20425982850', // Raw segment = abcd1234abcd5678
'value' => ArchiveWriter::DONE_OK,
'date1' => '2015-02-03',
'date2' => '2015-02-03',
@@ -256,7 +256,7 @@ class RawArchiveDataWithTempAndInvalidated extends Fixture
array(
'idarchive' => 21,
'idsite' => 1,
- 'name' => 'doneabcd1234abcd5678.MyPlugin',
+ 'name' => 'doneeb5d2797aedd15d819b1a20425982850.MyPlugin', // Raw segment = abcd1234abcd5678
'value' => ArchiveWriter::DONE_OK,
'date1' => '2015-02-03',
'date2' => '2015-02-03',
@@ -266,7 +266,7 @@ class RawArchiveDataWithTempAndInvalidated extends Fixture
array(
'idarchive' => 22,
'idsite' => 2,
- 'name' => 'doneabcd1234abcd5678',
+ 'name' => 'doneeb5d2797aedd15d819b1a20425982850', // Raw segment = abcd1234abcd5678
'value' => ArchiveWriter::DONE_OK,
'date1' => '2015-02-03',
'date2' => '2015-02-03',
@@ -276,7 +276,7 @@ class RawArchiveDataWithTempAndInvalidated extends Fixture
array(
'idarchive' => 23,
'idsite' => 2,
- 'name' => 'doneabcd1234abcd5678.MyPlugin',
+ 'name' => 'doneeb5d2797aedd15d819b1a20425982850.MyPlugin', // Raw segment = abcd1234abcd5678
'value' => ArchiveWriter::DONE_OK,
'date1' => '2015-02-03',
'date2' => '2015-02-03',
@@ -286,7 +286,7 @@ class RawArchiveDataWithTempAndInvalidated extends Fixture
array(
'idarchive' => 24,
'idsite' => 1,
- 'name' => 'done9876fedc5432abcd',
+ 'name' => 'done1e39a89fcc269acc36bd4d7c742763ed', // Raw segment = 9876fedc5432abcd
'value' => ArchiveWriter::DONE_OK,
'date1' => '2015-02-03',
'date2' => '2015-02-03',
@@ -296,7 +296,7 @@ class RawArchiveDataWithTempAndInvalidated extends Fixture
array(
'idarchive' => 25,
'idsite' => 2,
- 'name' => 'donehash1',
+ 'name' => 'done00c6ee2e21a7548de6260cf72c4f4b5b', // Raw segment = hash1
'value' => ArchiveWriter::DONE_OK,
'date1' => '2015-02-03',
'date2' => '2015-02-03',
@@ -306,7 +306,7 @@ class RawArchiveDataWithTempAndInvalidated extends Fixture
array(
'idarchive' => 26,
'idsite' => 2,
- 'name' => 'donehash2',
+ 'name' => 'done58833651db311ba4bc11cb26b1900b0f', // Raw segment = hash2
'value' => ArchiveWriter::DONE_OK,
'date1' => '2015-02-03',
'date2' => '2015-02-03',
@@ -316,7 +316,7 @@ class RawArchiveDataWithTempAndInvalidated extends Fixture
array(
'idarchive' => 27,
'idsite' => 2,
- 'name' => 'donehash2.MyPlugin',
+ 'name' => 'done58833651db311ba4bc11cb26b1900b0f.MyPlugin', // Raw segment = hash2
'value' => ArchiveWriter::DONE_OK,
'date1' => '2015-02-03',
'date2' => '2015-02-03',
@@ -326,7 +326,7 @@ class RawArchiveDataWithTempAndInvalidated extends Fixture
array(
'idarchive' => 28,
'idsite' => 2,
- 'name' => 'donehash3',
+ 'name' => 'done1a4ead8b39d17dfe89418452c9bba770', // Raw segment = hash3
'value' => ArchiveWriter::DONE_OK,
'date1' => '2015-02-03',
'date2' => '2015-02-03',
@@ -334,9 +334,9 @@ class RawArchiveDataWithTempAndInvalidated extends Fixture
'ts_archived' => '2015-02-03 12:12:12'
),
array(
- 'idarchive' => 29 ,
+ 'idarchive' => 29,
'idsite' => 2,
- 'name' => 'donehash3',
+ 'name' => 'done1a4ead8b39d17dfe89418452c9bba770', // Raw segment = hash3
'value' => ArchiveWriter::DONE_OK,
'date1' => '2015-02-03',
'date2' => '2015-02-03',
diff --git a/tests/PHPUnit/Integration/Archive/ArchivePurgerTest.php b/tests/PHPUnit/Integration/Archive/ArchivePurgerTest.php
index 465988f4bd..8045376478 100644
--- a/tests/PHPUnit/Integration/Archive/ArchivePurgerTest.php
+++ b/tests/PHPUnit/Integration/Archive/ArchivePurgerTest.php
@@ -125,18 +125,46 @@ class ArchivePurgerTest extends IntegrationTestCase
//Extra data set with segment and plugin archives
self::$fixture->insertSegmentArchives($this->january);
- $validSegmentIds = array(
- 0 => ['DUMMYHASHSTR'], //valid for all sites
- 1 => ['abcd1234abcd5678'], //valid for site 1. should be ignored for site 2
- 2 => ['hashthatdontexist', 'hash1', 'hash2']
+ $segmentsToDelete = array(
+ array('definition' => '9876fedc5432abcd', 'enable_only_idsite' => 0),
+ array('definition' => 'hash3', 'enable_only_idsite' => 0),
+ // This segment also has archives for idsite = 1, which will be retained
+ array('definition' => 'abcd1234abcd5678', 'enable_only_idsite' => 2)
);
//Archive #29 also has a deleted segment but it's before the purge threshold so it stays for now.
- $deletedRowCount = $this->archivePurger->purgeDeletedSegmentArchives($this->january, $validSegmentIds);
+ $deletedRowCount = $this->archivePurger->purgeDeletedSegmentArchives($this->january, $segmentsToDelete);
$this->assertEquals(4 * RawArchiveDataWithTempAndInvalidated::ROWS_PER_ARCHIVE, $deletedRowCount);
self::$fixture->assertArchivesDoNotExist(array(22, 23, 24, 28), $this->january);
}
+ public function test_purgeNoSegmentArchives_preservesSingleSiteSegmentArchivesForDeletedAllSiteSegment()
+ {
+ // Extra data set with segment and plugin archives
+ self::$fixture->insertSegmentArchives($this->january);
+
+ $segmentsToDelete = array(
+ // This segment also has archives for idsite = 1, which will be retained
+ array('definition' => 'abcd1234abcd5678', 'enable_only_idsite' => 0, 'idsites_to_preserve' => array(2))
+ );
+
+ // Archives for idsite=1 should be purged, but those for idsite=2 can stay
+ $deletedRowCount = $this->archivePurger->purgeDeletedSegmentArchives($this->january, $segmentsToDelete);
+ $this->assertEquals(2 * RawArchiveDataWithTempAndInvalidated::ROWS_PER_ARCHIVE, $deletedRowCount);
+ self::$fixture->assertArchivesDoNotExist(array(20, 21), $this->january);
+ }
+
+ public function test_purgeNoSegmentArchives_blankSegmentName()
+ {
+ $segmentsToDelete = array(
+ array('definition' => '', 'enable_only_idsite' => 0)
+ );
+
+ // Should not purge all the "done%" archives!
+ $deletedRowCount = $this->archivePurger->purgeDeletedSegmentArchives($this->january, $segmentsToDelete);
+ $this->assertEquals(0, $deletedRowCount);
+ }
+
private function configureCustomRangePurging()
{
Config::getInstance()->General['purge_date_range_archives_after_X_days'] = 3;