-
Notifications
You must be signed in to change notification settings - Fork 96
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Christian Wolf <[email protected]>
- Loading branch information
1 parent
d5a7eb0
commit 44e85bf
Showing
3 changed files
with
247 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php | ||
|
||
namespace OCA\Cookbook\Helper\Filter; | ||
|
||
use OCA\Cookbook\Exception\InvalidRecipeException; | ||
use OCP\Files\File; | ||
|
||
/** | ||
* An abstract filter on a recipe. | ||
* | ||
* A filter should have a single purpose that is serves and implement this interface | ||
*/ | ||
interface AbstractRecipeFilter { | ||
/** | ||
* Filter the given recipe according to the filter class specification. | ||
* | ||
* This function can make changes to the recipe array to carry out the needed changes. | ||
* In order to signal if the JSON file needs updating, the return value must be true if and only if the recipe was changed. | ||
* | ||
* @param array $json The recipe data as array | ||
* @param File $recipe The file containing the recipe for further processing | ||
* @return boolean true, if and only if the recipe was changed | ||
* @throws InvalidRecipeException if the recipe was not correctly filtered | ||
*/ | ||
public function apply(array &$json, File $recipe): bool; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
<?php | ||
|
||
namespace OCA\Cookbook\Helper\Filter\DB; | ||
|
||
use DateTime; | ||
use DateTimeImmutable; | ||
use OCA\Cookbook\Helper\Filter\AbstractRecipeFilter; | ||
use OCP\Files\File; | ||
|
||
/** | ||
* Ensure the dates of a recipe are valid | ||
* | ||
* This filter will update the recipe to have both valid dateCreated and dateModified. | ||
* If the dates are given in correct format, nothing is changed. | ||
* | ||
* If only the dateModified is given, the dateCreated is set to the same value. | ||
* | ||
* If neither is given, the file modification time of the JSON file is taken into account. | ||
*/ | ||
class RecipeDatesFilter implements AbstractRecipeFilter { | ||
|
||
private const DATE_CREATED = 'dateCreated'; | ||
private const DATE_MODIFIED = 'dateModified'; | ||
|
||
private const PATTERN_DATE = '\d{4}-\d{2}-\d{2}'; | ||
private const PATTERN_TIME = '\d{1,2}:\d{2}:\d{2}(?:\.\d+)?'; | ||
|
||
/** @var string */ | ||
private $patternDate; | ||
|
||
public function __construct() | ||
{ | ||
$this->patternDate = join('|', [ | ||
'^' . self::PATTERN_DATE . '$', | ||
'^' . self::PATTERN_DATE . 'T' . self::PATTERN_TIME . '$' | ||
]); | ||
} | ||
|
||
public function apply(array &$json, File $recipe): bool { | ||
$ret = false; | ||
|
||
// First ensure the entries are present in general | ||
$this->ensureEntryExists($json, self::DATE_CREATED, $ret); | ||
$this->ensureEntryExists($json, self::DATE_MODIFIED, $ret); | ||
|
||
// Ensure the date formats are valid | ||
$this->checkDateFormat($json, self::DATE_CREATED, $ret); | ||
$this->checkDateFormat($json, self::DATE_MODIFIED, $ret); | ||
|
||
if(is_null($json['dateCreated'])) { | ||
if (is_null($json['dateModified'])) { | ||
// No dates have been set. Fall back to time of file | ||
$json['dateCreated'] = $this->getTimeFromFile($recipe); | ||
$ret = true; | ||
} else { | ||
// Copy over the modification time to the creation time | ||
$json['dateCreated'] = $json['dateModified']; | ||
$ret = true; | ||
} | ||
} | ||
/* | ||
The else case is not considered: | ||
If only the creation time is given, this is a valid recipe (no modifications so far). | ||
If both are given, no problem is present. | ||
*/ | ||
|
||
return $ret; | ||
} | ||
|
||
private function getTimeFromFile(File $file): string { | ||
$timestamp = $file->getCreationTime(); | ||
if($timestamp === 0) { | ||
$timestamp = $file->getUploadTime(); | ||
} | ||
if($timestamp === 0) { | ||
$timestamp = $file->getMTime(); | ||
} | ||
|
||
return $this->getDateFromTimestamp($timestamp); | ||
} | ||
|
||
private function getDateFromTimestamp(int $timestamp): string { | ||
$date = new DateTime(); | ||
$date->setTimestamp($timestamp); | ||
|
||
return $date->format(DateTime::ISO8601); | ||
} | ||
|
||
private function ensureEntryExists(array &$json, string $name, bool &$ret) { | ||
if(!array_key_exists($name, $json)) { | ||
$json[$name] = null; | ||
$ret = true; | ||
} | ||
} | ||
|
||
private function checkDateFormat(array &$json, string $name, bool &$ret) { | ||
if($json[$name] === null) { | ||
return; | ||
} | ||
|
||
// Check for valid date format | ||
if(preg_match('/' . $this->patternDate . '/', $json[$name]) === 1) { | ||
return; | ||
} | ||
|
||
// Last desperate approach: Is it a timestamp? | ||
if(preg_match('/^\d+$/', $json[$name])) { | ||
if($json[$name] > 0) { | ||
$json[$name] = $this->getDateFromTimestamp($json[$name]); | ||
$ret = true; | ||
return; | ||
} | ||
} | ||
|
||
// We cannot read the format. Removing it from teh recipe | ||
$json[$name] = null; | ||
$ret = true; | ||
return; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
<?php | ||
|
||
namespace OCA\Cookbook\tests\Unit\Helper\Filter\DB; | ||
|
||
use OCA\Cookbook\Helper\Filter\DB\RecipeDatesFilter; | ||
use OCP\Files\File; | ||
use PHPUnit\Framework\MockObject\Stub; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
/** | ||
* @covers OCA\Cookbook\Helper\Filter\DB\RecipeDatesFilter | ||
*/ | ||
class RecipeDatesFilterTest extends TestCase { | ||
/** @var RecipeDatesFilter */ | ||
private $dut; | ||
|
||
protected function setUp(): void | ||
{ | ||
$this->dut = new RecipeDatesFilter(); | ||
} | ||
|
||
public function dpFromJson() { | ||
yield ['2022-07-06T11:08:54', null, '2022-07-06T11:08:54', null, false]; | ||
yield [1657098534, 0, '2022-07-06T09:08:54+0000', null, true]; | ||
yield [1657098534, 1657098540, '2022-07-06T09:08:54+0000', '2022-07-06T09:09:00+0000', true]; | ||
yield [null, 1657098540, '2022-07-06T09:09:00+0000', '2022-07-06T09:09:00+0000', true]; | ||
yield [0, 1657098540, '2022-07-06T09:09:00+0000', '2022-07-06T09:09:00+0000', true]; | ||
} | ||
|
||
/** | ||
* @dataProvider dpFromJson | ||
*/ | ||
public function testFilterFromJson($created, $modified, $expectedCreation, $expectedModification, $updated) { | ||
$recipe = [ | ||
'name' => 'my Recipe', | ||
'dateCreated' => $created, | ||
'dateModified' => $modified | ||
]; | ||
$copy = $recipe; | ||
|
||
$file = $this->createStub(File::class); | ||
|
||
$ret = $this->dut->apply($recipe, $file); | ||
|
||
$this->assertEquals($updated, $ret, 'Reporting of modification status'); | ||
$this->assertEquals($expectedCreation, $recipe['dateCreated'], 'Wrong creation date'); | ||
$this->assertEquals($expectedModification, $recipe['dateModified'], 'Wrong modification date'); | ||
|
||
unset($recipe['dateCreated']); | ||
unset($recipe['dateModified']); | ||
unset($copy['dateCreated']); | ||
unset($copy['dateModified']); | ||
|
||
$this->assertEquals($copy, $recipe, 'Other entries must not change.'); | ||
} | ||
|
||
public function dpFromFile() { | ||
yield ['2022-07-06T09:08:54+0000', false, false, 1657098534, 1657098535, 1657098536]; | ||
yield ['2022-07-06T09:08:55+0000', false, false, 0, 1657098535, 1657098536]; | ||
yield ['2022-07-06T09:08:56+0000', false, false, 0, 0, 1657098536]; | ||
yield ['2022-07-06T09:08:54+0000', true, true, 1657098534, 1657098535, 1657098536]; | ||
} | ||
|
||
/** | ||
* @dataProvider dpFromFile | ||
*/ | ||
public function testFilterFromFile($creation, $creationPresent, $modificationPresent, $creationTime, $uploadTime, $mTime) { | ||
$recipe = [ | ||
'name' => 'my Recipe', | ||
]; | ||
if($creationPresent) { | ||
$recipe['dateCreated'] = null; | ||
} | ||
if($modificationPresent) { | ||
$recipe['dateModified'] = null; | ||
} | ||
|
||
$copy = $recipe; | ||
|
||
/** @var Stub|File */ | ||
$file = $this->createStub(File::class); | ||
$file->method('getCreationTime')->willReturn($creationTime); | ||
$file->method('getUploadTime')->willReturn($uploadTime); | ||
$file->method('getMTime')->willReturn($mTime); | ||
|
||
$ret = $this->dut->apply($recipe, $file); | ||
|
||
$this->assertTrue($ret, 'Reporting of modification status'); | ||
|
||
$this->assertEquals($creation, $recipe['dateCreated'], 'Wrong creation date'); | ||
$this->assertNull($recipe['dateModified'], 'Wrong modification date'); | ||
|
||
unset($recipe['dateCreated']); | ||
unset($recipe['dateModified']); | ||
unset($copy['dateCreated']); | ||
unset($copy['dateModified']); | ||
|
||
$this->assertEquals($copy, $recipe, 'Other entries must not change.'); | ||
|
||
} | ||
} |