Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
PendingSignaturesWidget
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 14
462
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTitle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOrder
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getIconClass
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getIconUrl
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getUrl
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 load
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isEnabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getItemsV2
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
42
 getSubtitle
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getTimestamp
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getItems
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getWidgetButtons
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3declare(strict_types=1);
4
5/**
6 * SPDX-FileCopyrightText: 2025 LibreSign
7 * SPDX-License-Identifier: AGPL-3.0-or-later
8 */
9
10namespace OCA\Libresign\Dashboard;
11
12use OCA\Libresign\AppInfo\Application;
13use OCA\Libresign\Db\File;
14use OCA\Libresign\Db\SignRequest;
15use OCA\Libresign\Db\SignRequestMapper;
16use OCA\Libresign\Enum\FileStatus;
17use OCA\Libresign\Handler\CertificateEngine\CertificateEngineFactory;
18use OCP\Dashboard\IAPIWidget;
19use OCP\Dashboard\IAPIWidgetV2;
20use OCP\Dashboard\IButtonWidget;
21use OCP\Dashboard\IConditionalWidget;
22use OCP\Dashboard\IIconWidget;
23use OCP\Dashboard\Model\WidgetButton;
24use OCP\Dashboard\Model\WidgetItem;
25use OCP\Dashboard\Model\WidgetItems;
26use OCP\IL10N;
27use OCP\IURLGenerator;
28use OCP\IUserSession;
29use Override;
30
31class PendingSignaturesWidget implements IAPIWidget, IAPIWidgetV2, IButtonWidget, IConditionalWidget, IIconWidget {
32    public function __construct(
33        private IL10N $l10n,
34        private IURLGenerator $urlGenerator,
35        private SignRequestMapper $signRequestMapper,
36        private IUserSession $userSession,
37        private CertificateEngineFactory $certificateEngineFactory,
38    ) {
39    }
40
41    #[Override]
42    public function getId(): string {
43        return 'libresign_pending_signatures';
44    }
45
46    #[Override]
47    public function getTitle(): string {
48        // TRANSLATORS Dashboard widget title listing documents still waiting for the current user's signature.
49        return $this->l10n->t('Pending signatures');
50    }
51
52    #[Override]
53    public function getOrder(): int {
54        return 10;
55    }
56
57    #[Override]
58    public function getIconClass(): string {
59        return 'icon-libresign';
60    }
61
62    #[Override]
63    public function getIconUrl(): string {
64        return $this->urlGenerator->getAbsoluteURL(
65            $this->urlGenerator->imagePath(Application::APP_ID, 'app-dark.svg')
66        );
67    }
68
69    #[Override]
70    public function getUrl(): ?string {
71        return $this->urlGenerator->linkToRouteAbsolute('libresign.page.index');
72    }
73
74    #[Override]
75    public function load(): void {
76        // No special loading required
77    }
78
79    #[Override]
80    public function isEnabled(): bool {
81        return $this->certificateEngineFactory->getEngine()->isSetupOk();
82    }
83
84    #[Override]
85    public function getItemsV2(string $userId, ?string $since = null, int $limit = 7): WidgetItems {
86        $user = $this->userSession->getUser();
87        if (!$user) {
88            // TRANSLATORS Dashboard error shown when the logged-in account cannot be resolved.
89            return new WidgetItems([], $this->l10n->t('User not found'));
90        }
91
92        $result = $this->signRequestMapper->getFilesAssociatedFilesWithMe(
93            $user,
94            ['status' => [FileStatus::ABLE_TO_SIGN->value, FileStatus::PARTIAL_SIGNED->value]],
95            1,
96            $limit,
97            ['sortBy' => 'created_at', 'sortDirection' => 'desc']
98        );
99
100        $items = [];
101
102        foreach ($result['data'] as $fileEntity) {
103            $signRequests = $this->signRequestMapper->getByFileId($fileEntity->getId());
104
105            foreach ($signRequests as $signRequest) {
106                if ($signRequest->getSigned()) {
107                    continue;
108                }
109
110                $item = new WidgetItem(
111                    $fileEntity->getName(),
112                    $this->getSubtitle($signRequest, $fileEntity),
113                    $this->urlGenerator->linkToRouteAbsolute('libresign.page.signFPath', ['uuid' => $signRequest->getUuid(), 'path' => 'pdf']),
114                    $this->urlGenerator->getAbsoluteURL(
115                        $this->urlGenerator->imagePath('core', 'filetypes/application-pdf.svg')
116                    ),
117                    $this->getTimestamp($fileEntity)
118                );
119
120                $items[] = $item;
121            }
122        }
123
124        return new WidgetItems(
125            $items,
126            // TRANSLATORS Empty dashboard state shown when there are no documents waiting for the current user's signature.
127            empty($items) ? $this->l10n->t('No pending signatures') : '',
128        );
129    }
130
131    private function getSubtitle(SignRequest $signRequest, File $fileEntity): string {
132        $displayName = $signRequest->getDisplayName();
133        $createdAt = $fileEntity->getCreatedAt();
134        if ($createdAt instanceof \DateTime) {
135            $date = $createdAt->format('d/m/Y');
136            // TRANSLATORS Subtitle in pending-signature widget. %1$s is the requester name and %2$s is the request creation date.
137            return $this->l10n->t('From: %1$s • Date: %2$s', [$displayName, $date]);
138        }
139        // TRANSLATORS Subtitle in pending-signature widget. %s is the requester name.
140        return $this->l10n->t('From: %s', [$displayName]);
141    }
142
143    private function getTimestamp(File $fileEntity): string {
144        $createdAt = $fileEntity->getCreatedAt();
145        if ($createdAt instanceof \DateTime) {
146            return (string)$createdAt->getTimestamp();
147        }
148        return '';
149    }
150
151    #[Override]
152    public function getItems(string $userId, ?string $since = null, int $limit = 7): array {
153        $widgetItems = $this->getItemsV2($userId, $since, $limit);
154        return array_values($widgetItems->getItems());
155    }
156
157    #[Override]
158    public function getWidgetButtons(string $userId): array {
159        return [
160            new WidgetButton(
161                WidgetButton::TYPE_MORE,
162                $this->urlGenerator->linkToRouteAbsolute('libresign.page.index'),
163                // TRANSLATORS Dashboard button that opens the full list of documents related to signatures.
164                $this->l10n->t('View all documents')
165            ),
166        ];
167    }
168}