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
2 changes: 1 addition & 1 deletion js/app_api-adminSettings.js.license
Original file line number Diff line number Diff line change
Expand Up @@ -147,5 +147,5 @@ This file is generated from multiple sources. Included packages:
- version: 5.102.1
- license: MIT
- app_api
- version: 34.0.0-dev.1
- version: 35.0.0-dev.0
- license: ( AGPL-3.0-or-later)
2 changes: 1 addition & 1 deletion js/app_api-adminSettings.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/app_api-filesplugin.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/app_api-filesplugin.js.license
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,5 @@ This file is generated from multiple sources. Included packages:
- version: 5.102.1
- license: MIT
- app_api
- version: 34.0.0-dev.1
- version: 35.0.0-dev.0
- license: ( AGPL-3.0-or-later)
2 changes: 1 addition & 1 deletion js/app_api-filesplugin.js.map

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions lib/Controller/OCSUiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,20 @@ public function registerFileActionMenu(string $name, string $displayName, string
}

/**
* @param string $defaultAction One of '', 'default', or 'hidden'
* @throws OCSBadRequestException
*/
#[AppAPIAuth]
#[PublicPage]
#[NoCSRFRequired]
public function registerFileActionMenuV2(string $name, string $displayName, string $actionHandler,
string $icon = '', string $mime = 'file', int $permissions = 31,
int $order = 0): DataResponse {
int $order = 0, string $defaultAction = ''): DataResponse {
if (!in_array($defaultAction, ['', 'default', 'hidden'], true)) {
throw new OCSBadRequestException("Invalid defaultAction '$defaultAction' — must be '', 'default', or 'hidden'");
}
$result = $this->filesActionsMenuService->registerFileActionMenu(
$this->request->getHeader('EX-APP-ID'), $name, $displayName, $actionHandler, $icon, $mime, $permissions, $order, '2.0');
$this->request->getHeader('EX-APP-ID'), $name, $displayName, $actionHandler, $icon, $mime, $permissions, $order, '2.0', $defaultAction ?: null);
if (!$result) {
throw new OCSBadRequestException('File Action Menu entry could not be registered');
}
Expand Down
8 changes: 8 additions & 0 deletions lib/Db/UI/FilesActionsMenu.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
* @method string getIcon()
* @method string getActionHandler()
* @method string getVersion()
* @method string|null getDefaultAction()
* @method void setAppid(string $appid)
* @method void setName(string $name)
* @method void setDisplayName(string $displayName)
Expand All @@ -35,6 +36,7 @@
* @method void setIcon(string $icon)
* @method void setActionHandler(string $actionHandler)
* @method void setVersion(string $version)
* @method void setDefaultAction(string|null $defaultAction)
*/
class FilesActionsMenu extends Entity implements JsonSerializable {
protected $appid;
Expand All @@ -46,6 +48,7 @@ class FilesActionsMenu extends Entity implements JsonSerializable {
protected $icon;
protected $actionHandler;
protected $version;
protected $defaultAction;

/**
* @param array $params
Expand All @@ -60,6 +63,7 @@ public function __construct(array $params = []) {
$this->addType('icon', 'string');
$this->addType('actionHandler', 'string');
$this->addType('version', 'string');
$this->addType('defaultAction', 'string');

if (isset($params['id'])) {
$this->setId($params['id']);
Expand Down Expand Up @@ -91,6 +95,9 @@ public function __construct(array $params = []) {
if (isset($params['version'])) {
$this->setVersion($params['version']);
}
if (array_key_exists('default_action', $params)) {
$this->setDefaultAction($params['default_action']);
}
}

public function jsonSerialize(): array {
Expand All @@ -105,6 +112,7 @@ public function jsonSerialize(): array {
'icon' => $this->getIcon(),
'action_handler' => $this->getActionHandler(),
'version' => $this->getVersion(),
'default_action' => $this->getDefaultAction(),
];
}
}
40 changes: 40 additions & 0 deletions lib/Migration/Version035000Date20260518120000.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\AppAPI\Migration;

use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\Attributes\AddColumn;
use OCP\Migration\Attributes\ColumnType;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;

#[AddColumn('ex_ui_files_actions', 'default_action', ColumnType::STRING, 'DefaultType for ExApp file actions')]
class Version035000Date20260518120000 extends SimpleMigrationStep {
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();

if ($schema->hasTable('ex_ui_files_actions')) {
$table = $schema->getTable('ex_ui_files_actions');

if (!$table->hasColumn('default_action')) {
$table->addColumn('default_action', Types::STRING, [
'notnull' => false,
'length' => 16,
'default' => null,
]);
}
}

return $schema;
}
}
37 changes: 18 additions & 19 deletions lib/Service/UI/FilesActionsMenuService.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,39 +44,38 @@ public function __construct(
* @param int $permissions
* @param int $order
* @param string $version
* @param string|null $defaultAction
* @return FilesActionsMenu|null
*/
public function registerFileActionMenu(string $appId, string $name, string $displayName, string $actionHandler,
string $icon, string $mime, int $permissions, int $order, string $version): ?FilesActionsMenu {
string $icon, string $mime, int $permissions, int $order, string $version,
?string $defaultAction = null): ?FilesActionsMenu {
try {
$fileActionMenu = $this->mapper->findByAppidName($appId, $name);
$existing = $this->mapper->findByAppidName($appId, $name);
} catch (DoesNotExistException|MultipleObjectsReturnedException|Exception) {
$fileActionMenu = null;
$existing = null;
}
try {
$newFileActionMenu = new FilesActionsMenu([
'appid' => $appId,
'name' => $name,
'display_name' => $displayName,
'action_handler' => ltrim($actionHandler, '/'),
'icon' => ltrim($icon, '/'),
'mime' => $mime,
'permissions' => $permissions,
'order' => $order,
'version' => $version,
]);
if ($fileActionMenu !== null) {
$newFileActionMenu->setId($fileActionMenu->getId());
}
$fileActionMenu = $this->mapper->insertOrUpdate($newFileActionMenu);
$entity = $existing ?? new FilesActionsMenu();
$entity->setAppid($appId);
$entity->setName($name);
$entity->setDisplayName($displayName);
$entity->setActionHandler(ltrim($actionHandler, '/'));
$entity->setIcon(ltrim($icon, '/'));
$entity->setMime($mime);
$entity->setPermissions((string)$permissions);
$entity->setOrder($order);
$entity->setVersion($version);
$entity->setDefaultAction($defaultAction);
$result = $existing === null ? $this->mapper->insert($entity) : $this->mapper->update($entity);
$this->resetCacheEnabled();
Comment thread
jvdp marked this conversation as resolved.
return $result;
} catch (Exception $e) {
$this->logger->error(
sprintf('Failed to register ExApp %s FileActionMenu %s. Error: %s', $appId, $name, $e->getMessage()), ['exception' => $e]
);
return null;
}
return $fileActionMenu;
}

public function unregisterFileActionMenu(string $appId, string $name): ?FilesActionsMenu {
Expand Down
2 changes: 2 additions & 0 deletions src/filesplugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ export function registerFileAction33(fileAction, iconProvider) {
execBatch: async ({ nodes }) => execBatch(nodes),
}

action.default = fileAction.default_action

registerFileAction(action)
}

Expand Down
38 changes: 38 additions & 0 deletions src/filesplugin.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,44 @@ describe('registerFileAction33', () => {
expect(action.enabled({ nodes: [mockNode] })).toBe(false)
})

it('does not set default when default_action is missing', () => {
const action = getRegisteredAction({
appid: 'testapp',
action_handler: 'handler',
name: 'test',
display_name: 'Test',
mime: 'image/jpeg',
order: 0,
})
expect(action.default).toBeUndefined()
})

it('passes through default_action="default"', () => {
const action = getRegisteredAction({
appid: 'testapp',
action_handler: 'handler',
name: 'test',
display_name: 'Test',
mime: 'image/jpeg',
order: 0,
default_action: 'default',
})
expect(action.default).toBe('default')
})

it('passes through default_action="hidden"', () => {
const action = getRegisteredAction({
appid: 'testapp',
action_handler: 'handler',
name: 'test',
display_name: 'Test',
mime: 'image/jpeg',
order: 0,
default_action: 'hidden',
})
expect(action.default).toBe('hidden')
})

it('enabled returns false for empty nodes', () => {
const action = getRegisteredAction({
appid: 'testapp',
Expand Down
33 changes: 33 additions & 0 deletions tests/php/Controller/OCSUiControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use OCA\AppAPI\Service\UI\StylesService;
use OCA\AppAPI\Service\UI\TopMenuService;
use OCP\AppFramework\Http;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCS\OCSNotFoundException;
use OCP\IRequest;
use OCP\Server;
Expand Down Expand Up @@ -193,6 +194,38 @@ public function testFileActionsV2HasV2Version(): void {
self::assertSame('image', $data['mime']);
self::assertSame('31', (string)$data['permissions']);
self::assertSame('2.0', $data['version']);
self::assertNull($data['default_action']);
}

public function testFileActionsV2DefaultActionRoundTrip(): void {
$this->controller->registerFileActionMenuV2(
name: 'ui_action_v2', displayName: 'V2 Action', actionHandler: '/handler2',
icon: '', mime: 'image', permissions: 31, order: 0, defaultAction: 'default',
);
$data = self::asArray($this->controller->getFileActionMenu('ui_action_v2')->getData());
self::assertSame('default', $data['default_action']);

$this->controller->registerFileActionMenuV2(
name: 'ui_action_v2', displayName: 'V2 Action', actionHandler: '/handler2',
icon: '', mime: 'image', permissions: 31, order: 0, defaultAction: 'hidden',
);
$data = self::asArray($this->controller->getFileActionMenu('ui_action_v2')->getData());
self::assertSame('hidden', $data['default_action']);

$this->controller->registerFileActionMenuV2(
name: 'ui_action_v2', displayName: 'V2 Action', actionHandler: '/handler2',
icon: '', mime: 'image', permissions: 31, order: 0, defaultAction: '',
);
$data = self::asArray($this->controller->getFileActionMenu('ui_action_v2')->getData());
self::assertNull($data['default_action']);
}

public function testFileActionsV2RejectsUnknownDefaultAction(): void {
$this->expectException(OCSBadRequestException::class);
$this->controller->registerFileActionMenuV2(
name: 'ui_action_v2', displayName: 'V2 Action', actionHandler: '/handler2',
icon: '', mime: 'image', permissions: 31, order: 0, defaultAction: 'bogus',
);
}

public function testInitialStateRoundTrip(): void {
Expand Down
Loading