Skip to content

Commit a243de9

Browse files
authored
1.0.0
1 parent 5b23212 commit a243de9

File tree

38 files changed

+1046
-0
lines changed

38 files changed

+1046
-0
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace Espo\Modules\FileSharing\Classes\FieldProcessing\FileSharing;
4+
5+
use Espo\Core\FieldProcessing\Loader;
6+
use Espo\Core\FieldProcessing\Loader\Params;
7+
use Espo\Core\ORM\EntityManager;
8+
use Espo\ORM\Entity;
9+
use Espo\Core\Utils\Config;
10+
11+
class SetEntryPointUrl implements Loader
12+
{
13+
private EntityManager $entityManager;
14+
private Config $config;
15+
16+
public function __construct(EntityManager $entityManager, Config $config)
17+
{
18+
$this->entityManager = $entityManager;
19+
$this->config = $config;
20+
}
21+
22+
public function process(Entity $entity, Params $params): void
23+
{
24+
$id = $entity->getId();
25+
$token = $entity->get('accessToken');
26+
27+
$siteUrl = $this->config->get('siteUrl');
28+
29+
// Add a '/' at the end of the siteUrl if there's none
30+
if (substr($siteUrl, -1) !== '/') {
31+
$siteUrl .= '/';
32+
}
33+
34+
$entryPointUrl = "{$siteUrl}?entryPoint=FileSharing&id={$id}&token={$token}";
35+
36+
$entity->set('entryPointUrl', $entryPointUrl);
37+
}
38+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Espo\Modules\FileSharing\Classes\Select\PrimaryFilters;
4+
5+
use Espo\Core\Select\Primary\Filter;
6+
use Espo\ORM\Query\SelectBuilder;
7+
8+
class Active implements Filter
9+
{
10+
public function apply(SelectBuilder $queryBuilder): void
11+
{
12+
$queryBuilder->where([
13+
'status=' => ['Active']
14+
]);
15+
}
16+
}
17+
?>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Espo\Modules\FileSharing\Classes\Select\PrimaryFilters;
4+
5+
use Espo\Core\Select\Primary\Filter;
6+
use Espo\ORM\Query\SelectBuilder;
7+
8+
class Canceled implements Filter
9+
{
10+
public function apply(SelectBuilder $queryBuilder): void
11+
{
12+
$queryBuilder->where([
13+
'status=' => ['Canceled']
14+
]);
15+
}
16+
}
17+
?>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Espo\Modules\FileSharing\Classes\Select\PrimaryFilters;
4+
5+
use Espo\Core\Select\Primary\Filter;
6+
use Espo\ORM\Query\SelectBuilder;
7+
8+
class Draft implements Filter
9+
{
10+
public function apply(SelectBuilder $queryBuilder): void
11+
{
12+
$queryBuilder->where([
13+
'status=' => ['Draft']
14+
]);
15+
}
16+
}
17+
?>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Espo\Modules\FileSharing\Classes\Select\PrimaryFilters;
4+
5+
use Espo\Core\Select\Primary\Filter;
6+
use Espo\ORM\Query\SelectBuilder;
7+
8+
class Expired implements Filter
9+
{
10+
public function apply(SelectBuilder $queryBuilder): void
11+
{
12+
$queryBuilder->where([
13+
'status=' => ['Expired']
14+
]);
15+
}
16+
}
17+
?>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Espo\Modules\FileSharing\Controllers;
4+
5+
class FileSharing extends \Espo\Core\Templates\Controllers\Base
6+
{
7+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace Espo\Modules\FileSharing\Controllers;
4+
5+
use Espo\Core\Api\Request;
6+
use Espo\Core\Api\Response;
7+
use Espo\ORM\Entity;
8+
use Espo\ORM\EntityManager;
9+
use Espo\Core\Exceptions\Error;
10+
use Espo\Core\Exceptions\BadRequest;
11+
use Espo\Core\Exceptions\NotFound;
12+
13+
class GenerateToken
14+
{
15+
public function __construct(EntityManager $entityManager)
16+
{
17+
$this->entityManager = $entityManager;
18+
}
19+
20+
public function postActionGenerateNewToken(Request $request, Response $response): void
21+
{
22+
$data = $request->getParsedBody();
23+
24+
if (empty($data->id)) {
25+
throw new BadRequest('ID is missing.');
26+
}
27+
28+
$id = $data->id;
29+
30+
// Load the FileSharing entity
31+
$fileSharing = $this->entityManager->getEntity('FileSharing', $id);
32+
if (!$fileSharing) {
33+
throw new NotFound("FileSharing entity with ID '{$id}' not found.");
34+
}
35+
36+
// Generate a new access token
37+
$accessToken = bin2hex(random_bytes(18));
38+
39+
// Set the new access token to the FileSharing entity
40+
$fileSharing->set('accessToken', $accessToken);
41+
42+
// Save the FileSharing entity with the new access token
43+
$this->entityManager->saveEntity($fileSharing);
44+
45+
// Return the new access token in the response
46+
$response->writeBody(json_encode(['accessToken' => $accessToken]));
47+
}
48+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Espo\Modules\FileSharing\Entities;
4+
5+
class FileSharing extends \Espo\Core\Templates\Entities\Base
6+
{
7+
public const ENTITY_TYPE = 'FileSharing';
8+
9+
protected $entityType = 'FileSharing';
10+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
namespace Espo\Modules\FileSharing\EntryPoints;
4+
5+
use Espo\Core\Exceptions\NotFound;
6+
use Espo\Core\Exceptions\Forbidden;
7+
use Espo\Core\Exceptions\BadRequest;
8+
use Espo\Core\EntryPoints\Base;
9+
use Espo\Core\ORM\EntityManager;
10+
use Espo\Core\Api\Request;
11+
use Espo\Core\Api\Response;
12+
use Espo\Entities\Attachment as AttachmentEntity;
13+
use Espo\Core\FileStorage\Manager as FileStorageManager;
14+
use Espo\Core\EntryPoint\Traits\NoAuth;
15+
16+
class FileSharing extends Base
17+
{
18+
use NoAuth;
19+
20+
protected function getEntityManager(): EntityManager
21+
{
22+
return $this->getContainer()->get('entityManager');
23+
}
24+
25+
protected function fileStorageManager(): fileStorageManager
26+
{
27+
return $this->getContainer()->get('fileStorageManager');
28+
}
29+
30+
public function run(Request $request, Response $response): void
31+
{
32+
$id = $request->getQueryParam('id');
33+
if (!$id) {
34+
throw new BadRequest("Missing 'id' parameter.");
35+
}
36+
37+
$fileSharing = $this->getEntityManager()->getEntity('FileSharing', $id);
38+
if (!$fileSharing) {
39+
throw new NotFound("FileSharing entity with ID '{$id}' not found.");
40+
}
41+
42+
// Check if the accessToken matches the one in the entity
43+
$accessToken = $request->getQueryParam('token');
44+
if (!$accessToken || $accessToken !== $fileSharing->get('accessToken')) {
45+
throw new Forbidden("Token not valid.");
46+
}
47+
48+
$status = $fileSharing->get('status');
49+
if ($status !== 'Active') {
50+
throw new Forbidden("The requested file is not active and cannot be downloaded.");
51+
}
52+
53+
$validTill = $fileSharing->get('validTill');
54+
if ($validTill) {
55+
// Convert validTill to the server's timezone
56+
$validTillDateTime = new \DateTime($validTill);
57+
$serverTimezone = new \DateTimeZone(date_default_timezone_get());
58+
$validTillDateTime->setTimezone($serverTimezone);
59+
60+
$now = new \DateTime();
61+
if ($now > $validTillDateTime) {
62+
throw new Forbidden("The requested file has expired and is no longer available.");
63+
}
64+
}
65+
66+
$attachmentId = $fileSharing->get('attachmentId');
67+
if (!$attachmentId) {
68+
throw new NotFound("Attachment not found for FileSharing entity with ID '{$id}'.");
69+
}
70+
$attachment = $this->getEntityManager()->getEntityById(AttachmentEntity::ENTITY_TYPE, $attachmentId);
71+
$stream = $this->fileStorageManager()->getStream($attachment);
72+
if (!$attachment) {
73+
throw new NotFound("Attachment with ID '{$attachmentId}' not found.");
74+
}
75+
76+
$filePath = $this->getEntityManager()->getRepository('Attachment')->getFilePath($attachment);
77+
if (!$filePath) {
78+
throw new NotFound("File not found on the server.");
79+
}
80+
81+
$fileName = $attachment->getName();
82+
$fileType = $attachment->getType();
83+
$size = $stream->getSize() ?? $this->fileStorageManager()->getSize($attachment);
84+
$forceFileDownload = $fileSharing->get('forceFileDownload');
85+
86+
$accessCount = $fileSharing->get('accessCount');
87+
$allowedUsage = $fileSharing->get('allowedUsage');
88+
if ($allowedUsage != 0 && $accessCount >= $allowedUsage) {
89+
throw new NotFound("Download Limit exceeded.");
90+
}
91+
// Increment the accessCount field
92+
$accessCount = $fileSharing->get('accessCount');
93+
$fileSharing->set('accessCount', $accessCount + 1);
94+
$this->getEntityManager()->saveEntity($fileSharing);
95+
96+
$this->downloadFile($response, $stream, $fileName, $fileType, $forceFileDownload, $size);
97+
}
98+
99+
protected function downloadFile(Response $response, $stream, $fileName, $fileType, $forceFileDownload, $size)
100+
{
101+
$disposition = $forceFileDownload ? 'attachment' : 'inline';
102+
103+
$response->setHeader("Content-Disposition", "$disposition; filename=\"{$fileName}\"");
104+
$response->setHeader("Content-Type", $fileType);
105+
$response->setHeader('Pragma', 'public');
106+
$response->setHeader('Cache-Control', 'must-revalidate');
107+
$response->setHeader('Expires', '0');
108+
$response->setHeader('Content-Length', (string) $size);
109+
$response->setBody($stream);
110+
}
111+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Espo\Modules\FileSharing\Hooks\FileSharing;
4+
5+
use Espo\Core\Hooks\Base;
6+
use Espo\ORM\Entity;
7+
8+
class GenerateDownloadToken extends Base
9+
{
10+
public function beforeSave(Entity $entity, array $options = [])
11+
{
12+
// Generate a random 36-character alphanumeric token
13+
$accessToken = bin2hex(random_bytes(18));
14+
15+
if ($entity->isNew()) {
16+
$entity->set('accessToken', $accessToken);
17+
}
18+
}
19+
}

0 commit comments

Comments
 (0)