Hi all,
I wanted to allow users to download media files associated with an item directly via a link in the metadata. I have a custom module that does what I want but only works when logged in as the admin, while everyone else gets a permission error…
“Omeka\Mvc\Exception\PermissionDeniedException: Permission denied for the current user to access the download action of the CustomMediaDownload\Controller\DownloadController controller.”
I want all to be able to access and download media files via this module when the link is provided. Could someone help me adjust the code or permissions to resolve this issue?
Here is the relevant code from my custom module: Module.php
<?php declare(strict_types=1);
namespace CustomMediaDownload;
use Omeka\Module\AbstractModule;
class Module extends AbstractModule
{public function getConfig()
{return include __DIR__ . '/config/module.config.php';}}
**module.config.php**
<?php
namespace CustomMediaDownload;
use CustomMediaDownload\Controller\DownloadController;
return [
'router' => [
'routes' => [
'custom-media-download' => [
'type' => 'Segment',
'options' => [
'route' => '/media-download[/:item_id]',
'defaults' => [
'controller' => DownloadController::class,
'action' => 'download',
],
'constraints' => [
'item_id' => '[0-9]+',
],
],
],
],
],
'controllers' => [
'factories' => [
Controller\DownloadController::class => function ($container) {
return new Controller\DownloadController();
},
],
],
'view_manager' => [
'template_path_stack' => [
__DIR__ . '/../view',
],
],
];
**DownloadController.php**
<?php declare(strict_types=1);
namespace CustomMediaDownload\Controller;
use Laminas\Mvc\Controller\AbstractActionController;
use Omeka\Api\Exception\NotFoundException;
class DownloadController extends AbstractActionController
{ protected function streamFile($filePath): bool {
if (!file_exists($filePath)) {
return false; }
$chunkSize = 1024 * 1024; // 1MB
$fp = fopen($filePath, 'rb');
if (!$fp) {
return false;
} while (!feof($fp)) {
echo fread($fp, $chunkSize);
flush();
}
fclose($fp);
return true; }
public function downloadAction() {
$itemId = $this->params()->fromRoute('item_id');
if (!$itemId) {
return $this->getResponse()->setContent('No item ID provided.'); }
$api = $this->api();
try {
$item = $api->read('items', $itemId)->getContent();
} catch (NotFoundException $e) {
return $this->getResponse()->setContent('Item not found.'); }
$media = $item->media();
if (empty($media)) {
return $this->getResponse()->setContent('No media attached to this item.'); } $itemTitle = $item->displayTitle();
$safeTitle = preg_replace('/[^A-Za-z0-9_\-]/', '_', $itemTitle);
// Handle single file
if (count($media) === 1) {
$file = $media[0];
$extension = pathinfo($file->filename(), PATHINFO_EXTENSION);
$downloadFilename = $safeTitle . '.' . $extension;
$filePath = OMEKA_PATH . '/files/original/' . $file->filename();
if (!file_exists($filePath)) {
return $this->getResponse()->setContent('File not found on server.'); }
$mimeType = $file->mediaType();
// If it's a PDF, display inline
if ($mimeType === 'application/pdf') {
header('Content-Type: ' . $mimeType);
header('Content-Disposition: inline; filename="' . $downloadFilename . '"');
} else {
// For other file types, still force download
header('Content-Type: ' . $mimeType);
header('Content-Disposition: attachment; filename="' . $downloadFilename . '"'); } header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($filePath));
$this->streamFile($filePath);
exit; }
// Handle multiple files: Create ZIP
$zip = new \ZipArchive();
$zipFile = sys_get_temp_dir() . "/item_{$itemId}.zip";
if ($zip->open($zipFile, \ZipArchive::CREATE) === true) {
$count = 1;
foreach ($media as $file) {
$filePath = OMEKA_PATH . '/files/original/' . $file->filename();
if (!file_exists($filePath)) {
continue;
}
$mediaTitle = $file->displayTitle();
if (!$mediaTitle || $mediaTitle === $itemTitle) {
$baseName = $safeTitle . '_' . $count;
} else {
$safeMediaTitle = preg_replace('/[^A-Za-z0-9_\-]/', '_', $mediaTitle);
$baseName = $safeMediaTitle;
}
$extension = pathinfo($file->filename(), PATHINFO_EXTENSION);
$zipEntryName = $baseName . '.' . $extension;
$zip->addFile($filePath, $zipEntryName);
$count++; }
$zip->close();
$zipFilename = $safeTitle . '.zip';
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="' . $zipFilename . '"');
header('Content-Length: ' . filesize($zipFile));
header('Content-Transfer-Encoding: binary');
$this->streamFile($zipFile);
unlink($zipFile);
exit; }
return $this->getResponse()->setContent('Failed to create ZIP file.'); }}