diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index 143b25c19..04d3ef795 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -95,7 +95,7 @@ class PageController extends AEnvironmentPageAwareController { $this->provideSignerSignatues(); $this->initialState->provideInitialState('identify_methods', $this->identifyMethodService->getIdentifyMethodsSettings()); - $this->initialState->provideInitialState('signature_flow', $this->appConfig->getValueString(Application::APP_ID, 'signature_flow', \OCA\Libresign\Enum\SignatureFlow::PARALLEL->value)); + $this->initialState->provideInitialState('signature_flow', $this->appConfig->getValueString(Application::APP_ID, 'signature_flow', \OCA\Libresign\Enum\SignatureFlow::NONE->value)); $this->initialState->provideInitialState('legal_information', $this->appConfig->getValueString(Application::APP_ID, 'legal_information')); Util::addScript(Application::APP_ID, 'libresign-main'); diff --git a/lib/Controller/RequestSignatureController.php b/lib/Controller/RequestSignatureController.php index e436cfdbe..1557796bd 100644 --- a/lib/Controller/RequestSignatureController.php +++ b/lib/Controller/RequestSignatureController.php @@ -136,6 +136,7 @@ class RequestSignatureController extends AEnvironmentAwareController { * @param LibresignVisibleElement[]|null $visibleElements Visible elements on document * @param LibresignNewFile|array|null $file File object. * @param integer|null $status Numeric code of status * 0 - no signers * 1 - signed * 2 - pending + * @param string|null $signatureFlow Signature flow mode: 'parallel' or 'ordered_numeric'. If not provided, uses global configuration * @return DataResponse|DataResponse}, array{}> * * 200: OK @@ -145,7 +146,14 @@ class RequestSignatureController extends AEnvironmentAwareController { #[NoCSRFRequired] #[RequireManager] #[ApiRoute(verb: 'PATCH', url: '/api/{apiVersion}/request-signature', requirements: ['apiVersion' => '(v1)'])] - public function updateSign(?array $users = [], ?string $uuid = null, ?array $visibleElements = null, ?array $file = [], ?int $status = null): DataResponse { + public function updateSign( + ?array $users = [], + ?string $uuid = null, + ?array $visibleElements = null, + ?array $file = [], + ?int $status = null, + ?string $signatureFlow = null, + ): DataResponse { $user = $this->userSession->getUser(); $data = [ 'uuid' => $uuid, @@ -153,7 +161,8 @@ class RequestSignatureController extends AEnvironmentAwareController { 'users' => $users, 'userManager' => $user, 'status' => $status, - 'visibleElements' => $visibleElements + 'visibleElements' => $visibleElements, + 'signatureFlow' => $signatureFlow, ]; try { $this->validateHelper->validateExistingFile($data); diff --git a/lib/Db/File.php b/lib/Db/File.php index 07c112f51..04b93a1c2 100644 --- a/lib/Db/File.php +++ b/lib/Db/File.php @@ -57,7 +57,7 @@ class File extends Entity { protected ?string $callback = null; protected ?array $metadata = null; protected int $modificationStatus = 0; - protected int $signatureFlow = SignatureFlow::NUMERIC_PARALLEL; + protected int $signatureFlow = SignatureFlow::NUMERIC_NONE; protected int $docmdpLevel = 0; public const STATUS_NOT_LIBRESIGN_FILE = -1; public const STATUS_DRAFT = 0; diff --git a/lib/Enum/SignatureFlow.php b/lib/Enum/SignatureFlow.php index 71d31ba53..f0305c437 100644 --- a/lib/Enum/SignatureFlow.php +++ b/lib/Enum/SignatureFlow.php @@ -13,14 +13,17 @@ namespace OCA\Libresign\Enum; * Signature flow modes */ enum SignatureFlow: string { + case NONE = 'none'; case PARALLEL = 'parallel'; case ORDERED_NUMERIC = 'ordered_numeric'; + public const NUMERIC_NONE = 0; public const NUMERIC_PARALLEL = 1; public const NUMERIC_ORDERED_NUMERIC = 2; public function toNumeric(): int { return match($this) { + self::NONE => self::NUMERIC_NONE, self::PARALLEL => self::NUMERIC_PARALLEL, self::ORDERED_NUMERIC => self::NUMERIC_ORDERED_NUMERIC, }; @@ -28,6 +31,7 @@ enum SignatureFlow: string { public static function fromNumeric(int $value): self { return match($value) { + self::NUMERIC_NONE => self::NONE, self::NUMERIC_PARALLEL => self::PARALLEL, self::NUMERIC_ORDERED_NUMERIC => self::ORDERED_NUMERIC, default => throw new \ValueError("Invalid numeric value for SignatureFlow: $value"), diff --git a/lib/Files/TemplateLoader.php b/lib/Files/TemplateLoader.php index 10820b53d..3868b0e5a 100644 --- a/lib/Files/TemplateLoader.php +++ b/lib/Files/TemplateLoader.php @@ -60,7 +60,7 @@ class TemplateLoader implements IEventListener { $this->initialState->provideInitialState( 'signature_flow', - $this->appConfig->getValueString(Application::APP_ID, 'signature_flow', \OCA\Libresign\Enum\SignatureFlow::PARALLEL->value) + $this->appConfig->getValueString(Application::APP_ID, 'signature_flow', \OCA\Libresign\Enum\SignatureFlow::NONE->value) ); try { diff --git a/lib/Handler/CertificateEngine/AEngineHandler.php b/lib/Handler/CertificateEngine/AEngineHandler.php index 99faa7ed3..5854a11d0 100644 --- a/lib/Handler/CertificateEngine/AEngineHandler.php +++ b/lib/Handler/CertificateEngine/AEngineHandler.php @@ -363,8 +363,8 @@ abstract class AEngineHandler implements IEngineHandler { } $pkiDirName = $this->caIdentifierService->generatePkiDirectoryName($caId); $dataDir = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data/'); - $instanceId = $this->config->getSystemValue('instanceid'); - $pkiPath = $dataDir . '/appdata_' . $instanceId . '/libresign/' . $pkiDirName; + $systemInstanceId = $this->config->getSystemValue('instanceid'); + $pkiPath = $dataDir . '/appdata_' . $systemInstanceId . '/libresign/' . $pkiDirName; if (!is_dir($pkiPath)) { $this->createDirectoryWithCorrectOwnership($pkiPath); diff --git a/lib/Migration/Version15000Date20251209000000.php b/lib/Migration/Version15000Date20251209000000.php index d68f3947e..fd183c665 100644 --- a/lib/Migration/Version15000Date20251209000000.php +++ b/lib/Migration/Version15000Date20251209000000.php @@ -62,8 +62,8 @@ class Version15000Date20251209000000 extends SimpleMigrationStep { if (!$tableFile->hasColumn('signature_flow')) { $tableFile->addColumn('signature_flow', Types::SMALLINT, [ 'notnull' => true, - 'default' => SignatureFlow::NUMERIC_PARALLEL, - 'comment' => 'Signature flow mode: 1=parallel, 2=ordered_numeric', + 'default' => SignatureFlow::NUMERIC_NONE, + 'comment' => 'Signature flow mode: 0=none (no admin enforcement), 1=parallel, 2=ordered_numeric', ]); } } diff --git a/lib/Service/RequestSignatureService.php b/lib/Service/RequestSignatureService.php index 3308a1540..aee7d3bb1 100644 --- a/lib/Service/RequestSignatureService.php +++ b/lib/Service/RequestSignatureService.php @@ -79,6 +79,7 @@ class RequestSignatureService { public function saveFile(array $data): FileEntity { if (!empty($data['uuid'])) { $file = $this->fileMapper->getByUuid($data['uuid']); + $this->updateSignatureFlowIfAllowed($file, $data); return $this->fileStatusService->updateFileStatusIfUpgrade($file, $data['status'] ?? 0); } $fileId = null; @@ -90,6 +91,7 @@ class RequestSignatureService { if (!is_null($fileId)) { try { $file = $this->fileMapper->getByFileId($fileId); + $this->updateSignatureFlowIfAllowed($file, $data); return $this->fileStatusService->updateFileStatusIfUpgrade($file, $data['status'] ?? 0); } catch (\Throwable) { } @@ -118,27 +120,45 @@ class RequestSignatureService { $file->setStatus(FileEntity::STATUS_ABLE_TO_SIGN); } - if (isset($data['signatureFlow']) && is_string($data['signatureFlow'])) { - try { - $signatureFlow = \OCA\Libresign\Enum\SignatureFlow::from($data['signatureFlow']); - $file->setSignatureFlowEnum($signatureFlow); - } catch (\ValueError) { - $this->setSignatureFlowFromGlobalConfig($file); - } - } else { - $this->setSignatureFlowFromGlobalConfig($file); - } - + $this->setSignatureFlow($file, $data); $this->setDocMdpLevelFromGlobalConfig($file); $this->fileMapper->insert($file); return $file; } - private function setSignatureFlowFromGlobalConfig(FileEntity $file): void { - $globalFlowValue = $this->appConfig->getValueString(Application::APP_ID, 'signature_flow', SignatureFlow::PARALLEL->value); - $globalFlow = SignatureFlow::from($globalFlowValue); - $file->setSignatureFlowEnum($globalFlow); + private function updateSignatureFlowIfAllowed(FileEntity $file, array $data): void { + $adminFlow = $this->appConfig->getValueString(Application::APP_ID, 'signature_flow', SignatureFlow::NONE->value); + $adminForcedConfig = $adminFlow !== SignatureFlow::NONE->value; + + if ($adminForcedConfig) { + $adminFlowEnum = SignatureFlow::from($adminFlow); + if ($file->getSignatureFlowEnum() !== $adminFlowEnum) { + $file->setSignatureFlowEnum($adminFlowEnum); + $this->fileMapper->update($file); + } + return; + } + + if (isset($data['signatureFlow']) && !empty($data['signatureFlow'])) { + $newFlow = SignatureFlow::from($data['signatureFlow']); + if ($file->getSignatureFlowEnum() !== $newFlow) { + $file->setSignatureFlowEnum($newFlow); + $this->fileMapper->update($file); + } + } + } + + private function setSignatureFlow(FileEntity $file, array $data): void { + $adminFlow = $this->appConfig->getValueString(Application::APP_ID, 'signature_flow', SignatureFlow::NONE->value); + + if (isset($data['signatureFlow']) && !empty($data['signatureFlow'])) { + $file->setSignatureFlowEnum(SignatureFlow::from($data['signatureFlow'])); + } elseif ($adminFlow !== SignatureFlow::NONE->value) { + $file->setSignatureFlowEnum(SignatureFlow::from($adminFlow)); + } else { + $file->setSignatureFlowEnum(SignatureFlow::NONE); + } } private function setDocMdpLevelFromGlobalConfig(FileEntity $file): void { diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php index cd02c3bfe..68fe96018 100644 --- a/lib/Settings/Admin.php +++ b/lib/Settings/Admin.php @@ -79,7 +79,7 @@ class Admin implements ISettings { $this->initialState->provideInitialState('tsa_username', $this->appConfig->getValueString(Application::APP_ID, 'tsa_username', '')); $this->initialState->provideInitialState('tsa_password', $this->appConfig->getValueString(Application::APP_ID, 'tsa_password', self::PASSWORD_PLACEHOLDER)); $this->initialState->provideInitialState('docmdp_config', $this->docMdpConfigService->getConfig()); - $this->initialState->provideInitialState('signature_flow', $this->appConfig->getValueString(Application::APP_ID, 'signature_flow', '')); + $this->initialState->provideInitialState('signature_flow', $this->appConfig->getValueString(Application::APP_ID, 'signature_flow', \OCA\Libresign\Enum\SignatureFlow::NONE->value)); return new TemplateResponse(Application::APP_ID, 'admin_settings'); } diff --git a/openapi-full.json b/openapi-full.json index 4c065cb32..7e85c0293 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -6679,6 +6679,11 @@ "format": "int64", "nullable": true, "description": "Numeric code of status * 0 - no signers * 1 - signed * 2 - pending" + }, + "signatureFlow": { + "type": "string", + "nullable": true, + "description": "Signature flow mode: 'parallel' or 'ordered_numeric'. If not provided, uses global configuration" } } } diff --git a/openapi.json b/openapi.json index 0eb99d630..817312841 100644 --- a/openapi.json +++ b/openapi.json @@ -6529,6 +6529,11 @@ "format": "int64", "nullable": true, "description": "Numeric code of status * 0 - no signers * 1 - signed * 2 - pending" + }, + "signatureFlow": { + "type": "string", + "nullable": true, + "description": "Signature flow mode: 'parallel' or 'ordered_numeric'. If not provided, uses global configuration" } } } diff --git a/src/Components/RightSidebar/RequestSignatureTab.vue b/src/Components/RightSidebar/RequestSignatureTab.vue index b3327bbae..0b1fcfc33 100644 --- a/src/Components/RightSidebar/RequestSignatureTab.vue +++ b/src/Components/RightSidebar/RequestSignatureTab.vue @@ -15,6 +15,20 @@ @click="addSigner"> {{ t('libresign', 'Add signer') }} + + {{ t('libresign', 'Preserve signing order') }} + + + + {{ t('libresign', 'View signing order') }} + + + + + diff --git a/src/store/files.js b/src/store/files.js index c9efae283..913d12142 100644 --- a/src/store/files.js +++ b/src/store/files.js @@ -381,8 +381,15 @@ export const useFilesStore = function(...args) { filesSorted() { return this.ordered.map(key => this.files[key]) }, - async saveWithVisibleElements({ visibleElements = [], signers = null, uuid = null, nodeId = null }) { + async saveWithVisibleElements({ visibleElements = [], signers = null, uuid = null, nodeId = null, signatureFlow = null }) { const file = this.getFile() + + let flowValue = signatureFlow || file.signatureFlow + if (typeof flowValue === 'number') { + const flowMap = { 0: 'none', 1: 'parallel', 2: 'ordered_numeric' } + flowValue = flowMap[flowValue] || 'parallel' + } + const config = { url: generateOcsUrl('/apps/libresign/api/v1/request-signature'), method: uuid || file.uuid ? 'patch' : 'post', @@ -391,10 +398,12 @@ export const useFilesStore = function(...args) { users: signers || file.signers, visibleElements, status: 0, + signatureFlow: flowValue, }, } - if (uuid || file.uuid) { + + if (uuid || file.uuid) { config.data.uuid = uuid || file.uuid } else { config.data.file = { @@ -406,8 +415,15 @@ export const useFilesStore = function(...args) { this.addFile(data.ocs.data.data) return data.ocs.data }, - async updateSignatureRequest({ visibleElements = [], signers = null, uuid = null, nodeId = null, status = 1 }) { + async updateSignatureRequest({ visibleElements = [], signers = null, uuid = null, nodeId = null, status = 1, signatureFlow = null }) { const file = this.getFile() + + let flowValue = signatureFlow || file.signatureFlow + if (typeof flowValue === 'number') { + const flowMap = { 0: 'none', 1: 'parallel', 2: 'ordered_numeric' } + flowValue = flowMap[flowValue] || 'parallel' + } + const config = { url: generateOcsUrl('/apps/libresign/api/v1/request-signature'), method: uuid || file.uuid ? 'patch' : 'post', @@ -416,6 +432,7 @@ export const useFilesStore = function(...args) { users: signers || file.signers, visibleElements, status, + signatureFlow: flowValue, }, } @@ -426,7 +443,6 @@ export const useFilesStore = function(...args) { fileId: nodeId || this.selectedNodeId, } } - const { data } = await axios(config) this.addFile(data.ocs.data.data) return data.ocs.data diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index f06878022..eaa8ee4e7 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -4106,6 +4106,8 @@ export interface operations { * @description Numeric code of status * 0 - no signers * 1 - signed * 2 - pending */ status?: number | null; + /** @description Signature flow mode: 'parallel' or 'ordered_numeric'. If not provided, uses global configuration */ + signatureFlow?: string | null; }; }; }; diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts index 8e0923626..bdbee18ff 100644 --- a/src/types/openapi/openapi.ts +++ b/src/types/openapi/openapi.ts @@ -3628,6 +3628,8 @@ export interface operations { * @description Numeric code of status * 0 - no signers * 1 - signed * 2 - pending */ status?: number | null; + /** @description Signature flow mode: 'parallel' or 'ordered_numeric'. If not provided, uses global configuration */ + signatureFlow?: string | null; }; }; }; diff --git a/src/views/Settings/SignatureFlow.vue b/src/views/Settings/SignatureFlow.vue index ec981e575..caafeb155 100644 --- a/src/views/Settings/SignatureFlow.vue +++ b/src/views/Settings/SignatureFlow.vue @@ -104,9 +104,9 @@ export default { methods: { loadConfig() { try { - const mode = loadState('libresign', 'signature_flow', null) + const mode = loadState('libresign', 'signature_flow', 'none') - if (mode === null || mode === '') { + if (mode === 'none') { this.enabled = false this.selectedFlow = this.availableFlows[0] } else { diff --git a/tests/php/Unit/Enum/SignatureFlowTest.php b/tests/php/Unit/Enum/SignatureFlowTest.php index b3bc8ed99..8f321835c 100644 --- a/tests/php/Unit/Enum/SignatureFlowTest.php +++ b/tests/php/Unit/Enum/SignatureFlowTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\Attributes\DataProvider; final class SignatureFlowTest extends TestCase { public static function validFlowProvider(): array { return [ + 'none' => [SignatureFlow::NONE, SignatureFlow::NUMERIC_NONE, 'none'], 'parallel' => [SignatureFlow::PARALLEL, SignatureFlow::NUMERIC_PARALLEL, 'parallel'], 'ordered_numeric' => [SignatureFlow::ORDERED_NUMERIC, SignatureFlow::NUMERIC_ORDERED_NUMERIC, 'ordered_numeric'], ]; @@ -29,7 +30,6 @@ final class SignatureFlowTest extends TestCase { public static function invalidNumericProvider(): array { return [ - 'zero' => [0], 'negative' => [-1], 'three' => [3], 'large' => [999],