Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.63% |
1 / 158 |
|
0.00% |
0 / 26 |
CRAP | |
0.00% |
0 / 1 |
| MediaSource | |
0.63% |
1 / 158 |
|
0.00% |
0 / 26 |
3022.93 | |
0.00% |
0 / 1 |
| enableAccessControl | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
| getLanguage | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
| getRootClass | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| add | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
| delete | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getDirectories | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
| getDirectory | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getFileInfo | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
20 | |||
| getFileStream | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
| download | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
| getBaseName | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
| forceMimeType | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
| getOrCreatePath | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
42 | |||
| getArrayFromJson | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
| getServiceLocator | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getRootClassUri | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
| getLang | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
| getMediaService | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
| getFileManagement | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
| removeSchemaFromUriOrLink | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getProcessedFileStream | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
| getPreparer | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getFileSourceUnserializer | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| searchDirectories | |
0.00% |
0 / 40 |
|
0.00% |
0 / 1 |
56 | |||
| getPermissionsMapper | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
| __destruct | |
33.33% |
1 / 3 |
|
0.00% |
0 / 1 |
5.67 | |||
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * This program is free software; you can redistribute it and/or |
| 5 | * modify it under the terms of the GNU General Public License |
| 6 | * as published by the Free Software Foundation; under version 2 |
| 7 | * of the License (non-upgradable). |
| 8 | * |
| 9 | * This program is distributed in the hope that it will be useful, |
| 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | * GNU General Public License for more details. |
| 13 | * |
| 14 | * You should have received a copy of the GNU General Public License |
| 15 | * along with this program; if not, write to the Free Software |
| 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 17 | * |
| 18 | * Copyright (c) 2014-2020 (original work) Open Assessment Technologies SA; |
| 19 | */ |
| 20 | |
| 21 | namespace oat\taoMediaManager\model; |
| 22 | |
| 23 | use oat\generis\model\OntologyAwareTrait; |
| 24 | use oat\oatbox\Configurable; |
| 25 | use oat\oatbox\log\LoggerAwareTrait; |
| 26 | use oat\oatbox\service\ServiceManager; |
| 27 | use oat\tao\model\accessControl\AccessControlEnablerInterface; |
| 28 | use oat\tao\model\media\MediaManagement; |
| 29 | use oat\tao\model\media\mediaSource\DirectorySearchQuery; |
| 30 | use oat\tao\model\media\ProcessedFileStreamAware; |
| 31 | use oat\taoMediaManager\model\export\service\MediaResourcePreparerInterface; |
| 32 | use oat\taoMediaManager\model\mapper\MediaSourcePermissionsMapper; |
| 33 | use oat\taoMediaManager\model\fileManagement\FileManagement; |
| 34 | use oat\taoMediaManager\model\fileManagement\FileSourceUnserializer; |
| 35 | use Psr\Http\Message\StreamInterface; |
| 36 | use tao_helpers_Uri; |
| 37 | use tao_models_classes_FileNotFoundException; |
| 38 | |
| 39 | use function GuzzleHttp\Psr7\stream_for; |
| 40 | |
| 41 | class MediaSource extends Configurable implements |
| 42 | MediaManagement, |
| 43 | ProcessedFileStreamAware, |
| 44 | AccessControlEnablerInterface |
| 45 | { |
| 46 | use LoggerAwareTrait; |
| 47 | use OntologyAwareTrait; |
| 48 | |
| 49 | public const SCHEME_NAME = 'taomedia://mediamanager/'; |
| 50 | |
| 51 | /** @var MediaService */ |
| 52 | protected $mediaService; |
| 53 | |
| 54 | /** @var FileManagement */ |
| 55 | protected $fileManagementService; |
| 56 | |
| 57 | /** @var MediaSourcePermissionsMapper */ |
| 58 | private $permissionsMapper; |
| 59 | |
| 60 | /** @var string[] */ |
| 61 | private $tmpFiles = []; |
| 62 | |
| 63 | public function enableAccessControl(): AccessControlEnablerInterface |
| 64 | { |
| 65 | $this->getPermissionsMapper()->enableAccessControl(); |
| 66 | |
| 67 | return $this; |
| 68 | } |
| 69 | |
| 70 | /** |
| 71 | * Returns the language URI to be used |
| 72 | * @return string |
| 73 | */ |
| 74 | protected function getLanguage() |
| 75 | { |
| 76 | return $this->hasOption('lang') |
| 77 | ? $this->getOption('lang') |
| 78 | : ''; |
| 79 | } |
| 80 | |
| 81 | public function getRootClass() |
| 82 | { |
| 83 | return $this->getClass($this->getRootClassUri()); |
| 84 | } |
| 85 | |
| 86 | /** |
| 87 | * (non-PHPdoc) |
| 88 | * |
| 89 | * @see \oat\tao\model\media\MediaManagement::add |
| 90 | */ |
| 91 | public function add($source, $fileName, $parent, $mimetype = null) |
| 92 | { |
| 93 | if (!file_exists($source)) { |
| 94 | throw new tao_models_classes_FileNotFoundException($source); |
| 95 | } |
| 96 | |
| 97 | $clazz = $this->getOrCreatePath($parent); |
| 98 | |
| 99 | $service = $this->getMediaService(); |
| 100 | $instanceUri = $service->createMediaInstance($source, $clazz->getUri(), $this->getLang(), $fileName, $mimetype); |
| 101 | |
| 102 | return $this->getFileInfo($instanceUri); |
| 103 | } |
| 104 | |
| 105 | /** |
| 106 | * (non-PHPdoc) |
| 107 | * |
| 108 | * @see \oat\tao\model\media\MediaManagement::delete |
| 109 | */ |
| 110 | public function delete($link) |
| 111 | { |
| 112 | return $this->getMediaService()->deleteResource($this->getResource(tao_helpers_Uri::decode($link))); |
| 113 | } |
| 114 | |
| 115 | public function getDirectories(DirectorySearchQuery $params): array |
| 116 | { |
| 117 | return $this->searchDirectories( |
| 118 | $params->getParentLink(), |
| 119 | $params->getFilter(), |
| 120 | $params->getDepth(), |
| 121 | $params->getChildrenLimit(), |
| 122 | $params->getChildrenOffset() |
| 123 | ); |
| 124 | } |
| 125 | |
| 126 | /** |
| 127 | * @inheritDoc |
| 128 | */ |
| 129 | public function getDirectory($parentLink = '', $acceptableMime = [], $depth = 1) |
| 130 | { |
| 131 | return $this->searchDirectories($parentLink, $acceptableMime, $depth, 0, 0); |
| 132 | } |
| 133 | |
| 134 | /** |
| 135 | * (non-PHPdoc) |
| 136 | * |
| 137 | * @see \oat\tao\model\media\MediaBrowser::getFileInfo |
| 138 | */ |
| 139 | public function getFileInfo($link) |
| 140 | { |
| 141 | // get the media link from the resource |
| 142 | $resource = $this->getResource(tao_helpers_Uri::decode($this->removeSchemaFromUriOrLink($link))); |
| 143 | $properties = [ |
| 144 | $this->getProperty(TaoMediaOntology::PROPERTY_LINK), |
| 145 | $this->getProperty(TaoMediaOntology::PROPERTY_MIME_TYPE), |
| 146 | $this->getProperty(TaoMediaOntology::PROPERTY_ALT_TEXT) |
| 147 | ]; |
| 148 | |
| 149 | $propertiesValues = $resource->getPropertiesValues($properties); |
| 150 | |
| 151 | $fileLink = $propertiesValues[TaoMediaOntology::PROPERTY_LINK][0] ?? null; |
| 152 | $mime = $propertiesValues[TaoMediaOntology::PROPERTY_MIME_TYPE][0] ?? null; |
| 153 | $fileLink = $fileLink instanceof \core_kernel_classes_Resource ? $fileLink->getUri() : (string)$fileLink; |
| 154 | $fileLink = $this->getFileSourceUnserializer()->unserialize($fileLink); |
| 155 | |
| 156 | if (!isset($mime, $fileLink)) { |
| 157 | throw new tao_models_classes_FileNotFoundException($link); |
| 158 | } |
| 159 | |
| 160 | // add the alt text to file array |
| 161 | $altArray = $propertiesValues[TaoMediaOntology::PROPERTY_ALT_TEXT] ?? null; |
| 162 | $alt = $resource->getLabel(); |
| 163 | if (count($altArray) > 0) { |
| 164 | $alt = (string)$altArray[0]; |
| 165 | } |
| 166 | |
| 167 | return $this->getPermissionsMapper()->map( |
| 168 | [ |
| 169 | 'name' => $resource->getLabel(), |
| 170 | 'uri' => self::SCHEME_NAME . tao_helpers_Uri::encode($link), |
| 171 | 'mime' => (string)$mime, |
| 172 | 'size' => $this->getFileManagement()->getFileSize($fileLink), |
| 173 | 'alt' => $alt, |
| 174 | 'link' => $fileLink |
| 175 | ], |
| 176 | $resource->getUri() |
| 177 | ); |
| 178 | } |
| 179 | |
| 180 | /** |
| 181 | * @param string $link |
| 182 | * @return \Psr\Http\Message\StreamInterface |
| 183 | * @throws \core_kernel_persistence_Exception |
| 184 | * @throws tao_models_classes_FileNotFoundException |
| 185 | */ |
| 186 | public function getFileStream($link) |
| 187 | { |
| 188 | $resource = $this->getResource(tao_helpers_Uri::decode($link)); |
| 189 | $fileLink = $resource->getOnePropertyValue( |
| 190 | $this->getProperty(TaoMediaOntology::PROPERTY_LINK) |
| 191 | ); |
| 192 | |
| 193 | if (is_null($fileLink)) { |
| 194 | throw new tao_models_classes_FileNotFoundException($link); |
| 195 | } |
| 196 | |
| 197 | $fileLink = $fileLink instanceof \core_kernel_classes_Resource ? $fileLink->getUri() : (string)$fileLink; |
| 198 | $fileLink = $this->getFileSourceUnserializer()->unserialize($fileLink); |
| 199 | |
| 200 | return $this->getFileManagement()->getFileStream($fileLink); |
| 201 | } |
| 202 | |
| 203 | /** |
| 204 | * (non-PHPdoc) |
| 205 | * |
| 206 | * @see \oat\tao\model\media\MediaBrowser::download |
| 207 | * @deprecated |
| 208 | */ |
| 209 | public function download($link) |
| 210 | { |
| 211 | $this->logInfo('Deprecated, creates tmpfiles'); |
| 212 | $stream = $this->getFileStream($link); |
| 213 | $filename = tempnam(sys_get_temp_dir(), 'media'); |
| 214 | $fh = fopen($filename, 'w'); |
| 215 | while (!$stream->eof()) { |
| 216 | fwrite($fh, $stream->read(1048576)); |
| 217 | } |
| 218 | fclose($fh); |
| 219 | |
| 220 | $this->tmpFiles[] = $filename; |
| 221 | |
| 222 | return $filename; |
| 223 | } |
| 224 | |
| 225 | /** |
| 226 | * @param string $link |
| 227 | * @return string |
| 228 | * @throws \core_kernel_persistence_Exception |
| 229 | * @throws tao_models_classes_FileNotFoundException |
| 230 | */ |
| 231 | public function getBaseName($link) |
| 232 | { |
| 233 | $stream = $this->getFileStream($link); |
| 234 | $filename = $stream->getMetadata('uri'); |
| 235 | |
| 236 | if ($filename === 'php://temp') { |
| 237 | // We are currently retrieving a remote resource (e.g. on Amazon S3). |
| 238 | $fileinfo = $this->getFileInfo($link); |
| 239 | $filename = $fileinfo['link']; |
| 240 | } |
| 241 | |
| 242 | return basename($filename); |
| 243 | } |
| 244 | |
| 245 | /** |
| 246 | * Force the mime-type of a resource |
| 247 | * |
| 248 | * @param string $link |
| 249 | * @param string $mimeType |
| 250 | * @return boolean |
| 251 | */ |
| 252 | public function forceMimeType($link, $mimeType) |
| 253 | { |
| 254 | $resource = $this->getResource(tao_helpers_Uri::decode($link)); |
| 255 | return $resource->editPropertyValues( |
| 256 | $this->getProperty(TaoMediaOntology::PROPERTY_MIME_TYPE), |
| 257 | $mimeType |
| 258 | ); |
| 259 | } |
| 260 | |
| 261 | /** |
| 262 | * |
| 263 | * @param string $path |
| 264 | * @return \core_kernel_classes_Class |
| 265 | */ |
| 266 | private function getOrCreatePath($path) |
| 267 | { |
| 268 | $rootClass = $this->getRootClass(); |
| 269 | |
| 270 | if ($path === '') { |
| 271 | return $rootClass; |
| 272 | } |
| 273 | |
| 274 | // If the path is a class URI, returns the existing class. |
| 275 | $class = $this->getClass(tao_helpers_Uri::decode($path)); |
| 276 | if ($class->isSubClassOf($rootClass) || $class->equals($rootClass) || $class->exists()) { |
| 277 | return $class; |
| 278 | } |
| 279 | |
| 280 | // If the given path is a json-encoded array, creates the full path from root class. |
| 281 | $labels = $this->getArrayFromJson($path); |
| 282 | if ($labels) { |
| 283 | return $rootClass->createSubClassPathByLabel($labels); |
| 284 | } |
| 285 | |
| 286 | // Retrieve or create a direct subclass of the root class. |
| 287 | return $rootClass->retrieveOrCreateSubClassByLabel($path); |
| 288 | } |
| 289 | |
| 290 | /** |
| 291 | * Tries to find a json-encoded array in the given string. |
| 292 | * |
| 293 | * If string is actually a json string and a json-encoded array, returns the array. |
| 294 | * Else, returns false. |
| 295 | * |
| 296 | * @param string $string |
| 297 | * @return array|bool |
| 298 | */ |
| 299 | private function getArrayFromJson($string) |
| 300 | { |
| 301 | $decoded = json_decode($string); |
| 302 | |
| 303 | return $decoded !== null && is_array($decoded) |
| 304 | ? $decoded |
| 305 | : false; |
| 306 | } |
| 307 | |
| 308 | /** |
| 309 | * Get the service Locator |
| 310 | * |
| 311 | * @return ServiceManager |
| 312 | */ |
| 313 | protected function getServiceLocator() |
| 314 | { |
| 315 | return ServiceManager::getServiceManager(); |
| 316 | } |
| 317 | |
| 318 | protected function getRootClassUri() |
| 319 | { |
| 320 | return $this->hasOption('rootClass') |
| 321 | ? $this->getOption('rootClass') |
| 322 | : MediaService::singleton()->getRootClass(); |
| 323 | } |
| 324 | |
| 325 | protected function getLang() |
| 326 | { |
| 327 | return $this->hasOption('lang') ? $this->getOption('lang') : ''; |
| 328 | } |
| 329 | |
| 330 | /** |
| 331 | * @return MediaService |
| 332 | */ |
| 333 | protected function getMediaService() |
| 334 | { |
| 335 | if (!$this->mediaService) { |
| 336 | $this->mediaService = MediaService::singleton(); |
| 337 | } |
| 338 | return $this->mediaService; |
| 339 | } |
| 340 | |
| 341 | /** |
| 342 | * @return FileManagement |
| 343 | */ |
| 344 | protected function getFileManagement() |
| 345 | { |
| 346 | if (!$this->fileManagementService) { |
| 347 | $this->fileManagementService = $this->getServiceLocator()->get(FileManagement::SERVICE_ID); |
| 348 | } |
| 349 | return $this->fileManagementService; |
| 350 | } |
| 351 | |
| 352 | private function removeSchemaFromUriOrLink(string $uriOrLink): string |
| 353 | { |
| 354 | return str_replace(self::SCHEME_NAME, '', $uriOrLink); |
| 355 | } |
| 356 | |
| 357 | public function getProcessedFileStream(string $link): StreamInterface |
| 358 | { |
| 359 | return stream_for( |
| 360 | $this->getPreparer()->prepare( |
| 361 | $this->getResource(tao_helpers_Uri::decode($link)), |
| 362 | $this->getFileStream($link) |
| 363 | ) |
| 364 | ); |
| 365 | } |
| 366 | |
| 367 | private function getPreparer(): MediaResourcePreparerInterface |
| 368 | { |
| 369 | return $this->getServiceLocator()->get(MediaResourcePreparerInterface::SERVICE_ID); |
| 370 | } |
| 371 | |
| 372 | private function getFileSourceUnserializer(): FileSourceUnserializer |
| 373 | { |
| 374 | return $this->getServiceLocator()->get(FileSourceUnserializer::class); |
| 375 | } |
| 376 | |
| 377 | private function searchDirectories( |
| 378 | string $parentLink = '', |
| 379 | array $acceptableMime = [], |
| 380 | int $depth = 1, |
| 381 | int $childrenLimit = 0, |
| 382 | int $childrenOffset = 0 |
| 383 | ): array { |
| 384 | |
| 385 | $class = $this->getClass($parentLink == '' ? $this->getRootClassUri() : tao_helpers_Uri::decode($parentLink)); |
| 386 | |
| 387 | $data = $this->getPermissionsMapper()->map( |
| 388 | [ |
| 389 | 'path' => self::SCHEME_NAME . tao_helpers_Uri::encode($class->getUri()), |
| 390 | 'label' => $class->getLabel(), |
| 391 | 'childrenLimit' => $childrenLimit, |
| 392 | ], |
| 393 | $class->getUri() |
| 394 | ); |
| 395 | |
| 396 | if ($depth > 0) { |
| 397 | $children = []; |
| 398 | foreach ($class->getSubClasses() as $subclass) { |
| 399 | $children[] = $this->searchDirectories( |
| 400 | $subclass->getUri(), |
| 401 | $acceptableMime, |
| 402 | $depth - 1, |
| 403 | $childrenLimit, |
| 404 | $childrenOffset |
| 405 | ); |
| 406 | } |
| 407 | |
| 408 | $filter = []; |
| 409 | |
| 410 | if (!empty($acceptableMime)) { |
| 411 | $filter = array_merge($filter, [TaoMediaOntology::PROPERTY_MIME_TYPE => $acceptableMime]); |
| 412 | } |
| 413 | |
| 414 | $options = array_filter([ |
| 415 | 'limit' => $childrenLimit, |
| 416 | 'offset' => $childrenOffset, |
| 417 | ]); |
| 418 | |
| 419 | foreach ($class->searchInstances($filter, $options) as $instance) { |
| 420 | try { |
| 421 | $children[] = $this->getFileInfo($instance->getUri()); |
| 422 | } catch (tao_models_classes_FileNotFoundException $e) { |
| 423 | $this->logEmergency( |
| 424 | sprintf( |
| 425 | 'Encountered issues "%s" while fetching details for %s', |
| 426 | $e->getMessage(), |
| 427 | $instance->getUri() |
| 428 | ) |
| 429 | ); |
| 430 | } |
| 431 | } |
| 432 | $data['children'] = $children; |
| 433 | $data['total'] = $class->countInstances($filter); |
| 434 | } else { |
| 435 | $data['parent'] = $parentLink; |
| 436 | } |
| 437 | |
| 438 | return $data; |
| 439 | } |
| 440 | |
| 441 | private function getPermissionsMapper(): MediaSourcePermissionsMapper |
| 442 | { |
| 443 | if (!$this->permissionsMapper) { |
| 444 | $this->permissionsMapper = $this->getServiceLocator()->get(MediaSourcePermissionsMapper::class); |
| 445 | } |
| 446 | |
| 447 | return $this->permissionsMapper; |
| 448 | } |
| 449 | |
| 450 | public function __destruct() |
| 451 | { |
| 452 | foreach ($this->tmpFiles as $tmpFile) { |
| 453 | if (is_writable($tmpFile)) { |
| 454 | unlink($tmpFile); |
| 455 | } |
| 456 | } |
| 457 | } |
| 458 | } |