Skip to content
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,16 @@ return [

#### Add the trait

Add the `HasDrafts` trait to your model
Add the `HasDrafts` trait and `Draftable` contact to your model

```php
<?php

use Illuminate\Database\Eloquent\Model;
use Oddvalue\LaravelDrafts\Concerns\HasDrafts;
use Oddvalue\LaravelDrafts\Contacts\Draftable;

class Post extends Model
class Post extends Model implements Draftable
{
use HasDrafts;

Expand Down
5 changes: 5 additions & 0 deletions config/drafts.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
*/
'published_at' => 'published_at',

/*
* Timestamp column that stores the date and time when the row is scheduled for publishing.
*/
'will_publish_at' => 'will_publish_at',

/*
* UUID column that stores the unique identifier of the model drafts.
*/
Expand Down
26 changes: 11 additions & 15 deletions database/factories/PostFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,27 @@ class PostFactory extends \Illuminate\Database\Eloquent\Factories\Factory
/**
* @inheritDoc
*/
public function definition()
public function definition(): array
{
return [
'title' => $this->faker->sentence,
'is_current' => true,
];
}

public function draft()
public function draft(): PostFactory
{
return $this->state(function () {
return [
'published_at' => null,
'is_published' => false,
];
});
return $this->state(fn (): array => [
'published_at' => null,
'is_published' => false,
]);
}

public function published()
public function published(): PostFactory
{
return $this->state(function () {
return [
'published_at' => now()->toDateTimeString(),
'is_published' => true,
];
});
return $this->state(fn (): array => [
'published_at' => now()->toDateTimeString(),
'is_published' => true,
]);
}
}
36 changes: 36 additions & 0 deletions src/Commands/PublishScheduledDrafts.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace Oddvalue\LaravelDrafts\Commands;

use Illuminate\Console\Command;
use InvalidArgumentException;
use Oddvalue\LaravelDrafts\Contacts\Draftable;

class PublishScheduledDrafts extends Command
{
protected $signature = 'drafts:publish {model}';

protected $description = 'Published scheduled drafts';

public function handle(): int
{
$class = $this->argument('model');

if (! class_exists($class) || ! in_array(Draftable::class, class_implements($class), strict: true)) {
throw new InvalidArgumentException("The model `{$class}` either doesn't exist or doesn't implement `Draftable`.");
}

$model = new $class();

$model::query()
->onlyDrafts()
->where($model->getWillPublishAtColumn(), '<', now())
->whereNull($model->getPublishedAtColumn())
->each(function (Draftable $record): void {
$record->setLive();
$record->save();
});

return Command::SUCCESS;
}
}
71 changes: 49 additions & 22 deletions src/Concerns/HasDrafts.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Oddvalue\LaravelDrafts\Concerns;

use Carbon\CarbonInterface;
use Closure;
use Illuminate\Contracts\Database\Query\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
Expand All @@ -11,13 +13,13 @@
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use JetBrains\PhpStorm\ArrayShape;
use Oddvalue\LaravelDrafts\Contacts\Draftable;
use Oddvalue\LaravelDrafts\Facades\LaravelDrafts;

/**
* @method void Current(Builder $query)
* @method void WithoutCurrent(Builder $query)
* @method void ExcludeRevision(Builder $query, int | Model $exclude)
* @method \Illuminate\Database\Eloquent\Builder<Draftable> current()
* @method \Illuminate\Database\Eloquent\Builder<Draftable> withoutCurrent()
* @method \Illuminate\Database\Eloquent\Builder<Draftable> excludeRevision(int | Model $exclude)
*/
trait HasDrafts
{
Expand All @@ -33,7 +35,7 @@ trait HasDrafts
|--------------------------------------------------------------------------
*/

public function initializeHasDrafts()
public function initializeHasDrafts(): void
{
$this->mergeCasts([
$this->getIsCurrentColumn() => 'boolean',
Expand All @@ -50,7 +52,7 @@ public static function bootHasDrafts(): void
}
});

static::creating(function (Model $model): void {
static::creating(function (Draftable | Model $model): void {
$model->{$model->getIsCurrentColumn()} = true;
$model->setPublisher();
$model->generateUuid();
Expand All @@ -59,26 +61,26 @@ public static function bootHasDrafts(): void
}
});

static::updating(function (Model $model): void {
static::updating(function (Draftable | Model $model): void {
$model->newRevision();
});

static::publishing(function (Model $model): void {
static::publishing(function (Draftable | Model $model): void {
$model->setLive();
});

static::deleted(function (Model $model): void {
static::deleted(function (Draftable | Model $model): void {
$model->revisions()->delete();
});

if (method_exists(static::class, 'restored')) {
static::restored(function (Model $model): void {
static::restored(function (Draftable | Model $model): void {
$model->revisions()->restore();
});
}

if (method_exists(static::class, 'forceDeleted')) {
static::forceDeleted(function (Model $model): void {
static::forceDeleted(function (Draftable | Model $model): void {
$model->revisions()->forceDelete();
});
}
Expand Down Expand Up @@ -168,6 +170,7 @@ public function setLive(): void

if (! $published || $this->is($published)) {
$this->{$this->getPublishedAtColumn()} ??= now();
$this->{$this->getWillPublishAtColumn()} = null;
$this->{$this->getIsPublishedColumn()} = true;
$this->setCurrent();

Expand Down Expand Up @@ -226,11 +229,27 @@ public function setLive(): void

$this->{$this->getIsPublishedColumn()} = false;
$this->{$this->getPublishedAtColumn()} = null;
$this->{$this->getWillPublishAtColumn()} = null;
$this->{$this->getIsCurrentColumn()} = false;
$this->timestamps = false;
$this->shouldCreateRevision = false;
}

public function schedulePublishing(CarbonInterface $date): static
{
$this->{$this->getWillPublishAtColumn()} = $date;
$this->save();

return $this;
}

public function clearScheduledPublishing(): static
{
$this->{$this->getWillPublishAtColumn()} = null;

return $this;
}

public function getDraftableRelations(): array
{
return property_exists($this, 'draftableRelations') ? $this->draftableRelations : [];
Expand Down Expand Up @@ -287,12 +306,12 @@ public function save(array $options = []): bool
return parent::save($options);
}

public static function savingAsDraft(string|\Closure $callback): void
public static function savingAsDraft(string | Closure $callback): void
{
static::registerModelEvent('savingAsDraft', $callback);
}

public static function savedAsDraft(string|\Closure $callback): void
public static function savedAsDraft(string | Closure $callback): void
{
static::registerModelEvent('drafted', $callback);
}
Expand All @@ -306,7 +325,7 @@ public function updateAsDraft(array $attributes = [], array $options = []): bool
return $this->fill($attributes)->saveAsDraft($options);
}

public static function createDraft(...$attributes): self
public static function createDraft(...$attributes): static
{
return tap(static::make(...$attributes), function ($instance) {
$instance->{$instance->getIsPublishedColumn()} = false;
Expand All @@ -324,7 +343,7 @@ public function setPublisher(): static
return $this;
}

public function pruneRevisions()
public function pruneRevisions(): void
{
self::withoutEvents(function () {
$revisionsToKeep = $this->revisions()
Expand All @@ -344,11 +363,11 @@ public function pruneRevisions()
}

/**
* Get the name of the "publisher" relation columns.
*
* @return array
* @return array{
* id: string,
* type: string
* }
*/
#[ArrayShape(['id' => "string", 'type' => "string"])]
public function getPublisherColumns(): array
{
return [
Expand All @@ -362,9 +381,10 @@ public function getPublisherColumns(): array
}

/**
* Get the fully qualified "publisher" relation columns.
*
* @return array
* @return array{
* id: string,
* type: string
* }
*/
public function getQualifiedPublisherColumns(): array
{
Expand All @@ -378,6 +398,13 @@ public function getIsCurrentColumn(): string
: config('drafts.column_names.is_current', 'is_current');
}

public function getWillPublishAtColumn(): string
{
return defined(static::class.'::WILL_PUBLISH_AT')
? static::WILL_PUBLISH_AT
: config('drafts.column_names.will_publish_at', 'will_publish_at');
}

public function getUuidColumn(): string
{
return defined(static::class.'::UUID')
Expand Down
Loading