Downloader   A
last analyzed

Complexity

Total Complexity 17

Size/Duplication

Total Lines 152
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 44
c 3
b 0
f 0
dl 0
loc 152
rs 10
wmc 17

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getStream() 0 8 2
A logDownload() 0 6 1
A getPublicFile() 0 9 3
A getUserFile() 0 14 4
A __construct() 0 12 1
A isAllowed() 0 13 3
A getReadStreamCallback() 0 12 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AbterPhp\Files\Service\File;
6
7
use AbterPhp\Admin\Domain\Entities\User;
8
use AbterPhp\Files\Authorization\FileCategoryProvider;
9
use AbterPhp\Files\Domain\Entities\File;
10
use AbterPhp\Files\Domain\Entities\FileDownload;
11
use AbterPhp\Files\Orm\FileDownloadRepo;
12
use AbterPhp\Files\Orm\FileRepo as Repo;
13
use AbterPhp\Framework\Authorization\Constant\Role;
14
use AbterPhp\Framework\Filesystem\Uploader;
15
use Casbin\Enforcer;
16
use Casbin\Exceptions\CasbinException;
17
use League\Flysystem\FilesystemException;
18
use League\Flysystem\UnableToReadFile;
19
use Opulence\Orm\IUnitOfWork;
20
use Opulence\Orm\OrmException;
21
22
class Downloader
23
{
24
    const READ_LENGTH = 8192;
25
26
    /** @var Uploader */
27
    protected $uploader;
28
29
    /** @var Enforcer */
30
    protected $enforcer;
31
32
    /** @var Repo */
33
    protected $repo;
34
35
    /** @var FileDownloadRepo */
36
    protected $fileDownloadRepo;
37
38
    /** @var IUnitOfWork */
39
    protected $unitOfWork;
40
41
    /**
42
     * Download constructor.
43
     *
44
     * @param Uploader         $uploader
45
     * @param Enforcer         $enforcer
46
     * @param Repo             $repo
47
     * @param FileDownloadRepo $fileDownloadRepo
48
     * @param IUnitOfWork      $unitOfWork
49
     */
50
    public function __construct(
51
        Uploader $uploader,
52
        Enforcer $enforcer,
53
        Repo $repo,
54
        FileDownloadRepo $fileDownloadRepo,
55
        IUnitOfWork $unitOfWork
56
    ) {
57
        $this->uploader         = $uploader;
58
        $this->enforcer         = $enforcer;
59
        $this->repo             = $repo;
60
        $this->fileDownloadRepo = $fileDownloadRepo;
61
        $this->unitOfWork       = $unitOfWork;
62
    }
63
64
    /**
65
     * @param string    $filesystemName
66
     * @param User|null $user
67
     *
68
     * @return File|null
69
     * @throws CasbinException
70
     * @throws OrmException
71
     */
72
    public function getUserFile(string $filesystemName, ?User $user = null): ?File
73
    {
74
        /** @var File $entity */
75
        $entity = $this->repo->getByFilesystemName($filesystemName);
76
        if (!$entity || !$entity->getPublicName()) {
0 ignored issues
show
introduced by
$entity is of type AbterPhp\Files\Domain\Entities\File, thus it always evaluated to true.
Loading history...
77
            return null;
78
        }
79
80
        $categoryResource = $entity->getCategory()->getIdentifier();
81
        if (!$this->isAllowed($categoryResource, $user)) {
82
            throw new CasbinException(sprintf('not allowed: %s.', $categoryResource));
83
        }
84
85
        return $entity;
86
    }
87
88
    /**
89
     * @param string $filesystemName
90
     *
91
     * @return File|null
92
     * @throws OrmException
93
     */
94
    public function getPublicFile(string $filesystemName): ?File
95
    {
96
        /** @var File $entity */
97
        $entity = $this->repo->getPublicByFilesystemName($filesystemName);
98
        if (!$entity || !$entity->getPublicName()) {
0 ignored issues
show
introduced by
$entity is of type AbterPhp\Files\Domain\Entities\File, thus it always evaluated to true.
Loading history...
99
            return null;
100
        }
101
102
        return $entity;
103
    }
104
105
    /**
106
     * @param File $entity
107
     *
108
     * @return callable
109
     * @throws FilesystemException
110
     */
111
    public function getStream(File $entity): callable
112
    {
113
        $stream = $this->uploader->getStream($entity->getFilesystemName());
114
        if (!$stream) {
0 ignored issues
show
introduced by
$stream is of type false|resource, thus it always evaluated to false.
Loading history...
115
            throw new UnableToReadFile($this->uploader->getPath($entity->getFilesystemName()));
116
        }
117
118
        return $this->getReadStreamCallback($stream);
119
    }
120
121
    /**
122
     * @param string    $resourceIdentifier
123
     * @param User|null $user
124
     *
125
     * @return bool
126
     * @throws CasbinException
127
     */
128
    protected function isAllowed(string $resourceIdentifier, ?User $user = null): bool
129
    {
130
        if (!$user) {
131
            return false;
132
        }
133
134
        $username = $user->getUsername();
135
        $resource = sprintf('%s_%s', FileCategoryProvider::PREFIX, $resourceIdentifier);
136
        if (!$this->enforcer->enforce($username, $resource, Role::READ)) {
137
            return false;
138
        }
139
140
        return true;
141
    }
142
143
    /**
144
     * @param File $file
145
     * @param User $user
146
     *
147
     * @throws OrmException
148
     */
149
    public function logDownload(File $file, User $user)
150
    {
151
        $fileDownload = new FileDownload('', $file, $user, new \DateTime());
152
        $this->fileDownloadRepo->add($fileDownload);
153
154
        $this->unitOfWork->commit();
155
    }
156
157
    /**
158
     * @param $stream
159
     *
160
     * @return callable
161
     */
162
    protected function getReadStreamCallback($stream): callable
163
    {
164
        if (!is_resource($stream)) {
165
            return function () {
166
            };
167
        }
168
169
        return function () use ($stream) {
170
            while (!feof($stream)) {
171
                print(@fread($stream, static::READ_LENGTH));
0 ignored issues
show
Bug introduced by
Are you sure @fread($stream, static::READ_LENGTH) of type false|string can be used in print()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

171
                print(/** @scrutinizer ignore-type */ @fread($stream, static::READ_LENGTH));
Loading history...
172
                ob_flush();
173
                flush();
174
            }
175
        };
176
    }
177
}
178