Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Image fixes #2274

Merged
merged 5 commits into from
Feb 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/Core/Bootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ private function handleRequest(): void
private function setErrorHandler(int $debug): void
{
$incomingRequest = app(IncomingRequest::class);
app()->bind(\Illuminate\Contracts\Debug\ExceptionHandler::class, \Leantime\Core\ExceptionHandler::class);

if (
$debug == 0
Expand All @@ -333,7 +334,6 @@ private function setErrorHandler(int $debug): void

Debug::enable();

app()->bind(\Illuminate\Contracts\Debug\ExceptionHandler::class, \Leantime\Core\ExceptionHandler::class);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions app/Core/Mailer.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ public function __construct(Environment $config, Language $language)
$this->mailAgent->isMail();
}

$this->logo = $_SESSION["companysettings.logoPath"] ?? "/dist/images/logo.png";
$this->companyColor = $_SESSION["companysettings.primarycolor"] ?? "#1b75bb";
$this->logo = empty($_SESSION["companysettings.logoPath"]) ? "/dist/images/logo_blue.png" : $_SESSION["companysettings.logoPath"];
$this->companyColor = empty($_SESSION["companysettings.primarycolor"]) ? "#006c9e" : $_SESSION["companysettings.primarycolor"];

$this->language = $language;
}
Expand Down
17 changes: 10 additions & 7 deletions app/Core/Template.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,10 @@ public function setupBlade(
//@TODO: Check on callstack to make sure autoload loads before sessions
if (!is_a($plugin, '__PHP_Incomplete_Class')) {
if ($domainPaths->has($basename = strtolower($plugin->foldername))) {
throw new Exception("Plugin $basename conflicts with domain");
error_log("Plugin $basename conflicts with domain");
//Clear cache, something is up
unset($_SESSION['enabledPlugins']);
return [];
}

if ($plugin->format == "phar") {
Expand Down Expand Up @@ -944,14 +947,14 @@ public function convertRelativePaths(?string $text): ?string

// Replace links
$text = preg_replace(
'/<a([^>]*) href="([^http|ftp|https|mailto|#][^"]*)"/',
'/<a([^>]*) href="((?!(http|ftp|https|mailto|#))[^"]*)"/',
"<a\${1} href=\"$base\${2}\"",
$text
);

// Replace images
$text = preg_replace(
'/<img([^>]*) src="([^http|ftp|https][^"]*)"/',
'/<img([^>]*) src="((?!(http|ftp|https))[^"]*)"/',
"<img\${1} src=\"$base\${2}\"",
$text
);
Expand Down Expand Up @@ -980,22 +983,22 @@ public function getModulePicture(): string
}

/**
* patchDownloadUrlToFilenameOrAwsUrl - Replace all local download.php references in <img src=""> tags
* patchDownloadUrlToFilenameOrAwsUrl - Replace all local files/get references in <img src=""> tags
* by either local filenames or AWS URLs that can be accesse without being authenticated
*
* Note: This patch is required by the PDF generating engine as it retrieves URL data without being
* authenticated
*
* @access public
* @param string $textHtml HTML text, potentially containing <img srv="https://local.domain/download.php?xxxx"> tags
* @return string HTML text with the https://local.domain/download.php?xxxx replaced by either full qualified
* @param string $textHtml HTML text, potentially containing <img srv="https://local.domain/files/get?xxxx"> tags
* @return string HTML text with the https://local.domain/files/get?xxxx replaced by either full qualified
* local filenames or AWS URLs
*/
public function patchDownloadUrlToFilenameOrAwsUrl(string $textHtml): string
{
$patchedTextHtml = $this->convertRelativePaths($textHtml);

// TO DO: Replace local download.php
// TO DO: Replace local files/get
$patchedTextHtml = $patchedTextHtml;

return $patchedTextHtml;
Expand Down
6 changes: 3 additions & 3 deletions app/Domain/Clients/Templates/showClient.tpl.php
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@
</a>
<ul class="dropdown-menu">
<li class="nav-header"><?php echo $tpl->__("subtitles.file"); ?></li>
<li><a href="<?=BASE_URL ?>/download.php?module=<?php echo $file['module'] ?>&encName=<?php echo $file['encName'] ?>&ext=<?php echo $file['extension'] ?>&realName=<?php echo $file['realName'] ?>"><?php echo $tpl->__("links.download"); ?></a></li>
<li><a href="<?=BASE_URL ?>/files/get?module=<?php echo $file['module'] ?>&encName=<?php echo $file['encName'] ?>&ext=<?php echo $file['extension'] ?>&realName=<?php echo $file['realName'] ?>"><?php echo $tpl->__("links.download"); ?></a></li>

<?php
if ($login::userIsAtLeast($roles::$admin)) { ?>
Expand All @@ -234,9 +234,9 @@

</ul>
</div>
<a class="cboxElement" href="<?=BASE_URL ?>/download.php?module=<?php echo $file['module'] ?>&encName=<?php echo $file['encName'] ?>&ext=<?php $tpl->e($file['extension']); ?>&realName=<?php $tpl->e($file['realName']); ?>">
<a class="cboxElement" href="<?=BASE_URL ?>/files/get?module=<?php echo $file['module'] ?>&encName=<?php echo $file['encName'] ?>&ext=<?php $tpl->e($file['extension']); ?>&realName=<?php $tpl->e($file['realName']); ?>">
<?php if (in_array(strtolower($file['extension']), $tpl->get('imgExtensions'))) : ?>
<img style='max-height: 50px; max-width: 70px;' src="<?=BASE_URL ?>/download.php?module=<?php echo $file['module'] ?>&encName=<?php echo $file['encName'] ?>&ext=<?php $tpl->e($file['extension']); ?>&realName=<?php $tpl->e($file['realName']); ?>" alt="" />
<img style='max-height: 50px; max-width: 70px;' src="<?=BASE_URL ?>/files/get?module=<?php echo $file['module'] ?>&encName=<?php echo $file['encName'] ?>&ext=<?php $tpl->e($file['extension']); ?>&realName=<?php $tpl->e($file['realName']); ?>" alt="" />
<?php else : ?>
<img style='max-height: 50px; max-width: 70px;' src='<?=BASE_URL ?>/dist/images/thumbs/doc.png' />
<?php endif; ?>
Expand Down
15 changes: 11 additions & 4 deletions app/Domain/Cron/Controllers/Run.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ public function init(Environment $config)
public function run(): Response
{
if (! $this->config->poorMansCron) {
error_log("Poor mans cron off");
return new Response();
}

error_log("Adding Event Listener to request_terminated");
Events::add_event_listener('leantime.core.httpkernel.terminate.request_terminated', function () {
ignore_user_abort(true);

Expand All @@ -45,17 +47,22 @@ public function run(): Response

$output = new \Symfony\Component\Console\Output\BufferedOutput();

if ($this->config->debug) {

register_shutdown_function(function () use ($output) {
error_log("Command Output: " . $output->fetch());
error_log("Cron run finished");
if ($this->config->debug) {
error_log("Command Output: " . $output->fetch());
error_log("Cron run finished");
}
});

error_log("Cron run started");
}


error_log("Before calling schedule:run");
/** @return never **/
(new \Leantime\Core\ConsoleKernel())->call('schedule:run', [], $output);
error_log("After calling schedule:run");

});

return tap(new Response(), function ($response) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
<div class="btn-group pull-left" style="margin-right:5px;">
<button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown"><?=$tpl->__("links.new_with_icon") ?> <span class="caret"></span></button>
<ul class="dropdown-menu">

<li><a href="#/tickets/newTicket">Add Todo</a></li>
<li><a href="#/tickets/editMilestone">Add Milestone</a></li>
<li><a href="#/sprints/editSprint">Add List</a></li>

</ul>
</div>
Expand Down
182 changes: 182 additions & 0 deletions app/Domain/Files/Controllers/Get.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
<?php

namespace Leantime\Domain\Files\Controllers;

use Aws\S3\S3Client;
use Leantime\Core\Controller;
use Leantime\Core\Environment;
use Leantime\Core\Frontcontroller;
use Leantime\Domain\Files\Repositories\Files as FileRepository;
use Leantime\Domain\Files\Services\Files as FileService;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;

/**
*
*/
class Get extends Controller
{
private FileService $filesService;
private FileRepository $filesRepo;

private Environment $config;

/**
* @param FileRepository $filesRepo
* @param FileService $filesService
* @return void
*/
public function init(
FileRepository $filesRepo,
FileService $filesService,
Environment $config
): void {
$this->filesRepo = $filesRepo;
$this->filesService = $filesService;
$this->config = $config;
}

/**
* @return Response
* @throws \Exception
*/
public function get(): Response
{

$encName = preg_replace("/[^a-zA-Z0-9]+/", "", $_GET['encName']);
$realName = $_GET['realName'];
$ext = preg_replace("/[^a-zA-Z0-9]+/", "", $_GET['ext']);
$module = preg_replace("/[^a-zA-Z0-9]+/", "", $_GET['module'] ?? '');

if ($this->config->useS3) {
return $this->getFileFromS3($encName, $ext, $module, $realName);
} else {
return $this->getFileLocally($encName, $ext, $module, $realName);
}

}

/**
* @return void
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
private function getFileLocally($encName, $ext, $module, $realName): Response
{

$mimes = array(
'jpg' => 'image/jpg',
'jpeg' => 'image/jpg',
'gif' => 'image/gif',
'png' => 'image/png',
);

//TODO: Replace with ROOT
$path = realpath(APP_ROOT . "/" . $this->config->userFilePath . "/");

$fullPath = $path . "/" . $encName . '.' . $ext;

if (file_exists(realpath($fullPath))) {
if ($fd = fopen(realpath($fullPath), 'rb')) {
$path_parts = pathinfo($fullPath);

if ($ext == 'pdf') {
header('Content-type: application/pdf');
header("Content-Disposition: inline; filename=\"" . $realName . "." . $ext . "\"");
} elseif ($ext == 'jpg' || $ext == 'jpeg' || $ext == 'gif' || $ext == 'png') {
header('Content-type: ' . $mimes[$ext]);
header('Content-disposition: inline; filename="' . $realName . "." . $ext . '";');
} elseif ($ext == 'svg') {
header('Content-type: image/svg+xml');
header('Content-disposition: attachment; filename="' . $realName . "." . $ext . '";');
} else {
header("Content-type: application/octet-stream");
header("Content-Disposition: filename=\"" . $realName . "." . $ext . "\"");
}


$sLastModified = filemtime($fullPath);
$sEtag = md5_file($fullPath);

$sFileSize = filesize($fullPath);


$oStreamResponse = new StreamedResponse();
$oStreamResponse->headers->set("Content-Type", $mimes[$ext] );
$oStreamResponse->headers->set("Content-Length", $sFileSize);
$oStreamResponse->headers->set("ETag", $sEtag);

if(app()->make(Environment::class)->debug == false) {
$oStreamResponse->headers->set("Pragma", 'public');
$oStreamResponse->headers->set("Cache-Control", 'max-age=86400');
$oStreamResponse->headers->set("Last-Modified", gmdate("D, d M Y H:i:s", $sLastModified)." GMT");
}
$oStreamResponse->setCallback(function() use ($fullPath) {readfile($fullPath);});

return $oStreamResponse;

}
} else {
http_response_code(404);
die();
}
}

/**
* @return void
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
private function getFileFromS3($encName, $ext, $module, $realName): Response
{

$mimes = array(
'jpg' => 'image/jpg',
'jpeg' => 'image/jpg',
'gif' => 'image/gif',
'png' => 'image/png',
);

// Instantiate the client.

$s3Client = new S3Client([
'version' => 'latest',
'region' => $this->config->s3Region,
'endpoint' => $this->config->s3EndPoint == "" ? null : $this->config->s3EndPoint,
'use_path_style_endpoint' => !($this->config->s3UsePathStyleEndpoint == "false"),
'credentials' => [
'key' => $this->config->s3Key,
'secret' => $this->config->s3Secret,
],
]);

try {
// implode all non-empty elements to allow s3FolderName to be empty.
// otherwise you will get an error as the key starts with a slash
$fileName = implode('/', array_filter(array($this->config->s3FolderName, $encName . "." . $ext)));
$result = $s3Client->getObject([
'Bucket' => $this->config->s3Bucket,
'Key' => $fileName,
'Body' => 'this is the body!',
]);

$response = new Response($result->get('Body')->getContents());

if ($ext == 'pdf') {
$response->headers->set('Content-type', 'application/pdf');
} elseif ($ext == 'jpg' || $ext == 'jpeg' || $ext == 'gif' || $ext == 'png') {
$response->headers->set('Content-type', $result['ContentType']);
} elseif ($ext == 'svg') {
$response->headers->set('Content-type', 'image/svg+xml');

} else {
header('Content-disposition: attachment; filename="' . $realName . "." . $ext . '";');
}

$response->headers->set('Content-Disposition', "inline; filename=\"" . $realName . "." . $ext . "\"");

return $response;
} catch (Aws\S3\Exception\S3Exception $e) {
return new Response($e->getMessage());
}
}
}
1 change: 1 addition & 0 deletions app/Domain/Files/Services/Files.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public function deleteFile($fileId): bool
{
return $this->fileRepository->deleteFile($fileId);
}

}

}
12 changes: 6 additions & 6 deletions app/Domain/Files/Templates/browse.tpl.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
</a>
<ul class="dropdown-menu">
<li class="nav-header"><?php echo $tpl->__("subtitles.file"); ?></li>
<li><a target="_blank" href="<?=BASE_URL ?>/download.php?module=<?php echo $file['module'] ?>&encName=<?php echo $file['encName'] ?>&ext=<?php echo $file['extension'] ?>&realName=<?php echo $file['realName'] ?>"><?php echo $tpl->__("links.download"); ?></a></li>
<li><a target="_blank" href="<?=BASE_URL ?>/files/get?module=<?php echo $file['module'] ?>&encName=<?php echo $file['encName'] ?>&ext=<?php echo $file['extension'] ?>&realName=<?php echo $file['realName'] ?>"><?php echo $tpl->__("links.download"); ?></a></li>

<?php

Expand All @@ -85,9 +85,9 @@

</ul>
</div>
<a class="imageLink" data-ext="<?php echo $file['extension'] ?>" href="<?=BASE_URL?>/download.php?module=<?php echo $file['module'] ?>&encName=<?php echo $file['encName'] ?>&ext=<?php echo $file['extension'] ?>&realName=<?php echo $file['realName'] ?>">
<a class="imageLink" data-ext="<?php echo $file['extension'] ?>" href="<?=BASE_URL?>/files/get?module=<?php echo $file['module'] ?>&encName=<?php echo $file['encName'] ?>&ext=<?php echo $file['extension'] ?>&realName=<?php echo $file['realName'] ?>">
<?php if (in_array(strtolower($file['extension']), $tpl->get('imgExtensions'))) : ?>
<img style='max-height: 50px; max-width: 70px;' src="<?=BASE_URL ?>/download.php?module=<?php echo $file['module'] ?>&encName=<?php echo $file['encName'] ?>&ext=<?php echo $file['extension'] ?>&realName=<?php echo $file['realName'] ?>" alt="" />
<img style='max-height: 50px; max-width: 70px;' src="<?=BASE_URL ?>/files/get?module=<?php echo $file['module'] ?>&encName=<?php echo $file['encName'] ?>&ext=<?php echo $file['extension'] ?>&realName=<?php echo $file['realName'] ?>" alt="" />
<?php else : ?>
<img style='max-height: 50px; max-width: 70px;' src='<?=BASE_URL ?>/dist/images/doc.png' />
<?php endif; ?>
Expand Down Expand Up @@ -267,15 +267,15 @@
'</a>' +
'<ul class="dropdown-menu">' +
'<li class="nav-header"><?php echo $tpl->__("subtitles.file"); ?></li>' +
'<li><a target="_blank" href="<?=BASE_URL ?>/download.php?module='+ response.module +'&encName='+ response.encName +'&ext='+ response.extension +'&realName='+ response.realName +'"><?php echo str_replace("'", '"', $tpl->__("links.download")); ?></a></li>'+
'<li><a target="_blank" href="<?=BASE_URL ?>/files/get?module='+ response.module +'&encName='+ response.encName +'&ext='+ response.extension +'&realName='+ response.realName +'"><?php echo str_replace("'", '"', $tpl->__("links.download")); ?></a></li>'+
<?php
if ($login::userIsAtLeast($roles::$editor)) { ?>
'<li><a href="<?=BASE_URL ?>/files/showAll?delFile='+ response.fileId +'" class="delete deleteFile"><i class="fa fa-trash"></i> <?php echo str_replace("'", '"', $tpl->__("links.delete")); ?></a></li>'+
<?php } ?>
'</ul>'+
'</div>'+
'<a class="imageLink" href="<?=BASE_URL?>/download.php?module='+ response.module +'&encName='+ response.encName +'&ext='+ response.extension +'&realName='+ response.realName +'">'+
'<img style="max-height: 50px; max-width: 70px;" src="<?=BASE_URL ?>/download.php?module='+ response.module +'&encName='+ response.encName +'&ext='+ response.extension +'&realName='+ response.realName +'" alt="" />'+
'<a class="imageLink" href="<?=BASE_URL?>/files/get?module='+ response.module +'&encName='+ response.encName +'&ext='+ response.extension +'&realName='+ response.realName +'">'+
'<img style="max-height: 50px; max-width: 70px;" src="<?=BASE_URL ?>/files/get?module='+ response.module +'&encName='+ response.encName +'&ext='+ response.extension +'&realName='+ response.realName +'" alt="" />'+

'<span class="filename">'+response.realName+'.</span>'+
'</a>'+
Expand Down