Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -434,8 +434,14 @@ Craft 6 now uses [Laravel's authorization system](https://laravel.com/docs/12.x/
- Added `CraftCms\Cms\Element\ElementActivity`, `CraftCms\Cms\Element\Data\ElementActivity`, `CraftCms\Cms\Element\Enums\ElementActivityType`, and `CraftCms\Cms\Support\Facades\ElementActivity`.
- Deprecated `craft\services\Elements::getRecentActivity()`. `CraftCms\Cms\Element\ElementActivity::getRecentActivity()` should be used instead.
- Deprecated `craft\services\Elements::trackActivity()`. `CraftCms\Cms\Element\ElementActivity::trackActivity()` should be used instead.
- Added `CraftCms\Cms\Element\Actions\ElementAction`, `CraftCms\Cms\Element\ElementActions`, `CraftCms\Cms\Element\Contracts\DeleteActionInterface`, `CraftCms\Cms\Element\Contracts\ElementActionInterface`, `CraftCms\Cms\Element\Events\AfterPerformAction`, `CraftCms\Cms\Element\Events\BeforePerformAction`, `CraftCms\Cms\Http\Controllers\Elements\PerformElementActionController`, and `CraftCms\Cms\Support\Facades\ElementActions`.
- Added Laravel-native element action classes under `CraftCms\Cms\Element\Actions`, `CraftCms\Cms\Asset\Actions`, `CraftCms\Cms\Entry\Actions`, and `CraftCms\Cms\User\Actions`.
- Added `CraftCms\Cms\Element\ElementExporters`, `CraftCms\Cms\Element\Contracts\ElementExporterInterface`, `CraftCms\Cms\Element\Exporters\ElementExporter`, `CraftCms\Cms\Http\Controllers\Elements\ExportElementIndexController`, and `CraftCms\Cms\Support\Facades\ElementExporters`.
- Added Laravel-native element exporter classes under `CraftCms\Cms\Element\Exporters`.
- Deprecated `craft\errors\InvalidTypeException`. `CraftCms\Cms\Element\Exceptions\InvalidTypeException` should be used instead.
- Deprecated `craft\errors\UnsupportedSiteException`. `CraftCms\Cms\Element\Exceptions\UnsupportedSiteException` should be used instead.
- Deprecated `craft\base\ElementAction`, `craft\base\ElementActionInterface`, `craft\elements\actions\DeleteActionInterface`, and the legacy `craft\elements\actions\*` classes. The corresponding `CraftCms\Cms\Element\Actions\*`, `CraftCms\Cms\Asset\Actions\*`, `CraftCms\Cms\Entry\Actions\*`, and `CraftCms\Cms\User\Actions\*` classes should be used instead.
- Deprecated `craft\base\ElementExporter`, `craft\base\ElementExporterInterface`, and the legacy `craft\elements\exporters\*` classes. The corresponding `CraftCms\Cms\Element\Exporters\*` classes should be used instead.

### Validation

Expand Down
6 changes: 6 additions & 0 deletions routes/actions.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
use CraftCms\Cms\Http\Controllers\Dashboard\Widgets\NewUsersController;
use CraftCms\Cms\Http\Controllers\Dashboard\WidgetsController;
use CraftCms\Cms\Http\Controllers\EditionController;
use CraftCms\Cms\Http\Controllers\Elements\ExportElementIndexController;
use CraftCms\Cms\Http\Controllers\Elements\PerformElementActionController;
use CraftCms\Cms\Http\Controllers\Entries\CreateEntryController;
use CraftCms\Cms\Http\Controllers\Entries\MoveEntryToSectionController;
use CraftCms\Cms\Http\Controllers\Entries\StoreEntryController;
Expand Down Expand Up @@ -207,6 +209,10 @@
Route::post('app/switch-to-licensed-edition', [EditionController::class, 'switchToLicensedEdition']);
});

// Elements
Route::post('element-indexes/export', ExportElementIndexController::class);
Route::post('element-indexes/perform-action', PerformElementActionController::class);

// Entries
Route::post('entries/create', CreateEntryController::class);
Route::post('entries/save-entry', StoreEntryController::class);
Expand Down
2 changes: 1 addition & 1 deletion src/Address/Elements/Address.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
use Craft;
use craft\base\NestedElementInterface;
use craft\base\NestedElementTrait;
use craft\elements\actions\Copy;
use craft\elements\conditions\addresses\AddressCondition;
use CraftCms\Cms\Address\Addresses;
use CraftCms\Cms\Address\Models\Address as AddressModel;
use CraftCms\Cms\Address\Validation\AddressRules;
use CraftCms\Cms\Cms;
use CraftCms\Cms\Database\Table;
use CraftCms\Cms\Element\Actions\Copy;
use CraftCms\Cms\Element\Conditions\Contracts\ElementConditionInterface;
use CraftCms\Cms\Element\Element;
use CraftCms\Cms\Element\Queries\AddressQuery;
Expand Down
45 changes: 45 additions & 0 deletions src/Asset/Actions/CopyReferenceTag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace CraftCms\Cms\Asset\Actions;

use CraftCms\Cms\Element\Actions\ElementAction;
use CraftCms\Cms\Support\Facades\HtmlStack;
use RuntimeException;

use function CraftCms\Cms\t;

class CopyReferenceTag extends ElementAction
{
#[\Override]
public function getTriggerLabel(): string
{
return t('Copy reference tag');
}

public function getTriggerHtml(): ?string
{
$refHandle = $this->elementType::refHandle();
if ($refHandle === null) {
throw new RuntimeException("Element type \"$this->elementType\" doesn't have a reference handle.");
}

HtmlStack::jsWithVars(fn ($type, $refHandle) => <<<JS
(() => {
new Craft.ElementActionTrigger({
type: $type,
bulk: false,
activate: (selectedItems, elementIndex) => {
Craft.ui.createCopyTextPrompt({
label: Craft.t('app', 'Copy the reference tag'),
value: '{' + $refHandle + ':' + selectedItems.find('.element').data('id') + '}',
});
},
})
})();
JS, [static::class, $refHandle]);

return null;
}
}
40 changes: 40 additions & 0 deletions src/Asset/Actions/CopyUrl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace CraftCms\Cms\Asset\Actions;

use CraftCms\Cms\Element\Actions\ElementAction;
use CraftCms\Cms\Support\Facades\HtmlStack;

use function CraftCms\Cms\t;

class CopyUrl extends ElementAction
{
#[\Override]
public function getTriggerLabel(): string
{
return t('Copy URL');
}

public function getTriggerHtml(): ?string
{
HtmlStack::jsWithVars(fn ($type) => <<<JS
(() => {
new Craft.ElementActionTrigger({
type: $type,
bulk: false,
validateSelection: (selectedItems, elementIndex) => !!selectedItems.find('.element').data('url'),
activate: (selectedItems, elementIndex) => {
Craft.ui.createCopyTextPrompt({
label: Craft.t('app', 'Copy the URL'),
value: selectedItems.find('.element').data('url'),
});
},
})
})();
JS, [static::class]);

return null;
}
}
61 changes: 61 additions & 0 deletions src/Asset/Actions/DeleteAssets.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace CraftCms\Cms\Asset\Actions;

use CraftCms\Cms\Element\Actions\Delete;
use CraftCms\Cms\Support\Facades\HtmlStack;

class DeleteAssets extends Delete
{
#[\Override]
public function getTriggerHtml(): ?string
{
// Only enable for deletable elements, per canDelete()
HtmlStack::jsWithVars(fn ($type) => <<<JS
(() => {
const trigger = new Craft.ElementActionTrigger({
type: $type,
requireId: false,
validateSelection: (selectedItems, elementIndex) => {
for (let i = 0; i < selectedItems.length; i++) {
const element = selectedItems.eq(i).find('.element');
if (Garnish.hasAttr(element, 'data-is-folder')) {
if (selectedItems.length !== 1) {
// only one folder at a time
return false;
}
const sourcePath = element.data('source-path') || [];
if (!sourcePath.length || !sourcePath[sourcePath.length - 1].canDelete) {
return false;
}
} else {
if (!Garnish.hasAttr(element, 'data-deletable')) {
return false;
}
}
}

return true;
},

activate: (selectedItems, elementIndex) => {
const element = selectedItems.find('.element:first');
if (Garnish.hasAttr(element, 'data-is-folder')) {
const sourcePath = element.data('source-path');
elementIndex.deleteFolder(sourcePath[sourcePath.length - 1])
.then(() => {
elementIndex.updateElements();
});
} else {
elementIndex.submitAction(trigger.\$trigger.data('action'), Garnish.getPostData(trigger.\$trigger));
}
},
});
})();
JS, [static::class]);

return null;
}
}
54 changes: 54 additions & 0 deletions src/Asset/Actions/DownloadAssetFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace CraftCms\Cms\Asset\Actions;

use CraftCms\Cms\Element\Actions\ElementAction;
use CraftCms\Cms\Support\Facades\HtmlStack;

use function CraftCms\Cms\t;

class DownloadAssetFile extends ElementAction
{
#[\Override]
public function getTriggerLabel(): string
{
return t('Download');
}

public function getTriggerHtml(): ?string
{
HtmlStack::jsWithVars(fn ($type) => <<<JS
(() => {
new Craft.ElementActionTrigger({
type: $type,
activate: (selectedItems, elementIndex) => {
var \$form = Craft.createForm().appendTo(Garnish.\$bod);
$(Craft.getCsrfInput()).appendTo(\$form);
$('<input/>', {
type: 'hidden',
name: 'action',
value: 'assets/download-asset'
}).appendTo(\$form);
selectedItems.each(function() {
$('<input/>', {
type: 'hidden',
name: 'assetId[]',
value: $(this).data('id')
}).appendTo(\$form);
});
$('<input/>', {
type: 'submit',
value: 'Submit',
}).appendTo(\$form);
\$form.submit();
\$form.remove();
},
});
})();
JS, [static::class]);

return null;
}
}
47 changes: 47 additions & 0 deletions src/Asset/Actions/EditImage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace CraftCms\Cms\Asset\Actions;

use CraftCms\Cms\Element\Actions\ElementAction;
use CraftCms\Cms\Support\Facades\HtmlStack;

use function CraftCms\Cms\t;

class EditImage extends ElementAction
{
public string $label;

public function __construct(array|object $config = [])
{
parent::__construct($config);

$this->label ??= t('Edit Image');
}

#[\Override]
public function getTriggerLabel(): string
{
return $this->label;
}

public function getTriggerHtml(): ?string
{
HtmlStack::jsWithVars(fn ($type) => <<<JS
(() => {
new Craft.ElementActionTrigger({
type: $type,
bulk: false,
validateSelection: (selectedItems, elementIndex) => Garnish.hasAttr(selectedItems.find('.element'), 'data-editable-image'),
activate: (selectedItems, elementIndex) => {
const \$element = selectedItems.find('.element:first');
new Craft.AssetImageEditor(\$element.data('id'));
},
});
})();
JS, [static::class]);

return null;
}
}
Loading
Loading