dbxapp 4.1.3
CMS, Shop, Workflows und modulare Geschäftsanwendungen
Loading...
Searching...
No Matches
modules/dbxDownLoad/dbxDownload.class.php
Go to the documentation of this file.
1<?php
3
5
6 private function h($value) {
7 return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
8 }
9
10 private function tpl(string $name, array $data = array()) {
11 return dbx()->get_system_obj('dbxTPL')->get_tpl('dbxDownLoad|' . $name, $data);
12 }
13
14 private function config(string $key, $default = '') {
15 return dbx()->get_config('dbxDownLoad', $key, $default);
16 }
17
18 private function mail_from_param() {
19 $from = trim((string)$this->config('mail_from'));
20 $fromName = trim((string)$this->config('mail_from_name'));
21 return ($from !== '') ? array('email' => $from, 'name' => $fromName) : '';
22 }
23
24 private function mail_options(array $extra = array()) {
25 $profile = trim((string)$this->config('mail_profile'));
26 if ($profile !== '') {
27 $extra['mail_profile'] = $profile;
28 }
29 return $extra;
30 }
31
32 private function module_url(array $params = array()): string {
33 $base = rtrim((string)dbx()->get_base_url(), '/') . '/';
34 $query = array_merge(array('dbx_modul' => 'dbxDownLoad'), $params);
35 return $base . '?' . http_build_query($query, '', '&', PHP_QUERY_RFC3986);
36 }
37
38 private function secret(): string {
39 $secret = trim((string)$this->config('token_secret'));
40 if ($secret !== '') {
41 return $secret;
42 }
43
44 $config = dbx()->get_config('dbxDownLoad');
45 $secret = bin2hex(random_bytes(32));
46 $config['token_secret'] = $secret;
47 dbx()->set_config('dbxDownLoad', $config);
48 return $secret;
49 }
50
51 private function base64url(string $bytes): string {
52 return rtrim(strtr(base64_encode($bytes), '+/', '-_'), '=');
53 }
54
55 private function base64url_decode(string $value): string {
56 $value = strtr($value, '-_', '+/');
57 $pad = strlen($value) % 4;
58 if ($pad) {
59 $value .= str_repeat('=', 4 - $pad);
60 }
61 $decoded = base64_decode($value, true);
62 return is_string($decoded) ? $decoded : '';
63 }
64
65 private function create_token(string $name, string $email): string {
66 $ttl = max(1, (int)$this->config('download_ttl_hours', 48));
67 $payload = array(
68 'name' => $name,
69 'email' => $email,
70 'iat' => time(),
71 'exp' => time() + ($ttl * 3600),
72 'nonce' => bin2hex(random_bytes(8)),
73 );
74 $body = $this->base64url(json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
75 $sig = $this->base64url(hash_hmac('sha256', $body, $this->secret(), true));
76 return $body . '.' . $sig;
77 }
78
79 private function read_token(string $token): array {
80 $parts = explode('.', trim($token), 2);
81 if (count($parts) !== 2 || $parts[0] === '' || $parts[1] === '') {
82 return array();
83 }
84
85 $expected = $this->base64url(hash_hmac('sha256', $parts[0], $this->secret(), true));
86 if (!hash_equals($expected, $parts[1])) {
87 return array();
88 }
89
90 $payload = json_decode($this->base64url_decode($parts[0]), true);
91 if (!is_array($payload) || (int)($payload['exp'] ?? 0) < time()) {
92 return array();
93 }
94
95 return $payload;
96 }
97
98 private function download_file(): string {
99 $file = trim((string)$this->config('download_zip', 'files/download/dbxapp-demo.zip'));
100 $file = str_replace('\\', '/', $file);
101 if ($file === '') {
102 return '';
103 }
104 if (!preg_match('#^[A-Za-z]:/#', $file) && strpos($file, '/') !== 0) {
105 $file = dbx()->get_base_dir() . ltrim($file, '/');
106 }
107 return dbx()->os_path($file);
108 }
109
110 private function build_form() {
111 $form = dbx()->get_system_obj('dbxForm');
112 $form->init('download-link', 'download-form');
113 $form->_fd = 'dbxDownLoad|download-link';
114 $form->load_fd_messages();
115 $form->_action = '?dbx_modul=dbxDownLoad&dbx_run1=send_link';
116 $form->_try_reset = 6;
117 $form->_try_max = 5;
118 $form->_try_msg = $form->get_fd_message('try_limit');
119 $form->set_msg_info('');
120 $form->set_msg_error($form->get_fd_message('validation_error'));
121 $form->set_msg_ok($form->get_fd_message('send_success'));
122 // Den von dbxForm erzeugten Security-Wert beibehalten.
123 $form->_data = array_merge($form->_data, array(
124 'name' => trim((string)($_POST['name'] ?? $_REQUEST['name'] ?? '')),
125 'email' => trim((string)($_POST['email'] ?? $_REQUEST['email'] ?? '')),
126 ));
127 $form->add_module_bar(
128 $form->get_fd_message('bar_title'),
129 'bi-download',
130 $form->get_fd_message('bar_subtitle')
131 );
132 $form->prepare_form_shell(array('class' => 'dbx-download-form'));
133 $form->add_fld('name');
134 $form->add_fld('email');
135 return $form;
136 }
137
138 private function render_form() {
139 return $this->build_form()->run();
140 }
141
142 private function spam_reason(string $name, string $email, string $subject = ''): string {
143 $mail = dbx()->get_system_obj('dbxMail');
144 if (!is_object($mail) || !method_exists($mail, 'spam_reason_for_text')) {
145 return '';
146 }
147
148 return (string)$mail->spam_reason_for_text(implode("\n", array($name, $email, $subject)));
149 }
150
151 private function save_demo_contact_request(string $name, string $email): int {
152 try {
153 $db = dbx()->get_system_obj('dbxDB');
154 $message = "Demo-Link wurde angefordert.\n\n"
155 . "Name: " . $name . "\n"
156 . "E-Mail: " . $email . "\n"
157 . "Quelle: dbxDownLoad\n"
158 . "Zeit: " . date('Y-m-d H:i:s');
159
160 $values = array(
161 'uid' => (int)dbx()->user(),
162 'status' => 'open',
163 'priority' => 'normal',
164 'name' => $name,
165 'email' => $email,
166 'phone' => '',
167 'subject' => 'Demo-Link angefordert',
168 'message' => $message,
169 'request_ip' => (string)($_SERVER['REMOTE_ADDR'] ?? ''),
170 'request_user_agent' => (string)($_SERVER['HTTP_USER_AGENT'] ?? ''),
171 'mail_sent' => 0,
172 'confirm_mail_sent' => 0,
173 'user_hidden' => 0,
174 );
175
176 $ok = $db->insert('dbxContact|contactRequest', $values, 0, 1, 1, 1);
177 $ticketId = $ok > 0 ? (int)$db->get_insert_id() : 0;
178
179 if ($ticketId > 0) {
180 try {
181 dbx()->get_include_obj('dbxContactTicket', 'dbxContact');
182 if (class_exists('\\dbx\\dbxContact\\dbxContactTicket')) {
184 'author_uid' => (int)dbx()->user(),
185 'author_type' => 'requester',
186 'message_type' => 'request',
187 'visibility' => 'public',
188 'body' => $message,
189 'status_to' => 'open',
190 ));
192 }
193 } catch (\Throwable $e) {
194 dbx()->sys_msg('warning', 'dbxDownLoad', 'contact_request', 'Kontakt-Nachricht konnte nicht ergaenzt werden', $e->getMessage());
195 }
196 }
197
198 return $ticketId;
199 } catch (\Throwable $e) {
200 dbx()->sys_msg('error', 'dbxDownLoad', 'contact_request', 'Demo-Kontaktanfrage konnte nicht gespeichert werden', $e->getMessage());
201 return 0;
202 }
203 }
204
205 private function send_link() {
206 $form = $this->build_form();
207
208 if (!$form->submit()) {
209 return $form->run();
210 }
211
212 if ($form->errors()) {
213 return $form->run();
214 }
215
216 $name = trim((string)$form->get_post_data('name', '', 'varchar|min=2|max=160'));
217 $email = trim((string)$form->get_post_data('email', '', 'email|min=6|max=180'));
218 $subject = trim((string)$this->config('mail_subject', 'Ihr dbxapp Demo-Download'));
219
220 $spamReason = $this->spam_reason($name, $email, $subject);
221 if ($spamReason !== '') {
222 $form->set_msg_error($form->get_fd_message('spam_error'));
223 $form->add_fld_error(
224 'name',
225 $form->get_fd_message('spam_field_error')
226 );
227 dbx()->sys_msg('security', 'dbxDownLoad', 'spam_guard', 'Demo-Link blockiert', $spamReason . ' email=' . $email);
228 return $form->run();
229 }
230
231 $token = $this->create_token($name, $email);
232 $url = $this->module_url(array('dbx_run1' => 'download', 'token' => $token));
233 $contactId = $this->save_demo_contact_request($name, $email);
234 $html = $this->tpl('mail-download-link', array(
235 'name' => $this->h($name),
236 'download_url' => $this->h($url),
237 'ttl_hours' => (int)$this->config('download_ttl_hours', 48),
238 ));
239 $text = "Hallo " . $name . "\n\n"
240 . "hier ist Ihr dbxapp Demo-Download:\n" . $url . "\n\n"
241 . "Der Link ist " . (int)$this->config('download_ttl_hours', 48) . " Stunden gueltig.\n";
242
243 $ok = dbx()->send_mail($this->mail_from_param(), $email, $subject, $html, 'html', array(), $this->mail_options(array('text' => $text)));
244 if (!$ok) {
245 dbx()->sys_msg('error', 'dbxDownLoad', 'send_link', 'Download-Mail konnte nicht gesendet werden', $email);
246 $form->set_msg_error($form->get_fd_message('mail_error'));
247 $form->add_fld_error(
248 'email',
249 $form->get_fd_message('email_field_error')
250 );
251 return $form->run();
252 }
253
254 dbx()->sys_msg('info', 'dbxDownLoad', 'send_link', 'Download-Link versendet', $email . ' contact=' . $contactId);
255 $form->set_msg_ok($form->format_fd_message(
256 'sent_to',
257 array('email' => $this->h($email))
258 ));
259 return $form->run();
260 }
261
262 private function download_page() {
263 $token = trim((string)($_REQUEST['token'] ?? ''));
264 $payload = $this->read_token($token);
265 if (!$payload) {
266 return $this->tpl('download-invalid', array());
267 }
268
269 $file = $this->download_file();
270 $ready = is_file($file) && is_readable($file);
271 return $this->tpl('download-ready', array(
272 'name' => $this->h($payload['name'] ?? ''),
273 'email' => $this->h($payload['email'] ?? ''),
274 'expires' => date('d.m.Y H:i', (int)($payload['exp'] ?? time())),
275 'download_url' => $ready ? $this->h($this->module_url(array('dbx_run1' => 'file', 'token' => $token))) : '#',
276 'download_disabled' => $ready ? '' : 'disabled aria-disabled="true" tabindex="-1"',
277 'download_btn_class' => $ready ? 'btn btn-primary' : 'btn btn-secondary disabled',
278 'download_note' => $ready
279 ? 'Die Demo-ZIP und die Hinweise zur Installation stehen bereit.'
280 : 'Die Demo-ZIP ist auf diesem System noch nicht hinterlegt. Erwarteter Pfad: ' . $this->h((string)$this->config('download_zip')),
281 ));
282 }
283
284 private function stream_file() {
285 $payload = $this->read_token(trim((string)($_REQUEST['token'] ?? '')));
286 $file = $this->download_file();
287 if (!$payload || !is_file($file) || !is_readable($file)) {
288 http_response_code(404);
289 echo 'Download nicht verfuegbar.';
290 exit;
291 }
292
293 $name = trim((string)$this->config('download_name', basename($file)));
294 if ($name === '') {
295 $name = basename($file);
296 }
297
298 dbx()->sys_msg('info', 'dbxDownLoad', 'file', 'Demo-ZIP geladen', (string)($payload['email'] ?? ''));
299 while (ob_get_level()) {
300 ob_end_clean();
301 }
302 header('Content-Type: application/zip');
303 header('Content-Disposition: attachment; filename="' . str_replace('"', '', $name) . '"');
304 header('Content-Length: ' . filesize($file));
305 header('Cache-Control: private, no-store, max-age=0');
306 readfile($file);
307 exit;
308 }
309
310 public function run() {
311 $run = (string)dbx()->get_modul_var('dbx_run1', 'form', 'parameter');
312 switch ($run) {
313 case 'send_link':
314 return $this->send_link();
315 case 'download':
316 return $this->download_page();
317 case 'file':
318 return $this->stream_file();
319 case 'form':
320 default:
321 return $this->render_form();
322 }
323 }
324}
325?>
static touch($db, int $ticketId, array $values=array())
static addMessage($db, int $ticketId, array $data)
user($key='id')
Liest einen Wert des aktuellen Benutzers.
Definition dbxApi.php:1305
get_base_url()
Liefert die Basis-URL des aktuellen Requests.
Definition dbxApi.php:1488
$_SERVER['REQUEST_METHOD']
if($resolved !==$expectedBase . 'files/test/') $config
foreach( $iterator as $file) if(! $groups) $expected
exit
Definition index.php:146
DBX schema administration.
$profile
Definition run.php:6