Skip to content
Closed
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
58 changes: 39 additions & 19 deletions lib/AppInfo/SigningEventRegistrar.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Filinq Signing Event Registrar
*
* Wires the signing-related event listeners: the bridge from OpenRegister's
* ApprovalStep events into Filinq's typed Signer* events, and the cross-app
* task-sequence events into Filinq's typed Signer* events, and the cross-app
* delegated-signing request contract. Extracted from `Application`.
*
* @category AppInfo
Expand All @@ -26,45 +26,65 @@
namespace OCA\Filinq\AppInfo;

use OCA\Filinq\Event\DocumentSigningRequestedEvent;
use OCA\Filinq\EventListener\ApprovalStepListener;
use OCA\Filinq\EventListener\DocumentSigningRequestedListener;
use OCA\OpenRegister\Event\ApprovalStepApprovedEvent;
use OCA\OpenRegister\Event\ApprovalStepCompletedEvent;
use OCA\OpenRegister\Event\ApprovalStepInitiatedEvent;
use OCA\OpenRegister\Event\ApprovalStepRejectedEvent;
use OCA\Filinq\EventListener\SigningTaskListener;
use OCP\AppFramework\Bootstrap\IRegistrationContext;

/**
* Registers the approval-step bridge and the cross-app signing-request listener.
* Registers the task-sequence bridge and the cross-app signing-request listener.
*
* @category AppInfo
* @package OCA\Filinq\AppInfo
* @author Conduction B.V. <info@conduction.nl>
* @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
* @link https://www.filinq.app
*
* @spec openspec/specs/signing-via-or-approval-with-provider-plugins/spec.md
*/
class SigningEventRegistrar {

/**
* The OpenRegister task events the signing bridge consumes, as FQN
* string literals on purpose. `::class` on an imported name is a
* compile-time string too, but a literal keeps that true even if
* someone later adds the import — and during our own register() the
* `OCA\OpenRegister\` prefix is not on the autoloader yet, so neither a
* `class_exists()` probe (always false here) nor an eager reference
* (aborts register()) is an option; `BootstrapOrderIndependenceTest`
* pins both rules. Registering for an event class that never comes to
* exist is harmless: the dispatcher keys listeners by name, and the
* name is simply never dispatched. Mapping per openregister#3302
* (flow-approval-consolidation, approval-events-migration.md):
* transitioned-to-enabled replaces the retired step-initiated signal,
* committed terminality replaces step-approved and step-rejected, and
* sequence completion replaces chain completion.
*
* @var array<int, string>
*/
public const TASK_EVENTS = [
'OCA\\OpenRegister\\Event\\TaskTransitionedEvent',
'OCA\\OpenRegister\\Event\\TaskTerminalEvent',
'OCA\\OpenRegister\\Event\\TaskSequenceCompletedEvent',
];

/**
* Register the signing event listeners.
*
* @param IRegistrationContext $context The registration context.
*
* @return void
*
* @spec openspec/specs/document-signing/spec.md
* @spec openspec/specs/signing-via-or-approval-with-provider-plugins/spec.md
*/
public function register(IRegistrationContext $context): void {
// Bridge OR ApprovalStep events into typed filinq Signer*Events
// and invoke the configured SigningProviderInterface when a step
// becomes pending. Per migrate-signing-to-or-approval-workflow
// (D2.1) — OR's `add-approval-step-events` shipped upstream as of
// 2026-06-12 so the four event classes referenced below resolve at
// runtime; if the OR app is absent (degraded install) the listener
// simply never receives the events.
$context->registerEventListener(ApprovalStepInitiatedEvent::class, ApprovalStepListener::class);
$context->registerEventListener(ApprovalStepApprovedEvent::class, ApprovalStepListener::class);
$context->registerEventListener(ApprovalStepRejectedEvent::class, ApprovalStepListener::class);
$context->registerEventListener(ApprovalStepCompletedEvent::class, ApprovalStepListener::class);
// Bridge OR's task-sequence events into typed filinq Signer*Events
// and invoke the configured SigningProviderInterface when a sequence
// position becomes enabled. If the OR app is absent (degraded
// install) or predates the task surface, the listener simply never
// receives the events.
foreach (self::TASK_EVENTS as $taskEvent) {
$context->registerEventListener(event: $taskEvent, listener: SigningTaskListener::class);
}

// Cross-app delegated-signing contract (filinq-signing-events): any
// installed consumer app (e.g. shillinq) dispatches
Expand Down
94 changes: 61 additions & 33 deletions lib/Event/SignerChainCompletedEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
/**
* SignerChainCompletedEvent
*
* Typed filinq-side event fired ONCE per signing-request when the final OR
* approval step is approved — i.e. every signer has signed. Bridges OR's
* `ApprovalStepCompletedEvent`. Internal filinq subscribers (notifications,
* downstream archival, signed-document assembly) react to this event in place
* of polling the legacy `SigningService::updateRequestStatus()` flag.
* Typed filinq-side event fired when an OR task sequence belonging to a
* filinq signing-request completes: the final position completed with an
* approving outcome. Bridges OR's `TaskSequenceCompletedEvent`, which is
* dispatched at exactly that moment. Internal filinq subscribers
* (notifications, artifact production, UI state) react here.
*
* Carries scalars only, on purpose: the payload survives with OpenRegister
* older, newer or absent, which is what lets filinq load on either side of
* openregister#3302 (flow-approval-consolidation).
*
* @category Event
* @package OCA\Filinq\Event
Expand All @@ -20,80 +24,104 @@
* SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>
* SPDX-License-Identifier: EUPL-1.2
*
* @spec openspec/changes/migrate-signing-to-or-approval-workflow/tasks.md#D1-2
* @spec openspec/changes/migrate-signing-to-or-tasks/tasks.md#2-1
*/

declare(strict_types=1);

namespace OCA\Filinq\Event;

use OCA\OpenRegister\Db\ApprovalChain;
use OCA\OpenRegister\Db\ApprovalStep;
use OCP\EventDispatcher\Event;

/**
* Fired once when the OR approval chain backing a sign-request completes.
* Fired when the task sequence of a filinq sign-request completes.
*
* @category Event
* @package OCA\Filinq\Event
* @author Conduction B.V. <info@conduction.nl>
* @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
* @link https://www.filinq.app
*
* @spec openspec/specs/signing-via-or-approval-with-provider-plugins/spec.md
*/
class SignerChainCompletedEvent extends Event {
/**
* Constructor.
*
* @param ApprovalChain $chain The OR approval chain that completed.
* @param ApprovalStep $finalStep The final approved step.
* @param string $userId UID of the user who approved the final step.
* @param string $objectUuid Signing-request UUID.
* @param string $sequenceUuid UUID of the completed OR task sequence.
* @param string $finalTaskUuid UUID of the final position's task.
* @param string|null $userId The identity that decided the final position.
* @param string $statusOnApprove The approving status the frozen
* declaration resolves to.
* @param string $objectUuid UUID of the filinq signing request.
*
* @return void
*
* @spec openspec/specs/signing-via-or-approval-with-provider-plugins/spec.md
*/
public function __construct(
private readonly ApprovalChain $chain,
private readonly ApprovalStep $finalStep,
private readonly string $userId,
private readonly string $sequenceUuid,
private readonly string $finalTaskUuid,
private readonly ?string $userId,
private readonly string $statusOnApprove,
private readonly string $objectUuid,
) {
parent::__construct();

}//end __construct()

/**
* Get the completed chain.
* Get the sequence UUID.
*
* @return ApprovalChain The OR approval chain.
* @return string UUID of the completed OR task sequence.
*
* @spec openspec/specs/signing-via-or-approval-with-provider-plugins/spec.md
*/
public function getChain(): ApprovalChain {
return $this->chain;
}//end getChain()
public function getSequenceUuid(): string {
return $this->sequenceUuid;
}//end getSequenceUuid()

/**
* Get the final approved step.
* Get the final task's UUID.
*
* @return string UUID of the final position's task.
*
* @return ApprovalStep The final OR approval step.
* @spec openspec/specs/signing-via-or-approval-with-provider-plugins/spec.md
*/
public function getFinalStep(): ApprovalStep {
return $this->finalStep;
}//end getFinalStep()
public function getFinalTaskUuid(): string {
return $this->finalTaskUuid;
}//end getFinalTaskUuid()

/**
* Get the UID of the user who approved the final step.
* Get the deciding identity.
*
* @return string|null Who decided the final position, when known.
*
* @return string Nextcloud user ID.
* @spec openspec/specs/signing-via-or-approval-with-provider-plugins/spec.md
*/
public function getUserId(): string {
public function getUserId(): ?string {
return $this->userId;
}//end getUserId()

/**
* Get the filinq signing-request UUID this chain backed.
* Get the resolved approving status.
*
* @return string The `statusOnApprove` the frozen declaration resolves to.
*
* @spec openspec/specs/signing-via-or-approval-with-provider-plugins/spec.md
*/
public function getStatusOnApprove(): string {
return $this->statusOnApprove;
}//end getStatusOnApprove()

/**
* Get the signing-request object UUID.
*
* @return string UUID of the filinq signing request.
*
* @return string Signing-request UUID.
* @spec openspec/specs/signing-via-or-approval-with-provider-plugins/spec.md
*/
public function getSigningRequestUuid(): string {
public function getObjectUuid(): string {
return $this->objectUuid;
}//end getSigningRequestUuid()
}//end getObjectUuid()
}//end class
Loading
Loading