Skip to content

Commit

Permalink
streamlined response downloads
Browse files Browse the repository at this point in the history
  • Loading branch information
brainfoolong committed Apr 1, 2024
1 parent a03d7f5 commit 979c34c
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 129 deletions.
31 changes: 9 additions & 22 deletions appdata/modules/Framelix/src/Network/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@
use Framelix\Framelix\Utils\JsonUtils;

use function basename;
use function call_user_func_array;
use function file_exists;
use function header;
use function headers_sent;
use function http_response_code;
use function readfile;
use function substr;

/**
* Response utilities for frequent tasks
Expand All @@ -40,8 +38,7 @@ public static function header(string $header): void

/**
* Initialize a file download for the browser
* @param string|StorableFile|callable $fileOrData If starting with @, the parameter will be threaded as string
* rather than file
* @param string|StorableFile|callable $fileOrData A string is considered a file
* @param string|null $filename
* @param string|null $filetype
* @param callable|null $afterDownload A hook after download before script execution stops
Expand All @@ -53,44 +50,34 @@ public static function download(
?string $filetype = "application/octet-stream",
?callable $afterDownload = null
): never {
$isFile = false;
if ($fileOrData instanceof StorableFile) {
$filename = $filename ?? $fileOrData->filename;
$isFile = true;
$fileOrData = $fileOrData->getPath();
if (!$fileOrData) {
}
if (is_string($fileOrData)) {
if (!file_exists($fileOrData)) {
http_response_code(404);
throw new StopExecution();
}
} elseif (is_string($fileOrData)) {
$isFile = !str_starts_with($fileOrData, "@");
if (!$isFile) {
$fileOrData = substr($fileOrData, 1);
if (!$filename) {
$filename = basename($fileOrData);
}
}
if ($isFile && !file_exists($fileOrData)) {
http_response_code(404);
throw new StopExecution();
}
self::header('Content-Description: File Transfer');
self::header('Content-Type: ' . $filetype);
self::header(
'Content-Disposition: attachment; filename="' . basename(
$isFile && !$filename ? basename($fileOrData) : $filename ?? "download.txt"
) . '"'
'Content-Disposition: attachment; filename="' . ($filename ?? "download.txt") . '"'
);
self::header('Expires: 0');
self::header('Pragma: public');
if ($isFile) {
if (is_string($fileOrData)) {
self::header('Cache-Control: no-store');
readfile($fileOrData);
} elseif (is_callable($fileOrData)) {
call_user_func($fileOrData);
} else {
echo $fileOrData;
}
if ($afterDownload) {
call_user_func_array($afterDownload, []);
call_user_func($afterDownload);
}
throw new StopExecution();
}
Expand Down
193 changes: 99 additions & 94 deletions appdata/modules/Framelix/src/View/Backend/UserProfile/TwoFactor.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@

class TwoFactor extends View
{

protected string|bool $accessRole = true;

private User $storable;

public static function onJsCall(JsCall $jsCall): void
Expand All @@ -49,49 +51,49 @@ public static function onJsCall(JsCall $jsCall): void
Cookie::set(TwoFactorCode::COOKIE_NAME_SECRET, $secret, encrypted: true);
Cookie::set(TwoFactorCode::COOKIE_NAME_BACKUPCODES, $codes, encrypted: true);
?>
<div style="text-align: center">
<div><?= Lang::get('__framelix_view_backend_userprofile_2fa_enable_info__') ?></div>
<div class="framelix-spacer-x2"></div>
<framelix-button theme="primary" data-action="getcodes">
__framelix_view_backend_userprofile_2fa_download_codes__
</framelix-button>
<div class="framelix-spacer-x2"></div>
<div id="qrcode"></div>
<div class="framelix-spacer-x2"></div>
<?= $secret ?>
<div class="framelix-spacer-x2"></div>
<div><?= Lang::get('__framelix_view_backend_userprofile_2fa_enable_enter__') ?></div>
<div class="framelix-spacer-x2"></div>
<?php
$form->show();
?>
</div>
<script>
(async function () {
const container = $('#qrcode')
new QRCode(container[0], {
text: <?=JsonUtils::encode($tfa->getQRText(User::get()->email, $secret))?>,
width: Math.min(container.width(), 250),
height: Math.min(container.width(), 250),
colorDark: '#000',
colorLight: '#fff',
correctLevel: QRCode.CorrectLevel.H
})
setTimeout(function () {
container.find('img').removeAttr('style').css('max-width', '100%')
}, 10)
})()
</script>
<div style="text-align: center">
<div><?= Lang::get('__framelix_view_backend_userprofile_2fa_enable_info__') ?></div>
<div class="framelix-spacer-x2"></div>
<framelix-button theme="primary" data-action="getcodes">
__framelix_view_backend_userprofile_2fa_download_codes__
</framelix-button>
<div class="framelix-spacer-x2"></div>
<div id="qrcode"></div>
<div class="framelix-spacer-x2"></div>
<?= $secret ?>
<div class="framelix-spacer-x2"></div>
<div><?= Lang::get('__framelix_view_backend_userprofile_2fa_enable_enter__') ?></div>
<div class="framelix-spacer-x2"></div>
<?php
$form->show();
?>
</div>
<script>
(async function () {
const container = $('#qrcode')
new QRCode(container[0], {
text: <?=JsonUtils::encode($tfa->getQRText(User::get()->email, $secret))?>,
width: Math.min(container.width(), 250),
height: Math.min(container.width(), 250),
colorDark: '#000',
colorLight: '#fff',
correctLevel: QRCode.CorrectLevel.H
})
setTimeout(function () {
container.find('img').removeAttr('style').css('max-width', '100%')
}, 10)
})()
</script>
<?php
break;
case 'test':
?>
<div style="text-align: center">
<?php
$form = self::getTestForm();
$form->show();
?>
</div>
<div style="text-align: center">
<?php
$form = self::getTestForm();
$form->show();
?>
</div>
<?php
break;
case 'disable':
Expand All @@ -118,7 +120,9 @@ public static function onJsCall(JsCall $jsCall): void
$user = User::get();
$user->twoFactorBackupCodes = $codes;
$user->store();
Response::download("@" . implode("\n", $codes), "backup-codes.txt");
Response::download(function () use ($codes) {
echo implode("\n", $codes);
}, "backup-codes.txt");
}
}

Expand Down Expand Up @@ -212,74 +216,74 @@ public function showContent(): void
return;
}
?>
<framelix-alert>__framelix_view_backend_userprofile_2fa_info__</framelix-alert>
<framelix-alert>__framelix_view_backend_userprofile_2fa_info__</framelix-alert>
<?php
if ($this->storable->twoFactorSecret) {
?>
<framelix-alert>__framelix_view_backend_userprofile_2fa_disable_info__</framelix-alert>
<div class="framelix-responsive-grid-3">
<framelix-button theme="success"
data-action="disable"
icon="789">__framelix_view_backend_userprofile_2fa_disable__
</framelix-button>
<framelix-button theme="primary"
data-action="test"
icon="75a">__framelix_view_backend_userprofile_2fa_test__
</framelix-button>
<framelix-button theme="error"
data-action="regenerate"
icon="78a">__framelix_view_backend_userprofile_2fa_regenerate_codes__
</framelix-button>
</div>
<framelix-alert>__framelix_view_backend_userprofile_2fa_disable_info__</framelix-alert>
<div class="framelix-responsive-grid-3">
<framelix-button theme="success"
data-action="disable"
icon="789">__framelix_view_backend_userprofile_2fa_disable__
</framelix-button>
<framelix-button theme="primary"
data-action="test"
icon="75a">__framelix_view_backend_userprofile_2fa_test__
</framelix-button>
<framelix-button theme="error"
data-action="regenerate"
icon="78a">__framelix_view_backend_userprofile_2fa_regenerate_codes__
</framelix-button>
</div>
<?php
} else {
?>
<framelix-button theme="success" data-action="enable">__framelix_view_backend_userprofile_2fa_enable__
</framelix-button>
<framelix-button theme="success" data-action="enable">__framelix_view_backend_userprofile_2fa_enable__
</framelix-button>
<?php
}
?>
<script>
(function () {
$(document).on('click', 'framelix-button[data-action]', async function () {
switch ($(this).attr('data-action')) {
case 'enable':
await FramelixDom.includeCompiledFile('Framelix', 'js', 'qrcodejs', 'QRCode')
let name = await FramelixModal.prompt(await FramelixLang.get('__framelix_view_backend_userprofile_2fa_enable_name__'), '<?=FRAMELIX_MODULE?>').promptResult
await FramelixModal.show({
bodyContent: FramelixRequest.jsCall('<?=JsCall::getUrl(__CLASS__, 'enable')?>', { 'name': name })
})
break
case 'test':
<script>
(function () {
$(document).on('click', 'framelix-button[data-action]', async function () {
switch ($(this).attr('data-action')) {
case 'enable':
await FramelixDom.includeCompiledFile('Framelix', 'js', 'qrcodejs', 'QRCode')
let name = await FramelixModal.prompt(await FramelixLang.get('__framelix_view_backend_userprofile_2fa_enable_name__'), '<?=FRAMELIX_MODULE?>').promptResult
await FramelixModal.show({
bodyContent: FramelixRequest.jsCall('<?=JsCall::getUrl(__CLASS__, 'enable')?>', { 'name': name })
})
break
case 'test':
await FramelixModal.show({
bodyContent: FramelixRequest.jsCall('<?=JsCall::getUrl(__CLASS__, 'test')?>')
})
break
case 'disable':
if (await FramelixModal.confirm(await FramelixLang.get('__framelix_view_backend_userprofile_2fa_disable_warning__')).confirmed) {
await FramelixModal.show({
bodyContent: FramelixRequest.jsCall('<?=JsCall::getUrl(__CLASS__, 'test')?>')
bodyContent: FramelixRequest.jsCall('<?=JsCall::getUrl(__CLASS__, 'disable')?>')
})
break
case 'disable':
if (await FramelixModal.confirm(await FramelixLang.get('__framelix_view_backend_userprofile_2fa_disable_warning__')).confirmed) {
await FramelixModal.show({
bodyContent: FramelixRequest.jsCall('<?=JsCall::getUrl(__CLASS__, 'disable')?>')
})
}
break
case 'getcodes':
}
break
case 'getcodes':
await FramelixRequest.jsCall('<?=JsCall::getUrl(
__CLASS__,
'getcodes'
)?>').getResponseData()
break
case 'regenerate':
if (await FramelixModal.confirm(await FramelixLang.get('__framelix_view_backend_userprofile_2fa_regenerate_codes_warning__')).confirmed) {
await FramelixRequest.jsCall('<?=JsCall::getUrl(
__CLASS__,
'getcodes'
'regenerate'
)?>').getResponseData()
break
case 'regenerate':
if (await FramelixModal.confirm(await FramelixLang.get('__framelix_view_backend_userprofile_2fa_regenerate_codes_warning__')).confirmed) {
await FramelixRequest.jsCall('<?=JsCall::getUrl(
__CLASS__,
'regenerate'
)?>').getResponseData()
}
break
}
})
})()
</script>
}
break
}
})
})()
</script>
<?php
}

Expand All @@ -295,4 +299,5 @@ public function getPasswordVerifyForm(): Form

return $form;
}

}
18 changes: 11 additions & 7 deletions appdata/modules/Framelix/src/View/MPdfBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

class MPdfBase extends View
{

/**
* The x position where the address must start in case of a windowed post cuvert
*/
Expand Down Expand Up @@ -49,7 +50,7 @@ public function init(
"dejavusansmono",
"freesans",
"freeserif",
"freemono"
"freemono",
])]
string $defaultFont = 'helvetica',
int $defaultFontSize = 12,
Expand Down Expand Up @@ -149,9 +150,7 @@ public function stopHtmlAndWrite(): void
* Submit is done via post
* @param Form $form
*/
public function setOptionsFormFields(Form $form): void
{
}
public function setOptionsFormFields(Form $form): void {}

/**
* Save the pdf to disk
Expand Down Expand Up @@ -189,12 +188,15 @@ public function output(string $filename): never
*/
public function download(string $filename): never
{
Response::download("@" . $this->getFiledata(), $filename);
Response::download(function () {
echo $this->getFiledata();
}, $filename);
}

/**
* Show header html
* Available placeholders in html text: {PAGENO} (Current Page), $this->pdf->aliasNbPg (Number of total pages), $this->pdf->aliasNbPgGp
* Available placeholders in html text: {PAGENO} (Current Page), $this->pdf->aliasNbPg (Number of total pages),
* $this->pdf->aliasNbPgGp
* @return bool Return true when the header should be drawn, false to skip the header for the current page
*/
public function showHeaderHtml(): bool
Expand All @@ -204,7 +206,8 @@ public function showHeaderHtml(): bool

/**
* Show footer html
* Available placeholders in html text: {PAGENO} (Current Page), $this->pdf->aliasNbPg (Number of total pages), $this->pdf->aliasNbPgGp
* Available placeholders in html text: {PAGENO} (Current Page), $this->pdf->aliasNbPg (Number of total pages),
* $this->pdf->aliasNbPgGp
* @return bool Return true when the footer should be drawn, false to skip the footer for the current page
*/
public function showFooterHtml(): bool
Expand All @@ -223,4 +226,5 @@ private function getOptionsForm(): Form
$this->setOptionsFormFields($form);
return $form;
}

}
6 changes: 0 additions & 6 deletions appdata/modules/FramelixTests/tests/Network/ResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,6 @@ final class ResponseTest extends TestCase

public function tests(): void
{
Buffer::start();
try {
Response::download("@filecontent", "foo");
} catch (StopExecution) {
$this->assertSame("filecontent", Buffer::get());
}
Buffer::start();
$this->assertExceptionOnCall(function () {
Response::download(__FILE__, "foo", null, function () {});
Expand Down

0 comments on commit 979c34c

Please sign in to comment.