create-berna-stencil 1.0.8 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (378) hide show
  1. package/.eleventy.js +3 -1
  2. package/.env.example +8 -0
  3. package/.gitignore +2 -1
  4. package/README.md +1 -5
  5. package/bin/create.js +2 -1
  6. package/package.json +71 -70
  7. package/src/.htaccess +18 -0
  8. package/src/api/.htaccess +12 -0
  9. package/src/api/composer.json +6 -0
  10. package/src/api/composer.lock +574 -0
  11. package/src/api/endpoints/protected/secret.php +16 -0
  12. package/src/api/endpoints/protected/send-mail.php +75 -0
  13. package/src/api/endpoints/public/ping.php +16 -0
  14. package/src/api/index.php +95 -0
  15. package/src/api/init.php +43 -0
  16. package/src/api/modules/Response.php +37 -0
  17. package/src/api/vendor/autoload.php +22 -0
  18. package/src/api/vendor/composer/ClassLoader.php +579 -0
  19. package/src/api/vendor/composer/InstalledVersions.php +396 -0
  20. package/src/api/vendor/composer/LICENSE +21 -0
  21. package/src/api/vendor/composer/autoload_classmap.php +15 -0
  22. package/src/api/vendor/composer/autoload_files.php +12 -0
  23. package/src/api/vendor/composer/autoload_namespaces.php +9 -0
  24. package/src/api/vendor/composer/autoload_psr4.php +16 -0
  25. package/src/api/vendor/composer/autoload_real.php +50 -0
  26. package/src/api/vendor/composer/autoload_static.php +86 -0
  27. package/src/api/vendor/composer/installed.json +582 -0
  28. package/src/api/vendor/composer/installed.php +86 -0
  29. package/src/api/vendor/composer/platform_check.php +25 -0
  30. package/src/api/vendor/graham-campbell/result-type/.gitattributes +9 -0
  31. package/src/api/vendor/graham-campbell/result-type/.github/CODE_OF_CONDUCT.md +132 -0
  32. package/src/api/vendor/graham-campbell/result-type/.github/CONTRIBUTING.md +31 -0
  33. package/src/api/vendor/graham-campbell/result-type/.github/FUNDING.yml +2 -0
  34. package/src/api/vendor/graham-campbell/result-type/.github/SECURITY.md +14 -0
  35. package/src/api/vendor/graham-campbell/result-type/.github/workflows/stale.yml +11 -0
  36. package/src/api/vendor/graham-campbell/result-type/.github/workflows/tests.yml +40 -0
  37. package/src/api/vendor/graham-campbell/result-type/CHANGELOG.md +53 -0
  38. package/src/api/vendor/graham-campbell/result-type/LICENSE +21 -0
  39. package/src/api/vendor/graham-campbell/result-type/README.md +42 -0
  40. package/src/api/vendor/graham-campbell/result-type/composer.json +33 -0
  41. package/src/api/vendor/graham-campbell/result-type/phpunit.xml.dist +13 -0
  42. package/src/api/vendor/graham-campbell/result-type/src/Error.php +121 -0
  43. package/src/api/vendor/graham-campbell/result-type/src/Result.php +69 -0
  44. package/src/api/vendor/graham-campbell/result-type/src/Success.php +120 -0
  45. package/src/api/vendor/graham-campbell/result-type/tests/ResultTest.php +95 -0
  46. package/src/api/vendor/phpmailer/phpmailer/.codecov.yml +21 -0
  47. package/src/api/vendor/phpmailer/phpmailer/.editorconfig +15 -0
  48. package/src/api/vendor/phpmailer/phpmailer/.gitattributes +17 -0
  49. package/src/api/vendor/phpmailer/phpmailer/.github/FUNDING.yml +6 -0
  50. package/src/api/vendor/phpmailer/phpmailer/.github/ISSUE_TEMPLATE/bug_report.md +23 -0
  51. package/src/api/vendor/phpmailer/phpmailer/.github/actions/build-docs/Dockerfile +15 -0
  52. package/src/api/vendor/phpmailer/phpmailer/.github/actions/build-docs/entrypoint.sh +5 -0
  53. package/src/api/vendor/phpmailer/phpmailer/.github/dependabot.yml +21 -0
  54. package/src/api/vendor/phpmailer/phpmailer/.github/workflows/docs.yaml +30 -0
  55. package/src/api/vendor/phpmailer/phpmailer/.github/workflows/scorecards.yml +61 -0
  56. package/src/api/vendor/phpmailer/phpmailer/.github/workflows/tests.yml +237 -0
  57. package/src/api/vendor/phpmailer/phpmailer/.phan/config.php +41 -0
  58. package/src/api/vendor/phpmailer/phpmailer/COMMITMENT +46 -0
  59. package/src/api/vendor/phpmailer/phpmailer/LICENSE +502 -0
  60. package/src/api/vendor/phpmailer/phpmailer/README.md +232 -0
  61. package/src/api/vendor/phpmailer/phpmailer/SECURITY.md +37 -0
  62. package/src/api/vendor/phpmailer/phpmailer/SMTPUTF8.md +48 -0
  63. package/src/api/vendor/phpmailer/phpmailer/UPGRADING.md +125 -0
  64. package/src/api/vendor/phpmailer/phpmailer/VERSION +1 -0
  65. package/src/api/vendor/phpmailer/phpmailer/changelog.md +1122 -0
  66. package/src/api/vendor/phpmailer/phpmailer/composer.json +84 -0
  67. package/src/api/vendor/phpmailer/phpmailer/docs/README.md +9 -0
  68. package/src/api/vendor/phpmailer/phpmailer/examples/DKIM_gen_keys.phps +81 -0
  69. package/src/api/vendor/phpmailer/phpmailer/examples/DKIM_sign.phps +46 -0
  70. package/src/api/vendor/phpmailer/phpmailer/examples/README.md +96 -0
  71. package/src/api/vendor/phpmailer/phpmailer/examples/azure_xoauth2.phps +126 -0
  72. package/src/api/vendor/phpmailer/phpmailer/examples/callback.phps +76 -0
  73. package/src/api/vendor/phpmailer/phpmailer/examples/contactform-ajax.phps +146 -0
  74. package/src/api/vendor/phpmailer/phpmailer/examples/contactform.phps +95 -0
  75. package/src/api/vendor/phpmailer/phpmailer/examples/contents.html +17 -0
  76. package/src/api/vendor/phpmailer/phpmailer/examples/contentsutf8.html +23 -0
  77. package/src/api/vendor/phpmailer/phpmailer/examples/exceptions.phps +40 -0
  78. package/src/api/vendor/phpmailer/phpmailer/examples/extending.phps +72 -0
  79. package/src/api/vendor/phpmailer/phpmailer/examples/gmail.phps +106 -0
  80. package/src/api/vendor/phpmailer/phpmailer/examples/gmail_xoauth.phps +121 -0
  81. package/src/api/vendor/phpmailer/phpmailer/examples/images/PHPMailer card logo.afdesign +0 -0
  82. package/src/api/vendor/phpmailer/phpmailer/examples/images/PHPMailer card logo.png +0 -0
  83. package/src/api/vendor/phpmailer/phpmailer/examples/images/PHPMailer card logo.svg +209 -0
  84. package/src/api/vendor/phpmailer/phpmailer/examples/images/phpmailer.png +0 -0
  85. package/src/api/vendor/phpmailer/phpmailer/examples/images/phpmailer_mini.png +0 -0
  86. package/src/api/vendor/phpmailer/phpmailer/examples/mail.phps +35 -0
  87. package/src/api/vendor/phpmailer/phpmailer/examples/mailing_list.phps +96 -0
  88. package/src/api/vendor/phpmailer/phpmailer/examples/pop_before_smtp.phps +60 -0
  89. package/src/api/vendor/phpmailer/phpmailer/examples/send_file_upload.phps +60 -0
  90. package/src/api/vendor/phpmailer/phpmailer/examples/send_multiple_file_upload.phps +60 -0
  91. package/src/api/vendor/phpmailer/phpmailer/examples/sendmail.phps +37 -0
  92. package/src/api/vendor/phpmailer/phpmailer/examples/sendoauth2.phps +100 -0
  93. package/src/api/vendor/phpmailer/phpmailer/examples/simple_contact_form.phps +102 -0
  94. package/src/api/vendor/phpmailer/phpmailer/examples/smime_signed_mail.phps +99 -0
  95. package/src/api/vendor/phpmailer/phpmailer/examples/smtp.phps +62 -0
  96. package/src/api/vendor/phpmailer/phpmailer/examples/smtp_check.phps +60 -0
  97. package/src/api/vendor/phpmailer/phpmailer/examples/smtp_low_memory.phps +117 -0
  98. package/src/api/vendor/phpmailer/phpmailer/examples/smtp_no_auth.phps +53 -0
  99. package/src/api/vendor/phpmailer/phpmailer/examples/ssl_options.phps +81 -0
  100. package/src/api/vendor/phpmailer/phpmailer/get_oauth_token.php +182 -0
  101. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php +26 -0
  102. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-ar.php +27 -0
  103. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php +35 -0
  104. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php +27 -0
  105. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-ba.php +27 -0
  106. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php +27 -0
  107. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php +27 -0
  108. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php +35 -0
  109. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php +27 -0
  110. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php +28 -0
  111. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-da.php +39 -0
  112. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php +28 -0
  113. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-el.php +33 -0
  114. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-eo.php +37 -0
  115. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php +38 -0
  116. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php +28 -0
  117. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php +28 -0
  118. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php +27 -0
  119. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-fo.php +27 -0
  120. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php +36 -0
  121. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-gl.php +27 -0
  122. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php +27 -0
  123. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php +35 -0
  124. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php +27 -0
  125. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php +27 -0
  126. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-hy.php +27 -0
  127. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php +31 -0
  128. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php +28 -0
  129. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php +37 -0
  130. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php +27 -0
  131. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php +27 -0
  132. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-ku.php +27 -0
  133. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php +27 -0
  134. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php +27 -0
  135. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php +27 -0
  136. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php +27 -0
  137. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-ms.php +27 -0
  138. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php +37 -0
  139. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-nl.php +38 -0
  140. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php +33 -0
  141. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt.php +34 -0
  142. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php +38 -0
  143. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php +33 -0
  144. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php +36 -0
  145. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php +34 -0
  146. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php +30 -0
  147. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php +36 -0
  148. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php +28 -0
  149. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php +28 -0
  150. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php +27 -0
  151. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php +28 -0
  152. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php +38 -0
  153. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-uk.php +28 -0
  154. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-ur.php +30 -0
  155. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php +27 -0
  156. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php +29 -0
  157. package/src/api/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php +36 -0
  158. package/src/api/vendor/phpmailer/phpmailer/phpcs.xml.dist +52 -0
  159. package/src/api/vendor/phpmailer/phpmailer/phpdoc.dist.xml +20 -0
  160. package/src/api/vendor/phpmailer/phpmailer/phpunit.xml.dist +36 -0
  161. package/src/api/vendor/phpmailer/phpmailer/src/DSNConfigurator.php +245 -0
  162. package/src/api/vendor/phpmailer/phpmailer/src/Exception.php +40 -0
  163. package/src/api/vendor/phpmailer/phpmailer/src/OAuth.php +139 -0
  164. package/src/api/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php +44 -0
  165. package/src/api/vendor/phpmailer/phpmailer/src/PHPMailer.php +5525 -0
  166. package/src/api/vendor/phpmailer/phpmailer/src/POP3.php +470 -0
  167. package/src/api/vendor/phpmailer/phpmailer/src/SMTP.php +1617 -0
  168. package/src/api/vendor/phpmailer/phpmailer/test/DebugLogTestListener.php +47 -0
  169. package/src/api/vendor/phpmailer/phpmailer/test/Fixtures/FileIsAccessibleTest/accessible.txt +1 -0
  170. package/src/api/vendor/phpmailer/phpmailer/test/Fixtures/FileIsAccessibleTest/inaccessible.txt +1 -0
  171. package/src/api/vendor/phpmailer/phpmailer/test/Fixtures/LocalizationTest/phpmailer.lang-fr.php +10 -0
  172. package/src/api/vendor/phpmailer/phpmailer/test/Fixtures/LocalizationTest/phpmailer.lang-nl.php +10 -0
  173. package/src/api/vendor/phpmailer/phpmailer/test/Fixtures/LocalizationTest/phpmailer.lang-xa_scri_cc.php +9 -0
  174. package/src/api/vendor/phpmailer/phpmailer/test/Fixtures/LocalizationTest/phpmailer.lang-xb_scri.php +9 -0
  175. package/src/api/vendor/phpmailer/phpmailer/test/Fixtures/LocalizationTest/phpmailer.lang-xc_cc.php +9 -0
  176. package/src/api/vendor/phpmailer/phpmailer/test/Fixtures/LocalizationTest/phpmailer.lang-xd_cc.php +9 -0
  177. package/src/api/vendor/phpmailer/phpmailer/test/Fixtures/LocalizationTest/phpmailer.lang-xd_scri.php +9 -0
  178. package/src/api/vendor/phpmailer/phpmailer/test/Fixtures/LocalizationTest/phpmailer.lang-xe.php +9 -0
  179. package/src/api/vendor/phpmailer/phpmailer/test/Fixtures/LocalizationTest/phpmailer.lang-xx.php +9 -0
  180. package/src/api/vendor/phpmailer/phpmailer/test/Fixtures/LocalizationTest/phpmailer.lang-yy.php +16 -0
  181. package/src/api/vendor/phpmailer/phpmailer/test/Fixtures/LocalizationTest/phpmailer.lang-yz.php +16 -0
  182. package/src/api/vendor/phpmailer/phpmailer/test/Fixtures/LocalizationTest/phpmailer.lang-zz.php +20 -0
  183. package/src/api/vendor/phpmailer/phpmailer/test/Language/TranslationCompletenessTest.php +97 -0
  184. package/src/api/vendor/phpmailer/phpmailer/test/OAuth/OAuthTest.php +76 -0
  185. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/AddEmbeddedImageTest.php +178 -0
  186. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/AddStringAttachmentTest.php +147 -0
  187. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/AddStringEmbeddedImageTest.php +161 -0
  188. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/AddrFormatTest.php +70 -0
  189. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/AuthCRAMMD5Test.php +48 -0
  190. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/CustomHeaderTest.php +285 -0
  191. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/DKIMTest.php +258 -0
  192. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/DKIMWithoutExceptionsTest.php +46 -0
  193. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/DSNConfiguratorTest.php +221 -0
  194. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/EncodeQTest.php +110 -0
  195. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/EncodeStringTest.php +135 -0
  196. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/FileIsAccessibleTest.php +103 -0
  197. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/FilenameToTypeTest.php +74 -0
  198. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/GenerateIdTest.php +75 -0
  199. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/GetLastMessageIDTest.php +110 -0
  200. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/HasLineLongerThanMaxTest.php +158 -0
  201. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/Html2TextTest.php +231 -0
  202. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/ICalTest.php +126 -0
  203. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/IsPermittedPathTest.php +108 -0
  204. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/IsValidHostTest.php +107 -0
  205. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/LocalizationTest.php +505 -0
  206. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/MailTransportTest.php +261 -0
  207. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/MbPathinfoTest.php +171 -0
  208. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/MimeTypesTest.php +70 -0
  209. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/NormalizeBreaksTest.php +111 -0
  210. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/PHPMailerTest.php +1388 -0
  211. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/ParseAddressesTest.php +321 -0
  212. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/PunyencodeAddressTest.php +141 -0
  213. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/QuotedStringTest.php +70 -0
  214. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/ReplyToGetSetClearTest.php +444 -0
  215. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/SetErrorTest.php +154 -0
  216. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/SetFromTest.php +202 -0
  217. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/SetTest.php +81 -0
  218. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/SetWordWrapTest.php +120 -0
  219. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/Utf8CharBoundaryTest.php +66 -0
  220. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/ValidateAddressCustomValidatorTest.php +116 -0
  221. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/ValidateAddressTest.php +485 -0
  222. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/WrapTextTest.php +125 -0
  223. package/src/api/vendor/phpmailer/phpmailer/test/PHPMailer/XMailerTest.php +75 -0
  224. package/src/api/vendor/phpmailer/phpmailer/test/POP3/PopBeforeSmtpTest.php +139 -0
  225. package/src/api/vendor/phpmailer/phpmailer/test/PreSendTestCase.php +46 -0
  226. package/src/api/vendor/phpmailer/phpmailer/test/Security/DenialOfServiceVectorsTest.php +52 -0
  227. package/src/api/vendor/phpmailer/phpmailer/test/SendTestCase.php +126 -0
  228. package/src/api/vendor/phpmailer/phpmailer/test/TestCase.php +392 -0
  229. package/src/api/vendor/phpmailer/phpmailer/test/fakepopserver.sh +126 -0
  230. package/src/api/vendor/phpmailer/phpmailer/test/fakesendmail.sh +22 -0
  231. package/src/api/vendor/phpmailer/phpmailer/test/runfakepopserver.sh +12 -0
  232. package/src/api/vendor/phpmailer/phpmailer/test/testbootstrap-dist.php +8 -0
  233. package/src/api/vendor/phpmailer/phpmailer/test/validators.php +12 -0
  234. package/src/api/vendor/phpoption/phpoption/.gitattributes +13 -0
  235. package/src/api/vendor/phpoption/phpoption/.github/CODE_OF_CONDUCT.md +132 -0
  236. package/src/api/vendor/phpoption/phpoption/.github/CONTRIBUTING.md +30 -0
  237. package/src/api/vendor/phpoption/phpoption/.github/FUNDING.yml +2 -0
  238. package/src/api/vendor/phpoption/phpoption/.github/SECURITY.md +14 -0
  239. package/src/api/vendor/phpoption/phpoption/.github/workflows/static.yml +40 -0
  240. package/src/api/vendor/phpoption/phpoption/.github/workflows/tests.yml +40 -0
  241. package/src/api/vendor/phpoption/phpoption/LICENSE +201 -0
  242. package/src/api/vendor/phpoption/phpoption/Makefile +17 -0
  243. package/src/api/vendor/phpoption/phpoption/README.md +201 -0
  244. package/src/api/vendor/phpoption/phpoption/composer.json +50 -0
  245. package/src/api/vendor/phpoption/phpoption/phpstan-baseline.neon +44 -0
  246. package/src/api/vendor/phpoption/phpoption/phpstan.neon.dist +7 -0
  247. package/src/api/vendor/phpoption/phpoption/phpunit.xml.dist +13 -0
  248. package/src/api/vendor/phpoption/phpoption/src/PhpOption/LazyOption.php +175 -0
  249. package/src/api/vendor/phpoption/phpoption/src/PhpOption/None.php +136 -0
  250. package/src/api/vendor/phpoption/phpoption/src/PhpOption/Option.php +434 -0
  251. package/src/api/vendor/phpoption/phpoption/src/PhpOption/Some.php +169 -0
  252. package/src/api/vendor/phpoption/phpoption/tests/PhpOption/Tests/EnsureTest.php +72 -0
  253. package/src/api/vendor/phpoption/phpoption/tests/PhpOption/Tests/LazyOptionTest.php +357 -0
  254. package/src/api/vendor/phpoption/phpoption/tests/PhpOption/Tests/NoneTest.php +153 -0
  255. package/src/api/vendor/phpoption/phpoption/tests/PhpOption/Tests/OptionTest.php +166 -0
  256. package/src/api/vendor/phpoption/phpoption/tests/PhpOption/Tests/SomeTest.php +194 -0
  257. package/src/api/vendor/phpoption/phpoption/tests/bootstrap.php +8 -0
  258. package/src/api/vendor/phpoption/phpoption/vendor-bin/phpstan/composer.json +8 -0
  259. package/src/api/vendor/symfony/polyfill-ctype/Ctype.php +232 -0
  260. package/src/api/vendor/symfony/polyfill-ctype/LICENSE +19 -0
  261. package/src/api/vendor/symfony/polyfill-ctype/README.md +12 -0
  262. package/src/api/vendor/symfony/polyfill-ctype/bootstrap.php +50 -0
  263. package/src/api/vendor/symfony/polyfill-ctype/bootstrap80.php +46 -0
  264. package/src/api/vendor/symfony/polyfill-ctype/composer.json +38 -0
  265. package/src/api/vendor/symfony/polyfill-mbstring/LICENSE +19 -0
  266. package/src/api/vendor/symfony/polyfill-mbstring/Mbstring.php +1077 -0
  267. package/src/api/vendor/symfony/polyfill-mbstring/README.md +13 -0
  268. package/src/api/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php +119 -0
  269. package/src/api/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php +1397 -0
  270. package/src/api/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php +5 -0
  271. package/src/api/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php +1489 -0
  272. package/src/api/vendor/symfony/polyfill-mbstring/bootstrap.php +171 -0
  273. package/src/api/vendor/symfony/polyfill-mbstring/bootstrap80.php +167 -0
  274. package/src/api/vendor/symfony/polyfill-mbstring/composer.json +39 -0
  275. package/src/api/vendor/symfony/polyfill-php80/LICENSE +19 -0
  276. package/src/api/vendor/symfony/polyfill-php80/Php80.php +115 -0
  277. package/src/api/vendor/symfony/polyfill-php80/PhpToken.php +106 -0
  278. package/src/api/vendor/symfony/polyfill-php80/README.md +25 -0
  279. package/src/api/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php +31 -0
  280. package/src/api/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php +16 -0
  281. package/src/api/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php +20 -0
  282. package/src/api/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php +16 -0
  283. package/src/api/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php +16 -0
  284. package/src/api/vendor/symfony/polyfill-php80/bootstrap.php +42 -0
  285. package/src/api/vendor/symfony/polyfill-php80/composer.json +37 -0
  286. package/src/api/vendor/vlucas/phpdotenv/.editorconfig +15 -0
  287. package/src/api/vendor/vlucas/phpdotenv/.gitattributes +15 -0
  288. package/src/api/vendor/vlucas/phpdotenv/.github/CODE_OF_CONDUCT.md +132 -0
  289. package/src/api/vendor/vlucas/phpdotenv/.github/CONTRIBUTING.md +30 -0
  290. package/src/api/vendor/vlucas/phpdotenv/.github/FUNDING.yml +2 -0
  291. package/src/api/vendor/vlucas/phpdotenv/.github/SECURITY.md +14 -0
  292. package/src/api/vendor/vlucas/phpdotenv/.github/workflows/static.yml +40 -0
  293. package/src/api/vendor/vlucas/phpdotenv/.github/workflows/tests.yml +70 -0
  294. package/src/api/vendor/vlucas/phpdotenv/LICENSE +30 -0
  295. package/src/api/vendor/vlucas/phpdotenv/Makefile +17 -0
  296. package/src/api/vendor/vlucas/phpdotenv/README.md +370 -0
  297. package/src/api/vendor/vlucas/phpdotenv/UPGRADING.md +196 -0
  298. package/src/api/vendor/vlucas/phpdotenv/composer.json +60 -0
  299. package/src/api/vendor/vlucas/phpdotenv/phpstan-baseline.neon +157 -0
  300. package/src/api/vendor/vlucas/phpdotenv/phpstan.neon.dist +7 -0
  301. package/src/api/vendor/vlucas/phpdotenv/phpunit.xml.dist +13 -0
  302. package/src/api/vendor/vlucas/phpdotenv/src/Dotenv.php +267 -0
  303. package/src/api/vendor/vlucas/phpdotenv/src/Exception/ExceptionInterface.php +12 -0
  304. package/src/api/vendor/vlucas/phpdotenv/src/Exception/InvalidEncodingException.php +12 -0
  305. package/src/api/vendor/vlucas/phpdotenv/src/Exception/InvalidFileException.php +12 -0
  306. package/src/api/vendor/vlucas/phpdotenv/src/Exception/InvalidPathException.php +12 -0
  307. package/src/api/vendor/vlucas/phpdotenv/src/Exception/ValidationException.php +12 -0
  308. package/src/api/vendor/vlucas/phpdotenv/src/Loader/Loader.php +48 -0
  309. package/src/api/vendor/vlucas/phpdotenv/src/Loader/LoaderInterface.php +20 -0
  310. package/src/api/vendor/vlucas/phpdotenv/src/Loader/Resolver.php +65 -0
  311. package/src/api/vendor/vlucas/phpdotenv/src/Parser/Entry.php +59 -0
  312. package/src/api/vendor/vlucas/phpdotenv/src/Parser/EntryParser.php +299 -0
  313. package/src/api/vendor/vlucas/phpdotenv/src/Parser/Lexer.php +58 -0
  314. package/src/api/vendor/vlucas/phpdotenv/src/Parser/Lines.php +127 -0
  315. package/src/api/vendor/vlucas/phpdotenv/src/Parser/Parser.php +53 -0
  316. package/src/api/vendor/vlucas/phpdotenv/src/Parser/ParserInterface.php +19 -0
  317. package/src/api/vendor/vlucas/phpdotenv/src/Parser/Value.php +88 -0
  318. package/src/api/vendor/vlucas/phpdotenv/src/Repository/Adapter/AdapterInterface.php +15 -0
  319. package/src/api/vendor/vlucas/phpdotenv/src/Repository/Adapter/ApacheAdapter.php +89 -0
  320. package/src/api/vendor/vlucas/phpdotenv/src/Repository/Adapter/ArrayAdapter.php +80 -0
  321. package/src/api/vendor/vlucas/phpdotenv/src/Repository/Adapter/EnvConstAdapter.php +88 -0
  322. package/src/api/vendor/vlucas/phpdotenv/src/Repository/Adapter/GuardedWriter.php +85 -0
  323. package/src/api/vendor/vlucas/phpdotenv/src/Repository/Adapter/ImmutableWriter.php +110 -0
  324. package/src/api/vendor/vlucas/phpdotenv/src/Repository/Adapter/MultiReader.php +48 -0
  325. package/src/api/vendor/vlucas/phpdotenv/src/Repository/Adapter/MultiWriter.php +64 -0
  326. package/src/api/vendor/vlucas/phpdotenv/src/Repository/Adapter/PutenvAdapter.php +91 -0
  327. package/src/api/vendor/vlucas/phpdotenv/src/Repository/Adapter/ReaderInterface.php +17 -0
  328. package/src/api/vendor/vlucas/phpdotenv/src/Repository/Adapter/ReplacingWriter.php +104 -0
  329. package/src/api/vendor/vlucas/phpdotenv/src/Repository/Adapter/ServerConstAdapter.php +88 -0
  330. package/src/api/vendor/vlucas/phpdotenv/src/Repository/Adapter/WriterInterface.php +27 -0
  331. package/src/api/vendor/vlucas/phpdotenv/src/Repository/AdapterRepository.php +107 -0
  332. package/src/api/vendor/vlucas/phpdotenv/src/Repository/RepositoryBuilder.php +272 -0
  333. package/src/api/vendor/vlucas/phpdotenv/src/Repository/RepositoryInterface.php +51 -0
  334. package/src/api/vendor/vlucas/phpdotenv/src/Store/File/Paths.php +44 -0
  335. package/src/api/vendor/vlucas/phpdotenv/src/Store/File/Reader.php +81 -0
  336. package/src/api/vendor/vlucas/phpdotenv/src/Store/FileStore.php +72 -0
  337. package/src/api/vendor/vlucas/phpdotenv/src/Store/StoreBuilder.php +141 -0
  338. package/src/api/vendor/vlucas/phpdotenv/src/Store/StoreInterface.php +17 -0
  339. package/src/api/vendor/vlucas/phpdotenv/src/Store/StringStore.php +37 -0
  340. package/src/api/vendor/vlucas/phpdotenv/src/Util/Regex.php +112 -0
  341. package/src/api/vendor/vlucas/phpdotenv/src/Util/Str.php +108 -0
  342. package/src/api/vendor/vlucas/phpdotenv/src/Validator.php +207 -0
  343. package/src/api/vendor/vlucas/phpdotenv/tests/Dotenv/DotenvTest.php +387 -0
  344. package/src/api/vendor/vlucas/phpdotenv/tests/Dotenv/Loader/LoaderTest.php +86 -0
  345. package/src/api/vendor/vlucas/phpdotenv/tests/Dotenv/Parser/EntryParserTest.php +234 -0
  346. package/src/api/vendor/vlucas/phpdotenv/tests/Dotenv/Parser/LexerTest.php +40 -0
  347. package/src/api/vendor/vlucas/phpdotenv/tests/Dotenv/Parser/LinesTest.php +53 -0
  348. package/src/api/vendor/vlucas/phpdotenv/tests/Dotenv/Parser/ParserTest.php +98 -0
  349. package/src/api/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/Adapter/ArrayAdapterTest.php +57 -0
  350. package/src/api/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/Adapter/EnvConstAdapterTest.php +75 -0
  351. package/src/api/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/Adapter/PutenvAdapterTest.php +52 -0
  352. package/src/api/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/Adapter/ServerConstAdapterTest.php +75 -0
  353. package/src/api/vendor/vlucas/phpdotenv/tests/Dotenv/Repository/RepositoryTest.php +305 -0
  354. package/src/api/vendor/vlucas/phpdotenv/tests/Dotenv/Store/StoreTest.php +141 -0
  355. package/src/api/vendor/vlucas/phpdotenv/tests/Dotenv/ValidatorTest.php +479 -0
  356. package/src/api/vendor/vlucas/phpdotenv/tests/fixtures/env/.env +5 -0
  357. package/src/api/vendor/vlucas/phpdotenv/tests/fixtures/env/assertions.env +18 -0
  358. package/src/api/vendor/vlucas/phpdotenv/tests/fixtures/env/booleans.env +33 -0
  359. package/src/api/vendor/vlucas/phpdotenv/tests/fixtures/env/commented.env +15 -0
  360. package/src/api/vendor/vlucas/phpdotenv/tests/fixtures/env/empty.env +1 -0
  361. package/src/api/vendor/vlucas/phpdotenv/tests/fixtures/env/example.env +1 -0
  362. package/src/api/vendor/vlucas/phpdotenv/tests/fixtures/env/exported.env +7 -0
  363. package/src/api/vendor/vlucas/phpdotenv/tests/fixtures/env/immutable.env +1 -0
  364. package/src/api/vendor/vlucas/phpdotenv/tests/fixtures/env/integers.env +17 -0
  365. package/src/api/vendor/vlucas/phpdotenv/tests/fixtures/env/large.env +2 -0
  366. package/src/api/vendor/vlucas/phpdotenv/tests/fixtures/env/multibyte.env +3 -0
  367. package/src/api/vendor/vlucas/phpdotenv/tests/fixtures/env/multiline.env +14 -0
  368. package/src/api/vendor/vlucas/phpdotenv/tests/fixtures/env/multiple.env +4 -0
  369. package/src/api/vendor/vlucas/phpdotenv/tests/fixtures/env/mutable.env +1 -0
  370. package/src/api/vendor/vlucas/phpdotenv/tests/fixtures/env/nested.env +15 -0
  371. package/src/api/vendor/vlucas/phpdotenv/tests/fixtures/env/quoted.env +11 -0
  372. package/src/api/vendor/vlucas/phpdotenv/tests/fixtures/env/specialchars.env +8 -0
  373. package/src/api/vendor/vlucas/phpdotenv/tests/fixtures/env/unicodevarnames.env +2 -0
  374. package/src/api/vendor/vlucas/phpdotenv/tests/fixtures/env/utf8-with-bom-encoding.env +3 -0
  375. package/src/api/vendor/vlucas/phpdotenv/tests/fixtures/env/windows.env +1 -0
  376. package/src/api/vendor/vlucas/phpdotenv/vendor-bin/phpstan/composer.json +15 -0
  377. package/src/api/configExample.php +0 -28
  378. package/src/api/sendEmail.php +0 -131
@@ -0,0 +1,1617 @@
1
+ <?php
2
+
3
+ /**
4
+ * PHPMailer RFC821 SMTP email transport class.
5
+ * PHP Version 5.5.
6
+ *
7
+ * @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
8
+ *
9
+ * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
10
+ * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
11
+ * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
12
+ * @author Brent R. Matzelle (original founder)
13
+ * @copyright 2012 - 2020 Marcus Bointon
14
+ * @copyright 2010 - 2012 Jim Jagielski
15
+ * @copyright 2004 - 2009 Andy Prevost
16
+ * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
17
+ * @note This program is distributed in the hope that it will be useful - WITHOUT
18
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ * FITNESS FOR A PARTICULAR PURPOSE.
20
+ */
21
+
22
+ namespace PHPMailer\PHPMailer;
23
+
24
+ /**
25
+ * PHPMailer RFC821 SMTP email transport class.
26
+ * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server.
27
+ *
28
+ * @author Chris Ryan
29
+ * @author Marcus Bointon <phpmailer@synchromedia.co.uk>
30
+ */
31
+ class SMTP
32
+ {
33
+ /**
34
+ * The PHPMailer SMTP version number.
35
+ *
36
+ * @var string
37
+ * @deprecated This constant will be removed in PHPMailer 8.0. Use `PHPMailer::VERSION` instead.
38
+ */
39
+ const VERSION = '7.0.2';
40
+
41
+ /**
42
+ * SMTP line break constant.
43
+ *
44
+ * @var string
45
+ */
46
+ const LE = "\r\n";
47
+
48
+ /**
49
+ * The SMTP port to use if one is not specified.
50
+ *
51
+ * @var int
52
+ */
53
+ const DEFAULT_PORT = 25;
54
+
55
+ /**
56
+ * The SMTPs port to use if one is not specified.
57
+ *
58
+ * @var int
59
+ */
60
+ const DEFAULT_SECURE_PORT = 465;
61
+
62
+ /**
63
+ * The maximum line length allowed by RFC 5321 section 4.5.3.1.6,
64
+ * *excluding* a trailing CRLF break.
65
+ *
66
+ * @see https://www.rfc-editor.org/rfc/rfc5321#section-4.5.3.1.6
67
+ *
68
+ * @var int
69
+ */
70
+ const MAX_LINE_LENGTH = 998;
71
+
72
+ /**
73
+ * The maximum line length allowed for replies in RFC 5321 section 4.5.3.1.5,
74
+ * *including* a trailing CRLF line break.
75
+ *
76
+ * @see https://www.rfc-editor.org/rfc/rfc5321#section-4.5.3.1.5
77
+ *
78
+ * @var int
79
+ */
80
+ const MAX_REPLY_LENGTH = 512;
81
+
82
+ /**
83
+ * Debug level for no output.
84
+ *
85
+ * @var int
86
+ */
87
+ const DEBUG_OFF = 0;
88
+
89
+ /**
90
+ * Debug level to show client -> server messages.
91
+ *
92
+ * @var int
93
+ */
94
+ const DEBUG_CLIENT = 1;
95
+
96
+ /**
97
+ * Debug level to show client -> server and server -> client messages.
98
+ *
99
+ * @var int
100
+ */
101
+ const DEBUG_SERVER = 2;
102
+
103
+ /**
104
+ * Debug level to show connection status, client -> server and server -> client messages.
105
+ *
106
+ * @var int
107
+ */
108
+ const DEBUG_CONNECTION = 3;
109
+
110
+ /**
111
+ * Debug level to show all messages.
112
+ *
113
+ * @var int
114
+ */
115
+ const DEBUG_LOWLEVEL = 4;
116
+
117
+ /**
118
+ * Debug output level.
119
+ * Options:
120
+ * * self::DEBUG_OFF (`0`) No debug output, default
121
+ * * self::DEBUG_CLIENT (`1`) Client commands
122
+ * * self::DEBUG_SERVER (`2`) Client commands and server responses
123
+ * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status
124
+ * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages.
125
+ *
126
+ * @var int
127
+ */
128
+ public $do_debug = self::DEBUG_OFF;
129
+
130
+ /**
131
+ * How to handle debug output.
132
+ * Options:
133
+ * * `echo` Output plain-text as-is, appropriate for CLI
134
+ * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
135
+ * * `error_log` Output to error log as configured in php.ini
136
+ * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
137
+ *
138
+ * ```php
139
+ * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
140
+ * ```
141
+ *
142
+ * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug`
143
+ * level output is used:
144
+ *
145
+ * ```php
146
+ * $mail->Debugoutput = new myPsr3Logger;
147
+ * ```
148
+ *
149
+ * @var string|callable|\Psr\Log\LoggerInterface
150
+ */
151
+ public $Debugoutput = 'echo';
152
+
153
+ /**
154
+ * Whether to use VERP.
155
+ *
156
+ * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path
157
+ * @see https://www.postfix.org/VERP_README.html Info on VERP
158
+ *
159
+ * @var bool
160
+ */
161
+ public $do_verp = false;
162
+
163
+ /**
164
+ * Whether to use SMTPUTF8.
165
+ *
166
+ * @see https://www.rfc-editor.org/rfc/rfc6531
167
+ *
168
+ * @var bool
169
+ */
170
+ public $do_smtputf8 = false;
171
+
172
+ /**
173
+ * The timeout value for connection, in seconds.
174
+ * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
175
+ * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure.
176
+ *
177
+ * @see https://www.rfc-editor.org/rfc/rfc2821#section-4.5.3.2
178
+ *
179
+ * @var int
180
+ */
181
+ public $Timeout = 300;
182
+
183
+ /**
184
+ * How long to wait for commands to complete, in seconds.
185
+ * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
186
+ *
187
+ * @var int
188
+ */
189
+ public $Timelimit = 300;
190
+
191
+ /**
192
+ * Patterns to extract an SMTP transaction id from reply to a DATA command.
193
+ * The first capture group in each regex will be used as the ID.
194
+ * MS ESMTP returns the message ID, which may not be correct for internal tracking.
195
+ *
196
+ * @var string[]
197
+ */
198
+ protected $smtp_transaction_id_patterns = [
199
+ 'exim' => '/[\d]{3} OK id=(.*)/',
200
+ 'sendmail' => '/[\d]{3} 2\.0\.0 (.*) Message/',
201
+ 'postfix' => '/[\d]{3} 2\.0\.0 Ok: queued as (.*)/',
202
+ 'Microsoft_ESMTP' => '/[0-9]{3} 2\.[\d]\.0 (.*)@(?:.*) Queued mail for delivery/',
203
+ 'Amazon_SES' => '/[\d]{3} Ok (.*)/',
204
+ 'SendGrid' => '/[\d]{3} Ok: queued as (.*)/',
205
+ 'CampaignMonitor' => '/[\d]{3} 2\.0\.0 OK:([a-zA-Z\d]{48})/',
206
+ 'Haraka' => '/[\d]{3} Message Queued \((.*)\)/',
207
+ 'ZoneMTA' => '/[\d]{3} Message queued as (.*)/',
208
+ 'Mailjet' => '/[\d]{3} OK queued as (.*)/',
209
+ 'Gsmtp' => '/[\d]{3} 2\.0\.0 OK (.*) - gsmtp/',
210
+ ];
211
+
212
+ /**
213
+ * Allowed SMTP XCLIENT attributes.
214
+ * Must be allowed by the SMTP server. EHLO response is not checked.
215
+ *
216
+ * @see https://www.postfix.org/XCLIENT_README.html
217
+ *
218
+ * @var array
219
+ */
220
+ public static $xclient_allowed_attributes = [
221
+ 'NAME', 'ADDR', 'PORT', 'PROTO', 'HELO', 'LOGIN', 'DESTADDR', 'DESTPORT'
222
+ ];
223
+
224
+ /**
225
+ * The last transaction ID issued in response to a DATA command,
226
+ * if one was detected.
227
+ *
228
+ * @var string|bool|null
229
+ */
230
+ protected $last_smtp_transaction_id;
231
+
232
+ /**
233
+ * The socket for the server connection.
234
+ *
235
+ * @var ?resource
236
+ */
237
+ protected $smtp_conn;
238
+
239
+ /**
240
+ * Error information, if any, for the last SMTP command.
241
+ *
242
+ * @var array
243
+ */
244
+ protected $error = [
245
+ 'error' => '',
246
+ 'detail' => '',
247
+ 'smtp_code' => '',
248
+ 'smtp_code_ex' => '',
249
+ ];
250
+
251
+ /**
252
+ * The reply the server sent to us for HELO.
253
+ * If null, no HELO string has yet been received.
254
+ *
255
+ * @var string|null
256
+ */
257
+ protected $helo_rply;
258
+
259
+ /**
260
+ * The set of SMTP extensions sent in reply to EHLO command.
261
+ * Indexes of the array are extension names.
262
+ * Value at index 'HELO' or 'EHLO' (according to command that was sent)
263
+ * represents the server name. In case of HELO it is the only element of the array.
264
+ * Other values can be boolean TRUE or an array containing extension options.
265
+ * If null, no HELO/EHLO string has yet been received.
266
+ *
267
+ * @var array|null
268
+ */
269
+ protected $server_caps;
270
+
271
+ /**
272
+ * The most recent reply received from the server.
273
+ *
274
+ * @var string
275
+ */
276
+ protected $last_reply = '';
277
+
278
+ /**
279
+ * Output debugging info via a user-selected method.
280
+ *
281
+ * @param string $str Debug string to output
282
+ * @param int $level The debug level of this message; see DEBUG_* constants
283
+ *
284
+ * @see SMTP::$Debugoutput
285
+ * @see SMTP::$do_debug
286
+ */
287
+ protected function edebug($str, $level = 0)
288
+ {
289
+ if ($level > $this->do_debug) {
290
+ return;
291
+ }
292
+ //Is this a PSR-3 logger?
293
+ if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) {
294
+ //Remove trailing line breaks potentially added by calls to SMTP::client_send()
295
+ $this->Debugoutput->debug(rtrim($str, "\r\n"));
296
+
297
+ return;
298
+ }
299
+ //Avoid clash with built-in function names
300
+ if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) {
301
+ call_user_func($this->Debugoutput, $str, $level);
302
+
303
+ return;
304
+ }
305
+ switch ($this->Debugoutput) {
306
+ case 'error_log':
307
+ //Don't output, just log
308
+ /** @noinspection ForgottenDebugOutputInspection */
309
+ error_log($str);
310
+ break;
311
+ case 'html':
312
+ //Cleans up output a bit for a better looking, HTML-safe output
313
+ echo gmdate('Y-m-d H:i:s'), ' ', htmlentities(
314
+ preg_replace('/[\r\n]+/', '', $str),
315
+ ENT_QUOTES,
316
+ 'UTF-8'
317
+ ), "<br>\n";
318
+ break;
319
+ case 'echo':
320
+ default:
321
+ //Normalize line breaks
322
+ $str = preg_replace('/\r\n|\r/m', "\n", $str);
323
+ echo gmdate('Y-m-d H:i:s'),
324
+ "\t",
325
+ //Trim trailing space
326
+ trim(
327
+ //Indent for readability, except for trailing break
328
+ str_replace(
329
+ "\n",
330
+ "\n \t ",
331
+ trim($str)
332
+ )
333
+ ),
334
+ "\n";
335
+ }
336
+ }
337
+
338
+ /**
339
+ * Connect to an SMTP server.
340
+ *
341
+ * @param string $host SMTP server IP or host name
342
+ * @param int $port The port number to connect to
343
+ * @param int $timeout How long to wait for the connection to open
344
+ * @param array $options An array of options for stream_context_create()
345
+ *
346
+ * @return bool
347
+ */
348
+ public function connect($host, $port = null, $timeout = 30, $options = [])
349
+ {
350
+ //Clear errors to avoid confusion
351
+ $this->setError('');
352
+ //Make sure we are __not__ connected
353
+ if ($this->connected()) {
354
+ //Already connected, generate error
355
+ $this->setError('Already connected to a server');
356
+
357
+ return false;
358
+ }
359
+ if (empty($port)) {
360
+ $port = self::DEFAULT_PORT;
361
+ }
362
+ //Connect to the SMTP server
363
+ $this->edebug(
364
+ "Connection: opening to $host:$port, timeout=$timeout, options=" .
365
+ (count($options) > 0 ? var_export($options, true) : 'array()'),
366
+ self::DEBUG_CONNECTION
367
+ );
368
+
369
+ $this->smtp_conn = $this->getSMTPConnection($host, $port, $timeout, $options);
370
+
371
+ if ($this->smtp_conn === false) {
372
+ //Error info already set inside `getSMTPConnection()`
373
+ return false;
374
+ }
375
+
376
+ $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
377
+
378
+ //Get any announcement
379
+ $this->last_reply = $this->get_lines();
380
+ $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
381
+ $responseCode = (int)substr($this->last_reply, 0, 3);
382
+ if ($responseCode === 220) {
383
+ return true;
384
+ }
385
+ //Anything other than a 220 response means something went wrong
386
+ //RFC 5321 says the server will wait for us to send a QUIT in response to a 554 error
387
+ //https://www.rfc-editor.org/rfc/rfc5321#section-3.1
388
+ if ($responseCode === 554) {
389
+ $this->quit();
390
+ }
391
+ //This will handle 421 responses which may not wait for a QUIT (e.g. if the server is being shut down)
392
+ $this->edebug('Connection: closing due to error', self::DEBUG_CONNECTION);
393
+ $this->close();
394
+ return false;
395
+ }
396
+
397
+ /**
398
+ * Create connection to the SMTP server.
399
+ *
400
+ * @param string $host SMTP server IP or host name
401
+ * @param int $port The port number to connect to
402
+ * @param int $timeout How long to wait for the connection to open
403
+ * @param array $options An array of options for stream_context_create()
404
+ *
405
+ * @return false|resource
406
+ */
407
+ protected function getSMTPConnection($host, $port = null, $timeout = 30, $options = [])
408
+ {
409
+ static $streamok;
410
+ //This is enabled by default since 5.0.0 but some providers disable it
411
+ //Check this once and cache the result
412
+ if (null === $streamok) {
413
+ $streamok = function_exists('stream_socket_client');
414
+ }
415
+
416
+ $errno = 0;
417
+ $errstr = '';
418
+ if ($streamok) {
419
+ $socket_context = stream_context_create($options);
420
+ set_error_handler(function () {
421
+ call_user_func_array([$this, 'errorHandler'], func_get_args());
422
+ });
423
+ $connection = stream_socket_client(
424
+ $host . ':' . $port,
425
+ $errno,
426
+ $errstr,
427
+ $timeout,
428
+ STREAM_CLIENT_CONNECT,
429
+ $socket_context
430
+ );
431
+ } else {
432
+ //Fall back to fsockopen which should work in more places, but is missing some features
433
+ $this->edebug(
434
+ 'Connection: stream_socket_client not available, falling back to fsockopen',
435
+ self::DEBUG_CONNECTION
436
+ );
437
+ set_error_handler(function () {
438
+ call_user_func_array([$this, 'errorHandler'], func_get_args());
439
+ });
440
+ $connection = fsockopen(
441
+ $host,
442
+ $port,
443
+ $errno,
444
+ $errstr,
445
+ $timeout
446
+ );
447
+ }
448
+ restore_error_handler();
449
+
450
+ //Verify we connected properly
451
+ if (!is_resource($connection)) {
452
+ $this->setError(
453
+ 'Failed to connect to server',
454
+ '',
455
+ (string) $errno,
456
+ $errstr
457
+ );
458
+ $this->edebug(
459
+ 'SMTP ERROR: ' . $this->error['error']
460
+ . ": $errstr ($errno)",
461
+ self::DEBUG_CLIENT
462
+ );
463
+
464
+ return false;
465
+ }
466
+
467
+ //SMTP server can take longer to respond, give longer timeout for first read
468
+ //Windows does not have support for this timeout function
469
+ if (strpos(PHP_OS, 'WIN') !== 0) {
470
+ $max = (int)ini_get('max_execution_time');
471
+ //Don't bother if unlimited, or if set_time_limit is disabled
472
+ if (0 !== $max && $timeout > $max && strpos(ini_get('disable_functions'), 'set_time_limit') === false) {
473
+ @set_time_limit($timeout);
474
+ }
475
+ stream_set_timeout($connection, $timeout, 0);
476
+ }
477
+
478
+ return $connection;
479
+ }
480
+
481
+ /**
482
+ * Initiate a TLS (encrypted) session.
483
+ *
484
+ * @return bool
485
+ */
486
+ public function startTLS()
487
+ {
488
+ if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
489
+ return false;
490
+ }
491
+
492
+ //Allow the best TLS version(s) we can
493
+ $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
494
+
495
+ //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
496
+ //so add them back in manually if we can
497
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
498
+ // phpcs:ignore PHPCompatibility.Constants.NewConstants.stream_crypto_method_tlsv1_2_clientFound
499
+ $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
500
+ // phpcs:ignore PHPCompatibility.Constants.NewConstants.stream_crypto_method_tlsv1_1_clientFound
501
+ $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
502
+ }
503
+
504
+ //Begin encrypted connection
505
+ set_error_handler(function () {
506
+ call_user_func_array([$this, 'errorHandler'], func_get_args());
507
+ });
508
+ $crypto_ok = stream_socket_enable_crypto(
509
+ $this->smtp_conn,
510
+ true,
511
+ $crypto_method
512
+ );
513
+ restore_error_handler();
514
+
515
+ return (bool) $crypto_ok;
516
+ }
517
+
518
+ /**
519
+ * Perform SMTP authentication.
520
+ * Must be run after hello().
521
+ *
522
+ * @see hello()
523
+ *
524
+ * @param string $username The user name
525
+ * @param string $password The password
526
+ * @param string $authtype The auth type (CRAM-MD5, PLAIN, LOGIN, XOAUTH2)
527
+ * @param OAuthTokenProvider $OAuth An optional OAuthTokenProvider instance for XOAUTH2 authentication
528
+ *
529
+ * @return bool True if successfully authenticated
530
+ */
531
+ public function authenticate(
532
+ $username,
533
+ $password,
534
+ $authtype = null,
535
+ $OAuth = null
536
+ ) {
537
+ if (!$this->server_caps) {
538
+ $this->setError('Authentication is not allowed before HELO/EHLO');
539
+
540
+ return false;
541
+ }
542
+
543
+ if (array_key_exists('EHLO', $this->server_caps)) {
544
+ //SMTP extensions are available; try to find a proper authentication method
545
+ if (!array_key_exists('AUTH', $this->server_caps)) {
546
+ $this->setError('Authentication is not allowed at this stage');
547
+ //'at this stage' means that auth may be allowed after the stage changes
548
+ //e.g. after STARTTLS
549
+
550
+ return false;
551
+ }
552
+
553
+ $this->edebug('Auth method requested: ' . ($authtype ?: 'UNSPECIFIED'), self::DEBUG_LOWLEVEL);
554
+ $this->edebug(
555
+ 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
556
+ self::DEBUG_LOWLEVEL
557
+ );
558
+
559
+ //If we have requested a specific auth type, check the server supports it before trying others
560
+ if (null !== $authtype && !in_array($authtype, $this->server_caps['AUTH'], true)) {
561
+ $this->edebug('Requested auth method not available: ' . $authtype, self::DEBUG_LOWLEVEL);
562
+ $authtype = null;
563
+ }
564
+
565
+ if (empty($authtype)) {
566
+ //If no auth mechanism is specified, attempt to use these, in this order
567
+ //Try CRAM-MD5 first as it's more secure than the others
568
+ foreach (['CRAM-MD5', 'LOGIN', 'PLAIN', 'XOAUTH2'] as $method) {
569
+ if (in_array($method, $this->server_caps['AUTH'], true)) {
570
+ $authtype = $method;
571
+ break;
572
+ }
573
+ }
574
+ if (empty($authtype)) {
575
+ $this->setError('No supported authentication methods found');
576
+
577
+ return false;
578
+ }
579
+ $this->edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL);
580
+ }
581
+
582
+ if (!in_array($authtype, $this->server_caps['AUTH'], true)) {
583
+ $this->setError("The requested authentication method \"$authtype\" is not supported by the server");
584
+
585
+ return false;
586
+ }
587
+ } elseif (empty($authtype)) {
588
+ $authtype = 'LOGIN';
589
+ }
590
+ switch ($authtype) {
591
+ case 'PLAIN':
592
+ //Start authentication
593
+ if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
594
+ return false;
595
+ }
596
+ //Send encoded username and password
597
+ if (
598
+ //Format from https://www.rfc-editor.org/rfc/rfc4616#section-2
599
+ //We skip the first field (it's forgery), so the string starts with a null byte
600
+ !$this->sendCommand(
601
+ 'User & Password',
602
+ base64_encode("\0" . $username . "\0" . $password),
603
+ 235
604
+ )
605
+ ) {
606
+ return false;
607
+ }
608
+ break;
609
+ case 'LOGIN':
610
+ //Start authentication
611
+ if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
612
+ return false;
613
+ }
614
+ if (!$this->sendCommand('Username', base64_encode($username), 334)) {
615
+ return false;
616
+ }
617
+ if (!$this->sendCommand('Password', base64_encode($password), 235)) {
618
+ return false;
619
+ }
620
+ break;
621
+ case 'CRAM-MD5':
622
+ //Start authentication
623
+ if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
624
+ return false;
625
+ }
626
+ //Get the challenge
627
+ $challenge = base64_decode(substr($this->last_reply, 4));
628
+
629
+ //Build the response
630
+ $response = $username . ' ' . $this->hmac($challenge, $password);
631
+
632
+ //send encoded credentials
633
+ return $this->sendCommand('Username', base64_encode($response), 235);
634
+ case 'XOAUTH2':
635
+ //The OAuth instance must be set up prior to requesting auth.
636
+ if (null === $OAuth) {
637
+ return false;
638
+ }
639
+ try {
640
+ $oauth = $OAuth->getOauth64();
641
+ } catch (\Exception $e) {
642
+ // We catch all exceptions and convert them to PHPMailer exceptions to be able to
643
+ // handle them correctly later
644
+ throw new Exception("SMTP authentication error", 0, $e);
645
+ }
646
+ /*
647
+ * An SMTP command line can have a maximum length of 512 bytes, including the command name,
648
+ * so the base64-encoded OAUTH token has a maximum length of:
649
+ * 512 - 13 (AUTH XOAUTH2) - 2 (CRLF) = 497 bytes
650
+ * If the token is longer than that, the command and the token must be sent separately as described in
651
+ * https://www.rfc-editor.org/rfc/rfc4954#section-4
652
+ */
653
+ if ($oauth === '') {
654
+ //Sending an empty auth token is legitimate, but it must be encoded as '='
655
+ //to indicate it's not a 2-part command
656
+ if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 =', 235)) {
657
+ return false;
658
+ }
659
+ } elseif (strlen($oauth) <= 497) {
660
+ //Authenticate using a token in the initial-response part
661
+ if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
662
+ return false;
663
+ }
664
+ } else {
665
+ //The token is too long, so we need to send it in two parts.
666
+ //Send the auth command without a token and expect a 334
667
+ if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2', 334)) {
668
+ return false;
669
+ }
670
+ //Send the token
671
+ if (!$this->sendCommand('OAuth TOKEN', $oauth, [235, 334])) {
672
+ return false;
673
+ }
674
+ //If the server answers with 334, send an empty line and wait for a 235
675
+ if (
676
+ substr($this->last_reply, 0, 3) === '334'
677
+ && $this->sendCommand('AUTH End', '', 235)
678
+ ) {
679
+ return false;
680
+ }
681
+ }
682
+ break;
683
+ default:
684
+ $this->setError("Authentication method \"$authtype\" is not supported");
685
+
686
+ return false;
687
+ }
688
+
689
+ return true;
690
+ }
691
+
692
+ /**
693
+ * Calculate an MD5 HMAC hash.
694
+ * Works like hash_hmac('md5', $data, $key)
695
+ * in case that function is not available.
696
+ *
697
+ * @param string $data The data to hash
698
+ * @param string $key The key to hash with
699
+ *
700
+ * @return string
701
+ */
702
+ protected function hmac($data, $key)
703
+ {
704
+ if (function_exists('hash_hmac')) {
705
+ return hash_hmac('md5', $data, $key);
706
+ }
707
+
708
+ //The following borrowed from
709
+ //https://www.php.net/manual/en/function.mhash.php#27225
710
+
711
+ //RFC 2104 HMAC implementation for php.
712
+ //Creates an md5 HMAC.
713
+ //Eliminates the need to install mhash to compute a HMAC
714
+ //by Lance Rushing
715
+
716
+ $bytelen = 64; //byte length for md5
717
+ if (strlen($key) > $bytelen) {
718
+ $key = pack('H*', md5($key));
719
+ }
720
+ $key = str_pad($key, $bytelen, chr(0x00));
721
+ $ipad = str_pad('', $bytelen, chr(0x36));
722
+ $opad = str_pad('', $bytelen, chr(0x5c));
723
+ $k_ipad = $key ^ $ipad;
724
+ $k_opad = $key ^ $opad;
725
+
726
+ return md5($k_opad . pack('H*', md5($k_ipad . $data)));
727
+ }
728
+
729
+ /**
730
+ * Check connection state.
731
+ *
732
+ * @return bool True if connected
733
+ */
734
+ public function connected()
735
+ {
736
+ if (is_resource($this->smtp_conn)) {
737
+ $sock_status = stream_get_meta_data($this->smtp_conn);
738
+ if ($sock_status['eof']) {
739
+ //The socket is valid but we are not connected
740
+ $this->edebug(
741
+ 'SMTP NOTICE: EOF caught while checking if connected',
742
+ self::DEBUG_CLIENT
743
+ );
744
+ $this->close();
745
+
746
+ return false;
747
+ }
748
+
749
+ return true; //everything looks good
750
+ }
751
+
752
+ return false;
753
+ }
754
+
755
+ /**
756
+ * Close the socket and clean up the state of the class.
757
+ * Don't use this function without first trying to use QUIT.
758
+ *
759
+ * @see quit()
760
+ */
761
+ public function close()
762
+ {
763
+ $this->server_caps = null;
764
+ $this->helo_rply = null;
765
+ if (is_resource($this->smtp_conn)) {
766
+ //Close the connection and cleanup
767
+ fclose($this->smtp_conn);
768
+ $this->smtp_conn = null; //Makes for cleaner serialization
769
+ $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
770
+ }
771
+ }
772
+
773
+ private function iterateLines($s)
774
+ {
775
+ $start = 0;
776
+ $length = strlen($s);
777
+
778
+ for ($i = 0; $i < $length; $i++) {
779
+ $c = $s[$i];
780
+ if ($c === "\n" || $c === "\r") {
781
+ yield substr($s, $start, $i - $start);
782
+ if ($c === "\r" && $i + 1 < $length && $s[$i + 1] === "\n") {
783
+ $i++;
784
+ }
785
+ $start = $i + 1;
786
+ }
787
+ }
788
+
789
+ yield substr($s, $start);
790
+ }
791
+
792
+ /**
793
+ * Send an SMTP DATA command.
794
+ * Issues a data command and sends the msg_data to the server,
795
+ * finalizing the mail transaction. $msg_data is the message
796
+ * that is to be sent with the headers. Each header needs to be
797
+ * on a single line followed by a <CRLF> with the message headers
798
+ * and the message body being separated by an additional <CRLF>.
799
+ * Implements RFC 821: DATA <CRLF>.
800
+ *
801
+ * @param string $msg_data Message data to send
802
+ *
803
+ * @return bool
804
+ */
805
+ public function data($msg_data)
806
+ {
807
+ //This will use the standard timelimit
808
+ if (!$this->sendCommand('DATA', 'DATA', 354)) {
809
+ return false;
810
+ }
811
+
812
+ /* The server is ready to accept data!
813
+ * According to rfc821 we should not send more than 1000 characters on a single line (including the LE)
814
+ * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
815
+ * smaller lines to fit within the limit.
816
+ * We will also look for lines that start with a '.' and prepend an additional '.'.
817
+ * NOTE: this does not count towards line-length limit.
818
+ */
819
+
820
+ //Iterate over lines with normalized line breaks
821
+ $lines = $this->iterateLines($msg_data);
822
+
823
+ /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
824
+ * of the first line (':' separated) does not contain a space then it _should_ be a header, and we will
825
+ * process all lines before a blank line as headers.
826
+ */
827
+
828
+ $first_line = $lines->current();
829
+ $field = substr($first_line, 0, strpos($first_line, ':'));
830
+ $in_headers = false;
831
+ if (!empty($field) && strpos($field, ' ') === false) {
832
+ $in_headers = true;
833
+ }
834
+
835
+ foreach ($lines as $line) {
836
+ $lines_out = [];
837
+ if ($in_headers && $line === '') {
838
+ $in_headers = false;
839
+ }
840
+ //Break this line up into several smaller lines if it's too long
841
+ //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len),
842
+ while (isset($line[self::MAX_LINE_LENGTH])) {
843
+ //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
844
+ //so as to avoid breaking in the middle of a word
845
+ $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
846
+ //Deliberately matches both false and 0
847
+ if (!$pos) {
848
+ //No nice break found, add a hard break
849
+ $pos = self::MAX_LINE_LENGTH - 1;
850
+ $lines_out[] = substr($line, 0, $pos);
851
+ $line = substr($line, $pos);
852
+ } else {
853
+ //Break at the found point
854
+ $lines_out[] = substr($line, 0, $pos);
855
+ //Move along by the amount we dealt with
856
+ $line = substr($line, $pos + 1);
857
+ }
858
+ //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1
859
+ if ($in_headers) {
860
+ $line = "\t" . $line;
861
+ }
862
+ }
863
+ $lines_out[] = $line;
864
+
865
+ //Send the lines to the server
866
+ foreach ($lines_out as $line_out) {
867
+ //Dot-stuffing as per RFC5321 section 4.5.2
868
+ //https://www.rfc-editor.org/rfc/rfc5321#section-4.5.2
869
+ if (!empty($line_out) && $line_out[0] === '.') {
870
+ $line_out = '.' . $line_out;
871
+ }
872
+ $this->client_send($line_out . static::LE, 'DATA');
873
+ }
874
+ }
875
+
876
+ //Message data has been sent, complete the command
877
+ //Increase timelimit for end of DATA command
878
+ $savetimelimit = $this->Timelimit;
879
+ $this->Timelimit *= 2;
880
+ $result = $this->sendCommand('DATA END', '.', 250);
881
+ $this->recordLastTransactionID();
882
+ //Restore timelimit
883
+ $this->Timelimit = $savetimelimit;
884
+
885
+ return $result;
886
+ }
887
+
888
+ /**
889
+ * Send an SMTP HELO or EHLO command.
890
+ * Used to identify the sending server to the receiving server.
891
+ * This makes sure that client and server are in a known state.
892
+ * Implements RFC 821: HELO <SP> <domain> <CRLF>
893
+ * and RFC 2821 EHLO.
894
+ *
895
+ * @param string $host The host name or IP to connect to
896
+ *
897
+ * @return bool
898
+ */
899
+ public function hello($host = '')
900
+ {
901
+ //Try extended hello first (RFC 2821)
902
+ if ($this->sendHello('EHLO', $host)) {
903
+ return true;
904
+ }
905
+
906
+ //Some servers shut down the SMTP service here (RFC 5321)
907
+ if (substr($this->helo_rply, 0, 3) == '421') {
908
+ return false;
909
+ }
910
+
911
+ return $this->sendHello('HELO', $host);
912
+ }
913
+
914
+ /**
915
+ * Send an SMTP HELO or EHLO command.
916
+ * Low-level implementation used by hello().
917
+ *
918
+ * @param string $hello The HELO string
919
+ * @param string $host The hostname to say we are
920
+ *
921
+ * @return bool
922
+ *
923
+ * @see hello()
924
+ */
925
+ protected function sendHello($hello, $host)
926
+ {
927
+ $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
928
+ $this->helo_rply = $this->last_reply;
929
+ if ($noerror) {
930
+ $this->parseHelloFields($hello);
931
+ } else {
932
+ $this->server_caps = null;
933
+ }
934
+
935
+ return $noerror;
936
+ }
937
+
938
+ /**
939
+ * Parse a reply to HELO/EHLO command to discover server extensions.
940
+ * In case of HELO, the only parameter that can be discovered is a server name.
941
+ *
942
+ * @param string $type `HELO` or `EHLO`
943
+ */
944
+ protected function parseHelloFields($type)
945
+ {
946
+ $this->server_caps = [];
947
+ $lines = explode("\n", $this->helo_rply);
948
+
949
+ foreach ($lines as $n => $s) {
950
+ //First 4 chars contain response code followed by - or space
951
+ $s = trim(substr($s, 4));
952
+ if (empty($s)) {
953
+ continue;
954
+ }
955
+ $fields = explode(' ', $s);
956
+ if (!empty($fields)) {
957
+ if (!$n) {
958
+ $name = $type;
959
+ $fields = $fields[0];
960
+ } else {
961
+ $name = array_shift($fields);
962
+ switch ($name) {
963
+ case 'SIZE':
964
+ $fields = ($fields ? $fields[0] : 0);
965
+ break;
966
+ case 'AUTH':
967
+ if (!is_array($fields)) {
968
+ $fields = [];
969
+ }
970
+ break;
971
+ default:
972
+ $fields = true;
973
+ }
974
+ }
975
+ $this->server_caps[$name] = $fields;
976
+ }
977
+ }
978
+ }
979
+
980
+ /**
981
+ * Send an SMTP MAIL command.
982
+ * Starts a mail transaction from the email address specified in
983
+ * $from. Returns true if successful or false otherwise. If True
984
+ * the mail transaction is started and then one or more recipient
985
+ * commands may be called followed by a data command.
986
+ * Implements RFC 821: MAIL <SP> FROM:<reverse-path> <CRLF> and
987
+ * two extensions, namely XVERP and SMTPUTF8.
988
+ *
989
+ * The server's EHLO response is not checked. If use of either
990
+ * extensions is enabled even though the server does not support
991
+ * that, mail submission will fail.
992
+ *
993
+ * XVERP is documented at https://www.postfix.org/VERP_README.html
994
+ * and SMTPUTF8 is specified in RFC 6531.
995
+ *
996
+ * @param string $from Source address of this message
997
+ *
998
+ * @return bool
999
+ */
1000
+ public function mail($from)
1001
+ {
1002
+ $useVerp = ($this->do_verp ? ' XVERP' : '');
1003
+ $useSmtputf8 = ($this->do_smtputf8 ? ' SMTPUTF8' : '');
1004
+
1005
+ return $this->sendCommand(
1006
+ 'MAIL FROM',
1007
+ 'MAIL FROM:<' . $from . '>' . $useSmtputf8 . $useVerp,
1008
+ 250
1009
+ );
1010
+ }
1011
+
1012
+ /**
1013
+ * Send an SMTP QUIT command.
1014
+ * Closes the socket if there is no error or the $close_on_error argument is true.
1015
+ * Implements from RFC 821: QUIT <CRLF>.
1016
+ *
1017
+ * @param bool $close_on_error Should the connection close if an error occurs?
1018
+ *
1019
+ * @return bool
1020
+ */
1021
+ public function quit($close_on_error = true)
1022
+ {
1023
+ $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
1024
+ $err = $this->error; //Save any error
1025
+ if ($noerror || $close_on_error) {
1026
+ $this->close();
1027
+ $this->error = $err; //Restore any error from the quit command
1028
+ }
1029
+
1030
+ return $noerror;
1031
+ }
1032
+
1033
+ /**
1034
+ * Send an SMTP RCPT command.
1035
+ * Sets the TO argument to $toaddr.
1036
+ * Returns true if the recipient was accepted false if it was rejected.
1037
+ * Implements from RFC 821: RCPT <SP> TO:<forward-path> <CRLF>.
1038
+ *
1039
+ * @param string $address The address the message is being sent to
1040
+ * @param string $dsn Comma separated list of DSN notifications. NEVER, SUCCESS, FAILURE
1041
+ * or DELAY. If you specify NEVER all other notifications are ignored.
1042
+ *
1043
+ * @return bool
1044
+ */
1045
+ public function recipient($address, $dsn = '')
1046
+ {
1047
+ if (empty($dsn)) {
1048
+ $rcpt = 'RCPT TO:<' . $address . '>';
1049
+ } else {
1050
+ $dsn = strtoupper($dsn);
1051
+ $notify = [];
1052
+
1053
+ if (strpos($dsn, 'NEVER') !== false) {
1054
+ $notify[] = 'NEVER';
1055
+ } else {
1056
+ foreach (['SUCCESS', 'FAILURE', 'DELAY'] as $value) {
1057
+ if (strpos($dsn, $value) !== false) {
1058
+ $notify[] = $value;
1059
+ }
1060
+ }
1061
+ }
1062
+
1063
+ $rcpt = 'RCPT TO:<' . $address . '> NOTIFY=' . implode(',', $notify);
1064
+ }
1065
+
1066
+ return $this->sendCommand(
1067
+ 'RCPT TO',
1068
+ $rcpt,
1069
+ [250, 251]
1070
+ );
1071
+ }
1072
+
1073
+ /**
1074
+ * Send SMTP XCLIENT command to server and check its return code.
1075
+ *
1076
+ * @return bool True on success
1077
+ */
1078
+ public function xclient(array $vars)
1079
+ {
1080
+ $xclient_options = "";
1081
+ foreach ($vars as $key => $value) {
1082
+ if (in_array($key, SMTP::$xclient_allowed_attributes)) {
1083
+ $xclient_options .= " {$key}={$value}";
1084
+ }
1085
+ }
1086
+ if (!$xclient_options) {
1087
+ return true;
1088
+ }
1089
+ return $this->sendCommand('XCLIENT', 'XCLIENT' . $xclient_options, 250);
1090
+ }
1091
+
1092
+ /**
1093
+ * Send an SMTP RSET command.
1094
+ * Abort any transaction that is currently in progress.
1095
+ * Implements RFC 821: RSET <CRLF>.
1096
+ *
1097
+ * @return bool True on success
1098
+ */
1099
+ public function reset()
1100
+ {
1101
+ return $this->sendCommand('RSET', 'RSET', 250);
1102
+ }
1103
+
1104
+ /**
1105
+ * Send a command to an SMTP server and check its return code.
1106
+ *
1107
+ * @param string $command The command name - not sent to the server
1108
+ * @param string $commandstring The actual command to send
1109
+ * @param int|array $expect One or more expected integer success codes
1110
+ *
1111
+ * @return bool True on success
1112
+ */
1113
+ protected function sendCommand($command, $commandstring, $expect)
1114
+ {
1115
+ if (!$this->connected()) {
1116
+ $this->setError("Called $command without being connected");
1117
+
1118
+ return false;
1119
+ }
1120
+ //Reject line breaks in all commands
1121
+ if ((strpos($commandstring, "\n") !== false) || (strpos($commandstring, "\r") !== false)) {
1122
+ $this->setError("Command '$command' contained line breaks");
1123
+
1124
+ return false;
1125
+ }
1126
+ $this->client_send($commandstring . static::LE, $command);
1127
+
1128
+ $this->last_reply = $this->get_lines();
1129
+ //Fetch SMTP code and possible error code explanation
1130
+ $matches = [];
1131
+ if (preg_match('/^([\d]{3})[ -](?:([\d]\\.[\d]\\.[\d]{1,2}) )?/', $this->last_reply, $matches)) {
1132
+ $code = (int) $matches[1];
1133
+ $code_ex = (count($matches) > 2 ? $matches[2] : null);
1134
+ //Cut off error code from each response line
1135
+ $detail = preg_replace(
1136
+ "/{$code}[ -]" .
1137
+ ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m',
1138
+ '',
1139
+ $this->last_reply
1140
+ );
1141
+ } else {
1142
+ //Fall back to simple parsing if regex fails
1143
+ $code = (int) substr($this->last_reply, 0, 3);
1144
+ $code_ex = null;
1145
+ $detail = substr($this->last_reply, 4);
1146
+ }
1147
+
1148
+ $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
1149
+
1150
+ if (!in_array($code, (array) $expect, true)) {
1151
+ $this->setError(
1152
+ "$command command failed",
1153
+ $detail,
1154
+ $code,
1155
+ $code_ex
1156
+ );
1157
+ $this->edebug(
1158
+ 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
1159
+ self::DEBUG_CLIENT
1160
+ );
1161
+
1162
+ return false;
1163
+ }
1164
+
1165
+ //Don't clear the error store when using keepalive
1166
+ if ($command !== 'RSET') {
1167
+ $this->setError('');
1168
+ }
1169
+
1170
+ return true;
1171
+ }
1172
+
1173
+ /**
1174
+ * Send an SMTP SAML command.
1175
+ * Starts a mail transaction from the email address specified in $from.
1176
+ * Returns true if successful or false otherwise. If True
1177
+ * the mail transaction is started and then one or more recipient
1178
+ * commands may be called followed by a data command. This command
1179
+ * will send the message to the users terminal if they are logged
1180
+ * in and send them an email.
1181
+ * Implements RFC 821: SAML <SP> FROM:<reverse-path> <CRLF>.
1182
+ *
1183
+ * @param string $from The address the message is from
1184
+ *
1185
+ * @return bool
1186
+ */
1187
+ public function sendAndMail($from)
1188
+ {
1189
+ return $this->sendCommand('SAML', "SAML FROM:$from", 250);
1190
+ }
1191
+
1192
+ /**
1193
+ * Send an SMTP VRFY command.
1194
+ *
1195
+ * @param string $name The name to verify
1196
+ *
1197
+ * @return bool
1198
+ */
1199
+ public function verify($name)
1200
+ {
1201
+ return $this->sendCommand('VRFY', "VRFY $name", [250, 251]);
1202
+ }
1203
+
1204
+ /**
1205
+ * Send an SMTP NOOP command.
1206
+ * Used to keep keep-alives alive, doesn't actually do anything.
1207
+ *
1208
+ * @return bool
1209
+ */
1210
+ public function noop()
1211
+ {
1212
+ return $this->sendCommand('NOOP', 'NOOP', 250);
1213
+ }
1214
+
1215
+ /**
1216
+ * Send an SMTP TURN command.
1217
+ * This is an optional command for SMTP that this class does not support.
1218
+ * This method is here to make the RFC821 Definition complete for this class
1219
+ * and _may_ be implemented in future.
1220
+ * Implements from RFC 821: TURN <CRLF>.
1221
+ *
1222
+ * @return bool
1223
+ */
1224
+ public function turn()
1225
+ {
1226
+ $this->setError('The SMTP TURN command is not implemented');
1227
+ $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
1228
+
1229
+ return false;
1230
+ }
1231
+
1232
+ /**
1233
+ * Send raw data to the server.
1234
+ *
1235
+ * @param string $data The data to send
1236
+ * @param string $command Optionally, the command this is part of, used only for controlling debug output
1237
+ *
1238
+ * @return int|bool The number of bytes sent to the server or false on error
1239
+ */
1240
+ public function client_send($data, $command = '')
1241
+ {
1242
+ //If SMTP transcripts are left enabled, or debug output is posted online
1243
+ //it can leak credentials, so hide credentials in all but lowest level
1244
+ if (
1245
+ self::DEBUG_LOWLEVEL > $this->do_debug &&
1246
+ in_array($command, ['User & Password', 'Username', 'Password'], true)
1247
+ ) {
1248
+ $this->edebug('CLIENT -> SERVER: [credentials hidden]', self::DEBUG_CLIENT);
1249
+ } else {
1250
+ $this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT);
1251
+ }
1252
+ set_error_handler(function () {
1253
+ call_user_func_array([$this, 'errorHandler'], func_get_args());
1254
+ });
1255
+ $result = fwrite($this->smtp_conn, $data);
1256
+ restore_error_handler();
1257
+
1258
+ return $result;
1259
+ }
1260
+
1261
+ /**
1262
+ * Get the latest error.
1263
+ *
1264
+ * @return array
1265
+ */
1266
+ public function getError()
1267
+ {
1268
+ return $this->error;
1269
+ }
1270
+
1271
+ /**
1272
+ * Get SMTP extensions available on the server.
1273
+ *
1274
+ * @return array|null
1275
+ */
1276
+ public function getServerExtList()
1277
+ {
1278
+ return $this->server_caps;
1279
+ }
1280
+
1281
+ /**
1282
+ * Get metadata about the SMTP server from its HELO/EHLO response.
1283
+ * The method works in three ways, dependent on argument value and current state:
1284
+ * 1. HELO/EHLO has not been sent - returns null and populates $this->error.
1285
+ * 2. HELO has been sent -
1286
+ * $name == 'HELO': returns server name
1287
+ * $name == 'EHLO': returns boolean false
1288
+ * $name == any other string: returns null and populates $this->error
1289
+ * 3. EHLO has been sent -
1290
+ * $name == 'HELO'|'EHLO': returns the server name
1291
+ * $name == any other string: if extension $name exists, returns True
1292
+ * or its options (e.g. AUTH mechanisms supported). Otherwise returns False.
1293
+ *
1294
+ * @param string $name Name of SMTP extension or 'HELO'|'EHLO'
1295
+ *
1296
+ * @return string|bool|null
1297
+ */
1298
+ public function getServerExt($name)
1299
+ {
1300
+ if (!$this->server_caps) {
1301
+ $this->setError('No HELO/EHLO was sent');
1302
+
1303
+ return null;
1304
+ }
1305
+
1306
+ if (!array_key_exists($name, $this->server_caps)) {
1307
+ if ('HELO' === $name) {
1308
+ return $this->server_caps['EHLO'];
1309
+ }
1310
+ if ('EHLO' === $name || array_key_exists('EHLO', $this->server_caps)) {
1311
+ return false;
1312
+ }
1313
+ $this->setError('HELO handshake was used; No information about server extensions available');
1314
+
1315
+ return null;
1316
+ }
1317
+
1318
+ return $this->server_caps[$name];
1319
+ }
1320
+
1321
+ /**
1322
+ * Get the last reply from the server.
1323
+ *
1324
+ * @return string
1325
+ */
1326
+ public function getLastReply()
1327
+ {
1328
+ return $this->last_reply;
1329
+ }
1330
+
1331
+ /**
1332
+ * Read the SMTP server's response.
1333
+ * Either before eof or socket timeout occurs on the operation.
1334
+ * With SMTP we can tell if we have more lines to read if the
1335
+ * 4th character is '-' symbol. If it is a space then we don't
1336
+ * need to read anything else.
1337
+ *
1338
+ * @return string
1339
+ */
1340
+ protected function get_lines()
1341
+ {
1342
+ //If the connection is bad, give up straight away
1343
+ if (!is_resource($this->smtp_conn)) {
1344
+ return '';
1345
+ }
1346
+ $data = '';
1347
+ $endtime = 0;
1348
+ stream_set_timeout($this->smtp_conn, $this->Timeout);
1349
+ if ($this->Timelimit > 0) {
1350
+ $endtime = time() + $this->Timelimit;
1351
+ }
1352
+ $selR = [$this->smtp_conn];
1353
+ $selW = null;
1354
+ while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
1355
+ //Must pass vars in here as params are by reference
1356
+ //solution for signals inspired by https://github.com/symfony/symfony/pull/6540
1357
+ set_error_handler(function () {
1358
+ call_user_func_array([$this, 'errorHandler'], func_get_args());
1359
+ });
1360
+ $n = stream_select($selR, $selW, $selW, $this->Timelimit);
1361
+ restore_error_handler();
1362
+
1363
+ if ($n === false) {
1364
+ $message = $this->getError()['detail'];
1365
+
1366
+ $this->edebug(
1367
+ 'SMTP -> get_lines(): select failed (' . $message . ')',
1368
+ self::DEBUG_LOWLEVEL
1369
+ );
1370
+
1371
+ //stream_select returns false when the `select` system call is interrupted
1372
+ //by an incoming signal, try the select again
1373
+ if (
1374
+ stripos($message, 'interrupted system call') !== false ||
1375
+ (
1376
+ // on applications with a different locale than english, the message above is not found because
1377
+ // it's translated. So we also check for the SOCKET_EINTR constant which is defined under
1378
+ // Windows and UNIX-like platforms (if available on the platform).
1379
+ defined('SOCKET_EINTR') &&
1380
+ stripos($message, 'stream_select(): Unable to select [' . SOCKET_EINTR . ']') !== false
1381
+ )
1382
+ ) {
1383
+ $this->edebug(
1384
+ 'SMTP -> get_lines(): retrying stream_select',
1385
+ self::DEBUG_LOWLEVEL
1386
+ );
1387
+ $this->setError('');
1388
+ continue;
1389
+ }
1390
+
1391
+ break;
1392
+ }
1393
+
1394
+ if (!$n) {
1395
+ $this->edebug(
1396
+ 'SMTP -> get_lines(): select timed-out in (' . $this->Timelimit . ' sec)',
1397
+ self::DEBUG_LOWLEVEL
1398
+ );
1399
+ break;
1400
+ }
1401
+
1402
+ //Deliberate noise suppression - errors are handled afterwards
1403
+ $str = @fgets($this->smtp_conn, self::MAX_REPLY_LENGTH);
1404
+ $this->edebug('SMTP INBOUND: "' . trim($str) . '"', self::DEBUG_LOWLEVEL);
1405
+ $data .= $str;
1406
+ //If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
1407
+ //or 4th character is a space or a line break char, we are done reading, break the loop.
1408
+ //String array access is a significant micro-optimisation over strlen
1409
+ if (!isset($str[3]) || $str[3] === ' ' || $str[3] === "\r" || $str[3] === "\n") {
1410
+ break;
1411
+ }
1412
+ //Timed-out? Log and break
1413
+ $info = stream_get_meta_data($this->smtp_conn);
1414
+ if ($info['timed_out']) {
1415
+ $this->edebug(
1416
+ 'SMTP -> get_lines(): stream timed-out (' . $this->Timeout . ' sec)',
1417
+ self::DEBUG_LOWLEVEL
1418
+ );
1419
+ break;
1420
+ }
1421
+ //Now check if reads took too long
1422
+ if ($endtime && time() > $endtime) {
1423
+ $this->edebug(
1424
+ 'SMTP -> get_lines(): timelimit reached (' .
1425
+ $this->Timelimit . ' sec)',
1426
+ self::DEBUG_LOWLEVEL
1427
+ );
1428
+ break;
1429
+ }
1430
+ }
1431
+
1432
+ return $data;
1433
+ }
1434
+
1435
+ /**
1436
+ * Enable or disable VERP address generation.
1437
+ *
1438
+ * @param bool $enabled
1439
+ */
1440
+ public function setVerp($enabled = false)
1441
+ {
1442
+ $this->do_verp = $enabled;
1443
+ }
1444
+
1445
+ /**
1446
+ * Get VERP address generation mode.
1447
+ *
1448
+ * @return bool
1449
+ */
1450
+ public function getVerp()
1451
+ {
1452
+ return $this->do_verp;
1453
+ }
1454
+
1455
+ /**
1456
+ * Enable or disable use of SMTPUTF8.
1457
+ *
1458
+ * @param bool $enabled
1459
+ */
1460
+ public function setSMTPUTF8($enabled = false)
1461
+ {
1462
+ $this->do_smtputf8 = $enabled;
1463
+ }
1464
+
1465
+ /**
1466
+ * Get SMTPUTF8 use.
1467
+ *
1468
+ * @return bool
1469
+ */
1470
+ public function getSMTPUTF8()
1471
+ {
1472
+ return $this->do_smtputf8;
1473
+ }
1474
+
1475
+ /**
1476
+ * Set error messages and codes.
1477
+ *
1478
+ * @param string $message The error message
1479
+ * @param string $detail Further detail on the error
1480
+ * @param string $smtp_code An associated SMTP error code
1481
+ * @param string $smtp_code_ex Extended SMTP code
1482
+ */
1483
+ protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '')
1484
+ {
1485
+ $this->error = [
1486
+ 'error' => $message,
1487
+ 'detail' => $detail,
1488
+ 'smtp_code' => $smtp_code,
1489
+ 'smtp_code_ex' => $smtp_code_ex,
1490
+ ];
1491
+ }
1492
+
1493
+ /**
1494
+ * Set debug output method.
1495
+ *
1496
+ * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it
1497
+ */
1498
+ public function setDebugOutput($method = 'echo')
1499
+ {
1500
+ $this->Debugoutput = $method;
1501
+ }
1502
+
1503
+ /**
1504
+ * Get debug output method.
1505
+ *
1506
+ * @return string
1507
+ */
1508
+ public function getDebugOutput()
1509
+ {
1510
+ return $this->Debugoutput;
1511
+ }
1512
+
1513
+ /**
1514
+ * Set debug output level.
1515
+ *
1516
+ * @param int $level
1517
+ */
1518
+ public function setDebugLevel($level = 0)
1519
+ {
1520
+ $this->do_debug = $level;
1521
+ }
1522
+
1523
+ /**
1524
+ * Get debug output level.
1525
+ *
1526
+ * @return int
1527
+ */
1528
+ public function getDebugLevel()
1529
+ {
1530
+ return $this->do_debug;
1531
+ }
1532
+
1533
+ /**
1534
+ * Set SMTP timeout.
1535
+ *
1536
+ * @param int $timeout The timeout duration in seconds
1537
+ */
1538
+ public function setTimeout($timeout = 0)
1539
+ {
1540
+ $this->Timeout = $timeout;
1541
+ }
1542
+
1543
+ /**
1544
+ * Get SMTP timeout.
1545
+ *
1546
+ * @return int
1547
+ */
1548
+ public function getTimeout()
1549
+ {
1550
+ return $this->Timeout;
1551
+ }
1552
+
1553
+ /**
1554
+ * Reports an error number and string.
1555
+ *
1556
+ * @param int $errno The error number returned by PHP
1557
+ * @param string $errmsg The error message returned by PHP
1558
+ * @param string $errfile The file the error occurred in
1559
+ * @param int $errline The line number the error occurred on
1560
+ */
1561
+ protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0)
1562
+ {
1563
+ $notice = 'Connection failed.';
1564
+ $this->setError(
1565
+ $notice,
1566
+ $errmsg,
1567
+ (string) $errno
1568
+ );
1569
+ $this->edebug(
1570
+ "$notice Error #$errno: $errmsg [$errfile line $errline]",
1571
+ self::DEBUG_CONNECTION
1572
+ );
1573
+ }
1574
+
1575
+ /**
1576
+ * Extract and return the ID of the last SMTP transaction based on
1577
+ * a list of patterns provided in SMTP::$smtp_transaction_id_patterns.
1578
+ * Relies on the host providing the ID in response to a DATA command.
1579
+ * If no reply has been received yet, it will return null.
1580
+ * If no pattern was matched, it will return false.
1581
+ *
1582
+ * @return bool|string|null
1583
+ */
1584
+ protected function recordLastTransactionID()
1585
+ {
1586
+ $reply = $this->getLastReply();
1587
+
1588
+ if (empty($reply)) {
1589
+ $this->last_smtp_transaction_id = null;
1590
+ } else {
1591
+ $this->last_smtp_transaction_id = false;
1592
+ foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
1593
+ $matches = [];
1594
+ if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
1595
+ $this->last_smtp_transaction_id = trim($matches[1]);
1596
+ break;
1597
+ }
1598
+ }
1599
+ }
1600
+
1601
+ return $this->last_smtp_transaction_id;
1602
+ }
1603
+
1604
+ /**
1605
+ * Get the queue/transaction ID of the last SMTP transaction
1606
+ * If no reply has been received yet, it will return null.
1607
+ * If no pattern was matched, it will return false.
1608
+ *
1609
+ * @return bool|string|null
1610
+ *
1611
+ * @see recordLastTransactionID()
1612
+ */
1613
+ public function getLastTransactionID()
1614
+ {
1615
+ return $this->last_smtp_transaction_id;
1616
+ }
1617
+ }