...
 
Commits (2)
<?php
namespace Minds\Core\Boost\Delegates;
use Minds\Core\Boost\Network\Campaign;
use Minds\Core\Boost\Network\CampaignException;
class ValidateCampaignDatesDelegate
{
/**
* @param Campaign $campaign
* @return Campaign
* @throws CampaignException
*/
public function onCreate(Campaign $campaign)
{
$start = $this->normaliseStartTime($campaign->getStart());
$end = $this->normaliseEndTime($campaign->getEnd());
$this->validateStartTime($start);
$this->validateEndTime($end);
$this->validateStartAgainstEnd($start, $end);
return $campaign->setStart($start)->setEnd($end);
}
/**
* @param Campaign $campaign
* @param Campaign $campaignRef
* @return Campaign
* @throws CampaignException
*/
public function onUpdate(Campaign $campaign, Campaign $campaignRef)
{
// TODO: Ensure date updates from ref are valid against original campaign budget, etc.
$start = $this->normaliseStartTime($campaignRef->getStart());
$end = $this->normaliseEndTime($campaignRef->getEnd());
$this->validateStartTime($start);
$this->validateEndTime($end);
$this->validateStartAgainstEnd($start, $end);
if (!$campaign->hasStarted()) {
$campaign->setStart($start);
}
if (!$campaign->hasFinished() && $campaign->getEnd() < $end) {
$campaign->setEnd($end);
}
return $campaign;
}
private function normaliseStartTime(int $startTime): int
{
return strtotime(date('Y-m-d', $startTime / 1000) . ' 00:00:00') * 1000;
}
private function normaliseEndTime(int $endTime): int
{
return strtotime(date('Y-m-d', $endTime / 1000) . ' 23:59:59') * 1000;
}
private function validateStartTime(int $startTime): void
{
if ($startTime <= 0) {
throw new CampaignException('Campaign should have a start date');
}
$today = strtotime(date('Y-m-d') . ' 00:00:00') * 1000;
if ($startTime < $today) {
throw new CampaignException('Campaign start should not be in the past');
}
}
private function validateEndTime(int $endTime): void
{
if ($endTime <= 0) {
throw new CampaignException('Campaign should have an end date');
}
}
private function validateStartAgainstEnd(int $start, int $end): void
{
if ($start >= $end) {
throw new CampaignException('Campaign end before starting');
}
$startPlusOneMonth = strtotime('+1 month', $start / 1000) * 1000;
if ($startPlusOneMonth < $end) {
throw new CampaignException('Campaign must not be longer than 1 month');
}
}
}
......@@ -53,6 +53,8 @@ use Minds\Traits\MagicAttributes;
* @method Boost setRejectedReason(int $reason)
* @method string getChecksum()
* @method Boost setChecksum(string $checksum)
* @method string getBoostType()
* @method Boost setBoostType()
*/
class Boost
{
......@@ -70,71 +72,77 @@ class Boost
const RATING_SAFE = 1;
const RATING_OPEN = 2;
const BOOST_TYPE_NOW = 'now';
const BOOST_TYPE_CAMPAIGN = 'campaign';
/** @var int $guid */
private $guid;
protected $guid;
/** @var int $entityGuid */
private $entityGuid;
protected $entityGuid;
/** @var Entity $entity */
private $entity;
protected $entity;
/** @var double $bid */
private $bid;
protected $bid;
/** @var string $bidType */
private $bidType;
protected $bidType;
/** @var int $impressions */
private $impressions;
protected $impressions;
/** @var int $impressionsMet */
private $impressionsMet;
protected $impressionsMet;
/** @var int $ownerGuid */
private $ownerGuid;
protected $ownerGuid;
/** @var User $owner */
private $owner;
protected $owner;
/** @var int $createdTimestamp */
private $createdTimestamp;
protected $createdTimestamp;
/** @var int $reviewedTimestamp */
private $reviewedTimestamp;
protected $reviewedTimestamp;
/** @var int $rejectedTimestamp */
private $rejectedTimestamp;
protected $rejectedTimestamp;
/** @var int $revokedTimestamp */
private $revokedTimestamp;
protected $revokedTimestamp;
/** @var int $completedTimestamp */
private $completedTimestamp;
protected $completedTimestamp;
/** @var string $transactionId */
private $transactionId;
protected $transactionId;
/** @var string $type */
private $type = 'newsfeed';
protected $type = 'newsfeed';
/** @var bool $priority */
private $priority = false;
protected $priority = false;
/** @var int $rating */
private $rating;
protected $rating;
/** @var array $tags */
private $tags = [];
protected $tags = [];
/** @var array $nsfw */
private $nsfw = [];
protected $nsfw = [];
/** @var int $rejectedReason */
private $rejectedReason = -1;
protected $rejectedReason = -1;
/** @var string $checksum */
private $checksum;
protected $checksum;
/** @var string $boostType */
protected $boostType = self::BOOST_TYPE_NOW;
/**
* Return the state
......@@ -193,6 +201,7 @@ class Boost
'checksum' => $this->checksum,
'state' => $this->getState(),
'transaction_id' => $this->transactionId,
'boost_type' => $this->boostType,
];
}
......
<?php
namespace Minds\Core\Boost\Network;
use Minds\Entities\Entity;
use Minds\Entities\User;
use Minds\Traits\MagicAttributes;
/**
* Class Campaign
* @package Minds\Core\Boost\Network
* @method Campaign setGuid(int $guid)
* @method int getGuid()
* @method Campaign setEntityGuid(int $entityGuid)
* @method int getEntityGuid()
* @method Campaign setEntity($entity)
* @method Entity getEntity()
* @method Campaign setBid(double $bid)
* @method double getBid()
* @method Campaign setBidType(string $bidType)
* @method string getBidType()
* @method Campaign setImpressions(int $impressions)
* @method int getImpressions()
* @method Campaign setImpressionsMet(int $impressions)
* @method int getImpressionsMet()
* @method Campaign setOwnerGuid(int $ownerGuid)
* @method int getOwnerGuid()
* @method Campaign setOwner(User $owner)
* @method User getOwner()
* @method int getCreatedTimestamp()
* @method Campaign setCreatedTimestamp(int $ts)
* @method int getReviewedTimestamp()
* @method Campaign setReviewedTimestamp(int $ts)
* @method int getRejectedTimestamp()
* @method Campaign setRejectedTimestamp(int $ts)
* @method int getRevokedTimestamp()
* @method Campaign setRevokedTimestamp(int $ts)
* @method int getCompletedTimestamp()
* @method Campaign setCompletedTimestamp(int $ts)
* @method string getTransactionId()
* @method Campaign setTransactionId(string $transactionId)
* @method string getType()
* @method Campaign setType(string $value)
* @method bool getPriority()
* @method Campaign setPriority(bool $priority)
* @method int getRating()
* @method Campaign setRating(int $rating)
* @method array getTags()
* @method Campaign setTags(array $value)
* @method array getNsfw()
* @method Campaign setNsfw(array $nsfw)
* @method int getRejectedReason()
* @method Campaign setRejectedReason(int $reason)
* @method string getChecksum()
* @method Campaign setChecksum(string $checksum)
* @method string getBoostType()
* @method Campaign setBoostType()
* @method string getName()
* @method Campaign setName(string $name)
* @method int getStart()
* @method Campaign setStart(int $start)
* @method int getEnd()
* @method Campaign setEnd(int $end)
* @method int getBudget()
* @method Campaign setBudget(int $budget)
* @method int getDailyCap()
* @method Campaign setDailyCap(int $dailyCap)
*/
class Campaign extends Boost implements \JsonSerializable
{
use MagicAttributes;
const STATE_PENDING = 'pending';
/** @var string $boostType */
protected $boostType = self::BOOST_TYPE_CAMPAIGN;
/** @var string $name */
protected $name;
/** @var int $start */
protected $start;
/** @var int $end */
protected $end;
/** @var int $budget */
protected $budget;
/** @var int $dailyCap */
protected $dailyCap;
public function export($fields = []): array
{
$boostExport = parent::export($fields);
$campaignExport = [
'name' => $this->name,
'@start' => $this->start,
'@end' => $this->end,
'budget' => $this->budget,
'daily_cap' => $this->dailyCap,
'delivery_status' => $this->getDeliveryStatus(),
'cpm' => $this->cpm()
];
return array_merge($boostExport, $campaignExport);
}
public function getDeliveryStatus(): string
{
if ($this->completedTimestamp) {
return self::STATE_COMPLETED;
} elseif ($this->rejectedTimestamp) {
return self::STATE_REJECTED;
} elseif ($this->revokedTimestamp) {
return self::STATE_REVOKED;
} elseif ($this->reviewedTimestamp) {
return self::STATE_APPROVED;
} elseif ($this->createdTimestamp) {
return self::STATE_CREATED;
}
return self::STATE_PENDING;
}
public function cpm(): float
{
if (!$this->impressions || $this->impressions === 0) {
return 0;
}
return ($this->budget / $this->impressions) * 1000;
}
public function isDelivering(): bool
{
return $this->getDeliveryStatus() === self::STATE_APPROVED;
}
public function shouldBeStarted(int $now): bool
{
$isCreated = $this->getDeliveryStatus() === self::STATE_CREATED;
$started = $now >= $this->getStart() && $now < $this->getEnd();
return $isCreated && $started;
}
public function shouldBeCompleted(int $now): bool
{
$isDelivering = $this->isDelivering();
$ended = $now >= $this->getEnd();
$fulfilled = $this->getImpressionsMet() >= $this->getImpressions();
return $isDelivering && ($ended || $fulfilled);
}
public function hasStarted(): bool
{
return !in_array($this->getDeliveryStatus(), [self::STATE_PENDING, self::STATE_CREATED], true);
}
public function hasFinished(): bool
{
return in_array($this->getDeliveryStatus(), [
self::STATE_COMPLETED,
self::STATE_REJECTED,
self::STATE_REVOKED,
], false);
}
/**
* Specify data which should be serialized to JSON
* @link https://php.net/manual/en/jsonserializable.jsonserialize.php
* @return mixed data which can be serialized by <b>json_encode</b>,
* which is a value of any type other than a resource.
* @since 5.4.0
*/
public function jsonSerialize()
{
return $this->export();
}
public function getData(): array
{
$data = $this->export();
/* TODO: Filter data here */
return $data;
}
}
<?php
namespace Minds\Core\Boost\Network;
use Exception;
class CampaignException extends Exception
{
}
<?php
namespace Spec\Minds\Core\Boost\Delegates;
use Minds\Core\Boost\Network\Campaign;
use Minds\Core\Boost\Network\CampaignException;
use Minds\Core\Boost\Delegates\ValidateCampaignDatesDelegate;
use PhpSpec\ObjectBehavior;
class ValidateCampaignDatesDelegateSpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType(ValidateCampaignDatesDelegate::class);
}
public function it_should_reject_start_date_in_past_on_create(Campaign $campaign)
{
$campaign->getStart()->willReturn(strtotime('-1 day') * 1000);
$campaign->getEnd()->willReturn(strtotime('+3 days') * 1000);
$this->shouldThrow(CampaignException::class)->during('onCreate', [$campaign]);
}
public function it_should_reject_end_date_before_start_date_on_create(Campaign $campaign)
{
$campaign->getStart()->willReturn(strtotime('+2 days') * 1000);
$campaign->getEnd()->willReturn(strtotime('+1 days') * 1000);
$this->shouldThrow(CampaignException::class)->during('onCreate', [$campaign]);
}
public function it_should_adjust_start_and_end_to_first_and_last_timestamps_of_days_on_create(Campaign $campaign)
{
$campaign->getStart()->willReturn(strtotime('today 12:01:31') * 1000);
$campaign->getEnd()->willReturn(strtotime('+1 days 17:32:05') * 1000);
$campaign->setStart(strtotime('today 00:00:00') * 1000)->shouldBeCalled()->willReturn($campaign);
$campaign->setEnd(strtotime('+1 days 23:59:59') * 1000)->shouldBeCalled()->willReturn($campaign);
$this->onCreate($campaign);
}
public function it_should_accept_valid_days_on_create(Campaign $campaign)
{
$campaign->getStart()->willReturn(strtotime('+2 days') * 1000);
$campaign->getEnd()->willReturn(strtotime('+5 days') * 1000);
$campaign->setStart(strtotime('+2 days 00:00:00') * 1000)->shouldBeCalled()->willReturn($campaign);
$campaign->setEnd(strtotime('+5 days 23:59:59') * 1000)->shouldBeCalled()->willReturn($campaign);
$this->onCreate($campaign);
}
public function it_should_reject_campaign_longer_than_one_month(Campaign $campaign)
{
$campaign->getStart()->willReturn(strtotime('+2 days') * 1000);
$campaign->getEnd()->willReturn(strtotime('+34 days') * 1000);
$this->shouldThrow(CampaignException::class)->during('onCreate', [$campaign]);
}
/* public function it_should_validate_dates_are_valid_against_campaign_budget_on_update()
{
// TODO: Not Implemented yet
}*/
public function it_should_not_change_start_date_if_campaign_has_started_on_update(Campaign $campaign, Campaign $campaignRef)
{
$campaign->hasStarted()->shouldBeCalled()->willReturn(true);
$campaign->hasFinished()->shouldBeCalled()->willReturn(true);
$campaignRef->getStart()->willReturn(strtotime('+2 days') * 1000);
$campaignRef->getEnd()->willReturn(strtotime('+5 days') * 1000);
$campaign->getStart()->willReturn(strtotime('+2 days') * 1000);
$campaign->getEnd()->willReturn(strtotime('+5 days') * 1000);
$this->onUpdate($campaign, $campaignRef);
}
public function it_should_change_start_date_if_campaign_has_not_started_on_update(Campaign $campaign, Campaign $campaignRef)
{
$campaign->hasStarted()->shouldBeCalled()->willReturn(false);
$campaign->hasFinished()->shouldBeCalled()->willReturn(false);
$campaignRef->getStart()->willReturn(strtotime('+2 days') * 1000);
$campaignRef->getEnd()->willReturn(strtotime('+5 days') * 1000);
$campaign->getStart()->willReturn(strtotime('+2 days') * 1000);
$campaign->getEnd()->willReturn(strtotime('+5 days') * 1000);
$campaign->setStart(strtotime('+2 days 00:00:00') * 1000)->shouldBeCalled()->willReturn($campaign);
$campaign->setEnd(strtotime('+5 days 23:59:59') * 1000)->shouldBeCalled();
$this->onUpdate($campaign, $campaignRef);
}
public function it_should_only_change_end_date_if_campaign_hasnt_finished_on_update(Campaign $campaign, Campaign $campaignRef)
{
$campaign->hasStarted()->shouldBeCalled()->willReturn(true);
$campaign->hasFinished()->shouldBeCalled()->willReturn(false);
$campaignRef->getStart()->willReturn(strtotime('+2 days') * 1000);
$campaignRef->getEnd()->willReturn(strtotime('+7 days') * 1000);
$campaign->getStart()->willReturn(strtotime('+2 days') * 1000);
$campaign->getEnd()->willReturn(strtotime('+5 days') * 1000);
$campaign->setEnd(strtotime('+7 days 23:59:59') * 1000)->shouldBeCalled();
$this->onUpdate($campaign, $campaignRef);
}
}