PageRenderTime 51ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/baser/controllers/components/bc_email.php

https://github.com/hashing/basercms
PHP | 448 lines | 209 code | 60 blank | 179 comment | 36 complexity | 422c03734ed75224a5406f22866b2bd4 MD5 | raw file
Possible License(s): MIT
  1. <?php
  2. /* SVN FILE: $Id$ */
  3. /**
  4. * Email 拡張モデル
  5. *
  6. * PHP versions 5
  7. *
  8. * baserCMS : Based Website Development Project <http://basercms.net>
  9. * Copyright 2008 - 2012, baserCMS Users Community <http://sites.google.com/site/baserusers/>
  10. *
  11. * @copyright Copyright 2008 - 2012, baserCMS Users Community
  12. * @link http://basercms.net baserCMS Project
  13. * @package baser.plugins.feed.models
  14. * @since baserCMS v 0.1.0
  15. * @version $Revision$
  16. * @modifiedby $LastChangedBy$
  17. * @lastmodified $Date$
  18. * @license http://basercms.net/license/index.html
  19. */
  20. /**
  21. * Include files
  22. */
  23. App::import('Component','Email');
  24. /**
  25. * Email 拡張モデル
  26. *
  27. * @package baser.plugins.feed.controller.components
  28. */
  29. class BcEmailComponent extends EmailComponent {
  30. // CUSTOMIZE ADD 2011/05/07 ryuring
  31. // プラグインのテンプレートを指定できるようにした
  32. // >>>
  33. /**
  34. * プラグイン名
  35. *
  36. * @var string
  37. * @access public
  38. */
  39. var $plugin = null;
  40. // <<<
  41. /**
  42. * Send an email using the specified content, template and layout
  43. *
  44. * @param mixed $content Either an array of text lines, or a string with contents
  45. * @param string $template Template to use when sending email
  46. * @param string $layout Layout to use to enclose email body
  47. * @return boolean Success
  48. * @access public
  49. */
  50. function send($content = null, $template = null, $layout = null) {
  51. $this->__createHeader();
  52. if ($template) {
  53. $this->template = $template;
  54. }
  55. if ($layout) {
  56. $this->layout = $layout;
  57. }
  58. if (is_array($content)) {
  59. $content = implode("\n", $content) . "\n";
  60. }
  61. $message = $this->__wrap($content);
  62. if ($this->template === null) {
  63. $message = $this->__formatMessage($message);
  64. } else {
  65. $message = $this->__renderTemplate($message);
  66. }
  67. // テンプレート内の変数がラップされるように再ラップ
  68. $message = $this->___wrap($message);
  69. $message[] = '';
  70. foreach($message as $key => $line) {
  71. // 文字コード変換
  72. $enc = mb_detect_encoding($line);
  73. // 半角カタカナを全角カタカナに変換
  74. if (low($this->charset) !== 'jis') {
  75. $line = mb_convert_kana($line, 'K', $enc);
  76. }
  77. $message[$key] = mb_convert_encoding($line,$this->charset,$enc);
  78. }
  79. $this->__message = $message;
  80. if (!empty($this->attachments)) {
  81. $this->__attachFiles();
  82. }
  83. if (!is_null($this->__boundary)) {
  84. $this->__message[] = '';
  85. $this->__message[] = '--' . $this->__boundary . '--';
  86. $this->__message[] = '';
  87. }
  88. if ($this->_debug) {
  89. return $this->__debug();
  90. }
  91. $__method = '__' . $this->delivery;
  92. $sent = $this->$__method();
  93. $this->__header = array();
  94. $this->__message = array();
  95. return $sent;
  96. }
  97. /**
  98. * Wrap the message using EmailComponent::$lineLength
  99. *
  100. * @param string $message Message to wrap
  101. * @return string Wrapped message
  102. * @access private
  103. */
  104. function __wrap($message) {
  105. $message = $this->__strip($message, true);
  106. // MODIFIED 2008/6/22 ryuring
  107. //$message = str_replace(array("\r\n","\r","\n"), "", $message);
  108. //$message = str_replace("<br />", "\n", $message);
  109. // MODIFIED 2008/7/1
  110. // CakePHPは、PHPの閉じタグの直後の改行を削除する仕様だという事がわかった。
  111. // メールなど、明示的な改行タグがないものについては、PHP閉じタグの直後に半角スペースなど
  112. // を挿入する事により、改行が有効となる。よってテンプレート側で対応する事にし、
  113. // 処理を元にに戻した。
  114. $message = str_replace(array("\r\n","\r"), "\n", $message);
  115. $lines = explode("\n", $message);
  116. return $this->___wrap($lines);
  117. }
  118. /**
  119. * テンプレートを整形後に再度ラップする必要があるのでラップ処理の部分だけを分離
  120. *
  121. * @param array $lines
  122. * @return array
  123. * @access private
  124. */
  125. function ___wrap($lines) {
  126. $formatted = array();
  127. if ($this->_lineLength !== null) {
  128. trigger_error('_lineLength cannot be accessed please use lineLength', E_USER_WARNING);
  129. $this->lineLength = $this->_lineLength;
  130. }
  131. foreach ($lines as $line) {
  132. if(substr($line, 0, 1) == '.') {
  133. $line = '.' . $line;
  134. }
  135. $enc = mb_detect_encoding($line);
  136. $formatted = array_merge($formatted, $this->mbFold($line,$this->lineLength,$enc));
  137. }
  138. $formatted[] = '';
  139. return $formatted;
  140. }
  141. /**
  142. * Encode the specified string using the current charset
  143. *
  144. * @param string $subject String to encode
  145. * @return string Encoded string
  146. * @access private
  147. */
  148. function __encode($subject) {
  149. $subject = $this->__strip($subject);
  150. if (low($this->charset) !== 'iso-8859-15') {
  151. $enc = mb_detect_encoding($subject);
  152. $_enc = mb_internal_encoding();
  153. mb_internal_encoding($enc);
  154. /*
  155. $start = "=?" . $this->charset . "?B?";
  156. $end = "?=";
  157. $spacer = $end . "\n " . $start;
  158. $length = 75 - strlen($start) - strlen($end);
  159. $length = $length - ($length % 4);
  160. $subject = base64_encode($subject);
  161. $subject = chunk_split($subject, $length, $spacer);
  162. $spacer = preg_quote($spacer);
  163. $subject = preg_replace("/" . $spacer . "$/", "", $subject);
  164. $subject = $start . $subject . $end;
  165. */
  166. $subject = mb_encode_mimeheader($subject,$this->charset,'B', Configure::read('BcEmail.lfcode'));
  167. mb_internal_encoding($_enc);
  168. }
  169. return $subject;
  170. }
  171. /**
  172. * マルチバイト文字を考慮したfolding(折り畳み)処理
  173. *
  174. * @param mixed $str foldingを行う文字列or文字列の配列
  175. * 文字列に改行が含まれている場合は改行位置でも分割される
  176. * @param integer $width 一行の幅(バイト数)。4以上でなければならない
  177. * @param string $encoding $strの文字エンコーディング
  178. * 省略した場合は内部文字エンコーディングを使用する
  179. * @return array 一行ずつに分けた文字列の配列
  180. *
  181. * NOTE: いわゆる半角/全角といった見た目ではなく、
  182. * バイト数によって処理が行われるので、文字エンコーディングによって
  183. * 結果が変わる可能性がある。
  184. *
  185. * 例えば半角カナはShift-JISでは1バイトだが、EUC-JPでは2バイトなので、
  186. * $width=10の場合Shift-JISなら10文字だが、EUC-JPでは5文字になる。
  187. *
  188. * 全角/半角といった見た目で処理をするにはmb_strwidth()を利用した
  189. * 実装が必要となる。
  190. *
  191. * TODO: 日本語禁則処理(Japanese Hyphenation)
  192. * 行頭禁則文字は濁点/半濁点の応用でいけるので
  193. * 行末禁則文字の処理を加えれば対応できそう
  194. *
  195. * ……と思ったけど、禁則文字が$widthを超える分だけ並んでたら
  196. * どうすればいいんだろう
  197. * 禁則処理をした結果、桁あふれを起こす場合は禁則処理を無視して
  198. * 強制的に$widthで改行する、とか?
  199. */
  200. function mbFold($str, $width, $encoding = null) {
  201. assert('$width >= 4');
  202. if (!isset($str)) {
  203. return null;
  204. }
  205. if (!isset($encoding)) {
  206. $encoding = mb_internal_encoding();
  207. }
  208. // 元々の配列も文字列中の改行もとにかく展開してひとつの配列にする
  209. $strings = array();
  210. foreach ((array)$str as $s) {
  211. // NOTE: 何故かmb_split()だと改行でうまく分割できない
  212. // どうせメジャーなエンコーディングなら制御コードは
  213. // leading byteにもtrailing byteにもかぶらないので
  214. // preg_split()で良しとする ※JISはアウト
  215. // NOTE: mb_regex_encoding()を適切に設定してやることで
  216. // mb_split()でも正常に分割できるようになったが、
  217. // 何故かmb_regex_encoding()がJISを受け入れてくれない
  218. $strings = array_merge($strings,
  219. preg_split('/\x0d\x0a|\x0d|\x0a/', $s));
  220. }
  221. $lines = array();
  222. foreach ($strings as $string) {
  223. // 1文字ずつに分解して足していって、
  224. // バイト数が$widthを超えたら次の行に回す
  225. $len = mb_strlen($string, $encoding);
  226. for ($i = 0, $line = ''; $i < $len; $i++) {
  227. $char = mb_substr($string, $i, 1, $encoding);
  228. // 濁点や半濁点が続いていた場合のいい加減な禁則処理
  229. // ものすごく日本語依存...
  230. // TODO: Unicodeの結合文字の判定とかで汎用的に処理したい
  231. if ($i + 1 < $len) {
  232. $next = mb_substr($string, $i + 1, 1, $encoding);
  233. $uc = mb_convert_encoding($next, 'UCS-2', $encoding);
  234. if (in_array($uc, array("\x30\x99", "\x30\x9B", "\x30\x9C",
  235. "\xFF\x9E", "\xFF\x9F"))) {
  236. $char .= $next;
  237. $i++;
  238. }
  239. }
  240. if (strlen($line . $char) > $width) {
  241. $lines[] = $line;
  242. $line = $char;
  243. } else {
  244. $line .= $char;
  245. }
  246. }
  247. $lines[] = $line; // 端数or空行
  248. }
  249. return $lines;
  250. }
  251. /**
  252. * Format a string as an email address
  253. *
  254. * @param string $string String representing an email address
  255. * @return string Email address suitable for email headers or smtp pipe
  256. * @access private
  257. */
  258. function __formatAddress($string, $smtp = false) {
  259. $hasAlias = preg_match('/((.*)\s)?<(.+)>/', $string, $matches);
  260. if ($smtp && $hasAlias) {
  261. return $this->__strip('<' . $matches[3] . '>');
  262. } elseif ($smtp) {
  263. return $this->__strip('<' . $string . '>');
  264. }
  265. if ($hasAlias && !empty($matches[2])) {
  266. // >>> CUSTOMIZE MODIFY 2010/12/06 ryuring
  267. // 送信者名をエンコード
  268. //return $this->__strip($matches[2] . ' <' . $matches[3] . '>');
  269. // ---
  270. return $this->__strip($this->__encode($matches[2]) . ' <' . $matches[3] . '>');
  271. // <<<
  272. }
  273. return $this->__strip($string);
  274. }
  275. /**
  276. * Render the contents using the current layout and template.
  277. *
  278. * @param string $content Content to render
  279. * @return array Email ready to be sent
  280. * @access private
  281. */
  282. function __renderTemplate($content) {
  283. $viewClass = $this->Controller->view;
  284. if ($viewClass != 'View') {
  285. if (strpos($viewClass, '.') !== false) {
  286. list($plugin, $viewClass) = explode('.', $viewClass);
  287. }
  288. $viewClass = $viewClass . 'View';
  289. App::import('View', $this->Controller->view);
  290. }
  291. $View = new $viewClass($this->Controller);
  292. $View->layout = $this->layout;
  293. $msg = array();
  294. // CUSTOMIZE ADD 2012/04/23 ryuring
  295. // layoutPath / subDir を指定できるようにした
  296. // >>>
  297. $layoutPath = $subDir = '';
  298. if(!empty($this->layoutPath)) {
  299. $layoutPath = $this->layoutPath.DS;
  300. }
  301. if(!empty($this->subDir)) {
  302. $subDir = $this->subDir.DS;
  303. }
  304. // <<<
  305. $content = implode("\n", $content);
  306. if ($this->sendAs === 'both') {
  307. $htmlContent = $content;
  308. if (!empty($this->attachments)) {
  309. $msg[] = '--' . $this->__boundary;
  310. $msg[] = 'Content-Type: multipart/alternative; boundary="alt-' . $this->__boundary . '"';
  311. $msg[] = '';
  312. }
  313. $msg[] = '--alt-' . $this->__boundary;
  314. $msg[] = 'Content-Type: text/plain; charset=' . $this->charset;
  315. $msg[] = 'Content-Transfer-Encoding: 7bit';
  316. $msg[] = '';
  317. // CUSTOMIZE MODIRY 2012/04/23 ryuring
  318. // layoutPath / subDir を指定できるようにした
  319. // >>>
  320. //$content = $View->element('email' . DS . 'text' . DS . $this->template, array('content' => $content), true);
  321. //$View->layoutPath = 'email' . DS . 'text';
  322. // ---
  323. $content = $View->element($subDir . 'email' . DS . 'text' . DS . $this->template, array('content' => $content), true);
  324. $View->layoutPath = $layoutPath.'email' . DS . 'text';
  325. // >>>
  326. $content = explode("\n", str_replace(array("\r\n", "\r"), "\n", $View->renderLayout($content)));
  327. $msg = array_merge($msg, $content);
  328. $msg[] = '';
  329. $msg[] = '--alt-' . $this->__boundary;
  330. $msg[] = 'Content-Type: text/html; charset=' . $this->charset;
  331. $msg[] = 'Content-Transfer-Encoding: 7bit';
  332. $msg[] = '';
  333. // CUSTOMIZE MODIRY 2012/04/23 ryuring
  334. // layoutPath / subDir を指定できるようにした
  335. // >>>
  336. //$htmlContent = $View->element('email' . DS . 'html' . DS . $this->template, array('content' => $htmlContent), true);
  337. //$View->layoutPath = 'email' . DS . 'html';
  338. // ---
  339. $htmlContent = $View->element($subDir . 'email' . DS . 'html' . DS . $this->template, array('content' => $htmlContent), true);
  340. $View->layoutPath = $layoutPath.'email' . DS . 'html';
  341. // <<<
  342. $htmlContent = explode("\n", str_replace(array("\r\n", "\r"), "\n", $View->renderLayout($htmlContent)));
  343. $msg = array_merge($msg, $htmlContent);
  344. $msg[] = '';
  345. $msg[] = '--alt-' . $this->__boundary . '--';
  346. $msg[] = '';
  347. ClassRegistry::removeObject('view');
  348. return $msg;
  349. }
  350. if (!empty($this->attachments)) {
  351. if ($this->sendAs === 'html') {
  352. $msg[] = '';
  353. $msg[] = '--' . $this->__boundary;
  354. $msg[] = 'Content-Type: text/html; charset=' . $this->charset;
  355. $msg[] = 'Content-Transfer-Encoding: 7bit';
  356. $msg[] = '';
  357. } else {
  358. $msg[] = '--' . $this->__boundary;
  359. $msg[] = 'Content-Type: text/plain; charset=' . $this->charset;
  360. $msg[] = 'Content-Transfer-Encoding: 7bit';
  361. $msg[] = '';
  362. }
  363. }
  364. // CUSTOMIZE MODIFY 2011/04/25 ryuring
  365. // プラグインのテンプレートを指定できるようにした
  366. // CUSTOMIZE MODIRY 2012/04/23 ryuring
  367. // layoutPath / subDir を指定できるようにした
  368. // >>>
  369. //$content = $View->element('email' . DS . $this->sendAs . DS . $this->template, array('content' => $content), true);
  370. //$View->layoutPath = 'email' . DS . $this->sendAs;
  371. // ---
  372. if($this->plugin) {
  373. $options = array('content' => $content, 'plugin' => $this->plugin);
  374. } else {
  375. $options = array('content' => $content);
  376. }
  377. $content = $View->element($subDir . 'email' . DS . $this->sendAs . DS . $this->template, $options, true);
  378. $View->layoutPath = $layoutPath.'email' . DS . $this->sendAs;
  379. // <<<
  380. $content = explode("\n", str_replace(array("\r\n", "\r"), "\n", $View->renderLayout($content)));
  381. $msg = array_merge($msg, $content);
  382. ClassRegistry::removeObject('view');
  383. return $msg;
  384. }
  385. }
  386. ?>