add nova
This commit is contained in:
1126
nova/src/Actions/Action.php
Normal file
1126
nova/src/Actions/Action.php
Normal file
File diff suppressed because it is too large
Load Diff
86
nova/src/Actions/ActionCollection.php
Normal file
86
nova/src/Actions/ActionCollection.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Actions;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
|
||||
/**
|
||||
* @template TKey of array-key
|
||||
* @template TValue of \Laravel\Nova\Actions\Action
|
||||
*
|
||||
* @extends \Illuminate\Support\Collection<TKey, TValue>
|
||||
*/
|
||||
class ActionCollection extends Collection
|
||||
{
|
||||
/**
|
||||
* Get the actions that are authorized for viewing on the index.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
|
||||
* @return static<TKey, TValue>
|
||||
*/
|
||||
public function authorizedToSeeOnIndex(NovaRequest $request)
|
||||
{
|
||||
return $this->filter->shownOnIndex()
|
||||
->filter(function ($action) use ($request) {
|
||||
if ($action->sole === true) {
|
||||
return ! $request->allResourcesSelected()
|
||||
&& $request->selectedResourceIds()->count() === 1
|
||||
&& $action->authorizedToSee($request);
|
||||
}
|
||||
|
||||
return $action->authorizedToSee($request);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actions that are authorized for viewing on detail pages.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
|
||||
* @return static<TKey, TValue>
|
||||
*/
|
||||
public function authorizedToSeeOnDetail(NovaRequest $request)
|
||||
{
|
||||
return $this->filter->shownOnDetail()->filter->authorizedToSee($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actions that are authorized for viewing on table rows.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
|
||||
* @return static<TKey, TValue>
|
||||
*/
|
||||
public function authorizedToSeeOnTableRow(NovaRequest $request)
|
||||
{
|
||||
return $this->filter->shownOnTableRow()->filter->authorizedToSee($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the actions available for display can be executed.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return $this
|
||||
*/
|
||||
public function withAuthorizedToRun(NovaRequest $request, $model)
|
||||
{
|
||||
return $this->each(function (Action $action) use ($request, $model) {
|
||||
$action->authorizedToRun($request, $model);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return action counts by type on index.
|
||||
*
|
||||
* @return array{standalone: mixed, resource: mixed}
|
||||
*/
|
||||
public function countsByTypeOnIndex()
|
||||
{
|
||||
[$standalone, $resource] = $this->filter->shownOnIndex()->partition->isStandalone();
|
||||
|
||||
return [
|
||||
'standalone' => $standalone->count(),
|
||||
'resource' => $resource->count(),
|
||||
];
|
||||
}
|
||||
}
|
||||
495
nova/src/Actions/ActionEvent.php
Normal file
495
nova/src/Actions/ActionEvent.php
Normal file
@@ -0,0 +1,495 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Actions;
|
||||
|
||||
use DateTime;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Nova\Http\Requests\ActionRequest;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
use Laravel\Nova\Nova;
|
||||
use Laravel\Nova\Util;
|
||||
|
||||
/**
|
||||
* @property \Illuminate\Database\Eloquent\Model $target
|
||||
* @property \Illuminate\Foundation\Auth\User $user
|
||||
* @property array|null $changes
|
||||
* @property array|null $original
|
||||
*/
|
||||
class ActionEvent extends Model
|
||||
{
|
||||
/**
|
||||
* The attributes that aren't mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'changes' => 'array',
|
||||
'original' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
* The storage format of the model's date columns.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $dateFormat = 'Y-m-d H:i:s';
|
||||
|
||||
/**
|
||||
* Get the user that initiated the action.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(Util::userModel(), 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target of the action for user interface linking.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
|
||||
*/
|
||||
public function target()
|
||||
{
|
||||
$queryWithTrashed = function ($query) {
|
||||
return $query->withTrashed();
|
||||
};
|
||||
|
||||
return $this->morphTo('target', 'target_type', 'target_id')
|
||||
->constrain(
|
||||
collect(Nova::$resources)
|
||||
->filter(function ($resource) {
|
||||
return $resource::softDeletes();
|
||||
})->mapWithKeys(function ($resource) use ($queryWithTrashed) {
|
||||
return [$resource::$model => $queryWithTrashed];
|
||||
})->all()
|
||||
)->when(true, function ($query) use ($queryWithTrashed) {
|
||||
return $query->hasMacro('withTrashed') ? $queryWithTrashed($query) : $query;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new action event instance for a resource creation.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Auth\Authenticatable $user
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return static
|
||||
*/
|
||||
public static function forResourceCreate($user, $model)
|
||||
{
|
||||
return new static([
|
||||
'batch_id' => (string) Str::orderedUuid(),
|
||||
'user_id' => $user->getAuthIdentifier(),
|
||||
'name' => 'Create',
|
||||
'actionable_type' => $model->getMorphClass(),
|
||||
'actionable_id' => $model->getKey(),
|
||||
'target_type' => $model->getMorphClass(),
|
||||
'target_id' => $model->getKey(),
|
||||
'model_type' => $model->getMorphClass(),
|
||||
'model_id' => $model->getKey(),
|
||||
'fields' => '',
|
||||
'original' => null,
|
||||
'changes' => array_diff_key($model->attributesToArray(), array_flip($model->getHidden())),
|
||||
'status' => 'finished',
|
||||
'exception' => '',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new action event instance for a resource update.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Auth\Authenticatable $user
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return static
|
||||
*/
|
||||
public static function forResourceUpdate($user, $model)
|
||||
{
|
||||
return new static([
|
||||
'batch_id' => (string) Str::orderedUuid(),
|
||||
'user_id' => $user->getAuthIdentifier(),
|
||||
'name' => 'Update',
|
||||
'actionable_type' => $model->getMorphClass(),
|
||||
'actionable_id' => $model->getKey(),
|
||||
'target_type' => $model->getMorphClass(),
|
||||
'target_id' => $model->getKey(),
|
||||
'model_type' => $model->getMorphClass(),
|
||||
'model_id' => $model->getKey(),
|
||||
'fields' => '',
|
||||
'changes' => static::hydrateChangesPayload(
|
||||
$changes = array_diff_key($model->getDirty(), array_flip($model->getHidden()))
|
||||
),
|
||||
'original' => static::hydrateChangesPayload(
|
||||
array_intersect_key($model->newInstance()->setRawAttributes($model->getRawOriginal())->attributesToArray(), $changes)
|
||||
),
|
||||
'status' => 'finished',
|
||||
'exception' => '',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new action event instance for an attached resource.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @param \Illuminate\Database\Eloquent\Model $pivot
|
||||
* @return static
|
||||
*/
|
||||
public static function forAttachedResource(NovaRequest $request, $parent, $pivot)
|
||||
{
|
||||
return new static([
|
||||
'batch_id' => (string) Str::orderedUuid(),
|
||||
'user_id' => Nova::user($request)->getAuthIdentifier(),
|
||||
'name' => 'Attach',
|
||||
'actionable_type' => $parent->getMorphClass(),
|
||||
'actionable_id' => $parent->getKey(),
|
||||
'target_type' => Nova::modelInstanceForKey($request->relatedResource)->getMorphClass(),
|
||||
'target_id' => $request->input($request->relatedResource),
|
||||
'model_type' => $pivot->getMorphClass(),
|
||||
'model_id' => $pivot->getKey(),
|
||||
'fields' => '',
|
||||
'original' => null,
|
||||
'changes' => array_diff_key($pivot->attributesToArray(), $pivot->getHidden()),
|
||||
'status' => 'finished',
|
||||
'exception' => '',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new action event instance for an attached resource update.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @param \Illuminate\Database\Eloquent\Model $pivot
|
||||
* @return static
|
||||
*/
|
||||
public static function forAttachedResourceUpdate(NovaRequest $request, $parent, $pivot)
|
||||
{
|
||||
return new static([
|
||||
'batch_id' => (string) Str::orderedUuid(),
|
||||
'user_id' => Nova::user($request)->getAuthIdentifier(),
|
||||
'name' => 'Update Attached',
|
||||
'actionable_type' => $parent->getMorphClass(),
|
||||
'actionable_id' => $parent->getKey(),
|
||||
'target_type' => Nova::modelInstanceForKey($request->relatedResource)->getMorphClass(),
|
||||
'target_id' => $request->relatedResourceId,
|
||||
'model_type' => $pivot->getMorphClass(),
|
||||
'model_id' => $pivot->getKey(),
|
||||
'fields' => '',
|
||||
'changes' => static::hydrateChangesPayload(
|
||||
$changes = array_diff_key($pivot->getDirty(), array_flip($pivot->getHidden()))
|
||||
),
|
||||
'original' => static::hydrateChangesPayload(
|
||||
array_intersect_key($pivot->newInstance()->setRawAttributes($pivot->getRawOriginal())->attributesToArray(), $changes)
|
||||
),
|
||||
'status' => 'finished',
|
||||
'exception' => '',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new action event instances for resource deletes.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Auth\Authenticatable $user
|
||||
* @param \Illuminate\Support\Collection $models
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public static function forResourceDelete($user, Collection $models)
|
||||
{
|
||||
return static::forSoftDeleteAction('Delete', $user, $models);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new action event instances for resource restorations.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Auth\Authenticatable $user
|
||||
* @param \Illuminate\Support\Collection $models
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public static function forResourceRestore($user, Collection $models)
|
||||
{
|
||||
return static::forSoftDeleteAction('Restore', $user, $models);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new action event instances for resource soft deletions.
|
||||
*
|
||||
* @param string $action
|
||||
* @param \Illuminate\Contracts\Auth\Authenticatable $user
|
||||
* @param \Illuminate\Support\Collection $models
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public static function forSoftDeleteAction($action, $user, Collection $models)
|
||||
{
|
||||
$batchId = (string) Str::orderedUuid();
|
||||
|
||||
return $models->map(function ($model) use ($action, $user, $batchId) {
|
||||
return new static([
|
||||
'batch_id' => $batchId,
|
||||
'user_id' => $user->getAuthIdentifier(),
|
||||
'name' => $action,
|
||||
'actionable_type' => $model->getMorphClass(),
|
||||
'actionable_id' => $model->getKey(),
|
||||
'target_type' => $model->getMorphClass(),
|
||||
'target_id' => $model->getKey(),
|
||||
'model_type' => $model->getMorphClass(),
|
||||
'model_id' => $model->getKey(),
|
||||
'fields' => '',
|
||||
'original' => null,
|
||||
'changes' => null,
|
||||
'status' => 'finished',
|
||||
'exception' => '',
|
||||
'created_at' => new DateTime,
|
||||
'updated_at' => new DateTime,
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new action event instances for resource detachments.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Auth\Authenticatable $user
|
||||
* @param \Illuminate\Database\Eloquent\Model $parent
|
||||
* @param \Illuminate\Support\Collection $models
|
||||
* @param string $pivotClass
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public static function forResourceDetach($user, $parent, Collection $models, $pivotClass)
|
||||
{
|
||||
$batchId = (string) Str::orderedUuid();
|
||||
|
||||
return $models->map(function ($model) use ($user, $parent, $pivotClass, $batchId) {
|
||||
return new static([
|
||||
'batch_id' => $batchId,
|
||||
'user_id' => $user->getAuthIdentifier(),
|
||||
'name' => 'Detach',
|
||||
'actionable_type' => $parent->getMorphClass(),
|
||||
'actionable_id' => $parent->getKey(),
|
||||
'target_type' => $model->getMorphClass(),
|
||||
'target_id' => $model->getKey(),
|
||||
'model_type' => $pivotClass,
|
||||
'model_id' => null,
|
||||
'fields' => '',
|
||||
'original' => null,
|
||||
'changes' => null,
|
||||
'status' => 'finished',
|
||||
'exception' => '',
|
||||
'created_at' => new DateTime,
|
||||
'updated_at' => new DateTime,
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the action records for the given models.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\ActionRequest $request
|
||||
* @param \Laravel\Nova\Actions\Action $action
|
||||
* @param string $batchId
|
||||
* @param \Illuminate\Support\Collection $models
|
||||
* @param string $status
|
||||
* @return void
|
||||
*/
|
||||
public static function createForModels(ActionRequest $request, Action $action,
|
||||
$batchId, Collection $models, $status = 'running')
|
||||
{
|
||||
$models = $models->map(function ($model) use ($request, $action, $batchId, $status) {
|
||||
return array_merge(
|
||||
static::defaultAttributes($request, $action, $batchId, $status),
|
||||
[
|
||||
'actionable_id' => $request->actionableKey($model),
|
||||
'target_id' => $request->targetKey($model),
|
||||
'model_id' => $model->getKey(),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
$models->chunk(50)->each(function ($models) {
|
||||
static::insert($models->all());
|
||||
});
|
||||
|
||||
static::prune($models);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default attributes for creating a new action event.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\ActionRequest $request
|
||||
* @param \Laravel\Nova\Actions\Action $action
|
||||
* @param string $batchId
|
||||
* @param string $status
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function defaultAttributes(ActionRequest $request, Action $action,
|
||||
$batchId, $status = 'running')
|
||||
{
|
||||
if ($request->isPivotAction()) {
|
||||
$pivotClass = $request->pivotRelation()->getPivotClass();
|
||||
|
||||
$modelType = collect(Relation::$morphMap)->filter(function ($model) use ($pivotClass) {
|
||||
return $model === $pivotClass;
|
||||
})->keys()->first() ?? $pivotClass;
|
||||
} else {
|
||||
$modelType = $request->actionableModel()->getMorphClass();
|
||||
}
|
||||
|
||||
return [
|
||||
'batch_id' => $batchId,
|
||||
'user_id' => Nova::user($request)->getAuthIdentifier(),
|
||||
'name' => $action->name(),
|
||||
'actionable_type' => $request->actionableModel()->getMorphClass(),
|
||||
'target_type' => $request->model()->getMorphClass(),
|
||||
'model_type' => $modelType,
|
||||
'fields' => serialize($request->resolveFieldsForStorage()),
|
||||
'original' => null,
|
||||
'changes' => null,
|
||||
'status' => $status,
|
||||
'exception' => '',
|
||||
'created_at' => new DateTime,
|
||||
'updated_at' => new DateTime,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prune the action events for the given types.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $models
|
||||
* @param int $limit
|
||||
* @return void
|
||||
*/
|
||||
public static function prune($models, $limit = 25)
|
||||
{
|
||||
$models->each(function ($model) use ($limit) {
|
||||
static::where('actionable_id', $model['actionable_id'])
|
||||
->where('actionable_type', $model['actionable_type'])
|
||||
->whereNotIn('id', function ($query) use ($model, $limit) {
|
||||
$query->select('id')->fromSub(
|
||||
static::select('id')->orderBy('id', 'desc')
|
||||
->where('actionable_id', $model['actionable_id'])
|
||||
->where('actionable_type', $model['actionable_type'])
|
||||
->limit($limit)->toBase(),
|
||||
'action_events_temp'
|
||||
);
|
||||
})->delete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the given batch as running.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @return int
|
||||
*/
|
||||
public static function markBatchAsRunning($batchId)
|
||||
{
|
||||
return static::where('batch_id', $batchId)
|
||||
->whereNotIn('status', ['finished', 'failed'])->update([
|
||||
'status' => 'running',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the given batch as finished.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @return int
|
||||
*/
|
||||
public static function markBatchAsFinished($batchId)
|
||||
{
|
||||
return static::where('batch_id', $batchId)
|
||||
->whereNotIn('status', ['finished', 'failed'])->update([
|
||||
'status' => 'finished',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a given action event record as finished.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return int
|
||||
*/
|
||||
public static function markAsFinished($batchId, $model)
|
||||
{
|
||||
return static::updateStatus($batchId, $model, 'finished');
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the given batch as failed.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @param \Throwable $e
|
||||
* @return int
|
||||
*/
|
||||
public static function markBatchAsFailed($batchId, $e = null)
|
||||
{
|
||||
return static::where('batch_id', $batchId)
|
||||
->whereNotIn('status', ['finished', 'failed'])->update([
|
||||
'status' => 'failed',
|
||||
'exception' => $e ? (string) $e : '',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a given action event record as failed.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param \Throwable|string $e
|
||||
* @return int
|
||||
*/
|
||||
public static function markAsFailed($batchId, $model, $e = null)
|
||||
{
|
||||
return static::updateStatus($batchId, $model, 'failed', $e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the status of a given action event.
|
||||
*
|
||||
* @param string $batchId
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $status
|
||||
* @param \Throwable|string $e
|
||||
* @return int
|
||||
*/
|
||||
public static function updateStatus($batchId, $model, $status, $e = null)
|
||||
{
|
||||
return static::where('batch_id', $batchId)
|
||||
->where('model_type', $model->getMorphClass())
|
||||
->where('model_id', $model->getKey())
|
||||
->update(['status' => $status, 'exception' => (string) $e]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the table associated with the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTable()
|
||||
{
|
||||
return 'action_events';
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate the changes payuload.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return array
|
||||
*/
|
||||
protected static function hydrateChangesPayload(array $attributes)
|
||||
{
|
||||
return collect($attributes)
|
||||
->transform(function ($value) {
|
||||
return Util::hydrate($value);
|
||||
})->all();
|
||||
}
|
||||
}
|
||||
30
nova/src/Actions/ActionMethod.php
Normal file
30
nova/src/Actions/ActionMethod.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Actions;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ActionMethod
|
||||
{
|
||||
/**
|
||||
* Determine the appropriate "handle" method for the given models.
|
||||
*
|
||||
* @param \Laravel\Nova\Actions\Action $action
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @return string
|
||||
*/
|
||||
public static function determine(Action $action, $model)
|
||||
{
|
||||
if (! is_null($action->handleCallback)) {
|
||||
return 'handleUsingCallback';
|
||||
}
|
||||
|
||||
$method = 'handleFor'.Str::plural(class_basename($model));
|
||||
|
||||
if (method_exists($action, $method)) {
|
||||
return $method;
|
||||
}
|
||||
|
||||
return 'handle';
|
||||
}
|
||||
}
|
||||
48
nova/src/Actions/ActionModelCollection.php
Normal file
48
nova/src/Actions/ActionModelCollection.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Actions;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
||||
use Laravel\Nova\Http\Requests\ActionRequest;
|
||||
|
||||
/**
|
||||
* @template TKey of array-key
|
||||
* @template TModel of \Illuminate\Database\Eloquent\Model
|
||||
*
|
||||
* @extends \Illuminate\Database\Eloquent\Collection<TKey, TModel>
|
||||
*/
|
||||
class ActionModelCollection extends EloquentCollection
|
||||
{
|
||||
/**
|
||||
* Remove models the user does not have permission to execute the action against.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\ActionRequest $request
|
||||
* @return static
|
||||
*/
|
||||
public function filterForExecution(ActionRequest $request)
|
||||
{
|
||||
$action = $request->action();
|
||||
$isPivotAction = $request->isPivotAction();
|
||||
|
||||
return new static($this->filter(function ($model) use ($request, $action, $isPivotAction) {
|
||||
if ($isPivotAction || $action->runCallback) {
|
||||
return $action->authorizedToRun($request, $model);
|
||||
}
|
||||
|
||||
return $action->authorizedToRun($request, $model) && $this->filterByResourceAuthorization($request, $request->newResourceWith($model), $action);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove models the user does not have permission to execute the action against.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\ActionRequest $request
|
||||
* @param \Laravel\Nova\Resource $resource
|
||||
* @param \Laravel\Nova\Actions\Action|\Laravel\Nova\Actions\DestructiveAction $action
|
||||
* @return bool
|
||||
*/
|
||||
protected function filterByResourceAuthorization(ActionRequest $request, $resource, $action)
|
||||
{
|
||||
return $resource->authorizedToRunAction($request, $action);
|
||||
}
|
||||
}
|
||||
196
nova/src/Actions/ActionResource.php
Normal file
196
nova/src/Actions/ActionResource.php
Normal file
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Actions;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Nova\Fields\DateTime;
|
||||
use Laravel\Nova\Fields\ID;
|
||||
use Laravel\Nova\Fields\KeyValue;
|
||||
use Laravel\Nova\Fields\MorphToActionTarget;
|
||||
use Laravel\Nova\Fields\Status;
|
||||
use Laravel\Nova\Fields\Text;
|
||||
use Laravel\Nova\Fields\Textarea;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
use Laravel\Nova\Nova;
|
||||
use Laravel\Nova\Resource;
|
||||
|
||||
/**
|
||||
* @template TActionModel of \Laravel\Nova\Actions\ActionEvent
|
||||
*
|
||||
* @extends \Laravel\Nova\Resource<TActionModel>
|
||||
*/
|
||||
class ActionResource extends Resource
|
||||
{
|
||||
/**
|
||||
* The model the resource corresponds to.
|
||||
*
|
||||
* @var class-string<TActionModel>
|
||||
*/
|
||||
public static $model = ActionEvent::class;
|
||||
|
||||
/**
|
||||
* The single value that should be used to represent the resource when being displayed.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $title = 'name';
|
||||
|
||||
/**
|
||||
* Indicates if the resource should be globally searchable.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $globallySearchable = false;
|
||||
|
||||
/**
|
||||
* Indicates whether the resource should automatically poll for new resources.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $polling = true;
|
||||
|
||||
/**
|
||||
* Determine if the current user can create new resources.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public static function authorizedToCreate(Request $request)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current user can edit resources.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public function authorizedToUpdate(Request $request)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current user can replicate the given resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public function authorizedToReplicate(Request $request)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current user can delete resources.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public function authorizedToDelete(Request $request)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fields displayed by the resource.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
|
||||
* @return array
|
||||
*/
|
||||
public function fields(NovaRequest $request)
|
||||
{
|
||||
return [
|
||||
ID::make(Nova::__('ID'), 'id')->showOnPreview(),
|
||||
Text::make(Nova::__('Action Name'), 'name', function ($value) {
|
||||
return Nova::__($value);
|
||||
})->showOnPreview(),
|
||||
|
||||
Text::make(Nova::__('Action Initiated By'), function () {
|
||||
return $this->user->name ?? $this->user->email ?? __('Nova User');
|
||||
})->showOnPreview(),
|
||||
|
||||
MorphToActionTarget::make(Nova::__('Action Target'), 'target')->showOnPreview(),
|
||||
|
||||
Status::make(Nova::__('Action Status'), 'status', function ($value) {
|
||||
return Nova::__(ucfirst($value));
|
||||
})->loadingWhen([Nova::__('Waiting'), Nova::__('Running')])->failedWhen([Nova::__('Failed')]),
|
||||
|
||||
$this->when(isset($this->original), function () {
|
||||
return KeyValue::make(Nova::__('Original'), 'original')->showOnPreview();
|
||||
}),
|
||||
|
||||
$this->when(isset($this->changes), function () {
|
||||
return KeyValue::make(Nova::__('Changes'), 'changes')->showOnPreview();
|
||||
}),
|
||||
|
||||
Textarea::make(Nova::__('Exception'), 'exception')->showOnPreview(),
|
||||
|
||||
DateTime::make(Nova::__('Action Happened At'), 'created_at')->exceptOnForms()->showOnPreview(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an "index" query for the given resource.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public static function indexQuery(NovaRequest $request, $query)
|
||||
{
|
||||
return $query->with('user');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this resource is available for navigation.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public static function availableForNavigation(Request $request)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this resource is searchable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function searchable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the displayable label of the resource.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function label()
|
||||
{
|
||||
return Nova::__('Actions');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the displayable singular label of the resource.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function singularLabel()
|
||||
{
|
||||
return Nova::__('Action');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URI key for the resource.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function uriKey()
|
||||
{
|
||||
return 'action-events';
|
||||
}
|
||||
}
|
||||
311
nova/src/Actions/ActionResponse.php
Normal file
311
nova/src/Actions/ActionResponse.php
Normal file
@@ -0,0 +1,311 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Actions;
|
||||
|
||||
use ArrayAccess;
|
||||
use JsonSerializable;
|
||||
|
||||
class ActionResponse implements ArrayAccess, JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $danger;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $deleted;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $download;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $message;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $openInNewTab;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $redirect;
|
||||
|
||||
/**
|
||||
* @var array{path: string, options: array<string, mixed>}|null
|
||||
*/
|
||||
private $visit;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $modal;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $data = [];
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @return \Laravel\Nova\Actions\ActionResponse
|
||||
*/
|
||||
public static function message($message)
|
||||
{
|
||||
return tap(new static, function (self $response) use ($message) {
|
||||
$response->withMessage($message);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @return $this
|
||||
*/
|
||||
public function withMessage($message)
|
||||
{
|
||||
$this->message = $message;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @return $this
|
||||
*/
|
||||
public function withDangerMessage($message)
|
||||
{
|
||||
$this->danger = $message;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @return \Laravel\Nova\Actions\ActionResponse
|
||||
*/
|
||||
public static function danger(string $message)
|
||||
{
|
||||
return tap(new static, function (self $response) use ($message) {
|
||||
$response->withDangerMessage($message);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function withDeleted()
|
||||
{
|
||||
$this->deleted = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Laravel\Nova\Actions\ActionResponse
|
||||
*/
|
||||
public static function deleted()
|
||||
{
|
||||
return tap(new static, function (self $response) {
|
||||
$response->withDeleted();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @return $this
|
||||
*/
|
||||
public function withRedirect($url)
|
||||
{
|
||||
$this->redirect = $url;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @return \Laravel\Nova\Actions\ActionResponse
|
||||
*/
|
||||
public static function redirect($url)
|
||||
{
|
||||
return tap(new static, function (self $response) use ($url) {
|
||||
$response->withRedirect($url);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @return \Laravel\Nova\Actions\ActionResponse
|
||||
*/
|
||||
public static function openInNewTab($url)
|
||||
{
|
||||
return tap(new static, function (self $response) use ($url) {
|
||||
$response->usingNewTab($url);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|\Laravel\Nova\URL $path
|
||||
* @param array<string, mixed> $options
|
||||
* @return $this
|
||||
*/
|
||||
public function withVisitOptions($path, $options = [])
|
||||
{
|
||||
$this->visit = [
|
||||
'path' => '/'.ltrim($path, '/'),
|
||||
'options' => $options,
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|\Laravel\Nova\URL $path
|
||||
* @param array<string, mixed> $options
|
||||
* @return \Laravel\Nova\Actions\ActionResponse
|
||||
*/
|
||||
public static function visit($path, $options = [])
|
||||
{
|
||||
return tap(new static, function (self $response) use ($path, $options) {
|
||||
$response->withVisitOptions($path, $options);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @return $this
|
||||
*/
|
||||
private function usingNewTab($url)
|
||||
{
|
||||
$this->openInNewTab = $url;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $url
|
||||
* @return $this
|
||||
*/
|
||||
public function withDownload($name, $url)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->download = $url;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $url
|
||||
* @return \Laravel\Nova\Actions\ActionResponse
|
||||
*/
|
||||
public static function download(string $name, string $url)
|
||||
{
|
||||
return tap(new static, function (self $response) use ($name, $url) {
|
||||
$response->withDownload($name, $url);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $modal
|
||||
* @param array $data
|
||||
* @return $this
|
||||
*/
|
||||
public function withModal($modal, $data = [])
|
||||
{
|
||||
$this->modal = $modal;
|
||||
$this->data = $data;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $modal
|
||||
* @param array $data
|
||||
* @return \Laravel\Nova\Actions\ActionResponse
|
||||
*/
|
||||
public static function modal(string $modal, $data)
|
||||
{
|
||||
return tap(new static, function (self $response) use ($data, $modal) {
|
||||
$response->withModal($modal, $data);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given offset exists.
|
||||
*
|
||||
* @param string $offset
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($offset): bool
|
||||
{
|
||||
return isset($this->{$offset});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value for a given offset.
|
||||
*
|
||||
* @param string $offset
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function offsetGet($offset): mixed
|
||||
{
|
||||
return $this->{$offset};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value at the given offset.
|
||||
*
|
||||
* @param string $offset
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function offsetSet($offset, $value): void
|
||||
{
|
||||
if (property_exists($this, $offset)) {
|
||||
$this->{$offset} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the value at the given offset.
|
||||
*
|
||||
* @param string $offset
|
||||
* @return void
|
||||
*/
|
||||
public function offsetUnset($offset): void
|
||||
{
|
||||
unset($this->{$offset});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the element for JSON serialization.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return array_filter(array_merge([
|
||||
'danger' => $this->danger,
|
||||
'deleted' => $this->deleted,
|
||||
'download' => $this->download,
|
||||
'modal' => $this->modal,
|
||||
'message' => $this->message,
|
||||
'name' => $this->name,
|
||||
'openInNewTab' => $this->openInNewTab,
|
||||
'redirect' => $this->redirect,
|
||||
'visit' => $this->visit,
|
||||
], $this->data));
|
||||
}
|
||||
}
|
||||
18
nova/src/Actions/Actionable.php
Normal file
18
nova/src/Actions/Actionable.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Actions;
|
||||
|
||||
use Laravel\Nova\Nova;
|
||||
|
||||
trait Actionable
|
||||
{
|
||||
/**
|
||||
* Get all of the action events for the user.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
|
||||
*/
|
||||
public function actions()
|
||||
{
|
||||
return $this->morphMany(Nova::actionEvent(), 'actionable');
|
||||
}
|
||||
}
|
||||
117
nova/src/Actions/CallQueuedAction.php
Normal file
117
nova/src/Actions/CallQueuedAction.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Actions;
|
||||
|
||||
use Illuminate\Bus\Batchable;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Nova\Contracts\BatchableAction;
|
||||
use Laravel\Nova\Fields\ActionFields;
|
||||
use Laravel\Nova\Nova;
|
||||
|
||||
#[\AllowDynamicProperties]
|
||||
class CallQueuedAction
|
||||
{
|
||||
use Batchable;
|
||||
use CallsQueuedActions;
|
||||
|
||||
/**
|
||||
* The Eloquent model/data collection.
|
||||
*
|
||||
* @var \Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection
|
||||
*/
|
||||
public $models;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param \Laravel\Nova\Actions\Action $action
|
||||
* @param string $method
|
||||
* @param \Laravel\Nova\Fields\ActionFields $fields
|
||||
* @param \Illuminate\Support\Collection $models
|
||||
* @param string $actionBatchId
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Action $action, $method, ActionFields $fields, Collection $models, $actionBatchId)
|
||||
{
|
||||
$this->action = $action;
|
||||
$this->method = $method;
|
||||
$this->fields = $fields;
|
||||
$this->models = $models;
|
||||
$this->actionBatchId = $actionBatchId;
|
||||
|
||||
if (property_exists($action, 'timeout')) {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->timeout = $action->timeout;
|
||||
}
|
||||
|
||||
if (property_exists($action, 'tries')) {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->tries = $action->tries;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->callAction(function ($action) {
|
||||
if ($action instanceof BatchableAction) {
|
||||
$action->withBatchId($this->batchId);
|
||||
}
|
||||
|
||||
return $action->withActionBatchId($this->actionBatchId)
|
||||
->{$this->method}($this->fields, $this->models);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the failed method on the job instance.
|
||||
*
|
||||
* @param \Exception $e
|
||||
* @return void
|
||||
*/
|
||||
public function failed($e)
|
||||
{
|
||||
Nova::usingActionEvent(function ($actionEvent) use ($e) {
|
||||
if (! $this->action->withoutActionEvents) {
|
||||
$actionEvent->markBatchAsFailed($this->actionBatchId, $e);
|
||||
}
|
||||
});
|
||||
|
||||
if ($method = $this->failedMethodName()) {
|
||||
call_user_func([$this->action, $method], $this->fields, $this->models, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the "failed" method that should be called for the action.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function failedMethodName()
|
||||
{
|
||||
if (($method = $this->failedMethodForModel()) &&
|
||||
method_exists($this->action, $method)) {
|
||||
return $method;
|
||||
}
|
||||
|
||||
return method_exists($this->action, 'failed')
|
||||
? 'failed' : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate "failed" method name for the action's model type.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function failedMethodForModel()
|
||||
{
|
||||
if ($this->models->isNotEmpty()) {
|
||||
return 'failedFor'.Str::plural(class_basename($this->models->first()));
|
||||
}
|
||||
}
|
||||
}
|
||||
97
nova/src/Actions/CallsQueuedActions.php
Normal file
97
nova/src/Actions/CallsQueuedActions.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Actions;
|
||||
|
||||
use Illuminate\Bus\Batchable;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Laravel\Nova\Nova;
|
||||
|
||||
trait CallsQueuedActions
|
||||
{
|
||||
use Batchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* The action class name.
|
||||
*
|
||||
* @var \Laravel\Nova\Actions\Action
|
||||
*/
|
||||
public $action;
|
||||
|
||||
/**
|
||||
* The method that should be called on the action.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $method;
|
||||
|
||||
/**
|
||||
* The resolved fields.
|
||||
*
|
||||
* @var \Laravel\Nova\Fields\ActionFields
|
||||
*/
|
||||
public $fields;
|
||||
|
||||
/**
|
||||
* The batch ID of the action event records.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $actionBatchId;
|
||||
|
||||
/**
|
||||
* Call the action using the given callback.
|
||||
*
|
||||
* @param callable(\Laravel\Nova\Actions\Action):void $callback
|
||||
* @return void
|
||||
*/
|
||||
protected function callAction($callback)
|
||||
{
|
||||
Nova::usingActionEvent(function ($actionEvent) {
|
||||
if (! $this->action->withoutActionEvents) {
|
||||
$actionEvent->markBatchAsRunning($this->actionBatchId);
|
||||
}
|
||||
});
|
||||
|
||||
$action = $this->setJobInstanceIfNecessary($this->action);
|
||||
|
||||
$callback($action);
|
||||
|
||||
if (! $this->job->hasFailed() && ! $this->job->isReleased()) {
|
||||
Nova::usingActionEvent(function ($actionEvent) {
|
||||
if (! $this->action->withoutActionEvents) {
|
||||
$actionEvent->markBatchAsFinished($this->actionBatchId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the job instance of the given class if necessary.
|
||||
*
|
||||
* @param mixed $instance
|
||||
* @return mixed
|
||||
*/
|
||||
protected function setJobInstanceIfNecessary($instance)
|
||||
{
|
||||
if (in_array(InteractsWithQueue::class, class_uses_recursive(get_class($instance)))) {
|
||||
$instance->setJob($this->job);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the display name for the queued job.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function displayName()
|
||||
{
|
||||
return get_class($this->action);
|
||||
}
|
||||
}
|
||||
8
nova/src/Actions/DestructiveAction.php
Normal file
8
nova/src/Actions/DestructiveAction.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Actions;
|
||||
|
||||
class DestructiveAction extends Action
|
||||
{
|
||||
//
|
||||
}
|
||||
317
nova/src/Actions/DispatchAction.php
Normal file
317
nova/src/Actions/DispatchAction.php
Normal file
@@ -0,0 +1,317 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Actions;
|
||||
|
||||
use Illuminate\Bus\PendingBatch;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Laravel\Nova\Contracts\BatchableAction;
|
||||
use Laravel\Nova\Fields\ActionFields;
|
||||
use Laravel\Nova\Http\Requests\ActionRequest;
|
||||
use Laravel\Nova\Nova;
|
||||
|
||||
class DispatchAction
|
||||
{
|
||||
/**
|
||||
* The request instance.
|
||||
*
|
||||
* @var \Laravel\Nova\Http\Requests\ActionRequest
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* The action instance.
|
||||
*
|
||||
* @var \Laravel\Nova\Actions\Action
|
||||
*/
|
||||
protected $action;
|
||||
|
||||
/**
|
||||
* The fields for action instance.
|
||||
*
|
||||
* @var \Laravel\Nova\Fields\ActionFields
|
||||
*/
|
||||
protected $fields;
|
||||
|
||||
/**
|
||||
* The pending batch instance (if the action implements BatchableAction).
|
||||
*
|
||||
* @var \Illuminate\Bus\PendingBatch|null
|
||||
*/
|
||||
protected $batchJob;
|
||||
|
||||
/**
|
||||
* Set dispatchable callback.
|
||||
*
|
||||
* @var (callable(\Laravel\Nova\Actions\Response):(mixed))|null
|
||||
*/
|
||||
protected $dispatchableCallback;
|
||||
|
||||
/**
|
||||
* Create a new action dispatcher instance.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\ActionRequest $request
|
||||
* @param \Laravel\Nova\Actions\Action $action
|
||||
* @param \Laravel\Nova\Fields\ActionFields $fields
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(ActionRequest $request, Action $action, ActionFields $fields)
|
||||
{
|
||||
$this->request = $request;
|
||||
$this->action = $action;
|
||||
$this->fields = $fields;
|
||||
|
||||
if ($action instanceof BatchableAction) {
|
||||
$this->configureBatchJob($action, $fields);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the batch job for the action.
|
||||
*
|
||||
* @param \Laravel\Nova\Actions\Action $action
|
||||
* @param \Laravel\Nova\Fields\ActionFields $fields
|
||||
* @return void
|
||||
*/
|
||||
protected function configureBatchJob(Action $action, ActionFields $fields)
|
||||
{
|
||||
$this->batchJob = tap(Bus::batch([]), function (PendingBatch $batch) use ($action, $fields) {
|
||||
$batch->name($action->name());
|
||||
|
||||
if (! is_null($connection = $this->connection())) {
|
||||
$batch->onConnection($connection);
|
||||
}
|
||||
|
||||
if (! is_null($queue = $this->queue())) {
|
||||
$batch->onQueue($queue);
|
||||
}
|
||||
|
||||
$action->withBatch($fields, $batch);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the action.
|
||||
*
|
||||
* @return \Laravel\Nova\Actions\Response
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function dispatch()
|
||||
{
|
||||
if ($this->action instanceof ShouldQueue) {
|
||||
return tap(new Response(), function ($response) {
|
||||
with($response, $this->dispatchableCallback);
|
||||
|
||||
if (! is_null($this->batchJob)) {
|
||||
$this->batchJob->dispatch();
|
||||
}
|
||||
|
||||
return $response->successful();
|
||||
});
|
||||
}
|
||||
|
||||
return with(new Response(), $this->dispatchableCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the given action.
|
||||
*
|
||||
* @param string $method
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function handleStandalone($method)
|
||||
{
|
||||
$this->dispatchableCallback = function (Response $response) use ($method) {
|
||||
if ($this->action instanceof ShouldQueue) {
|
||||
$this->addQueuedActionJob($method, collect());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return $response->successful([
|
||||
$this->dispatchSynchronouslyForCollection($method, collect()),
|
||||
]);
|
||||
};
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the given action.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\ActionRequest $request
|
||||
* @param string $method
|
||||
* @param int $chunkCount
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function handleRequest(ActionRequest $request, $method, $chunkCount)
|
||||
{
|
||||
$this->dispatchableCallback = function (Response $response) use ($request, $method, $chunkCount) {
|
||||
if ($this->action instanceof ShouldQueue) {
|
||||
$request->chunks($chunkCount, function ($models) use ($request, $method) {
|
||||
$models = $models->filterForExecution($request);
|
||||
|
||||
return $this->forModels($method, $models);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$wasExecuted = false;
|
||||
|
||||
$results = $request->chunks(
|
||||
$chunkCount, function ($models) use ($request, $method, &$wasExecuted) {
|
||||
$models = $models->filterForExecution($request);
|
||||
|
||||
if (count($models) > 0) {
|
||||
$wasExecuted = true;
|
||||
}
|
||||
|
||||
return $this->forModels($method, $models);
|
||||
}
|
||||
);
|
||||
|
||||
return $wasExecuted ? $response->successful($results) : $response->failed();
|
||||
};
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the given action using custom handler.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\ActionRequest $request
|
||||
* @param \Closure(\Laravel\Nova\Http\Requests\ActionRequest, \Laravel\Nova\Actions\Response, \Laravel\Nova\Fields\ActionFields):\Laravel\Nova\Actions\Response $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function handleUsing(ActionRequest $request, $callback)
|
||||
{
|
||||
$this->dispatchableCallback = function (Response $response) use ($request, $callback) {
|
||||
return $callback($request, $response, $this->fields);
|
||||
};
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the given action.
|
||||
*
|
||||
* @param string $method
|
||||
* @param \Illuminate\Support\Collection $models
|
||||
* @return mixed|void
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function forModels($method, Collection $models)
|
||||
{
|
||||
if ($this->action->isStandalone() || $models->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->action instanceof ShouldQueue) {
|
||||
$this->addQueuedActionJob($method, $models);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return $this->dispatchSynchronouslyForCollection($method, $models);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the given action synchronously for a model collection.
|
||||
*
|
||||
* @param string $method
|
||||
* @param \Illuminate\Support\Collection $models
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
protected function dispatchSynchronouslyForCollection($method, Collection $models)
|
||||
{
|
||||
return Transaction::run(function ($batchId) use ($method, $models) {
|
||||
Nova::usingActionEvent(function ($actionEvent) use ($batchId, $models) {
|
||||
if (! $this->action->withoutActionEvents) {
|
||||
$actionEvent->createForModels(
|
||||
$this->request, $this->action, $batchId, $models
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return $this->action->withActionBatchId($batchId)->{$method}($this->fields, $models);
|
||||
}, function ($batchId) {
|
||||
Nova::usingActionEvent(function ($actionEvent) use ($batchId) {
|
||||
if (! $this->action->withoutActionEvents) {
|
||||
$actionEvent->markBatchAsFinished($batchId);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the given action to the queue for a model collection.
|
||||
*
|
||||
* @param string $method
|
||||
* @param \Illuminate\Support\Collection $models
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
protected function addQueuedActionJob($method, Collection $models)
|
||||
{
|
||||
return Transaction::run(function ($batchId) use ($method, $models) {
|
||||
Nova::usingActionEvent(function ($actionEvent) use ($batchId, $models) {
|
||||
if (! $this->action->withoutActionEvents) {
|
||||
$actionEvent->createForModels(
|
||||
$this->request, $this->action, $batchId, $models, 'waiting'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
$job = new CallQueuedAction(
|
||||
$this->action, $method, $this->request->resolveFields(), $models, $batchId
|
||||
);
|
||||
|
||||
if ($this->action instanceof BatchableAction) {
|
||||
$this->batchJob->add([$job]);
|
||||
|
||||
$this->batchJob->options['resourceIds'] = array_values(array_unique(array_merge(
|
||||
$this->batchJob->options['resourceIds'] ?? [],
|
||||
$models->map(function ($model) {
|
||||
return $model->getKey();
|
||||
})->all()
|
||||
)));
|
||||
} else {
|
||||
Queue::connection($this->connection())->pushOn(
|
||||
$this->queue(), $job
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the queue connection for the action.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function connection()
|
||||
{
|
||||
return property_exists($this->action, 'connection') ? $this->action->connection : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the queue name for the action.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function queue()
|
||||
{
|
||||
return property_exists($this->action, 'queue') ? $this->action->queue : null;
|
||||
}
|
||||
}
|
||||
242
nova/src/Actions/ExportAsCsv.php
Normal file
242
nova/src/Actions/ExportAsCsv.php
Normal file
@@ -0,0 +1,242 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Actions;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
use InvalidArgumentException;
|
||||
use Laravel\Nova\Fields\ActionFields;
|
||||
use Laravel\Nova\Fields\Select;
|
||||
use Laravel\Nova\Fields\Text;
|
||||
use Laravel\Nova\Http\Requests\ActionRequest;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
use Laravel\Nova\Nova;
|
||||
use Laravel\Nova\Rules\Filename;
|
||||
use Rap2hpoutre\FastExcel\FastExcel;
|
||||
use Symfony\Component\HttpFoundation\HeaderUtils;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class ExportAsCsv extends Action
|
||||
{
|
||||
/**
|
||||
* The XHR response type on executing the action.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $responseType = 'blob';
|
||||
|
||||
/**
|
||||
* All of the defined action fields.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
public $actionFields;
|
||||
|
||||
/**
|
||||
* The custom query callback.
|
||||
*
|
||||
* @var (\Closure(\Illuminate\Database\Eloquent\Builder, \Laravel\Nova\Fields\ActionFields):(\Illuminate\Database\Eloquent\Builder))|null
|
||||
*/
|
||||
public $withQueryCallback;
|
||||
|
||||
/**
|
||||
* The custom field callback.
|
||||
*
|
||||
* @var (\Closure(\Laravel\Nova\Http\Requests\NovaRequest):(array<int, \Laravel\Nova\Fields\Field>))|null
|
||||
*/
|
||||
public $withFieldsCallback;
|
||||
|
||||
/**
|
||||
* The custom format callback.
|
||||
*
|
||||
* @var (\Closure(\Illuminate\Database\Eloquent\Model):(array<string, mixed>))|null
|
||||
*/
|
||||
public $withFormatCallback;
|
||||
|
||||
/**
|
||||
* Indicates action events should be logged for models.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $withoutActionEvents = true;
|
||||
|
||||
/**
|
||||
* Construct a new action instance.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($name = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->actionFields = collect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fields available on the action.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
|
||||
* @return array
|
||||
*/
|
||||
public function fields(NovaRequest $request)
|
||||
{
|
||||
if ($this->withFieldsCallback instanceof Closure) {
|
||||
$this->actionFields = $this->actionFields->merge(call_user_func($this->withFieldsCallback, $request));
|
||||
}
|
||||
|
||||
return $this->actionFields->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the action request using custom dispatch handler.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\ActionRequest $request
|
||||
* @param \Laravel\Nova\Actions\Response $response
|
||||
* @param \Laravel\Nova\Fields\ActionFields $fields
|
||||
* @return \Laravel\Nova\Actions\Response
|
||||
*/
|
||||
protected function dispatchRequestUsing(ActionRequest $request, Response $response, ActionFields $fields)
|
||||
{
|
||||
$this->then(function ($results) {
|
||||
return $results->first();
|
||||
});
|
||||
|
||||
$query = $request->toSelectedResourceQuery();
|
||||
|
||||
$query->when($this->withQueryCallback instanceof Closure, function ($query) use ($fields) {
|
||||
return call_user_func($this->withQueryCallback, $query, $fields);
|
||||
});
|
||||
|
||||
$eloquentGenerator = function () use ($query) {
|
||||
foreach ($query->cursor() as $model) {
|
||||
yield $model;
|
||||
}
|
||||
};
|
||||
|
||||
$filename = $fields->get('filename') ?? sprintf('%s-%d.csv', $this->uriKey(), now()->format('YmdHis'));
|
||||
|
||||
$extension = 'csv';
|
||||
|
||||
if (Str::contains($filename, '.')) {
|
||||
[$filename, $extension] = explode('.', $filename);
|
||||
}
|
||||
|
||||
$exportFilename = sprintf(
|
||||
'%s.%s',
|
||||
$filename,
|
||||
$fields->get('writerType') ?? $extension
|
||||
);
|
||||
|
||||
return $response->successful([
|
||||
tap(
|
||||
(new FastExcel($eloquentGenerator()))->download($exportFilename, $this->withFormatCallback),
|
||||
function ($response) use ($exportFilename) {
|
||||
if ($response instanceof StreamedResponse && ! $response->headers->has('Content-Disposition')) {
|
||||
$response->headers->set(
|
||||
'Content-Disposition',
|
||||
HeaderUtils::makeDisposition(
|
||||
HeaderUtils::DISPOSITION_ATTACHMENT, $exportFilename, str_replace('%', '', Str::ascii($exportFilename))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
),
|
||||
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a callback that modifies the query used to retrieve the selected models.
|
||||
*
|
||||
* @param (\Closure(\Illuminate\Database\Eloquent\Builder, \Laravel\Nova\Fields\ActionFields):(\Illuminate\Database\Eloquent\Builder))|null $withQueryCallback
|
||||
* @return $this
|
||||
*/
|
||||
public function withQuery($withQueryCallback)
|
||||
{
|
||||
$this->withQueryCallback = $withQueryCallback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a callback that defines the fields that should be present within the generated file.
|
||||
*
|
||||
* @param (\Closure(\Laravel\Nova\Http\Requests\NovaRequest):(array<int, \Laravel\Nova\Fields\Field>))|null $withFieldsCallback
|
||||
* @return $this
|
||||
*/
|
||||
public function withFields($withFieldsCallback)
|
||||
{
|
||||
$this->withFieldsCallback = $withFieldsCallback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a callback that defines the field formatting for the generated file.
|
||||
*
|
||||
* @param (\Closure(\Illuminate\Database\Eloquent\Model):(array<string, mixed>))|null $withFormatCallback
|
||||
* @return $this
|
||||
*/
|
||||
public function withFormat($withFormatCallback)
|
||||
{
|
||||
$this->withFormatCallback = $withFormatCallback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Select field to the action that allows the selection of the generated file's type.
|
||||
*
|
||||
* @param (\Closure(\Laravel\Nova\Http\Requests\NovaRequest):(?string))|string|null $default
|
||||
* @return $this
|
||||
*/
|
||||
public function withTypeSelector($default = null)
|
||||
{
|
||||
$this->actionFields->push(
|
||||
Select::make(Nova::__('Type'), 'writerType')->options(function () {
|
||||
return [
|
||||
'csv' => Nova::__('CSV (.csv)'),
|
||||
'xlsx' => Nova::__('Excel (.xlsx)'),
|
||||
];
|
||||
})->default($default)->rules(['required', Rule::in(['csv', 'xlsx'])])
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Text field to the action to allow users to define the generated file's name.
|
||||
*
|
||||
* @param (\Closure(\Laravel\Nova\Http\Requests\NovaRequest):(?string))|string|null $default
|
||||
* @return $this
|
||||
*/
|
||||
public function nameable($default = null)
|
||||
{
|
||||
$this->actionFields->push(
|
||||
Text::make(Nova::__('Filename'), 'filename')->default($default)->rules(['required', 'min:1', new Filename()])
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the displayable name of the action.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name()
|
||||
{
|
||||
return $this->name ?: 'Export As CSV';
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the action as a standalone action.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function standalone()
|
||||
{
|
||||
throw new InvalidArgumentException('The Export As CSV action may not be registered as a standalone action.');
|
||||
}
|
||||
}
|
||||
48
nova/src/Actions/Response.php
Normal file
48
nova/src/Actions/Response.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Actions;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class Response
|
||||
{
|
||||
/**
|
||||
* Determine if action was executed.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $wasExecuted = false;
|
||||
|
||||
/**
|
||||
* List of action results.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $results = [];
|
||||
|
||||
/**
|
||||
* Mark response as successful.
|
||||
*
|
||||
* @param array|mixed|null $results
|
||||
* @return $this
|
||||
*/
|
||||
public function successful($results = null)
|
||||
{
|
||||
$this->wasExecuted = true;
|
||||
$this->results = Arr::wrap($results ?? []);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark response as failed.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function failed()
|
||||
{
|
||||
$this->wasExecuted = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
40
nova/src/Actions/Transaction.php
Normal file
40
nova/src/Actions/Transaction.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Actions;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Throwable;
|
||||
|
||||
class Transaction
|
||||
{
|
||||
/**
|
||||
* Perform the given callbacks within a batch transaction.
|
||||
*
|
||||
* @param callable(string):mixed $callback
|
||||
* @param (callable(string):(void))|null $finished
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public static function run($callback, $finished = null)
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$actionBatchId = (string) Str::orderedUuid();
|
||||
|
||||
return tap($callback($actionBatchId), function ($response) use ($finished, $actionBatchId) {
|
||||
if ($finished) {
|
||||
$finished($actionBatchId);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
});
|
||||
} catch (Throwable $e) {
|
||||
DB::rollBack();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
132
nova/src/Asset.php
Normal file
132
nova/src/Asset.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova;
|
||||
|
||||
use DateTime;
|
||||
use Illuminate\Contracts\Support\Responsable;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @method static static make(string|self $name, string|null $path, bool|null $remote = null)
|
||||
*/
|
||||
abstract class Asset implements Responsable
|
||||
{
|
||||
use Makeable;
|
||||
|
||||
/**
|
||||
* The Assert name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The Asset path.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* Determine Asset is remote.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $remote;
|
||||
|
||||
/**
|
||||
* Construct a new Asset instance.
|
||||
*
|
||||
* @param string|self $name
|
||||
* @param string|null $path
|
||||
* @param bool|null $remote
|
||||
*/
|
||||
public function __construct($name, $path, $remote = null)
|
||||
{
|
||||
if ($name instanceof self) {
|
||||
$this->name = $name->name();
|
||||
$this->path = $name->path();
|
||||
$this->remote = $name->isRemote();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_null($remote)) {
|
||||
$remote = Str::startsWith($path, ['http://', 'https://', '://']);
|
||||
}
|
||||
|
||||
$this->name = $name;
|
||||
$this->path = $path;
|
||||
$this->remote = $remote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a remote URL.
|
||||
*
|
||||
* @param string $path
|
||||
* @return static
|
||||
*/
|
||||
public static function remote($path)
|
||||
{
|
||||
return new static(md5($path), $path, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get asset name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get asset path.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function path()
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if URL is remote.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isRemote()
|
||||
{
|
||||
return $this->remote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an HTTP response that represents the object.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function toResponse($request)
|
||||
{
|
||||
abort_if($this->isRemote() || is_null($this->path), 404);
|
||||
|
||||
return response(
|
||||
file_get_contents($this->path), 200, $this->toResponseHeaders(),
|
||||
)->setLastModified(DateTime::createFromFormat('U', (string) filemtime($this->path)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Asset URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function url();
|
||||
|
||||
/**
|
||||
* Get response headers.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
abstract public function toResponseHeaders();
|
||||
}
|
||||
144
nova/src/Auth/Adapters/SessionImpersonator.php
Normal file
144
nova/src/Auth/Adapters/SessionImpersonator.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Auth\Adapters;
|
||||
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Contracts\Auth\StatefulGuard;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Nova\Contracts\ImpersonatesUsers;
|
||||
use Laravel\Nova\Events\StartedImpersonating;
|
||||
use Laravel\Nova\Events\StoppedImpersonating;
|
||||
use Laravel\Nova\Nova;
|
||||
|
||||
class SessionImpersonator implements ImpersonatesUsers
|
||||
{
|
||||
/**
|
||||
* Start impersonating a user.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Illuminate\Contracts\Auth\StatefulGuard $guard
|
||||
* @param \Illuminate\Contracts\Auth\Authenticatable $user
|
||||
* @return bool
|
||||
*/
|
||||
public function impersonate(Request $request, StatefulGuard $guard, Authenticatable $user)
|
||||
{
|
||||
return rescue(function () use ($request, $guard, $user) {
|
||||
$impersonator = Nova::user($request);
|
||||
|
||||
$request->session()->put(
|
||||
'nova_impersonated_by', $impersonator->getAuthIdentifier()
|
||||
);
|
||||
$request->session()->put(
|
||||
'nova_impersonated_remember', $guard->viaRemember()
|
||||
);
|
||||
|
||||
$novaGuard = config('nova.guard') ?? config('auth.defaults.guard');
|
||||
|
||||
$authGuard = method_exists($guard, 'getName')
|
||||
? Str::between($guard->getName(), 'login_', '_'.sha1(get_class($guard)))
|
||||
: null;
|
||||
|
||||
if (is_null($authGuard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($novaGuard !== $authGuard) {
|
||||
$request->session()->put(
|
||||
'nova_impersonated_guard', $authGuard
|
||||
);
|
||||
}
|
||||
|
||||
$guard->login($user);
|
||||
|
||||
event(new StartedImpersonating($impersonator, $user));
|
||||
|
||||
return true;
|
||||
}, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop impersonating the currently impersonated user and revert to the original session.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Illuminate\Contracts\Auth\StatefulGuard $guard
|
||||
* @param string $userModel
|
||||
* @return bool
|
||||
*/
|
||||
public function stopImpersonating(Request $request, StatefulGuard $guard, string $userModel)
|
||||
{
|
||||
return rescue(function () use ($request, $guard, $userModel) {
|
||||
if (! $this->impersonating($request)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$user = $request->user($userGuard = $request->session()->get('nova_impersonated_guard'));
|
||||
$impersonator = $userModel::findOrFail($request->session()->get('nova_impersonated_by', null));
|
||||
|
||||
if ($request->session()->has('nova_impersonated_guard')) {
|
||||
Auth::guard($userGuard)->logout();
|
||||
}
|
||||
|
||||
$guard->login($impersonator, $request->session()->get('nova_impersonated_remember') ?? false);
|
||||
|
||||
event(new StoppedImpersonating($impersonator, $user));
|
||||
|
||||
$this->flushImpersonationData($request);
|
||||
|
||||
return true;
|
||||
}, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a user is currently being impersonated.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public function impersonating(Request $request)
|
||||
{
|
||||
return $request->session()->has('nova_impersonated_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any impersonation data from the session.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return void
|
||||
*/
|
||||
public function flushImpersonationData(Request $request)
|
||||
{
|
||||
if ($request->hasSession()) {
|
||||
$request->session()->forget('nova_impersonated_by');
|
||||
$request->session()->forget('nova_impersonated_guard');
|
||||
$request->session()->forget('nova_impersonated_remember');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect an admin after starting impersonation.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function redirectAfterStartingImpersonation(Request $request)
|
||||
{
|
||||
return response()->json([
|
||||
'redirect' => config('nova.impersonation.started', '/'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect an admin after finishing impersonation.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function redirectAfterStoppingImpersonation(Request $request)
|
||||
{
|
||||
return response()->json([
|
||||
'redirect' => config('nova.impersonation.stopped', Nova::url('/')),
|
||||
]);
|
||||
}
|
||||
}
|
||||
28
nova/src/Auth/Impersonatable.php
Normal file
28
nova/src/Auth/Impersonatable.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Auth;
|
||||
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
trait Impersonatable
|
||||
{
|
||||
/**
|
||||
* Determine if the user can impersonate another user.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canImpersonate()
|
||||
{
|
||||
return Gate::forUser($this)->check('viewNova');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user can be impersonated.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canBeImpersonated()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
411
nova/src/Authorizable.php
Normal file
411
nova/src/Authorizable.php
Normal file
@@ -0,0 +1,411 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova;
|
||||
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Nova\Actions\Action;
|
||||
use Laravel\Nova\Actions\DestructiveAction;
|
||||
use Laravel\Nova\Contracts\ImpersonatesUsers;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
|
||||
trait Authorizable
|
||||
{
|
||||
/**
|
||||
* Determine if the given resource is authorizable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function authorizable()
|
||||
{
|
||||
return ! is_null(Gate::getPolicyFor(static::newModel()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the resource should be available for the given request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return void
|
||||
*
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function authorizeToViewAny(Request $request)
|
||||
{
|
||||
if (! static::authorizable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$gate = Gate::getPolicyFor(static::newModel());
|
||||
|
||||
if (! is_null($gate) && method_exists($gate, 'viewAny')) {
|
||||
$this->authorizeTo($request, 'viewAny');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the resource should be available for the given request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public static function authorizedToViewAny(Request $request)
|
||||
{
|
||||
if (! static::authorizable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$gate = Gate::getPolicyFor(static::newModel());
|
||||
|
||||
return ! is_null($gate) && method_exists($gate, 'viewAny')
|
||||
? Gate::forUser(Nova::user($request))->check('viewAny', get_class(static::newModel()))
|
||||
: true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current user can view the given resource or throw an exception.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return void
|
||||
*
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function authorizeToView(Request $request)
|
||||
{
|
||||
$this->authorizeTo($request, 'view');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current user can view the given resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public function authorizedToView(Request $request)
|
||||
{
|
||||
return $this->authorizedTo($request, 'view');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current user can create new resources or throw an exception.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return void
|
||||
*
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public static function authorizeToCreate(Request $request)
|
||||
{
|
||||
throw_unless(static::authorizedToCreate($request), AuthorizationException::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current user can create new resources.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public static function authorizedToCreate(Request $request)
|
||||
{
|
||||
if (static::authorizable()) {
|
||||
return Gate::forUser(Nova::user($request))->check('create', get_class(static::newModel()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current user can update the given resource or throw an exception.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return void
|
||||
*
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function authorizeToUpdate(Request $request)
|
||||
{
|
||||
$this->authorizeTo($request, 'update');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current user can update the given resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public function authorizedToUpdate(Request $request)
|
||||
{
|
||||
return $this->authorizedTo($request, 'update');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current user can replicate the given resource or throw an exception.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return void
|
||||
*
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function authorizeToReplicate(Request $request)
|
||||
{
|
||||
if (! static::authorizable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$gate = Gate::getPolicyFor(static::newModel());
|
||||
|
||||
if (! is_null($gate) && method_exists($gate, 'replicate')) {
|
||||
$this->authorizeTo($request, 'replicate');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->authorizeToCreate($request);
|
||||
$this->authorizeToUpdate($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current user can replicate the given resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public function authorizedToReplicate(Request $request)
|
||||
{
|
||||
if (! static::authorizable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$gate = Gate::getPolicyFor(static::newModel());
|
||||
|
||||
return ! is_null($gate) && method_exists($gate, 'replicate')
|
||||
? Gate::forUser(Nova::user($request))->check('replicate', $this->model())
|
||||
: $this->authorizedToCreate($request) && $this->authorizedToUpdate($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current user can delete the given resource or throw an exception.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return void
|
||||
*
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function authorizeToDelete(Request $request)
|
||||
{
|
||||
$this->authorizeTo($request, 'delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current user can delete the given resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public function authorizedToDelete(Request $request)
|
||||
{
|
||||
return $this->authorizedTo($request, 'delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current user can restore the given resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public function authorizedToRestore(Request $request)
|
||||
{
|
||||
return $this->authorizedTo($request, 'restore');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current user can force delete the given resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public function authorizedToForceDelete(Request $request)
|
||||
{
|
||||
return $this->authorizedTo($request, 'forceDelete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user can add / associate models of the given type to the resource.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
|
||||
* @param \Illuminate\Database\Eloquent\Model|string $model
|
||||
* @return bool
|
||||
*/
|
||||
public function authorizedToAdd(NovaRequest $request, $model)
|
||||
{
|
||||
if (! static::authorizable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$gate = Gate::getPolicyFor($this->model());
|
||||
$method = 'add'.class_basename($model);
|
||||
|
||||
return ! is_null($gate) && method_exists($gate, $method)
|
||||
? Gate::forUser(Nova::user($request))->check($method, $this->model())
|
||||
: true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user can attach any models of the given type to the resource.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
|
||||
* @param \Illuminate\Database\Eloquent\Model|string $model
|
||||
* @return bool
|
||||
*/
|
||||
public function authorizedToAttachAny(NovaRequest $request, $model)
|
||||
{
|
||||
if (! static::authorizable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$gate = Gate::getPolicyFor($this->model());
|
||||
$method = 'attachAny'.Str::singular(class_basename($model));
|
||||
|
||||
return ! is_null($gate) && method_exists($gate, $method)
|
||||
? Gate::forUser(Nova::user($request))->check($method, [$this->model()])
|
||||
: true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user can attach models of the given type to the resource.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
|
||||
* @param \Illuminate\Database\Eloquent\Model|string $model
|
||||
* @return bool
|
||||
*/
|
||||
public function authorizedToAttach(NovaRequest $request, $model)
|
||||
{
|
||||
if (! static::authorizable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$gate = Gate::getPolicyFor($this->model());
|
||||
$method = 'attach'.Str::singular(class_basename($model));
|
||||
|
||||
return ! is_null($gate) && method_exists($gate, $method)
|
||||
? Gate::forUser(Nova::user($request))->check($method, [$this->model(), $model])
|
||||
: true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user can detach models of the given type to the resource.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
|
||||
* @param \Illuminate\Database\Eloquent\Model|string $model
|
||||
* @param string $relationship
|
||||
* @return bool
|
||||
*/
|
||||
public function authorizedToDetach(NovaRequest $request, $model, $relationship)
|
||||
{
|
||||
if (! static::authorizable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$gate = Gate::getPolicyFor($this->model());
|
||||
$method = 'detach'.Str::singular(class_basename($model));
|
||||
|
||||
return ! is_null($gate) && method_exists($gate, $method)
|
||||
? Gate::forUser(Nova::user($request))->check($method, [$this->model(), $model])
|
||||
: true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user can run the given action.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
|
||||
* @param \Laravel\Nova\Actions\Action $action
|
||||
* @return bool
|
||||
*/
|
||||
public function authorizedToRunAction(NovaRequest $request, Action $action)
|
||||
{
|
||||
if ($action instanceof DestructiveAction) {
|
||||
return $this->authorizedToRunDestructiveAction($request, $action);
|
||||
}
|
||||
|
||||
if (! static::authorizable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$gate = Gate::getPolicyFor($this->model());
|
||||
|
||||
$method = 'runAction';
|
||||
|
||||
return ! is_null($gate) && method_exists($gate, $method)
|
||||
? Gate::forUser(Nova::user($request))->check($method, [$this->model(), $action])
|
||||
: $this->authorizedToUpdate($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user can run the given action.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
|
||||
* @param \Laravel\Nova\Actions\DestructiveAction $action
|
||||
* @return bool
|
||||
*/
|
||||
public function authorizedToRunDestructiveAction(NovaRequest $request, DestructiveAction $action)
|
||||
{
|
||||
if (! static::authorizable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$gate = Gate::getPolicyFor($this->model());
|
||||
|
||||
$method = 'runDestructiveAction';
|
||||
|
||||
return ! is_null($gate) && method_exists($gate, $method)
|
||||
? Gate::forUser(Nova::user($request))->check($method, [$this->model(), $action])
|
||||
: $this->authorizedToDelete($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current user can impersonate the given resource.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
|
||||
* @return bool
|
||||
*/
|
||||
public function authorizedToImpersonate(NovaRequest $request)
|
||||
{
|
||||
$user = Nova::user($request);
|
||||
|
||||
return app(ImpersonatesUsers::class)->impersonating($request) === false
|
||||
&& ! $this->resource->is($user)
|
||||
&& $this->resource instanceof Authenticatable
|
||||
&& (method_exists($this->resource, 'canBeImpersonated') && $this->resource->canBeImpersonated() === true)
|
||||
&& (method_exists($user, 'canImpersonate') && $user->canImpersonate() === true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current user has a given ability.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param string $ability
|
||||
* @return void
|
||||
*
|
||||
* @throws \Illuminate\Auth\Access\AuthorizationException
|
||||
*/
|
||||
public function authorizeTo(Request $request, $ability)
|
||||
{
|
||||
if (static::authorizable()) {
|
||||
Gate::forUser(Nova::user($request))->authorize($ability, $this->resource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current user can view the given resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param string $ability
|
||||
* @return bool
|
||||
*/
|
||||
public function authorizedTo(Request $request, $ability)
|
||||
{
|
||||
return static::authorizable() ? Gate::forUser(Nova::user($request))->check($ability, $this->resource) : true;
|
||||
}
|
||||
}
|
||||
40
nova/src/AuthorizedToSee.php
Normal file
40
nova/src/AuthorizedToSee.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
trait AuthorizedToSee
|
||||
{
|
||||
/**
|
||||
* The callback used to authorize viewing the filter or action.
|
||||
*
|
||||
* @var (\Closure(\Illuminate\Http\Request):(bool))|null
|
||||
*/
|
||||
public $seeCallback;
|
||||
|
||||
/**
|
||||
* Determine if the filter or action should be available for the given request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public function authorizedToSee(Request $request)
|
||||
{
|
||||
return $this->seeCallback ? call_user_func($this->seeCallback, $request) : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the callback to be run to authorize viewing the filter or action.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request):bool $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function canSee(Closure $callback)
|
||||
{
|
||||
$this->seeCallback = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
39
nova/src/AuthorizesRequests.php
Normal file
39
nova/src/AuthorizesRequests.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova;
|
||||
|
||||
trait AuthorizesRequests
|
||||
{
|
||||
/**
|
||||
* The callback that should be used to authenticate Nova users.
|
||||
*
|
||||
* @var (\Closure(\Illuminate\Http\Request):(bool))|null
|
||||
*/
|
||||
public static $authUsing;
|
||||
|
||||
/**
|
||||
* Register the Nova authentication callback.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request):bool $callback
|
||||
* @return static
|
||||
*/
|
||||
public static function auth($callback)
|
||||
{
|
||||
static::$authUsing = $callback;
|
||||
|
||||
return new static;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given request can access the Nova dashboard.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public static function check($request)
|
||||
{
|
||||
return (static::$authUsing ?: function () {
|
||||
return app()->environment('local');
|
||||
})($request);
|
||||
}
|
||||
}
|
||||
82
nova/src/Badge.php
Normal file
82
nova/src/Badge.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova;
|
||||
|
||||
use JsonSerializable;
|
||||
|
||||
class Badge implements JsonSerializable
|
||||
{
|
||||
use Makeable;
|
||||
|
||||
/**
|
||||
* The value for the badge.
|
||||
*
|
||||
* @var string|\Closure
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/**
|
||||
* The type for the badge.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $type;
|
||||
|
||||
public const SUCCESS_TYPE = 'success';
|
||||
|
||||
public const WARNING_TYPE = 'warning';
|
||||
|
||||
public const DANGER_TYPE = 'danger';
|
||||
|
||||
public const INFO_TYPE = 'info';
|
||||
|
||||
/**
|
||||
* The built-in badge types and their corresponding CSS classes.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public static $types = [
|
||||
'success' => 'bg-green-100 text-green-600 dark:bg-green-500 dark:text-green-900',
|
||||
'info' => 'bg-sky-100 text-sky-600 dark:bg-sky-600 dark:text-sky-900',
|
||||
'danger' => 'bg-red-100 text-red-600 dark:bg-red-400 dark:text-red-900',
|
||||
'warning' => 'bg-yellow-100 text-yellow-600 dark:bg-yellow-300 dark:text-yellow-800',
|
||||
];
|
||||
|
||||
/**
|
||||
* Create a new badge instance.
|
||||
*
|
||||
* @param string $value
|
||||
* @param string $type
|
||||
*/
|
||||
public function __construct($value, $type = 'info')
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type to be used for the badge.
|
||||
*
|
||||
* @param string $type
|
||||
* @return $this
|
||||
*/
|
||||
public function type($type)
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the element for JSON serialization.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'value' => value($this->value),
|
||||
'typeClass' => static::$types[$this->type],
|
||||
];
|
||||
}
|
||||
}
|
||||
103
nova/src/Card.php
Normal file
103
nova/src/Card.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova;
|
||||
|
||||
abstract class Card extends Element
|
||||
{
|
||||
public const FULL_WIDTH = 'full';
|
||||
|
||||
public const ONE_THIRD_WIDTH = '1/3';
|
||||
|
||||
public const ONE_HALF_WIDTH = '1/2';
|
||||
|
||||
public const ONE_QUARTER_WIDTH = '1/4';
|
||||
|
||||
public const TWO_THIRDS_WIDTH = '2/3';
|
||||
|
||||
public const THREE_QUARTERS_WIDTH = '3/4';
|
||||
|
||||
public const FIXED_HEIGHT = 'fixed';
|
||||
|
||||
public const DYNAMIC_HEIGHT = 'dynamic';
|
||||
|
||||
/**
|
||||
* The width of the card (1/3, 2/3, 1/2, 1/4, 3/4, or full).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $width = '1/3';
|
||||
|
||||
/**
|
||||
* The height strategy of the card.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $height = 'fixed';
|
||||
|
||||
/**
|
||||
* Set the width of the card.
|
||||
*
|
||||
* @param string $width
|
||||
* @return $this
|
||||
*/
|
||||
public function width($width)
|
||||
{
|
||||
$this->width = $width;
|
||||
|
||||
if ($this->width == static::FULL_WIDTH) {
|
||||
$this->height = static::DYNAMIC_HEIGHT;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the height of a card to use a fixed value.
|
||||
*
|
||||
* @param string $height
|
||||
* @return $this
|
||||
*/
|
||||
public function height($height)
|
||||
{
|
||||
$this->height = $height;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the height of a card to be dynamic.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function dynamicHeight()
|
||||
{
|
||||
$this->height = static::DYNAMIC_HEIGHT;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the height of a card to be fixed.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function fixedHeight()
|
||||
{
|
||||
$this->height = static::FIXED_HEIGHT;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the element for JSON serialization.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return array_merge([
|
||||
'width' => $this->width,
|
||||
'height' => $this->height,
|
||||
], parent::jsonSerialize());
|
||||
}
|
||||
}
|
||||
25
nova/src/Cards/Help.php
Normal file
25
nova/src/Cards/Help.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Cards;
|
||||
|
||||
use Laravel\Nova\Card;
|
||||
|
||||
class Help extends Card
|
||||
{
|
||||
/**
|
||||
* The width of the card (1/3, 1/2, or full).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $width = 'full';
|
||||
|
||||
/**
|
||||
* Get the component name for the element.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function component()
|
||||
{
|
||||
return 'help-card';
|
||||
}
|
||||
}
|
||||
33
nova/src/Concerns/HandlesRoutes.php
Normal file
33
nova/src/Concerns/HandlesRoutes.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Concerns;
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
trait HandlesRoutes
|
||||
{
|
||||
/**
|
||||
* Get url for Laravel Nova.
|
||||
*
|
||||
* @param string $url
|
||||
* @return string
|
||||
*/
|
||||
public static function url($url)
|
||||
{
|
||||
return rtrim(static::path(), '/').'/'.ltrim((string) $url, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Route Registrar for Nova.
|
||||
*
|
||||
* @param array<int, class-string|string>|null $middleware
|
||||
* @param string|null $prefix
|
||||
* @return \Illuminate\Routing\RouteRegistrar
|
||||
*/
|
||||
public static function router($middleware = null, $prefix = null)
|
||||
{
|
||||
return Route::domain(config('nova.domain', null))
|
||||
->prefix(static::url($prefix))
|
||||
->middleware($middleware ?? config('nova.middleware', []));
|
||||
}
|
||||
}
|
||||
41
nova/src/Concerns/InteractsWithActionEvent.php
Normal file
41
nova/src/Concerns/InteractsWithActionEvent.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Concerns;
|
||||
|
||||
use Laravel\Nova\Actions\ActionResource;
|
||||
|
||||
trait InteractsWithActionEvent
|
||||
{
|
||||
/**
|
||||
* Get the configured ActionResource class.
|
||||
*
|
||||
* @return class-string<\Laravel\Nova\Actions\ActionResource>
|
||||
*/
|
||||
public static function actionResource()
|
||||
{
|
||||
return config('nova.actions.resource') ?? ActionResource::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new instance of the configured ActionEvent.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Model|\Laravel\Nova\Actions\ActionEvent
|
||||
*/
|
||||
public static function actionEvent()
|
||||
{
|
||||
return static::actionResource()::newModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the callback with an instance of the configured ActionEvent if it is available.
|
||||
*
|
||||
* @param callable(\Laravel\Nova\Actions\ActionEvent):mixed $callback
|
||||
* @return mixed
|
||||
*/
|
||||
public static function usingActionEvent(callable $callback)
|
||||
{
|
||||
if (! is_null(config('nova.actions.resource'))) {
|
||||
return call_user_func($callback, static::actionEvent());
|
||||
}
|
||||
}
|
||||
}
|
||||
51
nova/src/Concerns/InteractsWithEvents.php
Normal file
51
nova/src/Concerns/InteractsWithEvents.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Concerns;
|
||||
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Laravel\Nova\Events\NovaServiceProviderRegistered;
|
||||
use Laravel\Nova\Events\ServingNova;
|
||||
|
||||
trait InteractsWithEvents
|
||||
{
|
||||
/**
|
||||
* Register an event listener for the Nova "booted" event.
|
||||
*
|
||||
* @param (\Closure(\Laravel\Nova\Events\NovaServiceProviderRegistered):(void))|string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function booted($callback)
|
||||
{
|
||||
Event::listen(NovaServiceProviderRegistered::class, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an event listener for the Nova "serving" event.
|
||||
*
|
||||
* @param (\Closure(\Laravel\Nova\Events\ServingNova):(void))|string $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function serving($callback)
|
||||
{
|
||||
Event::listen(ServingNova::class, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the persistent Nova state.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function flushState()
|
||||
{
|
||||
static::$rtlCallback = null;
|
||||
static::$createUserCallback = null;
|
||||
static::$createUserCommandCallback = null;
|
||||
static::$dashboards = [];
|
||||
static::$jsonVariables = [];
|
||||
static::$resources = [];
|
||||
static::$resourcesByModel = [];
|
||||
static::$scripts = [];
|
||||
static::$styles = [];
|
||||
static::$tools = [];
|
||||
}
|
||||
}
|
||||
74
nova/src/Console/ActionCommand.php
Normal file
74
nova/src/Console/ActionCommand.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Console\GeneratorCommand;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
#[AsCommand(name: 'nova:action')]
|
||||
class ActionCommand extends GeneratorCommand
|
||||
{
|
||||
use ResolvesStubPath;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'nova:action';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new action class';
|
||||
|
||||
/**
|
||||
* The type of class being generated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Action';
|
||||
|
||||
/**
|
||||
* Get the stub file for the generator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub()
|
||||
{
|
||||
$extension = $this->option('queued') ? 'queued.stub' : 'stub';
|
||||
|
||||
if ($this->option('destructive')) {
|
||||
return $this->resolveStubPath("/stubs/nova/destructive-action.{$extension}");
|
||||
}
|
||||
|
||||
return $this->resolveStubPath("/stubs/nova/action.{$extension}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default namespace for the class.
|
||||
*
|
||||
* @param string $rootNamespace
|
||||
* @return string
|
||||
*/
|
||||
protected function getDefaultNamespace($rootNamespace)
|
||||
{
|
||||
return $rootNamespace.'\Nova\Actions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['destructive', null, InputOption::VALUE_NONE, 'Indicate that the action deletes / destroys resources'],
|
||||
['queued', null, InputOption::VALUE_NONE, 'Indicates the action should be queued'],
|
||||
];
|
||||
}
|
||||
}
|
||||
97
nova/src/Console/AssetCommand.php
Normal file
97
nova/src/Console/AssetCommand.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'nova:asset')]
|
||||
class AssetCommand extends ComponentGeneratorCommand
|
||||
{
|
||||
use RenamesStubs;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'nova:asset {name}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new asset';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->hasValidNameArgument()) {
|
||||
return;
|
||||
}
|
||||
|
||||
(new Filesystem)->copyDirectory(
|
||||
__DIR__.'/asset-stubs',
|
||||
$this->componentPath()
|
||||
);
|
||||
|
||||
// AssetServiceProvider.php replacements...
|
||||
$this->replace('{{ namespace }}', $this->componentNamespace(), $this->componentPath().'/src/AssetServiceProvider.stub');
|
||||
$this->replace('{{ component }}', $this->componentName(), $this->componentPath().'/src/AssetServiceProvider.stub');
|
||||
$this->replace('{{ name }}', $this->componentName(), $this->componentPath().'/src/AssetServiceProvider.stub');
|
||||
|
||||
// asset.js replacements...
|
||||
$this->replace('{{ class }}', $this->componentClass(), $this->componentPath().'/resources/js/asset.js');
|
||||
$this->replace('{{ name }}', $this->componentName(), $this->componentPath().'/resources/js/asset.js');
|
||||
|
||||
// webpack.mix.js replacements...
|
||||
$this->replace('{{ name }}', $this->component(), $this->componentPath().'/webpack.mix.js');
|
||||
|
||||
// Asset composer.json replacements...
|
||||
$this->prepareComposerReplacements();
|
||||
|
||||
// Rename the stubs with the proper file extensions...
|
||||
$this->renameStubs();
|
||||
|
||||
// Register the asset...
|
||||
$this->buildComponent('asset');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of stubs that need PHP file extensions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function stubsToRename()
|
||||
{
|
||||
return [
|
||||
$this->componentPath().'/src/AssetServiceProvider.stub',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "title" name of the asset.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function componentTitle()
|
||||
{
|
||||
return Str::title(str_replace('-', ' ', $this->componentName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the component's "snake" name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function componentSlug()
|
||||
{
|
||||
return Str::snake($this->componentName(), '-');
|
||||
}
|
||||
}
|
||||
71
nova/src/Console/BaseResourceCommand.php
Normal file
71
nova/src/Console/BaseResourceCommand.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Console\GeneratorCommand;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'nova:base-resource')]
|
||||
class BaseResourceCommand extends GeneratorCommand
|
||||
{
|
||||
use ResolvesStubPath;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'nova:base-resource';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new base resource class';
|
||||
|
||||
/**
|
||||
* Indicates whether the command should be shown in the Artisan command list.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hidden = true;
|
||||
|
||||
/**
|
||||
* The type of class being generated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Resource';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
parent::handle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stub file for the generator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub()
|
||||
{
|
||||
return $this->resolveStubPath('/stubs/nova/base-resource.stub');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default namespace for the class.
|
||||
*
|
||||
* @param string $rootNamespace
|
||||
* @return string
|
||||
*/
|
||||
protected function getDefaultNamespace($rootNamespace)
|
||||
{
|
||||
return $rootNamespace.'\Nova';
|
||||
}
|
||||
}
|
||||
98
nova/src/Console/CardCommand.php
Normal file
98
nova/src/Console/CardCommand.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'nova:card')]
|
||||
class CardCommand extends ComponentGeneratorCommand
|
||||
{
|
||||
use RenamesStubs;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'nova:card {name}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new card';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->hasValidNameArgument()) {
|
||||
return;
|
||||
}
|
||||
|
||||
(new Filesystem)->copyDirectory(
|
||||
__DIR__.'/card-stubs',
|
||||
$this->componentPath()
|
||||
);
|
||||
|
||||
// Card.js replacements...
|
||||
$this->replace('{{ title }}', $this->componentTitle(), $this->componentPath().'/resources/js/components/Card.vue');
|
||||
$this->replace('{{ component }}', $this->componentName(), $this->componentPath().'/resources/js/card.js');
|
||||
|
||||
// Card.php replacements...
|
||||
$this->replace('{{ namespace }}', $this->componentNamespace(), $this->componentPath().'/src/Card.stub');
|
||||
$this->replace('{{ class }}', $this->componentClass(), $this->componentPath().'/src/Card.stub');
|
||||
$this->replace('{{ component }}', $this->componentName(), $this->componentPath().'/src/Card.stub');
|
||||
|
||||
(new Filesystem)->move(
|
||||
$this->componentPath().'/src/Card.stub',
|
||||
$this->componentPath().'/src/'.$this->componentClass().'.php'
|
||||
);
|
||||
|
||||
// CardServiceProvider.php replacements...
|
||||
$this->replace('{{ namespace }}', $this->componentNamespace(), $this->componentPath().'/src/CardServiceProvider.stub');
|
||||
$this->replace('{{ component }}', $this->componentName(), $this->componentPath().'/src/CardServiceProvider.stub');
|
||||
$this->replace('{{ name }}', $this->componentName(), $this->componentPath().'/src/CardServiceProvider.stub');
|
||||
|
||||
// webpack.mix.js replacements...
|
||||
$this->replace('{{ name }}', $this->component(), $this->componentPath().'/webpack.mix.js');
|
||||
|
||||
// Card composer.json replacements...
|
||||
$this->prepareComposerReplacements();
|
||||
|
||||
// Rename the stubs with the proper file extensions...
|
||||
$this->renameStubs();
|
||||
|
||||
// Register the card...
|
||||
$this->buildComponent('card');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of stubs that need PHP file extensions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function stubsToRename()
|
||||
{
|
||||
return [
|
||||
$this->componentPath().'/src/CardServiceProvider.stub',
|
||||
$this->componentPath().'/routes/api.stub',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "title" name of the card.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function componentTitle()
|
||||
{
|
||||
return Str::title(str_replace('-', ' ', $this->componentName()));
|
||||
}
|
||||
}
|
||||
47
nova/src/Console/CheckLicenseCommand.php
Normal file
47
nova/src/Console/CheckLicenseCommand.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Laravel\Nova\Nova;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'nova:check-license')]
|
||||
class CheckLicenseCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'nova:check-license';
|
||||
|
||||
/** * The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Verify your Nova license key';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
Cache::forget('nova_valid_license_key');
|
||||
|
||||
$response = Nova::checkLicense();
|
||||
|
||||
if ($response->status() == 204) {
|
||||
$this->info('Your license key is valid and correctly configured! Thank you for being a Nova customer. 🚀');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->error($response->json('message'));
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
276
nova/src/Console/ComponentGeneratorCommand.php
Normal file
276
nova/src/Console/ComponentGeneratorCommand.php
Normal file
@@ -0,0 +1,276 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Nova\Console\Concerns\AcceptsNameAndVendor;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
abstract class ComponentGeneratorCommand extends Command
|
||||
{
|
||||
use AcceptsNameAndVendor, ResolvesStubPath;
|
||||
|
||||
/**
|
||||
* Prepare composer replacements.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareComposerReplacements()
|
||||
{
|
||||
$composerJson = $this->componentPath().'/composer.json';
|
||||
|
||||
$this->replace('{{ name }}', $this->component(), $composerJson);
|
||||
$this->replace('{{ escapedNamespace }}', $this->escapedComponentNamespace(), $composerJson);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register and build the component.
|
||||
*
|
||||
* @param string $componentType
|
||||
* @param bool $interactsWithComposer
|
||||
* @param bool $interactsWithNpm
|
||||
* @return void
|
||||
*/
|
||||
protected function buildComponent($componentType, $interactsWithComposer = true, $interactsWithNpm = true)
|
||||
{
|
||||
if ($interactsWithComposer === true) {
|
||||
$this->addRepositoryToRootComposer();
|
||||
$this->addRequireToRootComposer();
|
||||
|
||||
if ($this->confirm('Would you like to update your Composer packages?', true)) {
|
||||
$this->composerUpdate();
|
||||
|
||||
$this->output->newLine();
|
||||
}
|
||||
}
|
||||
|
||||
if ($interactsWithNpm === true) {
|
||||
if (file_exists(base_path('package.json'))) {
|
||||
$this->addScriptsToRootNpmPackage();
|
||||
} else {
|
||||
$this->warn('Please create a package.json to the root of your project.');
|
||||
}
|
||||
|
||||
if ($this->confirm("Would you like to install the {$componentType}'s NPM dependencies?", true)) {
|
||||
$this->installNpmDependencies();
|
||||
|
||||
$this->output->newLine();
|
||||
}
|
||||
|
||||
if ($this->confirm("Would you like to compile the {$componentType}'s assets?", true)) {
|
||||
$this->compileAssets();
|
||||
|
||||
$this->output->newLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the given command as a process.
|
||||
*
|
||||
* @param string $command
|
||||
* @param string $path
|
||||
* @return void
|
||||
*/
|
||||
protected function executeCommand($command, $path)
|
||||
{
|
||||
$process = Process::fromShellCommandline($command, $path)->setTimeout(null);
|
||||
|
||||
if ('\\' !== DIRECTORY_SEPARATOR && file_exists('/dev/tty') && is_readable('/dev/tty')) {
|
||||
$process->setTty(true);
|
||||
}
|
||||
|
||||
$process->run(function ($type, $line) {
|
||||
$this->output->write($line);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the project's composer dependencies.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function composerUpdate()
|
||||
{
|
||||
$this->executeCommand('composer update', getcwd());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a package entry for the component to the application's composer.json file.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function addRequireToRootComposer()
|
||||
{
|
||||
$composer = json_decode(file_get_contents(base_path('composer.json')), true);
|
||||
|
||||
$composer['require'][$this->component()] = '@dev';
|
||||
|
||||
file_put_contents(
|
||||
base_path('composer.json'),
|
||||
json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a path repository for the component to the application's composer.json file.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function addRepositoryToRootComposer()
|
||||
{
|
||||
$composer = json_decode(file_get_contents(base_path('composer.json')), true);
|
||||
|
||||
$composer['repositories'][] = [
|
||||
'type' => 'path',
|
||||
'url' => './'.$this->relativeComponentPath(),
|
||||
];
|
||||
|
||||
file_put_contents(
|
||||
base_path('composer.json'),
|
||||
json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a path repository for the component to the application's composer.json file.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function addScriptsToRootNpmPackage()
|
||||
{
|
||||
$package = json_decode(file_get_contents(base_path('package.json')), true);
|
||||
|
||||
$package['scripts']['build-'.$this->componentName()] = 'cd '.$this->relativeComponentPath().' && npm run dev';
|
||||
$package['scripts']['build-'.$this->componentName().'-prod'] = 'cd '.$this->relativeComponentPath().' && npm run prod';
|
||||
|
||||
file_put_contents(
|
||||
base_path('package.json'),
|
||||
json_encode($package, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the component's NPM dependencies.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function installNpmDependencies()
|
||||
{
|
||||
$this->executeCommand('npm set progress=false && npm install', $this->componentPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the Nova's NPM dependencies.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function installNovaNpmDependencies()
|
||||
{
|
||||
$this->executeCommand('npm set progress=false && npm ci', realpath(__DIR__.'/../../'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the component's assets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function compileAssets()
|
||||
{
|
||||
$this->executeCommand('npm run dev', $this->componentPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the given string in the given file.
|
||||
*
|
||||
* @param string|array $search
|
||||
* @param string|array $replace
|
||||
* @param string $path
|
||||
* @return void
|
||||
*/
|
||||
protected function replace($search, $replace, $path)
|
||||
{
|
||||
file_put_contents($path, str_replace($search, $replace, file_get_contents($path)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the component.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function componentPath()
|
||||
{
|
||||
return base_path('nova-components/'.$this->componentClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relative path to the component.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function relativeComponentPath()
|
||||
{
|
||||
return 'nova-components/'.$this->componentClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the component's namespace.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function componentNamespace()
|
||||
{
|
||||
return Str::studly($this->componentVendor()).'\\'.$this->componentClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the component's escaped namespace.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function escapedComponentNamespace()
|
||||
{
|
||||
return str_replace('\\', '\\\\', $this->componentNamespace());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the component's class name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function componentClass()
|
||||
{
|
||||
return Str::studly($this->componentName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the component's vendor.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function componentVendor()
|
||||
{
|
||||
return explode('/', $this->component())[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the component's base name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function componentName()
|
||||
{
|
||||
return explode('/', $this->component())[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the component's name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function component()
|
||||
{
|
||||
return $this->argument('name');
|
||||
}
|
||||
}
|
||||
26
nova/src/Console/Concerns/AcceptsNameAndVendor.php
Normal file
26
nova/src/Console/Concerns/AcceptsNameAndVendor.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console\Concerns;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait AcceptsNameAndVendor
|
||||
{
|
||||
/**
|
||||
* Determine if the name argument is valid.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasValidNameArgument()
|
||||
{
|
||||
$name = $this->argument('name');
|
||||
|
||||
if (! Str::contains($name, '/')) {
|
||||
$this->error("The name argument expects a vendor and name in 'Composer' format. Here's an example: `vendor/name`.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
116
nova/src/Console/CustomFilterCommand.php
Normal file
116
nova/src/Console/CustomFilterCommand.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'nova:custom-filter')]
|
||||
class CustomFilterCommand extends ComponentGeneratorCommand
|
||||
{
|
||||
use RenamesStubs;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'nova:custom-filter {name}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new custom filter';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->hasValidNameArgument()) {
|
||||
return;
|
||||
}
|
||||
|
||||
(new Filesystem)->copyDirectory(
|
||||
__DIR__.'/filter-stubs',
|
||||
$this->componentPath()
|
||||
);
|
||||
|
||||
// Filter.js replacements...
|
||||
$this->replace('{{ component }}', $this->componentName(), $this->componentPath().'/resources/js/filter.js');
|
||||
|
||||
// Filter.php replacements...
|
||||
$this->replace('{{ namespace }}', $this->componentNamespace(), $this->componentPath().'/src/Filter.stub');
|
||||
$this->replace('{{ class }}', $this->componentClass(), $this->componentPath().'/src/Filter.stub');
|
||||
$this->replace('{{ component }}', $this->componentName(), $this->componentPath().'/src/Filter.stub');
|
||||
|
||||
(new Filesystem)->move(
|
||||
$this->componentPath().'/src/Filter.stub',
|
||||
$this->componentPath().'/src/'.$this->componentClass().'.php'
|
||||
);
|
||||
|
||||
// FilterServiceProvider.php replacements...
|
||||
$this->replace('{{ namespace }}', $this->componentNamespace(), $this->componentPath().'/src/FilterServiceProvider.stub');
|
||||
$this->replace('{{ component }}', $this->componentName(), $this->componentPath().'/src/FilterServiceProvider.stub');
|
||||
|
||||
// webpack.mix.js replacements...
|
||||
$this->replace('{{ name }}', $this->component(), $this->componentPath().'/webpack.mix.js');
|
||||
|
||||
// Filter composer.json replacements...
|
||||
$this->prepareComposerReplacements();
|
||||
|
||||
// Rename the stubs with the proper file extensions...
|
||||
$this->renameStubs();
|
||||
|
||||
// Register the filter...
|
||||
$this->buildComponent('filter');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stub file for the generator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub()
|
||||
{
|
||||
return __DIR__.'/stubs/filter.stub';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default namespace for the class.
|
||||
*
|
||||
* @param string $rootNamespace
|
||||
* @return string
|
||||
*/
|
||||
protected function getDefaultNamespace($rootNamespace)
|
||||
{
|
||||
return $rootNamespace.'\Nova\Filters';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of stubs that need PHP file extensions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function stubsToRename()
|
||||
{
|
||||
return [
|
||||
$this->componentPath().'/src/FilterServiceProvider.stub',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "title" name of the filter.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function componentTitle()
|
||||
{
|
||||
return Str::title(str_replace('-', ' ', $this->componentName()));
|
||||
}
|
||||
}
|
||||
74
nova/src/Console/DashboardCommand.php
Normal file
74
nova/src/Console/DashboardCommand.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Console\GeneratorCommand;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'nova:dashboard')]
|
||||
class DashboardCommand extends GeneratorCommand
|
||||
{
|
||||
use ResolvesStubPath;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'nova:dashboard {name}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new dashboard.';
|
||||
|
||||
/**
|
||||
* The type of class being generated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Dashboard';
|
||||
|
||||
/**
|
||||
* Build the class with the given name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function buildClass($name)
|
||||
{
|
||||
$stub = parent::buildClass($name);
|
||||
|
||||
$stub = str_replace('uri-key', Str::snake($this->argument('name'), '-'), $stub);
|
||||
|
||||
return str_replace('dashboard-name', ucwords(Str::snake($this->argument('name'), ' ')), $stub);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stub file for the generator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub()
|
||||
{
|
||||
if ($this->argument('name') === 'Main') {
|
||||
return $this->resolveStubPath('/stubs/nova/main-dashboard.stub');
|
||||
}
|
||||
|
||||
return $this->resolveStubPath('/stubs/nova/dashboard.stub');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default namespace for the class.
|
||||
*
|
||||
* @param string $rootNamespace
|
||||
* @return string
|
||||
*/
|
||||
protected function getDefaultNamespace($rootNamespace)
|
||||
{
|
||||
return $rootNamespace.'\Nova\Dashboards';
|
||||
}
|
||||
}
|
||||
73
nova/src/Console/FieldCommand.php
Normal file
73
nova/src/Console/FieldCommand.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'nova:field')]
|
||||
class FieldCommand extends ComponentGeneratorCommand
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'nova:field {name}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new field';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->hasValidNameArgument()) {
|
||||
return;
|
||||
}
|
||||
|
||||
(new Filesystem)->copyDirectory(
|
||||
__DIR__.'/field-stubs',
|
||||
$this->componentPath()
|
||||
);
|
||||
|
||||
// Field.js replacements...
|
||||
$this->replace('{{ component }}', $this->componentName(), $this->componentPath().'/resources/js/field.js');
|
||||
|
||||
// Field.php replacements...
|
||||
$this->replace('{{ namespace }}', $this->componentNamespace(), $this->componentPath().'/src/Field.stub');
|
||||
$this->replace('{{ class }}', $this->componentClass(), $this->componentPath().'/src/Field.stub');
|
||||
$this->replace('{{ component }}', $this->componentName(), $this->componentPath().'/src/Field.stub');
|
||||
|
||||
(new Filesystem)->move(
|
||||
$this->componentPath().'/src/Field.stub',
|
||||
$this->componentPath().'/src/'.$this->componentClass().'.php'
|
||||
);
|
||||
|
||||
// FieldServiceProvider.php replacements...
|
||||
$this->replace('{{ namespace }}', $this->componentNamespace(), $this->componentPath().'/src/FieldServiceProvider.stub');
|
||||
$this->replace('{{ component }}', $this->componentName(), $this->componentPath().'/src/FieldServiceProvider.stub');
|
||||
|
||||
// webpack.mix.js replacements...
|
||||
$this->replace('{{ name }}', $this->component(), $this->componentPath().'/webpack.mix.js');
|
||||
|
||||
(new Filesystem)->move(
|
||||
$this->componentPath().'/src/FieldServiceProvider.stub',
|
||||
$this->componentPath().'/src/FieldServiceProvider.php'
|
||||
);
|
||||
|
||||
// Field composer.json replacements...
|
||||
$this->prepareComposerReplacements();
|
||||
|
||||
// Register the field...
|
||||
$this->installNovaNpmDependencies();
|
||||
$this->buildComponent('field');
|
||||
}
|
||||
}
|
||||
74
nova/src/Console/FilterCommand.php
Normal file
74
nova/src/Console/FilterCommand.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Console\GeneratorCommand;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
#[AsCommand(name: 'nova:filter')]
|
||||
class FilterCommand extends GeneratorCommand
|
||||
{
|
||||
use ResolvesStubPath;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'nova:filter';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new filter class';
|
||||
|
||||
/**
|
||||
* The type of class being generated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Filter';
|
||||
|
||||
/**
|
||||
* Get the stub file for the generator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub()
|
||||
{
|
||||
if ($this->option('boolean')) {
|
||||
return $this->resolveStubPath('/stubs/nova/boolean-filter.stub');
|
||||
} elseif ($this->option('date')) {
|
||||
return $this->resolveStubPath('/stubs/nova/date-filter.stub');
|
||||
}
|
||||
|
||||
return $this->resolveStubPath('/stubs/nova/filter.stub');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default namespace for the class.
|
||||
*
|
||||
* @param string $rootNamespace
|
||||
* @return string
|
||||
*/
|
||||
protected function getDefaultNamespace($rootNamespace)
|
||||
{
|
||||
return $rootNamespace.'\Nova\Filters';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['boolean', null, InputOption::VALUE_NONE, 'Indicates if the generated filter should be a boolean filter'],
|
||||
['date', null, InputOption::VALUE_NONE, 'Indicates if the generated filter should be a date filter'],
|
||||
];
|
||||
}
|
||||
}
|
||||
147
nova/src/Console/InstallCommand.php
Normal file
147
nova/src/Console/InstallCommand.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Foundation\Configuration\ApplicationBuilder;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'nova:install')]
|
||||
class InstallCommand extends Command
|
||||
{
|
||||
use ResolvesStubPath;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'nova:install';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Install all of the Nova resources';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->comment('Publishing Nova Assets / Resources...');
|
||||
$this->callSilent('nova:publish');
|
||||
|
||||
$this->comment('Publishing Nova Service Provider...');
|
||||
$this->callSilent('vendor:publish', ['--tag' => 'nova-provider']);
|
||||
|
||||
$this->comment('Generating Main Dashboard...');
|
||||
$this->callSilent('nova:dashboard', ['name' => 'Main']);
|
||||
copy($this->resolveStubPath('/stubs/nova/main-dashboard.stub'), app_path('Nova/Dashboards/Main.php'));
|
||||
|
||||
$this->installNovaServiceProvider();
|
||||
|
||||
$this->comment('Generating User Resource...');
|
||||
$this->callSilent('nova:resource', ['name' => 'User']);
|
||||
copy($this->resolveStubPath('/stubs/nova/user-resource.stub'), app_path('Nova/User.php'));
|
||||
|
||||
if (file_exists(app_path('Models/User.php'))) {
|
||||
file_put_contents(
|
||||
app_path('Nova/User.php'),
|
||||
str_replace(
|
||||
['App\User::class', 'class-string<\App\User>'],
|
||||
['App\Models\User::class', 'class-string<\App\Models\User>'],
|
||||
file_get_contents(app_path('Nova/User.php'))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->setAppNamespace();
|
||||
|
||||
$this->info('Nova scaffolding installed successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the Nova service providers in the application configuration file.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function installNovaServiceProvider()
|
||||
{
|
||||
$namespace = Str::replaceLast('\\', '', $this->laravel->getNamespace());
|
||||
|
||||
$appConfig = file_get_contents(config_path('app.php'));
|
||||
|
||||
$lineEndingCount = [
|
||||
"\r\n" => substr_count($appConfig, "\r\n"),
|
||||
"\r" => substr_count($appConfig, "\r"),
|
||||
"\n" => substr_count($appConfig, "\n"),
|
||||
];
|
||||
|
||||
$eol = array_keys($lineEndingCount, max($lineEndingCount))[0];
|
||||
|
||||
if (class_exists(ApplicationBuilder::class) && is_file(base_path('bootstrap/providers.php'))) {
|
||||
ServiceProvider::addProviderToBootstrapFile("{$namespace}\\Providers\\NovaServiceProvider");
|
||||
|
||||
if (
|
||||
! $this->laravel['router']->has('login')
|
||||
&& $this->confirm('Would you like to use the Nova login screen as your application\'s default login screen?', true)
|
||||
) {
|
||||
file_put_contents(
|
||||
app_path('Providers/NovaServiceProvider.php'),
|
||||
str_replace(
|
||||
[$eol.' ->withAuthenticationRoutes()'.$eol],
|
||||
[$eol.' ->withAuthenticationRoutes(default: true)'.$eol],
|
||||
file_get_contents(app_path('Providers/NovaServiceProvider.php'))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (Str::contains($appConfig, "{$namespace}\\Providers\\NovaServiceProvider::class")) {
|
||||
return;
|
||||
}
|
||||
|
||||
file_put_contents(config_path('app.php'), str_replace(
|
||||
"{$namespace}\\Providers\EventServiceProvider::class,".$eol,
|
||||
"{$namespace}\\Providers\EventServiceProvider::class,".$eol." {$namespace}\Providers\NovaServiceProvider::class,".$eol,
|
||||
$appConfig
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the proper application namespace on the installed files.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setAppNamespace()
|
||||
{
|
||||
$namespace = $this->laravel->getNamespace();
|
||||
|
||||
$this->setAppNamespaceOn(app_path('Nova/User.php'), $namespace);
|
||||
$this->setAppNamespaceOn(app_path('Providers/NovaServiceProvider.php'), $namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the namespace on the given file.
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $namespace
|
||||
* @return void
|
||||
*/
|
||||
protected function setAppNamespaceOn($file, $namespace)
|
||||
{
|
||||
file_put_contents($file, str_replace(
|
||||
'App\\',
|
||||
$namespace,
|
||||
file_get_contents($file)
|
||||
));
|
||||
}
|
||||
}
|
||||
70
nova/src/Console/LensCommand.php
Normal file
70
nova/src/Console/LensCommand.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Console\GeneratorCommand;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'nova:lens')]
|
||||
class LensCommand extends GeneratorCommand
|
||||
{
|
||||
use ResolvesStubPath;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'nova:lens';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new lens class';
|
||||
|
||||
/**
|
||||
* The type of class being generated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Lens';
|
||||
|
||||
/**
|
||||
* Build the class with the given name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function buildClass($name)
|
||||
{
|
||||
$stub = parent::buildClass($name);
|
||||
|
||||
$key = preg_replace('/[^a-zA-Z0-9]+/', '', $this->argument('name'));
|
||||
|
||||
return str_replace('uri-key', Str::kebab($key), $stub);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stub file for the generator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub()
|
||||
{
|
||||
return $this->resolveStubPath('/stubs/nova/lens.stub');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default namespace for the class.
|
||||
*
|
||||
* @param string $rootNamespace
|
||||
* @return string
|
||||
*/
|
||||
protected function getDefaultNamespace($rootNamespace)
|
||||
{
|
||||
return $rootNamespace.'\Nova\Lenses';
|
||||
}
|
||||
}
|
||||
70
nova/src/Console/PartitionCommand.php
Normal file
70
nova/src/Console/PartitionCommand.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Console\GeneratorCommand;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'nova:partition')]
|
||||
class PartitionCommand extends GeneratorCommand
|
||||
{
|
||||
use ResolvesStubPath;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'nova:partition';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new metric (partition) class';
|
||||
|
||||
/**
|
||||
* The type of class being generated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Metric';
|
||||
|
||||
/**
|
||||
* Build the class with the given name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function buildClass($name)
|
||||
{
|
||||
$stub = parent::buildClass($name);
|
||||
|
||||
$key = preg_replace('/[^a-zA-Z0-9]+/', '', $this->argument('name'));
|
||||
|
||||
return str_replace('uri-key', Str::kebab($key), $stub);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stub file for the generator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub()
|
||||
{
|
||||
return $this->resolveStubPath('/stubs/nova/partition.stub');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default namespace for the class.
|
||||
*
|
||||
* @param string $rootNamespace
|
||||
* @return string
|
||||
*/
|
||||
protected function getDefaultNamespace($rootNamespace)
|
||||
{
|
||||
return $rootNamespace.'\Nova\Metrics';
|
||||
}
|
||||
}
|
||||
70
nova/src/Console/ProgressCommand.php
Normal file
70
nova/src/Console/ProgressCommand.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Console\GeneratorCommand;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'nova:progress')]
|
||||
class ProgressCommand extends GeneratorCommand
|
||||
{
|
||||
use ResolvesStubPath;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'nova:progress';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new metric (progress) class';
|
||||
|
||||
/**
|
||||
* The type of class being generated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Metric';
|
||||
|
||||
/**
|
||||
* Build the class with the given name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function buildClass($name)
|
||||
{
|
||||
$stub = parent::buildClass($name);
|
||||
|
||||
$key = preg_replace('/[^a-zA-Z0-9]+/', '', $this->argument('name'));
|
||||
|
||||
return str_replace('uri-key', Str::kebab($key), $stub);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stub file for the generator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub()
|
||||
{
|
||||
return $this->resolveStubPath('/stubs/nova/progress.stub');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default namespace for the class.
|
||||
*
|
||||
* @param string $rootNamespace
|
||||
* @return string
|
||||
*/
|
||||
protected function getDefaultNamespace($rootNamespace)
|
||||
{
|
||||
return $rootNamespace.'\Nova\Metrics';
|
||||
}
|
||||
}
|
||||
49
nova/src/Console/PublishCommand.php
Normal file
49
nova/src/Console/PublishCommand.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'nova:publish')]
|
||||
class PublishCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'nova:publish {--force : Overwrite any existing files}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Publish all of the Nova resources';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->call('vendor:publish', [
|
||||
'--tag' => 'nova-config',
|
||||
'--force' => $this->option('force'),
|
||||
]);
|
||||
|
||||
$this->call('vendor:publish', [
|
||||
'--tag' => 'nova-assets',
|
||||
'--force' => true,
|
||||
]);
|
||||
|
||||
$this->call('vendor:publish', [
|
||||
'--tag' => 'nova-lang',
|
||||
'--force' => $this->option('force'),
|
||||
]);
|
||||
|
||||
$this->call('view:clear');
|
||||
}
|
||||
}
|
||||
27
nova/src/Console/RenamesStubs.php
Normal file
27
nova/src/Console/RenamesStubs.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
|
||||
trait RenamesStubs
|
||||
{
|
||||
/**
|
||||
* Rename the stubs with PHP file extensions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function renameStubs()
|
||||
{
|
||||
foreach ($this->stubsToRename() as $stub) {
|
||||
(new Filesystem)->move($stub, str_replace('.stub', '.php', $stub));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of stubs that need PHP file extensions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract protected function stubsToRename();
|
||||
}
|
||||
129
nova/src/Console/RepeatableCommand.php
Normal file
129
nova/src/Console/RepeatableCommand.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Console\GeneratorCommand;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
#[AsCommand(name: 'nova:repeatable')]
|
||||
class RepeatableCommand extends GeneratorCommand
|
||||
{
|
||||
use ResolvesStubPath;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'nova:repeatable';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new repeatable class';
|
||||
|
||||
/**
|
||||
* The type of class being generated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Repeatable';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return bool|null
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
parent::handle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the class with the given name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function buildClass($name)
|
||||
{
|
||||
$resourceName = $this->argument('name');
|
||||
|
||||
/** @var string|null $model */
|
||||
$model = $this->option('model');
|
||||
$modelNamespace = $this->getModelNamespace();
|
||||
|
||||
if (is_null($model)) {
|
||||
$model = $modelNamespace.str_replace('/', '\\', $resourceName);
|
||||
} elseif (! Str::startsWith($model, [
|
||||
$modelNamespace, '\\',
|
||||
])) {
|
||||
$model = $modelNamespace.$model;
|
||||
}
|
||||
|
||||
$replace = [
|
||||
'DummyFullModel' => $model,
|
||||
'{{ namespacedModel }}' => $model,
|
||||
'{{namespacedModel}}' => $model,
|
||||
];
|
||||
|
||||
return str_replace(
|
||||
array_keys($replace), array_values($replace), parent::buildClass($name)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stub file for the generator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub()
|
||||
{
|
||||
if ($this->option('model')) {
|
||||
return $this->resolveStubPath('/stubs/nova/repeatable-model.stub');
|
||||
}
|
||||
|
||||
return $this->resolveStubPath('/stubs/nova/repeatable.stub');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default namespace for the class.
|
||||
*
|
||||
* @param string $rootNamespace
|
||||
* @return string
|
||||
*/
|
||||
protected function getDefaultNamespace($rootNamespace)
|
||||
{
|
||||
return $rootNamespace.'\Nova\Repeater';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default namespace for the class.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getModelNamespace()
|
||||
{
|
||||
$rootNamespace = $this->laravel->getNamespace();
|
||||
|
||||
return is_dir(app_path('Models')) ? $rootNamespace.'Models\\' : $rootNamespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['model', 'm', InputOption::VALUE_REQUIRED, 'The model class being represented.'],
|
||||
];
|
||||
}
|
||||
}
|
||||
19
nova/src/Console/ResolvesStubPath.php
Normal file
19
nova/src/Console/ResolvesStubPath.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
trait ResolvesStubPath
|
||||
{
|
||||
/**
|
||||
* Resolve the fully-qualified path to the stub.
|
||||
*
|
||||
* @param string $stub
|
||||
* @return string
|
||||
*/
|
||||
protected function resolveStubPath($stub)
|
||||
{
|
||||
return file_exists($customPath = $this->laravel->basePath(trim($stub, '/')))
|
||||
? $customPath
|
||||
: __DIR__.str_replace('nova/', '', $stub);
|
||||
}
|
||||
}
|
||||
184
nova/src/Console/ResourceCommand.php
Normal file
184
nova/src/Console/ResourceCommand.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Console\GeneratorCommand;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
#[AsCommand(name: 'nova:resource')]
|
||||
class ResourceCommand extends GeneratorCommand
|
||||
{
|
||||
use ResolvesStubPath;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'nova:resource';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new resource class';
|
||||
|
||||
/**
|
||||
* The type of class being generated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Resource';
|
||||
|
||||
/**
|
||||
* A list of resource names which are protected.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $protectedNames = [
|
||||
'card',
|
||||
'cards',
|
||||
'dashboard',
|
||||
'dashboards',
|
||||
'metric',
|
||||
'metrics',
|
||||
'script',
|
||||
'scripts',
|
||||
'search',
|
||||
'searches',
|
||||
'style',
|
||||
'styles',
|
||||
];
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
parent::handle();
|
||||
|
||||
$this->callSilent('nova:base-resource', [
|
||||
'name' => 'Resource',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the class with the given name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function buildClass($name)
|
||||
{
|
||||
$resourceName = $this->argument('name');
|
||||
$model = $this->option('model');
|
||||
|
||||
$modelNamespace = $this->getModelNamespace();
|
||||
|
||||
if (is_null($model)) {
|
||||
$model = $modelNamespace.str_replace('/', '\\', $resourceName);
|
||||
} elseif (! Str::startsWith($model, [
|
||||
$modelNamespace, '\\',
|
||||
])) {
|
||||
$model = $modelNamespace.$model;
|
||||
}
|
||||
|
||||
if (in_array(strtolower($resourceName), $this->protectedNames)) {
|
||||
$this->warn("You *must* override the uriKey method for your {$resourceName} resource.");
|
||||
}
|
||||
|
||||
$replace = [
|
||||
'DummyFullModel' => $model,
|
||||
'{{ namespacedModel }}' => $model,
|
||||
'{{namespacedModel}}' => $model,
|
||||
];
|
||||
|
||||
$result = str_replace(
|
||||
array_keys($replace), array_values($replace), parent::buildClass($name)
|
||||
);
|
||||
|
||||
$baseResourceClass = $this->getBaseResourceClass();
|
||||
|
||||
if (! class_exists($baseResourceClass)) {
|
||||
$baseResourceClass = 'Laravel\Nova\Resource';
|
||||
} elseif (! Str::contains($resourceName, '/') && class_exists($baseResourceClass)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$lineEndingCount = [
|
||||
"\r\n" => substr_count($result, "\r\n"),
|
||||
"\r" => substr_count($result, "\r"),
|
||||
"\n" => substr_count($result, "\n"),
|
||||
];
|
||||
|
||||
$eol = array_keys($lineEndingCount, max($lineEndingCount))[0];
|
||||
|
||||
return str_replace(
|
||||
'use Laravel\Nova\Http\Requests\NovaRequest;'.$eol,
|
||||
'use Laravel\Nova\Http\Requests\NovaRequest;'.$eol."use {$baseResourceClass};".$eol,
|
||||
$result
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stub file for the generator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub()
|
||||
{
|
||||
return $this->resolveStubPath('/stubs/nova/resource.stub');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default namespace for the class.
|
||||
*
|
||||
* @param string $rootNamespace
|
||||
* @return string
|
||||
*/
|
||||
protected function getDefaultNamespace($rootNamespace)
|
||||
{
|
||||
return $rootNamespace.'\Nova';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base resource class.
|
||||
*
|
||||
* @return class-string
|
||||
*/
|
||||
protected function getBaseResourceClass()
|
||||
{
|
||||
$rootNamespace = $this->laravel->getNamespace();
|
||||
|
||||
return "{$rootNamespace}Nova\Resource";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default namespace for the class.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getModelNamespace()
|
||||
{
|
||||
$rootNamespace = $this->laravel->getNamespace();
|
||||
|
||||
return is_dir(app_path('Models')) ? $rootNamespace.'Models\\' : $rootNamespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['model', 'm', InputOption::VALUE_REQUIRED, 'The model class being represented.'],
|
||||
];
|
||||
}
|
||||
}
|
||||
101
nova/src/Console/ResourceToolCommand.php
Normal file
101
nova/src/Console/ResourceToolCommand.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'nova:resource-tool')]
|
||||
class ResourceToolCommand extends ComponentGeneratorCommand
|
||||
{
|
||||
use RenamesStubs;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'nova:resource-tool {name}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new resource tool';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->hasValidNameArgument()) {
|
||||
return;
|
||||
}
|
||||
|
||||
(new Filesystem)->copyDirectory(
|
||||
__DIR__.'/resource-tool-stubs',
|
||||
$this->componentPath()
|
||||
);
|
||||
|
||||
// Tool.js replacements...
|
||||
$this->replace('{{ component }}', $this->componentName(), $this->componentPath().'/resources/js/tool.js');
|
||||
|
||||
// Tool.vue replacements...
|
||||
$this->replace('{{ title }}', $this->componentTitle(), $this->componentPath().'/resources/js/components/Tool.vue');
|
||||
|
||||
// Tool.php replacements...
|
||||
$this->replace('{{ namespace }}', $this->componentNamespace(), $this->componentPath().'/src/Tool.stub');
|
||||
$this->replace('{{ class }}', $this->componentClass(), $this->componentPath().'/src/Tool.stub');
|
||||
$this->replace('{{ component }}', $this->componentName(), $this->componentPath().'/src/Tool.stub');
|
||||
$this->replace('{{ title }}', $this->componentTitle(), $this->componentPath().'/src/Tool.stub');
|
||||
|
||||
(new Filesystem)->move(
|
||||
$this->componentPath().'/src/Tool.stub',
|
||||
$this->componentPath().'/src/'.$this->componentClass().'.php'
|
||||
);
|
||||
|
||||
// ToolServiceProvider.php replacements...
|
||||
$this->replace('{{ namespace }}', $this->componentNamespace(), $this->componentPath().'/src/ToolServiceProvider.stub');
|
||||
$this->replace('{{ component }}', $this->componentName(), $this->componentPath().'/src/ToolServiceProvider.stub');
|
||||
$this->replace('{{ name }}', $this->componentName(), $this->componentPath().'/src/ToolServiceProvider.stub');
|
||||
|
||||
// webpack.mix.js replacements...
|
||||
$this->replace('{{ name }}', $this->component(), $this->componentPath().'/webpack.mix.js');
|
||||
|
||||
// Tool composer.json replacements...
|
||||
$this->prepareComposerReplacements();
|
||||
|
||||
// Rename the stubs with the proper file extensions...
|
||||
$this->renameStubs();
|
||||
|
||||
// Register the tool...
|
||||
$this->buildComponent('resource-tool');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of stubs that need PHP file extensions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function stubsToRename()
|
||||
{
|
||||
return [
|
||||
$this->componentPath().'/src/ToolServiceProvider.stub',
|
||||
$this->componentPath().'/routes/api.stub',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "title" name of the tool.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function componentTitle()
|
||||
{
|
||||
return Str::title(str_replace('-', ' ', $this->componentName()));
|
||||
}
|
||||
}
|
||||
64
nova/src/Console/StubPublishCommand.php
Normal file
64
nova/src/Console/StubPublishCommand.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'nova:stubs')]
|
||||
class StubPublishCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'nova:stubs {--force : Overwrite any existing files}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Publish all stubs that are available for customization';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! is_dir($stubsPath = $this->laravel->basePath('stubs/nova'))) {
|
||||
(new Filesystem)->makeDirectory($stubsPath, 0755, true);
|
||||
}
|
||||
|
||||
$files = [
|
||||
__DIR__.'/stubs/action.stub' => $stubsPath.'/action.stub',
|
||||
__DIR__.'/stubs/action.queued.stub' => $stubsPath.'/action.queued.stub',
|
||||
__DIR__.'/stubs/base-resource.stub' => $stubsPath.'/base-resource.stub',
|
||||
__DIR__.'/stubs/boolean-filter.stub' => $stubsPath.'/boolean-filter.stub',
|
||||
__DIR__.'/stubs/dashboard.stub' => $stubsPath.'/dashboard.stub',
|
||||
__DIR__.'/stubs/date-filter.stub' => $stubsPath.'/date-filter.stub',
|
||||
__DIR__.'/stubs/destructive-action.stub' => $stubsPath.'/destructive-action.stub',
|
||||
__DIR__.'/stubs/destructive-action.queued.stub' => $stubsPath.'/destructive-action.queued.stub',
|
||||
__DIR__.'/stubs/filter.stub' => $stubsPath.'/filter.stub',
|
||||
__DIR__.'/stubs/lens.stub' => $stubsPath.'/lens.stub',
|
||||
__DIR__.'/stubs/main-dashboard.stub' => $stubsPath.'/main-dashboard.stub',
|
||||
__DIR__.'/stubs/partition.stub' => $stubsPath.'/partition.stub',
|
||||
__DIR__.'/stubs/resource.stub' => $stubsPath.'/resource.stub',
|
||||
__DIR__.'/stubs/trend.stub' => $stubsPath.'/trend.stub',
|
||||
__DIR__.'/stubs/user-resource.stub' => $stubsPath.'/user-resource.stub',
|
||||
__DIR__.'/stubs/value.stub' => $stubsPath.'/value.stub',
|
||||
];
|
||||
|
||||
foreach ($files as $from => $to) {
|
||||
if (! file_exists($to) || $this->option('force')) {
|
||||
file_put_contents($to, file_get_contents($from));
|
||||
}
|
||||
}
|
||||
|
||||
$this->info('Nova stubs published successfully.');
|
||||
}
|
||||
}
|
||||
70
nova/src/Console/TableCommand.php
Normal file
70
nova/src/Console/TableCommand.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Console\GeneratorCommand;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'nova:table')]
|
||||
class TableCommand extends GeneratorCommand
|
||||
{
|
||||
use ResolvesStubPath;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'nova:table';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new metric (table) class';
|
||||
|
||||
/**
|
||||
* The type of class being generated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Metric';
|
||||
|
||||
/**
|
||||
* Build the class with the given name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function buildClass($name)
|
||||
{
|
||||
$stub = parent::buildClass($name);
|
||||
|
||||
$key = preg_replace('/[^a-zA-Z0-9]+/', '', $this->argument('name'));
|
||||
|
||||
return str_replace('uri-key', Str::kebab($key), $stub);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stub file for the generator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub()
|
||||
{
|
||||
return $this->resolveStubPath('/stubs/nova/table.stub');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default namespace for the class.
|
||||
*
|
||||
* @param string $rootNamespace
|
||||
* @return string
|
||||
*/
|
||||
protected function getDefaultNamespace($rootNamespace)
|
||||
{
|
||||
return $rootNamespace.'\Nova\Metrics';
|
||||
}
|
||||
}
|
||||
115
nova/src/Console/ToolCommand.php
Normal file
115
nova/src/Console/ToolCommand.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'nova:tool')]
|
||||
class ToolCommand extends ComponentGeneratorCommand
|
||||
{
|
||||
use RenamesStubs;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'nova:tool {name}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new tool';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->hasValidNameArgument()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$noInteraction = $this->option('no-interaction');
|
||||
|
||||
(new Filesystem)->copyDirectory(
|
||||
__DIR__.'/tool-stubs',
|
||||
$this->componentPath()
|
||||
);
|
||||
|
||||
// Route replacements...
|
||||
$this->replace(['{{ component }}', '{{ name }}'], $this->componentName(), $this->componentPath().'/routes/api.stub');
|
||||
$this->replace(['{{ component }}', '{{ name }}'], $this->componentName(), $this->componentPath().'/routes/inertia.stub');
|
||||
$this->replace('{{ class }}', $this->componentClass(), $this->componentPath().'/routes/inertia.stub');
|
||||
|
||||
// Tool.js replacements...
|
||||
$this->replace(['{{ component }}', '{{ name }}'], $this->componentName(), $this->componentPath().'/resources/js/tool.js');
|
||||
$this->replace('{{ class }}', $this->componentClass(), $this->componentPath().'/resources/js/tool.js');
|
||||
|
||||
// Tool.vue replacements...
|
||||
$this->replace('{{ title }}', $this->componentTitle(), $this->componentPath().'/resources/js/pages/Tool.vue');
|
||||
$this->replace('{{ class }}', $this->componentClass(), $this->componentPath().'/resources/js/pages/Tool.vue');
|
||||
|
||||
// Tool.php replacements...
|
||||
$this->replace('{{ namespace }}', $this->componentNamespace(), $this->componentPath().'/src/Tool.stub');
|
||||
$this->replace('{{ class }}', $this->componentClass(), $this->componentPath().'/src/Tool.stub');
|
||||
$this->replace('{{ title }}', $this->componentTitle(), $this->componentPath().'/src/Tool.stub');
|
||||
$this->replace(['{{ component }}', '{{ name }}'], $this->componentName(), $this->componentPath().'/src/Tool.stub');
|
||||
|
||||
(new Filesystem)->move(
|
||||
$this->componentPath().'/src/Tool.stub',
|
||||
$this->componentPath().'/src/'.$this->componentClass().'.php'
|
||||
);
|
||||
|
||||
// ToolServiceProvider.php replacements...
|
||||
$this->replace('{{ namespace }}', $this->componentNamespace(), $this->componentPath().'/src/ToolServiceProvider.stub');
|
||||
$this->replace(['{{ component }}', '{{ name }}'], $this->componentName(), $this->componentPath().'/src/ToolServiceProvider.stub');
|
||||
|
||||
// webpack.mix.js replacements...
|
||||
$this->replace('{{ name }}', $this->component(), $this->componentPath().'/webpack.mix.js');
|
||||
|
||||
// Authorize.php replacements...
|
||||
$this->replace('{{ namespace }}', $this->componentNamespace(), $this->componentPath().'/src/Http/Middleware/Authorize.stub');
|
||||
$this->replace('{{ class }}', $this->componentClass(), $this->componentPath().'/src/Http/Middleware/Authorize.stub');
|
||||
|
||||
// Tool composer.json replacements...
|
||||
$this->prepareComposerReplacements();
|
||||
|
||||
// Rename the stubs with the proper file extensions...
|
||||
$this->renameStubs();
|
||||
|
||||
// Register the tool...
|
||||
$this->buildComponent('tool');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of stubs that need PHP file extensions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function stubsToRename()
|
||||
{
|
||||
return [
|
||||
$this->componentPath().'/src/ToolServiceProvider.stub',
|
||||
$this->componentPath().'/src/Http/Middleware/Authorize.stub',
|
||||
$this->componentPath().'/routes/api.stub',
|
||||
$this->componentPath().'/routes/inertia.stub',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "title" name of the tool.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function componentTitle()
|
||||
{
|
||||
return Str::title(str_replace('-', ' ', $this->componentName()));
|
||||
}
|
||||
}
|
||||
43
nova/src/Console/TranslateCommand.php
Normal file
43
nova/src/Console/TranslateCommand.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'nova:translate')]
|
||||
class TranslateCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'nova:translate
|
||||
{language}
|
||||
{--force : Overwrite any existing files}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create translation files for Nova';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$language = $this->argument('language');
|
||||
|
||||
$jsonLanguageFile = lang_path("vendor/nova/{$language}.json");
|
||||
|
||||
if (! File::exists($jsonLanguageFile) || $this->option('force')) {
|
||||
File::copy(__DIR__.'/../../resources/lang/en.json', $jsonLanguageFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
70
nova/src/Console/TrendCommand.php
Normal file
70
nova/src/Console/TrendCommand.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Console\GeneratorCommand;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'nova:trend')]
|
||||
class TrendCommand extends GeneratorCommand
|
||||
{
|
||||
use ResolvesStubPath;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'nova:trend';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new metric (trend) class';
|
||||
|
||||
/**
|
||||
* The type of class being generated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Metric';
|
||||
|
||||
/**
|
||||
* Build the class with the given name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function buildClass($name)
|
||||
{
|
||||
$stub = parent::buildClass($name);
|
||||
|
||||
$key = preg_replace('/[^a-zA-Z0-9]+/', '', $this->argument('name'));
|
||||
|
||||
return str_replace('uri-key', Str::kebab($key), $stub);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stub file for the generator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub()
|
||||
{
|
||||
return $this->resolveStubPath('/stubs/nova/trend.stub');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default namespace for the class.
|
||||
*
|
||||
* @param string $rootNamespace
|
||||
* @return string
|
||||
*/
|
||||
protected function getDefaultNamespace($rootNamespace)
|
||||
{
|
||||
return $rootNamespace.'\Nova\Metrics';
|
||||
}
|
||||
}
|
||||
111
nova/src/Console/UpgradeCommand.php
Normal file
111
nova/src/Console/UpgradeCommand.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'nova:upgrade')]
|
||||
class UpgradeCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'nova:upgrade';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Upgrade Laravel Nova 3 to 4';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// 1. Prepare Main Dashboard.
|
||||
$this->call('nova:dashboard', ['name' => 'Main']);
|
||||
|
||||
// 2. Publish assets
|
||||
$this->call('vendor:publish', [
|
||||
'--tag' => 'nova-assets',
|
||||
'--force' => true,
|
||||
]);
|
||||
|
||||
// 3. Replace nova config file
|
||||
if ($this->confirm('Backup existing `nova.php` configuration file?')) {
|
||||
$this->backupFiles([
|
||||
config_path('nova.php'),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->call('vendor:publish', [
|
||||
'--tag' => 'nova-config',
|
||||
'--force' => true,
|
||||
]);
|
||||
|
||||
$path = $this->laravel['config']->get('nova.path', '/');
|
||||
|
||||
$this->replace("'path' => '/nova',", "'path' => '{$path}',", config_path('nova.php'));
|
||||
|
||||
// 4. Replace nova language files
|
||||
if ($this->confirm('Backup existing `en.json` language file?')) {
|
||||
$this->backupFiles([
|
||||
lang_path('vendor/nova/en.json'),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->call('vendor:publish', [
|
||||
'--tag' => 'nova-lang',
|
||||
'--force' => true,
|
||||
]);
|
||||
|
||||
// 5. Delete Nova 3 layout.blade.php if available.
|
||||
$this->backupFiles([
|
||||
resource_path('views/vendor/nova/layout.blade.php'),
|
||||
], true);
|
||||
|
||||
// 6. Clear view caches
|
||||
$this->call('view:clear');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create backup to the files.
|
||||
*
|
||||
* @param array<int, string> $files
|
||||
* @param bool $removeOriginal
|
||||
* @return void
|
||||
*/
|
||||
protected function backupFiles(array $files, $removeOriginal = false)
|
||||
{
|
||||
collect($files)->each(function ($file) use ($removeOriginal) {
|
||||
if (File::exists($file)) {
|
||||
File::copy($file, "{$file}.backup");
|
||||
|
||||
if ($removeOriginal === true) {
|
||||
File::delete($file);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the given string in the given file.
|
||||
*
|
||||
* @param string|array $search
|
||||
* @param string|array $replace
|
||||
* @param string $path
|
||||
* @return void
|
||||
*/
|
||||
protected function replace($search, $replace, $path)
|
||||
{
|
||||
file_put_contents($path, str_replace($search, $replace, file_get_contents($path)));
|
||||
}
|
||||
}
|
||||
37
nova/src/Console/UserCommand.php
Normal file
37
nova/src/Console/UserCommand.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Laravel\Nova\Nova;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'nova:user')]
|
||||
class UserCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'nova:user';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new user';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
Nova::createUser($this);
|
||||
|
||||
$this->info('User created successfully.');
|
||||
}
|
||||
}
|
||||
70
nova/src/Console/ValueCommand.php
Normal file
70
nova/src/Console/ValueCommand.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Nova\Console;
|
||||
|
||||
use Illuminate\Console\GeneratorCommand;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(name: 'nova:value')]
|
||||
class ValueCommand extends GeneratorCommand
|
||||
{
|
||||
use ResolvesStubPath;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'nova:value';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a new metric (single value) class';
|
||||
|
||||
/**
|
||||
* The type of class being generated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Metric';
|
||||
|
||||
/**
|
||||
* Build the class with the given name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
protected function buildClass($name)
|
||||
{
|
||||
$stub = parent::buildClass($name);
|
||||
|
||||
$key = preg_replace('/[^a-zA-Z0-9]+/', '', $this->argument('name'));
|
||||
|
||||
return str_replace('uri-key', Str::kebab($key), $stub);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stub file for the generator.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getStub()
|
||||
{
|
||||
return $this->resolveStubPath('/stubs/nova/value.stub');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default namespace for the class.
|
||||
*
|
||||
* @param string $rootNamespace
|
||||
* @return string
|
||||
*/
|
||||
protected function getDefaultNamespace($rootNamespace)
|
||||
{
|
||||
return $rootNamespace.'\Nova\Metrics';
|
||||
}
|
||||
}
|
||||
10
nova/src/Console/asset-stubs/.gitignore
vendored
Normal file
10
nova/src/Console/asset-stubs/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/.idea
|
||||
/vendor
|
||||
/node_modules
|
||||
package-lock.json
|
||||
composer.phar
|
||||
composer.lock
|
||||
phpunit.xml
|
||||
.phpunit.result.cache
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
29
nova/src/Console/asset-stubs/composer.json
Normal file
29
nova/src/Console/asset-stubs/composer.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "{{ name }}",
|
||||
"description": "A Laravel Nova asset.",
|
||||
"keywords": [
|
||||
"laravel",
|
||||
"nova"
|
||||
],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^7.3|^8.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"{{ escapedNamespace }}\\": "src/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"{{ escapedNamespace }}\\AssetServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
||||
24
nova/src/Console/asset-stubs/nova.mix.js
Normal file
24
nova/src/Console/asset-stubs/nova.mix.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const mix = require('laravel-mix')
|
||||
const webpack = require('webpack')
|
||||
|
||||
class NovaExtension {
|
||||
name() {
|
||||
return 'nova-extension'
|
||||
}
|
||||
|
||||
register(name) {
|
||||
this.name = name
|
||||
}
|
||||
|
||||
webpackConfig(webpackConfig) {
|
||||
webpackConfig.externals = {
|
||||
vue: 'Vue',
|
||||
}
|
||||
|
||||
webpackConfig.output = {
|
||||
uniqueName: this.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mix.extend('nova', new NovaExtension())
|
||||
20
nova/src/Console/asset-stubs/package.json
Normal file
20
nova/src/Console/asset-stubs/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "npm run development",
|
||||
"development": "mix",
|
||||
"watch": "mix watch",
|
||||
"watch-poll": "mix watch -- --watch-options-poll=1000",
|
||||
"hot": "mix watch --hot",
|
||||
"prod": "npm run production",
|
||||
"production": "mix --production",
|
||||
"nova:install": "npm --prefix='../../vendor/laravel/nova' ci"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/compiler-sfc": "^3.2.22",
|
||||
"laravel-mix": "^6.0.41",
|
||||
"postcss": "^8.3.11",
|
||||
"vue-loader": "^16.8.3"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
1
nova/src/Console/asset-stubs/postcss.config.js
Normal file
1
nova/src/Console/asset-stubs/postcss.config.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = {}
|
||||
1
nova/src/Console/asset-stubs/resources/css/asset.css
Normal file
1
nova/src/Console/asset-stubs/resources/css/asset.css
Normal file
@@ -0,0 +1 @@
|
||||
/* Nova Asset CSS */
|
||||
5
nova/src/Console/asset-stubs/resources/js/asset.js
Normal file
5
nova/src/Console/asset-stubs/resources/js/asset.js
Normal file
@@ -0,0 +1,5 @@
|
||||
// import {{ class }} from './components/{{ class }}'
|
||||
|
||||
// Nova.booting(app => {
|
||||
// app.component('{{ name }}', {{ class }})
|
||||
// })
|
||||
33
nova/src/Console/asset-stubs/src/AssetServiceProvider.stub
Normal file
33
nova/src/Console/asset-stubs/src/AssetServiceProvider.stub
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace {{ namespace }};
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Laravel\Nova\Events\ServingNova;
|
||||
use Laravel\Nova\Nova;
|
||||
|
||||
class AssetServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
Nova::serving(function (ServingNova $event) {
|
||||
Nova::script('{{ component }}', __DIR__.'/../dist/js/asset.js');
|
||||
Nova::style('{{ component }}', __DIR__.'/../dist/css/asset.css');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
14
nova/src/Console/asset-stubs/webpack.mix.js
Normal file
14
nova/src/Console/asset-stubs/webpack.mix.js
Normal file
@@ -0,0 +1,14 @@
|
||||
let mix = require('laravel-mix')
|
||||
let path = require('path')
|
||||
|
||||
require('./nova.mix')
|
||||
|
||||
mix
|
||||
.setPublicPath('dist')
|
||||
.js('resources/js/asset.js', 'js')
|
||||
.vue({ version: 3 })
|
||||
.css('resources/css/asset.css', 'css')
|
||||
.alias({
|
||||
'@': path.join(__dirname, 'resources/js/'),
|
||||
})
|
||||
.nova('{{ name }}')
|
||||
10
nova/src/Console/card-stubs/.gitignore
vendored
Normal file
10
nova/src/Console/card-stubs/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/.idea
|
||||
/vendor
|
||||
/node_modules
|
||||
package-lock.json
|
||||
composer.phar
|
||||
composer.lock
|
||||
phpunit.xml
|
||||
.phpunit.result.cache
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
29
nova/src/Console/card-stubs/composer.json
Normal file
29
nova/src/Console/card-stubs/composer.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "{{ name }}",
|
||||
"description": "A Laravel Nova card.",
|
||||
"keywords": [
|
||||
"laravel",
|
||||
"nova"
|
||||
],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^7.3|^8.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"{{ escapedNamespace }}\\": "src/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"{{ escapedNamespace }}\\CardServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
||||
33
nova/src/Console/card-stubs/nova.mix.js
Normal file
33
nova/src/Console/card-stubs/nova.mix.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const mix = require('laravel-mix')
|
||||
const webpack = require('webpack')
|
||||
const path = require('path')
|
||||
|
||||
class NovaExtension {
|
||||
name() {
|
||||
return 'nova-extension'
|
||||
}
|
||||
|
||||
register(name) {
|
||||
this.name = name
|
||||
}
|
||||
|
||||
webpackConfig(webpackConfig) {
|
||||
webpackConfig.externals = {
|
||||
vue: 'Vue',
|
||||
}
|
||||
|
||||
webpackConfig.resolve.alias = {
|
||||
...(webpackConfig.resolve.alias || {}),
|
||||
'laravel-nova': path.join(
|
||||
__dirname,
|
||||
'../../vendor/laravel/nova/resources/js/mixins/packages.js'
|
||||
),
|
||||
}
|
||||
|
||||
webpackConfig.output = {
|
||||
uniqueName: this.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mix.extend('nova', new NovaExtension())
|
||||
20
nova/src/Console/card-stubs/package.json
Normal file
20
nova/src/Console/card-stubs/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "npm run development",
|
||||
"development": "mix",
|
||||
"watch": "mix watch",
|
||||
"watch-poll": "mix watch -- --watch-options-poll=1000",
|
||||
"hot": "mix watch --hot",
|
||||
"prod": "npm run production",
|
||||
"production": "mix --production",
|
||||
"nova:install": "npm --prefix='../../vendor/laravel/nova' ci"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/compiler-sfc": "^3.2.22",
|
||||
"laravel-mix": "^6.0.41",
|
||||
"postcss": "^8.3.11",
|
||||
"vue-loader": "^16.8.3"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
1
nova/src/Console/card-stubs/postcss.config.js
Normal file
1
nova/src/Console/card-stubs/postcss.config.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = {}
|
||||
1
nova/src/Console/card-stubs/resources/css/card.css
Normal file
1
nova/src/Console/card-stubs/resources/css/card.css
Normal file
@@ -0,0 +1 @@
|
||||
/* Nova Card CSS */
|
||||
5
nova/src/Console/card-stubs/resources/js/card.js
Normal file
5
nova/src/Console/card-stubs/resources/js/card.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import Card from './components/Card'
|
||||
|
||||
Nova.booting((app, store) => {
|
||||
app.component('{{ component }}', Card)
|
||||
})
|
||||
24
nova/src/Console/card-stubs/resources/js/components/Card.vue
Normal file
24
nova/src/Console/card-stubs/resources/js/components/Card.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<Card class="flex flex-col items-center justify-center">
|
||||
<div class="px-3 py-3">
|
||||
<h1 class="text-center text-3xl text-gray-500 font-light">{{ title }}</h1>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'card',
|
||||
|
||||
// The following props are only available on resource detail cards...
|
||||
// 'resource',
|
||||
// 'resourceId',
|
||||
// 'resourceName',
|
||||
],
|
||||
|
||||
mounted() {
|
||||
//
|
||||
},
|
||||
}
|
||||
</script>
|
||||
19
nova/src/Console/card-stubs/routes/api.stub
Normal file
19
nova/src/Console/card-stubs/routes/api.stub
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Card API Routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here is where you may register API routes for your card. These routes
|
||||
| are loaded by the ServiceProvider of your card. You're free to add
|
||||
| as many additional routes to this file as your card may require.
|
||||
|
|
||||
*/
|
||||
|
||||
// Route::get('/endpoint', function (Request $request) {
|
||||
// //
|
||||
// });
|
||||
25
nova/src/Console/card-stubs/src/Card.stub
Normal file
25
nova/src/Console/card-stubs/src/Card.stub
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace {{ namespace }};
|
||||
|
||||
use Laravel\Nova\Card;
|
||||
|
||||
class {{ class }} extends Card
|
||||
{
|
||||
/**
|
||||
* The width of the card (1/3, 1/2, or full).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $width = '1/3';
|
||||
|
||||
/**
|
||||
* Get the component name for the element.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function component()
|
||||
{
|
||||
return '{{ component }}';
|
||||
}
|
||||
}
|
||||
54
nova/src/Console/card-stubs/src/CardServiceProvider.stub
Normal file
54
nova/src/Console/card-stubs/src/CardServiceProvider.stub
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace {{ namespace }};
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Laravel\Nova\Events\ServingNova;
|
||||
use Laravel\Nova\Nova;
|
||||
|
||||
class CardServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->app->booted(function () {
|
||||
$this->routes();
|
||||
});
|
||||
|
||||
Nova::serving(function (ServingNova $event) {
|
||||
Nova::script('{{ component }}', __DIR__.'/../dist/js/card.js');
|
||||
Nova::style('{{ component }}', __DIR__.'/../dist/css/card.css');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the card's routes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function routes()
|
||||
{
|
||||
if ($this->app->routesAreCached()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Route::middleware(['nova'])
|
||||
->prefix('nova-vendor/{{ name }}')
|
||||
->group(__DIR__.'/../routes/api.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Register any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
10
nova/src/Console/card-stubs/webpack.mix.js
Normal file
10
nova/src/Console/card-stubs/webpack.mix.js
Normal file
@@ -0,0 +1,10 @@
|
||||
let mix = require('laravel-mix')
|
||||
|
||||
require('./nova.mix')
|
||||
|
||||
mix
|
||||
.setPublicPath('dist')
|
||||
.js('resources/js/card.js', 'js')
|
||||
.vue({ version: 3 })
|
||||
.css('resources/css/card.css', 'css')
|
||||
.nova('{{ name }}')
|
||||
10
nova/src/Console/field-stubs/.gitignore
vendored
Normal file
10
nova/src/Console/field-stubs/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/.idea
|
||||
/vendor
|
||||
/node_modules
|
||||
package-lock.json
|
||||
composer.phar
|
||||
composer.lock
|
||||
phpunit.xml
|
||||
.phpunit.result.cache
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
29
nova/src/Console/field-stubs/composer.json
Normal file
29
nova/src/Console/field-stubs/composer.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "{{ name }}",
|
||||
"description": "A Laravel Nova field.",
|
||||
"keywords": [
|
||||
"laravel",
|
||||
"nova"
|
||||
],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^7.3|^8.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"{{ escapedNamespace }}\\": "src/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"{{ escapedNamespace }}\\FieldServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
||||
40
nova/src/Console/field-stubs/nova.mix.js
Normal file
40
nova/src/Console/field-stubs/nova.mix.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const mix = require('laravel-mix')
|
||||
const webpack = require('webpack')
|
||||
const path = require('path')
|
||||
|
||||
class NovaExtension {
|
||||
name() {
|
||||
return 'nova-extension'
|
||||
}
|
||||
|
||||
register(name) {
|
||||
this.name = name
|
||||
}
|
||||
|
||||
webpackPlugins() {
|
||||
return new webpack.ProvidePlugin({
|
||||
_: 'lodash',
|
||||
Errors: 'form-backend-validation',
|
||||
})
|
||||
}
|
||||
|
||||
webpackConfig(webpackConfig) {
|
||||
webpackConfig.externals = {
|
||||
vue: 'Vue',
|
||||
}
|
||||
|
||||
webpackConfig.resolve.alias = {
|
||||
...(webpackConfig.resolve.alias || {}),
|
||||
'laravel-nova': path.join(
|
||||
__dirname,
|
||||
'../../vendor/laravel/nova/resources/js/mixins/packages.js'
|
||||
),
|
||||
}
|
||||
|
||||
webpackConfig.output = {
|
||||
uniqueName: this.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mix.extend('nova', new NovaExtension())
|
||||
22
nova/src/Console/field-stubs/package.json
Normal file
22
nova/src/Console/field-stubs/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "npm run development",
|
||||
"development": "mix",
|
||||
"watch": "mix watch",
|
||||
"watch-poll": "mix watch -- --watch-options-poll=1000",
|
||||
"hot": "mix watch --hot",
|
||||
"prod": "npm run production",
|
||||
"production": "mix --production",
|
||||
"nova:install": "npm --prefix='../../vendor/laravel/nova' ci"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/compiler-sfc": "^3.2.22",
|
||||
"form-backend-validation": "^2.3.3",
|
||||
"laravel-mix": "^6.0.41",
|
||||
"lodash": "^4.17.21",
|
||||
"postcss": "^8.3.11",
|
||||
"vue-loader": "^16.8.3"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
1
nova/src/Console/field-stubs/postcss.config.js
Normal file
1
nova/src/Console/field-stubs/postcss.config.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = {}
|
||||
1
nova/src/Console/field-stubs/resources/css/field.css
Normal file
1
nova/src/Console/field-stubs/resources/css/field.css
Normal file
@@ -0,0 +1 @@
|
||||
/* Nova Field CSS */
|
||||
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<DefaultField
|
||||
:field="field"
|
||||
:errors="errors"
|
||||
:show-help-text="showHelpText"
|
||||
:full-width-content="fullWidthContent"
|
||||
>
|
||||
<template #field>
|
||||
<input
|
||||
:id="field.attribute"
|
||||
type="text"
|
||||
class="w-full form-control form-input form-control-bordered"
|
||||
:class="errorClasses"
|
||||
:placeholder="field.name"
|
||||
v-model="value"
|
||||
/>
|
||||
</template>
|
||||
</DefaultField>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { FormField, HandlesValidationErrors } from 'laravel-nova'
|
||||
|
||||
export default {
|
||||
mixins: [FormField, HandlesValidationErrors],
|
||||
|
||||
props: ['resourceName', 'resourceId', 'field'],
|
||||
|
||||
methods: {
|
||||
/*
|
||||
* Set the initial, internal value for the field.
|
||||
*/
|
||||
setInitialValue() {
|
||||
this.value = this.field.value || ''
|
||||
},
|
||||
|
||||
/**
|
||||
* Fill the given FormData object with the field's internal value.
|
||||
*/
|
||||
fill(formData) {
|
||||
formData.append(this.fieldAttribute, this.value || '')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<span>{{ fieldValue }}</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['resourceName', 'field'],
|
||||
|
||||
computed: {
|
||||
fieldValue() {
|
||||
return this.field.displayedAs || this.field.value
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
9
nova/src/Console/field-stubs/resources/js/field.js
Normal file
9
nova/src/Console/field-stubs/resources/js/field.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import IndexField from './components/IndexField'
|
||||
import DetailField from './components/DetailField'
|
||||
import FormField from './components/FormField'
|
||||
|
||||
Nova.booting((app, store) => {
|
||||
app.component('index-{{ component }}', IndexField)
|
||||
app.component('detail-{{ component }}', DetailField)
|
||||
app.component('form-{{ component }}', FormField)
|
||||
})
|
||||
15
nova/src/Console/field-stubs/src/Field.stub
Normal file
15
nova/src/Console/field-stubs/src/Field.stub
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace {{ namespace }};
|
||||
|
||||
use Laravel\Nova\Fields\Field;
|
||||
|
||||
class {{ class }} extends Field
|
||||
{
|
||||
/**
|
||||
* The field's component.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $component = '{{ component }}';
|
||||
}
|
||||
33
nova/src/Console/field-stubs/src/FieldServiceProvider.stub
Normal file
33
nova/src/Console/field-stubs/src/FieldServiceProvider.stub
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace {{ namespace }};
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Laravel\Nova\Events\ServingNova;
|
||||
use Laravel\Nova\Nova;
|
||||
|
||||
class FieldServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
Nova::serving(function (ServingNova $event) {
|
||||
Nova::script('{{ component }}', __DIR__.'/../dist/js/field.js');
|
||||
Nova::style('{{ component }}', __DIR__.'/../dist/css/field.css');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
10
nova/src/Console/field-stubs/webpack.mix.js
Normal file
10
nova/src/Console/field-stubs/webpack.mix.js
Normal file
@@ -0,0 +1,10 @@
|
||||
let mix = require('laravel-mix')
|
||||
|
||||
require('./nova.mix')
|
||||
|
||||
mix
|
||||
.setPublicPath('dist')
|
||||
.js('resources/js/field.js', 'js')
|
||||
.vue({ version: 3 })
|
||||
.css('resources/css/field.css', 'css')
|
||||
.nova('{{ name }}')
|
||||
10
nova/src/Console/filter-stubs/.gitignore
vendored
Normal file
10
nova/src/Console/filter-stubs/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/.idea
|
||||
/vendor
|
||||
/node_modules
|
||||
package-lock.json
|
||||
composer.phar
|
||||
composer.lock
|
||||
phpunit.xml
|
||||
.phpunit.result.cache
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
29
nova/src/Console/filter-stubs/composer.json
Normal file
29
nova/src/Console/filter-stubs/composer.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "{{ name }}",
|
||||
"description": "A Laravel Nova filter.",
|
||||
"keywords": [
|
||||
"laravel",
|
||||
"nova"
|
||||
],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^7.3|^8.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"{{ escapedNamespace }}\\": "src/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"{{ escapedNamespace }}\\FilterServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
||||
33
nova/src/Console/filter-stubs/nova.mix.js
Normal file
33
nova/src/Console/filter-stubs/nova.mix.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const mix = require('laravel-mix')
|
||||
const webpack = require('webpack')
|
||||
const path = require('path')
|
||||
|
||||
class NovaExtension {
|
||||
name() {
|
||||
return 'nova-extension'
|
||||
}
|
||||
|
||||
register(name) {
|
||||
this.name = name
|
||||
}
|
||||
|
||||
webpackConfig(webpackConfig) {
|
||||
webpackConfig.externals = {
|
||||
vue: 'Vue',
|
||||
}
|
||||
|
||||
webpackConfig.resolve.alias = {
|
||||
...(webpackConfig.resolve.alias || {}),
|
||||
'laravel-nova': path.join(
|
||||
__dirname,
|
||||
'../../vendor/laravel/nova/resources/js/mixins/packages.js'
|
||||
),
|
||||
}
|
||||
|
||||
webpackConfig.output = {
|
||||
uniqueName: this.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mix.extend('nova', new NovaExtension())
|
||||
20
nova/src/Console/filter-stubs/package.json
Normal file
20
nova/src/Console/filter-stubs/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "npm run development",
|
||||
"development": "mix",
|
||||
"watch": "mix watch",
|
||||
"watch-poll": "mix watch -- --watch-options-poll=1000",
|
||||
"hot": "mix watch --hot",
|
||||
"prod": "npm run production",
|
||||
"production": "mix --production",
|
||||
"nova:install": "npm --prefix='../../vendor/laravel/nova' ci"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/compiler-sfc": "^3.2.22",
|
||||
"laravel-mix": "^6.0.41",
|
||||
"postcss": "^8.3.11",
|
||||
"vue-loader": "^16.8.3"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
1
nova/src/Console/filter-stubs/postcss.config.js
Normal file
1
nova/src/Console/filter-stubs/postcss.config.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = {}
|
||||
1
nova/src/Console/filter-stubs/resources/css/filter.css
Normal file
1
nova/src/Console/filter-stubs/resources/css/filter.css
Normal file
@@ -0,0 +1 @@
|
||||
/* Nova Filter CSS */
|
||||
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<FilterContainer>
|
||||
<span>{{ filter.name }}</span>
|
||||
|
||||
<template #filter>
|
||||
<SelectControl
|
||||
:dusk="`${filter.name}-select-filter`"
|
||||
label="label"
|
||||
class="w-full block"
|
||||
size="sm"
|
||||
@change="handleChange"
|
||||
:value="value"
|
||||
:options="filter.options"
|
||||
>
|
||||
<option value="" :selected="value === ''">—</option>
|
||||
</SelectControl>
|
||||
</template>
|
||||
</FilterContainer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import debounce from 'lodash/debounce'
|
||||
|
||||
export default {
|
||||
emits: ['change'],
|
||||
|
||||
props: {
|
||||
resourceName: { type: String, required: true },
|
||||
filterKey: { type: String, required: true },
|
||||
lens: String,
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
value: null,
|
||||
debouncedEmit: null,
|
||||
}),
|
||||
|
||||
created() {
|
||||
this.debouncedEmit = debounce(() => this.emitChange(), 500)
|
||||
this.setCurrentFilterValue()
|
||||
},
|
||||
|
||||
mounted() {
|
||||
Nova.$on('filter-reset', this.setCurrentFilterValue)
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
Nova.$off('filter-reset', this.setCurrentFilterValue)
|
||||
},
|
||||
|
||||
watch: {
|
||||
value() {
|
||||
this.debouncedHandleChange()
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
setCurrentFilterValue() {
|
||||
this.value = this.filter.currentValue
|
||||
},
|
||||
|
||||
handleChange(e) {
|
||||
this.value = e
|
||||
this.debouncedEmit()
|
||||
},
|
||||
|
||||
emitChange() {
|
||||
this.$emit('change', {
|
||||
filterClass: this.filterKey,
|
||||
value: this.value,
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
filter() {
|
||||
return this.$store.getters[`${this.resourceName}/getFilter`](
|
||||
this.filterKey
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
5
nova/src/Console/filter-stubs/resources/js/filter.js
Normal file
5
nova/src/Console/filter-stubs/resources/js/filter.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import Filter from './components/Filter'
|
||||
|
||||
Nova.booting((app, store) => {
|
||||
app.component('{{ component }}', Filter)
|
||||
})
|
||||
40
nova/src/Console/filter-stubs/src/Filter.stub
Normal file
40
nova/src/Console/filter-stubs/src/Filter.stub
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace {{ namespace }};
|
||||
|
||||
use Laravel\Nova\Filters\Filter;
|
||||
use Laravel\Nova\Http\Requests\NovaRequest;
|
||||
|
||||
class {{ class }} extends Filter
|
||||
{
|
||||
/**
|
||||
* The filter's component.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $component = '{{ component }}';
|
||||
|
||||
/**
|
||||
* Apply the filter to the given query.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param mixed $value
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function apply(NovaRequest $request, $query, $value)
|
||||
{
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filter's available options.
|
||||
*
|
||||
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
|
||||
* @return array
|
||||
*/
|
||||
public function options(NovaRequest $request)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
33
nova/src/Console/filter-stubs/src/FilterServiceProvider.stub
Normal file
33
nova/src/Console/filter-stubs/src/FilterServiceProvider.stub
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace {{ namespace }};
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Laravel\Nova\Events\ServingNova;
|
||||
use Laravel\Nova\Nova;
|
||||
|
||||
class FilterServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
Nova::serving(function (ServingNova $event) {
|
||||
Nova::script('{{ component }}', __DIR__.'/../dist/js/filter.js');
|
||||
Nova::style('{{ component }}', __DIR__.'/../dist/css/filter.css');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user