From 643991c3fdcdab33f2b6c4232002ab044de5da54 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 28 Jun 2022 17:29:31 +0900 Subject: [PATCH] Update Debug\Support, add Create\Email update debug support to add html escape for html strings on request. Default is keep as is. debugString gets new third parameter for this as bool flag. Add Create\Email to send basic text emails to several too addresses. Content replace in subject and body is possible with {} entries. Default encoding is UTF-8 but others can be set and content will be converted to this. The dynamic replace works on all data or can be set per receiver. --- 4dev/tests/CoreLibsCreateEmailTest.php | 204 +++++++++++++++++++ 4dev/tests/CoreLibsCreateHashTest.php | 6 +- 4dev/tests/CoreLibsDebugSupportTest.php | 76 +++++-- www/admin/class_test.create_email.php | 123 +++++++++++ www/admin/class_test.debug.php | 1 + www/admin/class_test.php | 1 + www/lib/CoreLibs/Create/Email.php | 238 ++++++++++++++++++++++ www/lib/CoreLibs/Debug/Support.php | 25 ++- www/vendor/composer/autoload_classmap.php | 1 + www/vendor/composer/autoload_static.php | 1 + 10 files changed, 652 insertions(+), 24 deletions(-) create mode 100644 4dev/tests/CoreLibsCreateEmailTest.php create mode 100644 www/admin/class_test.create_email.php create mode 100644 www/lib/CoreLibs/Create/Email.php diff --git a/4dev/tests/CoreLibsCreateEmailTest.php b/4dev/tests/CoreLibsCreateEmailTest.php new file mode 100644 index 00000000..95bffc8e --- /dev/null +++ b/4dev/tests/CoreLibsCreateEmailTest.php @@ -0,0 +1,204 @@ + DIRECTORY_SEPARATOR . 'tmp', + 'file_id' => 'CoreLibs-Create-Email-Test', + 'debug_all' => true, + 'echo_all' => false, + 'print_all' => true, + ]); + } + + /** + * Undocumented function + * + * @return array + */ + public function encodeEmailNameProvider(): array + { + // 0: email + // 1: name + // 2: encoding + // 3: expected + return [ + 'all empty' => [ + '', + null, + null, + '' + ], + 'email only' => [ + 'test@test.com', + null, + null, + 'test@test.com' + ], + 'email and name' => [ + 'test@test.com', + 'Test Name', + null, + '"Test Name" ' + ], + 'name in mime encoded, default UTF-8' => [ + 'test@test.com', + '日本語', + null, + '"=?UTF-8?B?5pel5pys6Kqe?=" ' + ], + 'name in mime encoded, UTF-8 parameter' => [ + 'test@test.com', + '日本語', + 'UTF-8', + '"=?UTF-8?B?5pel5pys6Kqe?=" ' + ], + // does internal UTF-8 to ISO-2022-JP convert + 'encoding in ISO-2022-JP' => [ + 'test@test.com', + '日本語', + 'ISO-2022-JP', + '"=?ISO-2022-JP?B?GyRCRnxLXA==?=" ' + ] + ]; + } + + /** + * Undocumented function + * + * @dataProvider encodeEmailNameProvider + * @testdox encode email $email, name $name, encoding $encoding will be $expected [$_dataName] + * + * @return void + */ + public function testEncodeEmailName( + string $email, + ?string $name, + ?string $encoding, + string $expected + ): void { + if ($name === null && $encoding === null) { + $encoded_email = \CoreLibs\Create\Email::encodeEmailName($email); + } elseif ($encoding === null) { + $encoded_email = \CoreLibs\Create\Email::encodeEmailName($email, $name); + } else { + $encoded_email = \CoreLibs\Create\Email::encodeEmailName($email, $name, $encoding); + } + $this->assertEquals( + $expected, + $encoded_email + ); + } + + public function sendEmailProvider(): array + { + // 0: subject + // 1: body + // 2: from email + // 3: from name ('') + // 4: array for to email + // 5: replace content ([]/null) + // 6: encoding (UTF-8/null) + // 8: return status + // 9: expected content + return [ + 'all empty, fail -1' => [ + 'subject' => '', + 'body' => '', + 'from_email' => '', + 'from_name' => '', + 'to_email' => [], + 'replace' => null, + 'encoding' => null, + 'expected_status' => -1, + 'expected_content' => [], + ], + 'missing to entry, fail -2' => [ + 'subject' => 'SUBJECT', + 'body' => 'BODY', + 'from_email' => 'test@test.com', + 'from_name' => '', + 'to_email' => [], + 'replace' => null, + 'encoding' => null, + 'expected_status' => -2, + 'expected_content' => [], + ] + ]; + } + + /** + * Undocumented function + * + * @dataProvider sendEmailProvider + * @testdox email sending with expected status $expected_status [$_dataName] + * + * @param string $subject + * @param string $body + * @param string $from_email + * @param string $from_name + * @param array $to_email + * @param array|null $replace + * @param string|null $encoding + * @param int $expected_status + * @param array $expected_content + * @return void + */ + public function testSendEmail( + string $subject, + string $body, + string $from_email, + string $from_name, + array $to_email, + ?array $replace, + ?string $encoding, + int $expected_status, + array $expected_content + ): void { + if ($replace === null) { + $replace = []; + } + if ($encoding === null) { + $encoding = 'UTF-8'; + } + $status = \CoreLibs\Create\Email::sendEmail( + $subject, + $body, + $from_email, + $from_name, + $to_email, + $replace, + $encoding, + true, + self::$log + ); + $this->assertEquals( + $expected_status, + $status, + 'Assert sending status' + ); + // assert content: must load JSON from log file? + } +} + +// __END__ diff --git a/4dev/tests/CoreLibsCreateHashTest.php b/4dev/tests/CoreLibsCreateHashTest.php index 0ef7393b..fcab739e 100644 --- a/4dev/tests/CoreLibsCreateHashTest.php +++ b/4dev/tests/CoreLibsCreateHashTest.php @@ -13,7 +13,11 @@ use PHPUnit\Framework\TestCase; */ final class CoreLibsCreateHashTest extends TestCase { - + /** + * Undocumented function + * + * @return array + */ public function hashData(): array { return [ diff --git a/4dev/tests/CoreLibsDebugSupportTest.php b/4dev/tests/CoreLibsDebugSupportTest.php index 306741ba..7d8c0229 100644 --- a/4dev/tests/CoreLibsDebugSupportTest.php +++ b/4dev/tests/CoreLibsDebugSupportTest.php @@ -120,6 +120,16 @@ final class CoreLibsDebugSupportTest extends TestCase null, 'a string', ], + 'string with html chars, encode' => [ + 'a string with <> &', + true, + 'a string with <> &', + ], + 'string with html chars' => [ + 'a string with <> &', + null, + 'a string with <> &', + ], 'a number' => [ 1234, null, @@ -180,22 +190,41 @@ final class CoreLibsDebugSupportTest extends TestCase */ public function debugStringProvider(): array { + // 0: input string + // 1: replace + // 2: html flag + // 3: expected return [ 'null string, default' => [ - 0 => null, - 1 => null, - 2 => '-' + null, + null, + null, + '-' ], 'empty string, ... replace' => [ - 0 => '', - 1 => '...', - 2 => '...' + '', + '...', + null, + '...' ], 'filled string' => [ - 0 => 'some string', - 1 => null, - 2 => 'some string' - ] + 'some string', + null, + null, + 'some string' + ], + 'string with html chars, encode' => [ + 'a string with <> &', + '-', + true, + 'a string with <> &', + ], + 'string with html chars' => [ + 'a string with <> &', + '-', + null, + 'a string with <> &', + ], ]; } @@ -366,12 +395,14 @@ final class CoreLibsDebugSupportTest extends TestCase if (count($compare) == 10) { $this->assertEquals( $expected, - \CoreLibs\Debug\Support::getCallerMethodList() + \CoreLibs\Debug\Support::getCallerMethodList(), + 'assert expected 10' ); } else { $this->assertEquals( $expected_group, - \CoreLibs\Debug\Support::getCallerMethodList() + \CoreLibs\Debug\Support::getCallerMethodList(), + 'assert expected group' ); } } @@ -398,24 +429,33 @@ final class CoreLibsDebugSupportTest extends TestCase * * @cover ::debugString * @dataProvider debugStringProvider - * @testdox debugString $input with replace $replace will be $expected [$_dataName] + * @testdox debugString $input with replace $replace and html $flag will be $expected [$_dataName] * * @param string|null $input * @param string|null $replace - * @param string $expected + * @param bool|null $flag + * @param string $expected * @return void */ - public function testDebugString(?string $input, ?string $replace, string $expected) + public function testDebugString(?string $input, ?string $replace, ?bool $flag, string $expected): void { - if ($replace === null) { + if ($replace === null && $flag === null) { $this->assertEquals( $expected, - \CoreLibs\Debug\Support::debugString($input) + \CoreLibs\Debug\Support::debugString($input), + 'assert all default' + ); + } elseif ($flag === null) { + $this->assertEquals( + $expected, + \CoreLibs\Debug\Support::debugString($input, $replace), + 'assert flag default' ); } else { $this->assertEquals( $expected, - \CoreLibs\Debug\Support::debugString($input, $replace) + \CoreLibs\Debug\Support::debugString($input, $replace, $flag), + 'assert all set' ); } } diff --git a/www/admin/class_test.create_email.php b/www/admin/class_test.create_email.php new file mode 100644 index 00000000..40f77554 --- /dev/null +++ b/www/admin/class_test.create_email.php @@ -0,0 +1,123 @@ + BASE . LOG, + 'file_id' => $LOG_FILE_ID, + // add file date + 'print_file_date' => true, + // set debug and print flags + 'debug_all' => $DEBUG_ALL, + 'echo_all' => $ECHO_ALL, + 'print_all' => $PRINT_ALL, +]); + +// define a list of from to color sets for conversion test + +$PAGE_NAME = 'TEST CLASS: CREATE EMAIL'; +print ""; +print "" . $PAGE_NAME . ""; +print ""; +print ''; +print '

' . $PAGE_NAME . '

'; + +$from_name = '日本語'; +$from_email = 'test@test.com'; +print "SET: $from_name / $from_email: " + . Html::htmlent(Email::encodeEmailName($from_email, $from_name)) . "
"; + +$status = Email::sendEmail( + 'TEST', + 'BODY', + 'test@test.com', + 'Test Name', + [ + [ + 'name' => 'To 1', + 'email' => 'to1@test.com' + ], + ], + [], + 'UTF-8', + true, + $log +); +print "SENDING: " . $status . "
"; +$status = Email::sendEmail( + 'TEST {REPLACE}', + 'BODY {OTHER}', + 'test@test.com', + 'Test Name', + [ + [ + 'name' => 'To 1-A', + 'email' => 'to1-a@test.com' + ], + [ + 'name' => 'To 2-A', + 'email' => 'to2-a@test.com', + 'replace' => [ + 'OTHER' => '--FOR 2 A other--' + ] + ], + ], + [ + 'REPLACE' => '**replaced**', + 'OTHER' => '**other**' + ], + 'UTF-8', + true, + $log +); +print "SENDING: " . $status . "
"; + +$status = Email::sendEmail( + 'TEST', + 'BODY', + 'test@test.com', + 'Test Name', + ['a@a.com', 'b@b.com'], + [], + 'UTF-8', + true, + $log +); +print "SENDING: " . $status . "
"; + +// error message +print $log->printErrorMsg(); + +print ""; + +// __END__ diff --git a/www/admin/class_test.debug.php b/www/admin/class_test.debug.php index 694a26b1..c8b037e7 100644 --- a/www/admin/class_test.debug.php +++ b/www/admin/class_test.debug.php @@ -71,6 +71,7 @@ print "S::PRINTBOOL(name): " . DebugSupport::printBool(true, 'Name') . "
"; print "S::PRINTBOOL(name, ok): " . DebugSupport::printBool(true, 'Name', 'ok') . "
"; print "S::PRINTBOOL(name, ok, not): " . DebugSupport::printBool(false, 'Name', 'ok', 'not') . "
"; print "S::DEBUSTRING(s): " . DebugSupport::debugString('SET') . "
"; +print "S::DEBUSTRING(s>): " . DebugSupport::debugString('') . "
"; print "S::DEBUSTRING(''): " . DebugSupport::debugString('') . "
"; print "S::DEBUSTRING(,s): " . DebugSupport::debugString(null, '{-}') . "
"; diff --git a/www/admin/class_test.php b/www/admin/class_test.php index cfd84749..4c8c685b 100644 --- a/www/admin/class_test.php +++ b/www/admin/class_test.php @@ -63,6 +63,7 @@ print ''; print ''; print ''; print ''; +print ''; print ''; print ''; print ''; diff --git a/www/lib/CoreLibs/Create/Email.php b/www/lib/CoreLibs/Create/Email.php new file mode 100644 index 00000000..37d06206 --- /dev/null +++ b/www/lib/CoreLibs/Create/Email.php @@ -0,0 +1,238 @@ +'; + } else { + return $email; + } + } + + /** + * Subject/Body replace sub function + * + * @param string $subject Subject string, in UTF-8 + * @param string $body Body string, in UTF-8 + * @param array $replace Replace the array as key -> value, in UTF-8 + * @param string $encoding Encoding for subject encode mime header + * @return array Pos 0: Subject, Pos 1: Body + */ + private static function replaceContent( + string $subject, + string $body, + array $replace, + string $encoding + ): array { + foreach (['subject', 'body'] as $element) { + $$element = str_replace( + array_map( + function ($key) { + return '{' . $key . '}'; + }, + array_keys($replace) + ), + array_values($replace), + $$element + ); + } + // if encoding is NOT UTF-8 convert to target + if ($encoding != 'UTF-8') { + $out_subject = mb_convert_encoding($out_subject, $encoding, 'UTF-8'); + $out_body = mb_convert_encoding($out_body, $encoding, 'UTF-8'); + } + // we need to encodde the subject + $subject = mb_encode_mimeheader($subject, $encoding); + return [$subject, $body]; + } + + /** + * Send plain text email with possible to replace subject/body data + * either global or per to email set. + * replace to tags are in {} in the subject or body + * + * @param string $subject Mail subject, mandatory, in UTF-8 + * @param string $body Mail body, mandatory, in UTF-8 + * @param string $from_email From email, mandatory + * @param string $from_name From email name, in UTF-8 + * if empty '' then not set + * @param array $send_to_emails to email or array for email/replace + * If array: name/email/replace[key,value] + * name and replace must be in UTF-8 + * At least one must be set + * @param array $replace_content Subject/Body replace as + * search -> replace, in UTF-8 + * @param string $encoding E-Mail encoding, default UTF-8 + * @param bool $test test flag, default off + * @param \CoreLibs\Debug\Logging|null $log Logging class, + * only used if test flag is true + * @return int 2 test only, no sent + * 1 for ok, + * 0 for send not ok + * -1 for nothing set (emails, subject, body) + * -2 for empty to list + */ + public static function sendEmail( + string $subject, + string $body, + string $from_email, + string $from_name, + array $send_to_emails, + array $replace_content = [], + string $encoding = 'UTF-8', + bool $test = false, + ?\CoreLibs\Debug\Logging $log = null + ): int { + /** @var array */ + $to_emails = []; + /** @var array> */ + $to_replace = []; + /** @var string */ + $out_subject = $subject; + /** @var string */ + $out_body = $body; + // check basic set + if (empty($subject) || empty($body) || empty($from_email)) { + return -1; + } + // if not one valid to, abort + foreach ($send_to_emails as $to_email) { + // to_email can be string, then only to email + // else expect 'email' & 'name' + if ( + is_array($to_email) && + isset($to_email['name']) && isset($to_email['email']) + ) { + $_to_email = self::encodeEmailName( + $to_email['email'], + $to_email['name'], + $encoding + ); + $to_emails[] = $_to_email; + // if we have to replacement, this override replace content + if (isset($to_email['replace']) && count($to_email['replace'])) { + // merge with original replace content, + // to data will override original data + $to_replace[$_to_email] = array_merge( + $replace_content, + $to_email['replace'] + ); + } + } elseif (is_string($to_email)) { + $to_emails[] = $to_email; + } + } + if (!count($to_emails)) { + return -2; + } + + // the email headers needed + $headers = [ + 'From' => self::encodeEmailName($from_email, $from_name, $encoding), + 'Content-type' => "text/plain; charset=" . $encoding, + 'MIME-Version' => "1.0", + ]; + + // if we have a replace string, we need to do replace run + // only if there is no dedicated to replace + if (count($replace_content) && !count($to_replace)) { + list($out_subject, $out_body) = self::replaceContent( + $subject, + $body, + $replace_content, + $encoding + ); + } + + $mail_sent_status = 1; + // send the email + foreach ($to_emails as $to_email) { + // if there is a to replace, if not use the original replace content + if (count($to_replace)) { + $_replace = []; + if (!empty($to_replace[$to_email])) { + $_replace = $to_replace[$to_email]; + } elseif (count($replace_content)) { + $_replace = $replace_content; + } + if (count($_replace)) { + list($out_subject, $out_body) = self::replaceContent( + $subject, + $body, + $_replace, + $encoding + ); + } + } + if ($test === false) { + $status = mail($to_email, $out_subject, $out_body, $headers); + } else { + if ($log instanceof \CoreLibs\Debug\Logging) { + // build debug strings: convert to UTF-8 if not utf-8 + $log->debug('SEND EMAIL', 'HEADERS: ' . $log->prAr($headers) . ', ' + . 'TO: ' . $to_email . ', ' + . 'SUBJECT: ' . $out_subject . ', ' + . 'BODY: ' . ($encoding == 'UTF-8' ? + $out_body : + mb_convert_encoding($out_body, 'UTF-8', $encoding))); + $log->debug('SEND EMAIL JSON', json_encode([ + 'header' => $headers, + 'to' => $to_email, + 'subject' => $out_subject, + 'body' => ($encoding == 'UTF-8' ? + $out_body : + mb_convert_encoding($out_body, 'UTF-8', $encoding)) + ])); + } + $mail_sent_status = 2; + } + if (!$status) { + $mail_sent_status = 0; + } + } + return $mail_sent_status; + } +} + +// __END__ diff --git a/www/lib/CoreLibs/Debug/Support.php b/www/lib/CoreLibs/Debug/Support.php index 5d2ba60b..36af4a39 100644 --- a/www/lib/CoreLibs/Debug/Support.php +++ b/www/lib/CoreLibs/Debug/Support.php @@ -8,6 +8,8 @@ declare(strict_types=1); namespace CoreLibs\Debug; +use CoreLibs\Convert\Html; + class Support { /** @@ -89,7 +91,7 @@ class Support * Debug\Logging compatible output * * @param mixed $mixed - * @param bool $no_html set to true to use ##HTMLPRE## + * @param bool $no_html set to true to use ##HTMLPRE##or html escape * @return string */ public static function printToString($mixed, bool $no_html = false): string @@ -103,6 +105,12 @@ class Support } elseif (is_array($mixed)) { // use the pre one OR debug one return self::printAr($mixed, $no_html); + } elseif (is_string($mixed)) { + if ($no_html) { + return Html::htmlent((string)$mixed); + } else { + return (string)$mixed; + } } else { // should be int/float/string return (string)$mixed; @@ -190,12 +198,19 @@ class Support * @param string $replace [default '-'] What to replace the empty string with * @return string String itself or the replaced value */ - public static function debugString(?string $string, string $replace = '-'): string - { + public static function debugString( + ?string $string, + string $replace = '-', + bool $no_html = false + ): string { if (empty($string)) { - return $replace; + $string = $replace; + } + if ($no_html) { + return Html::htmlent($string); + } else { + return $string; } - return $string; } } diff --git a/www/vendor/composer/autoload_classmap.php b/www/vendor/composer/autoload_classmap.php index c7f04f9e..6abe21a4 100644 --- a/www/vendor/composer/autoload_classmap.php +++ b/www/vendor/composer/autoload_classmap.php @@ -27,6 +27,7 @@ return array( 'CoreLibs\\Convert\\Math' => $baseDir . '/lib/CoreLibs/Convert/Math.php', 'CoreLibs\\Convert\\MimeAppName' => $baseDir . '/lib/CoreLibs/Convert/MimeAppName.php', 'CoreLibs\\Convert\\MimeEncode' => $baseDir . '/lib/CoreLibs/Convert/MimeEncode.php', + 'CoreLibs\\Create\\Email' => $baseDir . '/lib/CoreLibs/Create/Email.php', 'CoreLibs\\Create\\Hash' => $baseDir . '/lib/CoreLibs/Create/Hash.php', 'CoreLibs\\Create\\RandomKey' => $baseDir . '/lib/CoreLibs/Create/RandomKey.php', 'CoreLibs\\Create\\Session' => $baseDir . '/lib/CoreLibs/Create/Session.php', diff --git a/www/vendor/composer/autoload_static.php b/www/vendor/composer/autoload_static.php index d9cb4fc2..e150912e 100644 --- a/www/vendor/composer/autoload_static.php +++ b/www/vendor/composer/autoload_static.php @@ -92,6 +92,7 @@ class ComposerStaticInit10fe8fe2ec4017b8644d2b64bcf398b9 'CoreLibs\\Convert\\Math' => __DIR__ . '/../..' . '/lib/CoreLibs/Convert/Math.php', 'CoreLibs\\Convert\\MimeAppName' => __DIR__ . '/../..' . '/lib/CoreLibs/Convert/MimeAppName.php', 'CoreLibs\\Convert\\MimeEncode' => __DIR__ . '/../..' . '/lib/CoreLibs/Convert/MimeEncode.php', + 'CoreLibs\\Create\\Email' => __DIR__ . '/../..' . '/lib/CoreLibs/Create/Email.php', 'CoreLibs\\Create\\Hash' => __DIR__ . '/../..' . '/lib/CoreLibs/Create/Hash.php', 'CoreLibs\\Create\\RandomKey' => __DIR__ . '/../..' . '/lib/CoreLibs/Create/RandomKey.php', 'CoreLibs\\Create\\Session' => __DIR__ . '/../..' . '/lib/CoreLibs/Create/Session.php',