Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
350a41e
feat(files-app-integration): show static signature icon on files
AntDavidLima May 16, 2025
8df8a4a
feat(files-app-integration): conditionally show signature icon
AntDavidLima May 16, 2025
902d361
feat(files-app-integration): create end-point to get file status
AntDavidLima May 19, 2025
aea64b6
chore(files-app-integration): fix problems to pass failing checks
AntDavidLima May 21, 2025
0b1b9df
feat(file-list): adapt /file/list end-point to receive a list of node…
AntDavidLima May 21, 2025
25ecd28
chore(file-list): adapt /file/list utilization to use node ids as list
AntDavidLima May 21, 2025
a56095d
chore(files-app-integration): remove unecessary controller and status
AntDavidLima May 21, 2025
b54d1c0
feat(files-app-integration): use dynamic status icons and labels
AntDavidLima May 22, 2025
d72dc09
feat(files-app-integration): show status on signed and original files
AntDavidLima May 22, 2025
5e2278f
chore(files-app-integration): fix copyright text
AntDavidLima May 23, 2025
d78edbd
refactor(files-app-integration): remove unnecessary if condition
AntDavidLima May 23, 2025
3d90513
docs(files-app-integration): update openapi documentation
AntDavidLima May 23, 2025
6494d69
fix(app-files-integration): add signed_node_id to group by
AntDavidLima May 26, 2025
e93cbb1
feat(files-app-integration): create and register stub plugin
AntDavidLima Jul 15, 2025
c77cc1d
chore: add phpactor config file to .gitignore
AntDavidLima Jul 16, 2025
0056a1c
feat(files-app-integration): inject signature status and signed node …
AntDavidLima Jul 19, 2025
cb5bda7
feat(files-app-integration): change action icon and tooltip text base…
AntDavidLima Jul 19, 2025
4a0f15f
docs(reuse): add licensing information to new file
AntDavidLima Jul 21, 2025
b0a970d
style: fix js lint problems
AntDavidLima Jul 21, 2025
66cbaec
chore(psalm): update baseline
AntDavidLima Jul 21, 2025
b10bda8
chore: remove phpactor config file
AntDavidLima Jul 21, 2025
7eaf235
chore: change types property position on info.xml
AntDavidLima Jul 21, 2025
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
.idea
.vscode
.phpactor*
.env
.secrets
*.iml
Expand Down
8 changes: 8 additions & 0 deletions appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ Developed with ❤️ by [LibreCode](https://librecode.coop). Help us transform
<version>12.0.0-dev.1</version>
<licence>agpl</licence>
<author mail="[email protected]" homepage="https://librecode.coop">LibreCode</author>
<types>
<dav/>
</types>
<documentation>
<admin>https://github.com/LibreSign/libresign/blob/master/README.md</admin>
</documentation>
Expand Down Expand Up @@ -90,4 +93,9 @@ Developed with ❤️ by [LibreCode](https://librecode.coop). Help us transform
<route>libresign.page.index</route>
</navigation>
</navigations>
<sabre>
<plugins>
<plugin>OCA\Libresign\Dav\SignatureStatusPlugin</plugin>
</plugins>
</sabre>
</info>
6 changes: 3 additions & 3 deletions lib/Controller/FileController.php
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ private function validate(?string $type = null, $identifier = null): DataRespons
* List account files that need to be approved
*
* @param string|null $signer_uuid Signer UUID
* @param string|null $nodeId The nodeId (also called fileId). Is the id of a file at Nextcloud
* @param list<string>|null $nodeIds The list of nodeIds (also called fileIds). It's the ids of files at Nextcloud
* @param list<int>|null $status Status could be none or many of 0 = draft, 1 = able to sign, 2 = partial signed, 3 = signed, 4 = deleted.
* @param int|null $page the number of page to return
* @param int|null $length Total of elements to return
Expand All @@ -255,7 +255,7 @@ public function list(
?int $page = null,
?int $length = null,
?string $signer_uuid = null,
?string $nodeId = null,
?array $nodeIds = null,
?array $status = null,
?int $start = null,
?int $end = null,
Expand All @@ -264,7 +264,7 @@ public function list(
): DataResponse {
$filter = array_filter([
'signer_uuid' => $signer_uuid,
'nodeId' => $nodeId,
'nodeIds' => $nodeIds,
'status' => $status,
'start' => $start,
'end' => $end,
Expand Down
39 changes: 39 additions & 0 deletions lib/Dav/SignatureStatusPlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

/**
* SPDX-FileCopyrightText: 2025 LibreCode coop and contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Libresign\Dav;

use OC;
use OCA\DAV\Connector\Sabre\File;
use OCA\Libresign\Service\FileService;
use Sabre\DAV\INode;
use Sabre\DAV\PropFind;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;

class SignatureStatusPlugin extends ServerPlugin {
protected $server;

public function initialize(Server $server): void {
$this->server = $server;
$server->on('propFind', [$this, 'propFind']);
}

public function propFind(PropFind $propFind, INode $node): void {
if ($node instanceof File) {
$fileService = OC::$server->get(FileService::class);
$nodeId = $node->getId();

if ($fileService->isLibresignFile($nodeId)) {
$fileService->setFileByType('FileId', $nodeId);

$propFind->handle('{http://nextcloud.org/ns}signature-status', $fileService->getStatus());
$propFind->handle('{http://nextcloud.org/ns}signed-node-id', $fileService->getSignedNodeId());
}
}
}
}
24 changes: 24 additions & 0 deletions lib/Db/FileMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,30 @@ public function getByFileId(?int $nodeId = null): File {
return $file;
}

/**
* Check if file exists
*/
public function fileIdExists(int $nodeId): bool {
$exists = array_filter($this->file, fn ($f) => $f->getNodeId() === $nodeId || $f->getSignedNodeId() === $nodeId);
if (!empty($exists)) {
return true;
}

$qb = $this->db->getQueryBuilder();

$qb->select('id')
->from($this->getTableName())
->where(
$qb->expr()->orX(
$qb->expr()->eq('node_id', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT)),
$qb->expr()->eq('signed_node_id', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT))
)
);

$files = $this->findEntities($qb);
return !empty($files);
}

/**
* @return File[]
*/
Expand Down
8 changes: 6 additions & 2 deletions lib/Db/SignRequestMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ private function getFilesAssociatedFilesWithMeQueryBuilder(string $userId, array
$qb->select(
'f.id',
'f.node_id',
'f.signed_node_id',
'f.user_id',
'f.uuid',
'f.name',
Expand All @@ -443,6 +444,7 @@ private function getFilesAssociatedFilesWithMeQueryBuilder(string $userId, array
->groupBy(
'f.id',
'f.node_id',
'f.signed_node_id',
'f.user_id',
'f.uuid',
'f.name',
Expand Down Expand Up @@ -482,9 +484,9 @@ private function getFilesAssociatedFilesWithMeQueryBuilder(string $userId, array
$qb->expr()->eq('sr.uuid', $qb->createNamedParameter($filter['signer_uuid']))
);
}
if (!empty($filter['nodeId'])) {
if (!empty($filter['nodeIds'])) {
$qb->andWhere(
$qb->expr()->eq('f.node_id', $qb->createNamedParameter($filter['nodeId'], IQueryBuilder::PARAM_INT))
$qb->expr()->in('f.node_id', $qb->createNamedParameter($filter['nodeIds'], IQueryBuilder::PARAM_STR_ARRAY))
);
}
if (!empty($filter['status'])) {
Expand Down Expand Up @@ -544,6 +546,7 @@ private function formatListRow(array $row): array {
$row['status'] = (int)$row['status'];
$row['statusText'] = $this->fileMapper->getTextOfStatus($row['status']);
$row['nodeId'] = (int)$row['node_id'];
$row['signedNodeId'] = (int)$row['signed_node_id'];
$row['requested_by'] = [
'userId' => $row['user_id'],
'displayName' => $this->userManager->get($row['user_id'])?->getDisplayName(),
Expand All @@ -554,6 +557,7 @@ private function formatListRow(array $row): array {
unset(
$row['user_id'],
$row['node_id'],
$row['signed_node_id'],
);
return $row;
}
Expand Down
1 change: 1 addition & 0 deletions lib/ResponseDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@
* file: array{
* type: string,
* nodeId: non-negative-int,
* signedNodeId: non-negative-int,
* url: string,
* },
* callback: ?string,
Expand Down
21 changes: 21 additions & 0 deletions lib/Service/FileService.php
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,19 @@ private function getFile(): \OCP\Files\File {
return current($fileToValidate);
}

public function getStatus(): int {
return $this->file->getStatus();
}

public function getSignedNodeId(): ?int {
$status = $this->file->getStatus();

if (!in_array($status, [File::STATUS_PARTIAL_SIGNED, File::STATUS_SIGNED])) {
return null;
}
return $this->file->getSignedNodeId();
}

private function getFileContent(): string {
if ($this->fileContent) {
return $this->fileContent;
Expand All @@ -265,6 +278,14 @@ private function getFileContent(): string {
return '';
}

public function isLibresignFile(int $nodeId): bool {
try {
return $this->fileMapper->fileIdExists($nodeId);
} catch (\Throwable) {
throw new LibresignException($this->l10n->t('Invalid data to validate file'), 404);
}
}

private function loadFileMetadata(): void {
if (!$content = $this->getFileContent()) {
return;
Expand Down
17 changes: 13 additions & 4 deletions openapi-full.json
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@
"required": [
"type",
"nodeId",
"signedNodeId",
"url"
],
"properties": {
Expand All @@ -354,6 +355,11 @@
"format": "int64",
"minimum": 0
},
"signedNodeId": {
"type": "integer",
"format": "int64",
"minimum": 0
},
"url": {
"type": "string"
}
Expand Down Expand Up @@ -4118,12 +4124,15 @@
}
},
{
"name": "nodeId",
"name": "nodeIds[]",
"in": "query",
"description": "The nodeId (also called fileId). Is the id of a file at Nextcloud",
"description": "The list of nodeIds (also called fileIds). It's the ids of files at Nextcloud",
"schema": {
"type": "string",
"nullable": true
"type": "array",
"nullable": true,
"items": {
"type": "string"
}
}
},
{
Expand Down
17 changes: 13 additions & 4 deletions openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@
"required": [
"type",
"nodeId",
"signedNodeId",
"url"
],
"properties": {
Expand All @@ -284,6 +285,11 @@
"format": "int64",
"minimum": 0
},
"signedNodeId": {
"type": "integer",
"format": "int64",
"minimum": 0
},
"url": {
"type": "string"
}
Expand Down Expand Up @@ -4000,12 +4006,15 @@
}
},
{
"name": "nodeId",
"name": "nodeIds[]",
"in": "query",
"description": "The nodeId (also called fileId). Is the id of a file at Nextcloud",
"description": "The list of nodeIds (also called fileIds). It's the ids of files at Nextcloud",
"schema": {
"type": "string",
"nullable": true
"type": "array",
"nullable": true,
"items": {
"type": "string"
}
}
},
{
Expand Down
46 changes: 46 additions & 0 deletions src/actions/showStatusInlineAction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* SPDX-FileCopyrightText: 2025 LibreCode coop and contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { FileAction, registerFileAction } from '@nextcloud/files'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'

import { SIGN_STATUS } from '../domains/sign/enum.js'
import { fileStatus } from '../helpers/fileStatus.js'

const action = new FileAction({
id: 'show-status-inline',
displayName: () => '',
title: (nodes) => {
const node = nodes[0]

const signedNodeId = node.attributes['signed-node-id']

return !signedNodeId || node.fileid === signedNodeId
? fileStatus.find(status => status.id === node.attributes['signature-status']).label
: t('libresign', 'original file')
},
exec: async () => null,
iconSvgInline: (nodes) => {
const node = nodes[0]

const signedNodeId = node.attributes['signed-node-id']

return !signedNodeId || node.fileid === signedNodeId
? fileStatus.find(status => status.id === node.attributes['signature-status']).icon
: fileStatus.find(status => status.id === SIGN_STATUS.ABLE_TO_SIGN).icon
},
inline: () => true,
enabled: (nodes) => {
return loadState('libresign', 'certificate_ok')
&& nodes.length > 0
&& nodes
.map(node => node.mime)
.every(mime => mime === 'application/pdf')
&& nodes.every(node => node.attributes['signature-status'])
},
order: -1,
})

registerFileAction(action)
5 changes: 4 additions & 1 deletion src/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import Vue from 'vue'

import axios from '@nextcloud/axios'
import { addNewFileMenuEntry, Permission } from '@nextcloud/files'
import { addNewFileMenuEntry, Permission, registerDavProperty } from '@nextcloud/files'
import { translate, translatePlural } from '@nextcloud/l10n'
import { generateOcsUrl } from '@nextcloud/router'
import { getUploader } from '@nextcloud/upload'
Expand All @@ -21,6 +21,9 @@ Vue.prototype.n = translatePlural
Vue.prototype.OC = OC
Vue.prototype.OCA = OCA

registerDavProperty('nc:signature-status', { nc: 'http://nextcloud.org/ns' })
registerDavProperty('nc:signed-node-id', { nc: 'http://nextcloud.org/ns' })

addNewFileMenuEntry({
id: 'libresign-request',
displayName: t('libresign', 'New signature request'),
Expand Down
2 changes: 1 addition & 1 deletion src/store/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const useFilesStore = function(...args) {
},
async flushSelectedFile() {
const files = await this.getAllFiles({
nodeId: this.selectedNodeId,
'nodeIds[]': [this.selectedNodeId],
})
this.addFile(files[this.selectedNodeId])
},
Expand Down
1 change: 1 addition & 0 deletions src/tab.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { translate, translatePlural } from '@nextcloud/l10n'
import AppFilesTab from './Components/RightSidebar/AppFilesTab.vue'

import './actions/openInLibreSignAction.js'
import './actions/showStatusInlineAction.js'
import './plugins/vuelidate.js'

import './style/icons.scss'
Expand Down
6 changes: 4 additions & 2 deletions src/types/openapi/openapi-full.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1291,6 +1291,8 @@ export type components = {
type: string;
/** Format: int64 */
nodeId: number;
/** Format: int64 */
signedNodeId: number;
url: string;
};
callback: string | null;
Expand Down Expand Up @@ -2806,8 +2808,8 @@ export interface operations {
length?: number | null;
/** @description Signer UUID */
signer_uuid?: string | null;
/** @description The nodeId (also called fileId). Is the id of a file at Nextcloud */
nodeId?: string | null;
/** @description The list of nodeIds (also called fileIds). It's the ids of files at Nextcloud */
"nodeIds[]"?: string[] | null;
/** @description Status could be none or many of 0 = draft, 1 = able to sign, 2 = partial signed, 3 = signed, 4 = deleted. */
"status[]"?: number[] | null;
/** @description Start date of signature request (UNIX timestamp) */
Expand Down
Loading
Loading