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 '
Class Test Master
'; +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',