PageRenderTime 80ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/zem_contact_reborn/zem_contact_reborn.php

https://bitbucket.org/mrdale/txp-plugins
PHP | 2068 lines | 1641 code | 374 blank | 53 comment | 168 complexity | 549b458cc69ee7d9169fee9f3c3303ea MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. // This is a PLUGIN TEMPLATE for Textpattern CMS.
  3. // Copy this file to a new name like abc_myplugin.php. Edit the code, then
  4. // run this file at the command line to produce a plugin for distribution:
  5. // $ php abc_myplugin.php > abc_myplugin-0.1.txt
  6. // Plugin name is optional. If unset, it will be extracted from the current
  7. // file name. Plugin names should start with a three letter prefix which is
  8. // unique and reserved for each plugin author ("abc" is just an example).
  9. // Uncomment and edit this line to override:
  10. $plugin['name'] = 'zem_contact_reborn';
  11. // Allow raw HTML help, as opposed to Textile.
  12. // 0 = Plugin help is in Textile format, no raw HTML allowed (default).
  13. // 1 = Plugin help is in raw HTML. Not recommended.
  14. # $plugin['allow_html_help'] = 1;
  15. $plugin['version'] = '4.5.0.0';
  16. $plugin['author'] = 'TXP Community';
  17. $plugin['author_uri'] = 'http://forum.textpattern.com/viewtopic.php?id=23728';
  18. $plugin['description'] = 'Form mailer for Textpattern';
  19. // Plugin load order:
  20. // The default value of 5 would fit most plugins, while for instance comment
  21. // spam evaluators or URL redirectors would probably want to run earlier
  22. // (1...4) to prepare the environment for everything else that follows.
  23. // Values 6...9 should be considered for plugins which would work late.
  24. // This order is user-overrideable.
  25. $plugin['order'] = '5';
  26. // Plugin 'type' defines where the plugin is loaded
  27. // 0 = public : only on the public side of the website (default)
  28. // 1 = public+admin : on both the public and admin side
  29. // 2 = library : only when include_plugin() or require_plugin() is called
  30. // 3 = admin : only on the admin side (no AJAX)
  31. // 4 = admin+ajax : only on the admin side (AJAX supported)
  32. // 5 = public+admin+ajax : on both the public and admin side (AJAX supported)
  33. $plugin['type'] = '0';
  34. // Plugin "flags" signal the presence of optional capabilities to the core plugin loader.
  35. // Use an appropriately OR-ed combination of these flags.
  36. // The four high-order bits 0xf000 are available for this plugin's private use
  37. if (!defined('PLUGIN_HAS_PREFS')) define('PLUGIN_HAS_PREFS', 0x0001); // This plugin wants to receive "plugin_prefs.{$plugin['name']}" events
  38. if (!defined('PLUGIN_LIFECYCLE_NOTIFY')) define('PLUGIN_LIFECYCLE_NOTIFY', 0x0002); // This plugin wants to receive "plugin_lifecycle.{$plugin['name']}" events
  39. $plugin['flags'] = '0';
  40. // Plugin 'textpack' is optional. It provides i18n strings to be used in conjunction with gTxt().
  41. // Syntax:
  42. // ## arbitrary comment
  43. // #@event
  44. // #@language ISO-LANGUAGE-CODE
  45. // abc_string_name => Localized String
  46. $plugin['textpack'] = <<<EOT
  47. #@public
  48. zem_contact_checkbox => Checkbox
  49. zem_contact_contact => Contact
  50. zem_contact_email => Email
  51. zem_contact_email_subject => {site} > Inquiry
  52. zem_contact_email_thanks => Thank you, your message has been sent.
  53. zem_contact_field_missing => Required field, &#8220;<strong>{field}</strong>&#8221;, is missing.
  54. zem_contact_form_expired => The form has expired, please try again.
  55. zem_contact_form_used => The form was already submitted, please fill out a new form.
  56. zem_contact_general_inquiry => General inquiry
  57. zem_contact_invalid_email => &#8220;<strong>{email}</strong>&#8221; is not a valid email address.
  58. zem_contact_invalid_host => &#8220;<strong>{host}</strong>&#8221; is not a valid email host.
  59. zem_contact_invalid_utf8 => &#8220;<strong>{field}</strong>&#8221; contains invalid UTF-8 characters.
  60. zem_contact_invalid_value => Invalid value for &#8220;<strong>{field}</strong>&#8221;, &#8220;<strong>{value}</strong>&#8221; is not one of the available options.
  61. zem_contact_mail_sorry => Sorry, unable to send email.
  62. zem_contact_maxval_warning => &#8220;<strong>{field}</strong>&#8221; must not exceed {value}.
  63. zem_contact_max_warning => &#8220;<strong>{field}</strong>&#8221; must not contain more than {value} characters.
  64. zem_contact_message => Message
  65. zem_contact_minval_warning => &#8220;<strong>{field}</strong>&#8221; must be at least {value}.
  66. zem_contact_min_warning => &#8220;<strong>{field}</strong>&#8221; must contain at least {value} characters.
  67. zem_contact_name => Name
  68. zem_contact_option => Option
  69. zem_contact_radio => Radio
  70. zem_contact_recipient => Recipient
  71. zem_contact_refresh => Follow this link if the page does not refresh automatically.
  72. zem_contact_secret => Secret
  73. zem_contact_send => Send
  74. zem_contact_send_article => Send article
  75. zem_contact_spam => We do not accept spam, thank you!
  76. zem_contact_text => Text
  77. zem_contact_to_missing => &#8220;<strong>To</strong>&#8221; email address is missing.
  78. EOT;
  79. if (!defined('txpinterface'))
  80. @include_once('zem_tpl.php');
  81. # --- BEGIN PLUGIN CODE ---
  82. function zem_contact($atts, $thing = '')
  83. {
  84. global $sitename, $prefs, $production_status, $zem_contact_from, $DB,
  85. $zem_contact_recipient, $zem_contact_error, $zem_contact_submit,
  86. $zem_contact_form, $zem_contact_labels, $zem_contact_values, $zem_contact_encrypt, $enable_msg_callback;
  87. extract(zem_contact_lAtts(array(
  88. 'class' => 'zemContactForm',
  89. 'copysender' => 0,
  90. 'alsocopy' => '',
  91. 'form' => '',
  92. 'copy_form' => '',
  93. 'encrypt' => 0,
  94. 'from' => '',
  95. 'from_form' => '',
  96. 'label' => gTxt('zem_contact_contact'),
  97. 'redirect' => '',
  98. 'show_error' => 1,
  99. 'show_input' => 1,
  100. 'send_article' => 0,
  101. 'subject' => gTxt('zem_contact_email_subject', array('{site}' => html_entity_decode($sitename,ENT_QUOTES))),
  102. 'subject_form' => '',
  103. 'message_form' => '',
  104. 'html_message_form' => '',
  105. 'to' => '',
  106. 'to_form' => '',
  107. 'thanks' => graf(gTxt('zem_contact_email_thanks')),
  108. 'thanks_form' => '',
  109. 'enable_msg_callback' => 0
  110. ), $atts));
  111. unset($atts['show_error'], $atts['show_input']);
  112. $zem_contact_form_id = md5(serialize($atts).preg_replace('/[\t\s\r\n]/','',$thing));
  113. $zem_contact_submit = (ps('zem_contact_form_id') == $zem_contact_form_id);
  114. $zem_contact_encrypt = $encrypt;
  115. if (!is_callable('mail'))
  116. {
  117. return ($production_status == 'live') ?
  118. gTxt('zem_contact_mail_sorry') :
  119. gTxt('warn_mail_unavailable');
  120. }
  121. static $headers_sent = false;
  122. if (!$headers_sent) {
  123. header('Last-Modified: '.gmdate('D, d M Y H:i:s',time()-3600*24*7).' GMT');
  124. header('Expires: '.gmdate('D, d M Y H:i:s',time()+600).' GMT');
  125. header('Cache-Control: no-cache, must-revalidate');
  126. $headers_sent = true;
  127. }
  128. $nonce = mysqli_real_escape_string($DB->link, ps('zem_contact_nonce'));
  129. $renonce = false;
  130. if ($zem_contact_submit)
  131. {
  132. safe_delete('txp_discuss_nonce', 'issue_time < date_sub(now(), interval 10 minute)');
  133. if ($rs = safe_row('used', 'txp_discuss_nonce', "nonce = '$nonce'"))
  134. {
  135. if ($rs['used'])
  136. {
  137. unset($zem_contact_error);
  138. $zem_contact_error[] = gTxt('zem_contact_form_used');
  139. $renonce = true;
  140. $_POST = array();
  141. $_POST['zem_contact_submit'] = TRUE;
  142. $_POST['zem_contact_form_id'] = $zem_contact_form_id;
  143. $_POST['zem_contact_nonce'] = $nonce;
  144. }
  145. }
  146. else
  147. {
  148. $zem_contact_error[] = gTxt('zem_contact_form_expired');
  149. $renonce = true;
  150. }
  151. }
  152. if ($zem_contact_submit and $nonce and !$renonce)
  153. {
  154. $zem_contact_nonce = $nonce;
  155. }
  156. elseif (!$show_error or $show_input)
  157. {
  158. $zem_contact_nonce = md5(uniqid(rand(), true));
  159. safe_insert('txp_discuss_nonce', "issue_time = now(), nonce = '$zem_contact_nonce'");
  160. }
  161. $form = ($form) ? fetch_form($form) : $thing;
  162. if (empty($form))
  163. {
  164. $form = '
  165. <txp:zem_contact_text label="'.gTxt('zem_contact_name').'" /><br />
  166. <txp:zem_contact_email /><br />'.
  167. ($send_article ? '<txp:zem_contact_email send_article="1" label="'.gTxt('zem_contact_recipient').'" /><br />' : '').
  168. '<txp:zem_contact_textarea /><br />
  169. <txp:zem_contact_submit />
  170. ';
  171. }
  172. $form = parse($form);
  173. callback_event('zemcontact.parsed');
  174. if ($to_form)
  175. {
  176. $to = parse(fetch_form($to_form));
  177. }
  178. if (!$to and !$send_article)
  179. {
  180. return gTxt('zem_contact_to_missing');
  181. }
  182. $out = '';
  183. if (!$zem_contact_submit) {
  184. # don't show errors or send mail
  185. }
  186. elseif (!empty($zem_contact_error))
  187. {
  188. if ($show_error or !$show_input)
  189. {
  190. $out .= n.'<ul class="zemError">';
  191. foreach (array_unique($zem_contact_error) as $error)
  192. {
  193. $out .= n.t.'<li>'.$error.'</li>';
  194. }
  195. $out .= n.'</ul>';
  196. if (!$show_input) return $out;
  197. }
  198. }
  199. elseif ($show_input and is_array($zem_contact_form))
  200. {
  201. /// load and check spam plugins/
  202. callback_event('zemcontact.submit');
  203. $evaluation =& get_zemcontact_evaluator();
  204. $clean = $evaluation->get_zemcontact_status();
  205. if ($clean != 0) {
  206. return gTxt('zem_contact_spam');
  207. }
  208. if ($from_form)
  209. {
  210. $from = parse(fetch_form($from_form));
  211. }
  212. if ($subject_form)
  213. {
  214. $subject = parse(fetch_form($subject_form));
  215. }
  216. $sep = !is_windows() ? "\n" : "\r\n";
  217. $msg = array();
  218. foreach ($zem_contact_labels as $name => $label)
  219. {
  220. if (!trim($zem_contact_values[$name])) continue;
  221. $msg[] = $label.': '.$zem_contact_values[$name];
  222. }
  223. if ($send_article)
  224. {
  225. global $thisarticle;
  226. $subject = str_replace('&#38;', '&', $thisarticle['title']);
  227. $msg[] = permlinkurl($thisarticle);
  228. $msg[] = $subject;
  229. $s_ar = array('&#8216;', '&#8217;', '&#8220;', '&#8221;', '&#8217;', '&#8242;', '&#8243;', '&#8230;', '&#8211;', '&#8212;', '&#215;', '&#8482;', '&#174;', '&#169;', '&lt;', '&gt;', '&quot;', '&amp;', '&#38;', "\t", '<p');
  230. if ($prefs['override_emailcharset'] and is_callable('utf8_decode')) {
  231. $r_ar = array("'", "'", '"', '"', "'", "'", '"', '...', '-', '--', 'x', '[tm]', '(r)', '(c)', '<', '>', '"', '&', '&', ' ', "\n<p");
  232. }
  233. else
  234. {
  235. $r_ar = array('‘', '’', '“', '”', '’', '?', '?', '…', '–', '—', '×', '™', '®', '©', '<', '>', '"', '&', '&', ' ', "\n<p");
  236. }
  237. $msg[] = trim(strip_tags(str_replace($s_ar,$r_ar,(trim(strip_tags($thisarticle['excerpt'])) ? $thisarticle['excerpt'] : $thisarticle['body']))));
  238. if (empty($zem_contact_recipient))
  239. {
  240. return gTxt('zem_contact_field_missing', array('{field}' => gTxt('zem_contact_recipient')));
  241. }
  242. else
  243. {
  244. $to = $zem_contact_recipient;
  245. }
  246. }
  247. $msg = join("\n\n", $msg);
  248. $msg = str_replace("\r\n", "\n", $msg);
  249. $msg = str_replace("\r", "\n", $msg);
  250. $msg = str_replace("\n", $sep, $msg);
  251. if ($from)
  252. {
  253. $reply = $zem_contact_from;
  254. }
  255. else
  256. {
  257. $from = $zem_contact_from;
  258. $reply = '';
  259. }
  260. $from = zem_contact_strip($from);
  261. $to = zem_contact_strip($to);
  262. $subject = zem_contact_strip($subject);
  263. $reply = zem_contact_strip($reply);
  264. $msg = zem_contact_strip($msg, FALSE);
  265. if ($prefs['override_emailcharset'] and is_callable('utf8_decode'))
  266. {
  267. $charset = 'ISO-8859-1';
  268. $subject = utf8_decode($subject);
  269. $msg = utf8_decode($msg);
  270. }
  271. else
  272. {
  273. $charset = 'UTF-8';
  274. }
  275. $content_type = 'text/plain';
  276. $mime_boundary = '';
  277. $GLOBALS['hak_wine_hide_cc'] = false;
  278. $has_plain_msg = !empty($message_form);
  279. $has_html_msg = !empty($html_message_form);
  280. if ($has_plain_msg)
  281. {
  282. $plain_msg = parse_form($message_form);
  283. }
  284. if ($has_html_msg)
  285. {
  286. $html_msg = parse_form($html_message_form);
  287. }
  288. // block text/html when encrypting due to known enigmail issues
  289. if ( ($has_plain_msg && $has_html_msg) && !$zem_contact_encrypt)
  290. {
  291. $content_type = 'multipart/alternative';
  292. $mime_boundary = '----------SDLkjasldfkjaldsfn234rlk';
  293. if ($charset != 'UTF-8' && is_callable('utf8_decode'))
  294. {
  295. $plain_msg = utf8_decode($plain_msg);
  296. $html_msg = utf8_decode($html_msg);
  297. }
  298. if ($enable_msg_callback == '1')
  299. {
  300. $evaluation->set_zemcontact_msg($plain_msg);
  301. callback_event('zemcontact.msg');
  302. $plain_msg = $evaluation->get_zemcontact_msg();
  303. $evaluation->set_zemcontact_msg($html_msg);
  304. callback_event('zemcontact.msg');
  305. $html_msg = $evaluation->get_zemcontact_msg();
  306. }
  307. $msg = <<< EOM
  308. --{$mime_boundary}
  309. Content-Type: text/plain; charset={$charset}
  310. Content-Transfer-Encoding: 8bit
  311. $plain_msg
  312. --{$mime_boundary}
  313. Content-Type: text/html; charset={$charset}
  314. Content-Transfer-Encoding: 8bit
  315. $html_msg
  316. --{$mime_boundary}--
  317. EOM;
  318. }
  319. // block text/html when encrypting due to known enigmail issues
  320. else if ($has_html_msg && !$zem_contact_encrypt)
  321. {
  322. $content_type = 'text/html';
  323. $msg = ($charset != 'UTF-8' && is_callable('utf8_decode')) ? utf8_decode($html_msg) : $html_msg;
  324. }
  325. else if ($has_plain_msg)
  326. {
  327. $content_type = 'text/plain';
  328. $msg = ($charset != 'UTF-8' && is_callable('utf8_decode')) ? utf8_decode($plain_msg) : $plain_msg;
  329. }
  330. else
  331. {
  332. $content_type = 'text/plain';
  333. $msg = join('\n', zem_build_msg_array());
  334. }
  335. if ( ($zem_contact_encrypt || !($has_plain_msg && $has_html_msg)) && $enable_msg_callback == '1' )
  336. {
  337. $evaluation->set_zemcontact_msg($msg);
  338. callback_event('zemcontact.msg');
  339. $msg = $evaluation->get_zemcontact_msg();
  340. }
  341. $msg = str_replace("\r\n", "\n", $msg);
  342. $msg = str_replace("\r", "\n", $msg);
  343. $msg = str_replace("\n", $sep, $msg);
  344. $msg = zem_contact_strip($msg, FALSE);
  345. $subject = zem_contact_mailheader($subject, 'text');
  346. $headers = 'From: '.$from.
  347. ($reply ? ($sep.'Reply-To: '.$reply) : '').
  348. $sep.'X-Mailer: Textpattern (zem_contact_reborn)'.
  349. $sep.'X-Originating-IP: '.zem_contact_strip((!empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'].' via ' : '').$_SERVER['REMOTE_ADDR']).
  350. $sep.'Content-Transfer-Encoding: 8bit'.
  351. $sep.'Content-Type: '.$content_type.';'.
  352. (!empty($mime_boundary) ? ' boundary="'.$mime_boundary.'"': ' charset='.$charset);
  353. safe_update('txp_discuss_nonce', "used = '1', issue_time = now()", "nonce = '$nonce'");
  354. if (mail($to, $subject, $msg, $headers))
  355. {
  356. $_POST = array();
  357. if ($copysender and $zem_contact_from)
  358. {
  359. $GLOBALS['hak_wine_hide_cc'] = true;
  360. // we want the raw $msg again so empty it out
  361. unset($msg);
  362. $mime_boundary = '----------SDLkjasldfkjaldsfn234rlk';
  363. if ($has_plain_msg)
  364. {
  365. $content_type = 'text/plain';
  366. $plain_msg = parse_form($message_form);
  367. }
  368. if ($has_html_msg)
  369. {
  370. $content_type = 'text/html';
  371. $html_msg = parse_form($html_message_form);
  372. }
  373. if ($has_plain_msg && $has_html_msg)
  374. {
  375. $content_type = 'multipart/alternative';
  376. if ($charset != 'UTF-8' && is_callable('utf8_decode'))
  377. {
  378. $plain_msg = utf8_decode($plain_msg);
  379. $html_msg = utf8_decode($html_msg);
  380. }
  381. $msg = <<< EOM
  382. --{$mime_boundary}
  383. Content-Type: text/plain; charset="$charset"
  384. Content-Transfer-Encoding: 8bit
  385. $plain_msg
  386. --{$mime_boundary}
  387. Content-Type: text/html; charset="$charset"
  388. Content-Transfer-Encoding: 8bit
  389. $html_msg
  390. --{$mime_boundary}--
  391. EOM;
  392. }
  393. else if ($has_html_msg)
  394. {
  395. $msg = ($charset != 'UTF-8' && is_callable('utf8_decode')) ? utf8_decode($html_msg) : $html_msg;
  396. }
  397. else if ($has_plain_msg)
  398. {
  399. $msg = ($charset != 'UTF-8' && is_callable('utf8_decode')) ? utf8_decode($plain_msg) : $plain_msg;
  400. }
  401. else
  402. {
  403. $msg = zem_build_msg_array();
  404. }
  405. $msg = trim($msg);
  406. $msg = str_replace("\r\n", "\n", $msg);
  407. $msg = str_replace("\r", "\n", $msg);
  408. $msg = str_replace("\n", $sep, $msg);
  409. $msg = zem_contact_strip($msg, FALSE);
  410. $headers = 'From: '.$from.
  411. ($reply ? ($sep.'Reply-To: '.$reply) : '').
  412. $sep.'X-Mailer: Textpattern (zem_contact_reborn)'.
  413. $sep.'X-Originating-IP: '.zem_contact_strip((!empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'].' via ' : '').$_SERVER['REMOTE_ADDR']).
  414. $sep.'Content-Transfer-Encoding: 8bit'.
  415. $sep.'Content-Type: '.$content_type.';'.
  416. (!empty($mime_boundary) ? ' boundary="'.$mime_boundary.'"': ' charset='.$charset);
  417. $_POST = array();
  418. mail(zem_contact_strip($zem_contact_from), $subject, $msg, $headers);
  419. if (!empty($alsocopy)) {
  420. mail(zem_contact_strip($alsocopy), $subject, $msg, $headers);
  421. }
  422. }
  423. $_POST = array();
  424. if ($redirect)
  425. {
  426. while (@ob_end_clean());
  427. $uri = hu.ltrim($redirect,'/');
  428. if (empty($_SERVER['FCGI_ROLE']) and empty($_ENV['FCGI_ROLE']))
  429. {
  430. txp_status_header('303 See Other');
  431. header('Location: '.$uri);
  432. header('Connection: close');
  433. header('Content-Length: 0');
  434. }
  435. else
  436. {
  437. $uri = htmlspecialchars($uri);
  438. $refresh = gTxt('zem_contact_refresh');
  439. echo <<<END
  440. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  441. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  442. <head>
  443. <title>$sitename</title>
  444. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  445. <meta http-equiv="refresh" content="0;url=$uri" />
  446. </head>
  447. <body>
  448. <a href="$uri">$refresh</a>
  449. </body>
  450. </html>
  451. END;
  452. }
  453. exit;
  454. }
  455. else
  456. {
  457. return '<div class="zemThanks" id="zcr'.$zem_contact_form_id.'">' .
  458. ($thanks_form ? fetch_form($thanks_form) : $thanks) .
  459. '</div>';
  460. }
  461. }
  462. else
  463. {
  464. $out .= graf(gTxt('zem_contact_mail_sorry'));
  465. }
  466. }
  467. if ($show_input and !$send_article or gps('zem_contact_send_article'))
  468. {
  469. return '<form method="post"'.((!$show_error and $zem_contact_error) ? '' : ' id="zcr'.$zem_contact_form_id.'"').' class="'.$class.'" action="'.htmlspecialchars(serverSet('REQUEST_URI')).'#zcr'.$zem_contact_form_id.'">'.
  470. ( $label ? n.'<fieldset>' : n.'<div>' ).
  471. ( $label ? n.'<legend>'.htmlspecialchars($label).'</legend>' : '' ).
  472. $out.
  473. n.'<input type="hidden" name="zem_contact_nonce" value="'.$zem_contact_nonce.'" />'.
  474. n.'<input type="hidden" name="zem_contact_form_id" value="'.$zem_contact_form_id.'" />'.
  475. $form.
  476. callback_event('zemcontact.form').
  477. ( $label ? (n.'</fieldset>') : (n.'</div>') ).
  478. n.'</form>';
  479. }
  480. return '';
  481. }
  482. function zem_contact_strip($str, $header = TRUE) {
  483. if ($header) $str = strip_rn($str);
  484. return preg_replace('/[\x00]/', ' ', $str);
  485. }
  486. function zem_contact_text($atts)
  487. {
  488. global $zem_contact_error, $zem_contact_submit;
  489. extract(zem_contact_lAtts(array(
  490. 'type' => 'text',
  491. 'class' => 'zemText',
  492. 'break' => br,
  493. 'default' => '',
  494. 'isError' => '',
  495. 'label' => gTxt('zem_contact_text'),
  496. 'max' => 100,
  497. 'min' => 0,
  498. 'step' => '',
  499. 'name' => '',
  500. 'required' => 1,
  501. 'pattern' => '',
  502. 'placeholder' => '',
  503. 'autofocus' => '',
  504. 'autocomplete' => '',
  505. 'size' => '',
  506. ), $atts));
  507. $size = intval($size);
  508. $numeric_types = array(
  509. 'date',
  510. 'datetime',
  511. 'datetime-local',
  512. 'month',
  513. 'number',
  514. 'range',
  515. 'time',
  516. 'week',
  517. );
  518. if (empty($name)) $name = zem_contact_label2name($label);
  519. if ($zem_contact_submit)
  520. {
  521. $value = trim(ps($name));
  522. $utf8len = preg_match_all("/./su", $value, $utf8ar);
  523. $hlabel = htmlspecialchars($label);
  524. if (strlen($value))
  525. {
  526. if (!$utf8len)
  527. {
  528. $zem_contact_error[] = gTxt('zem_contact_invalid_utf8', array('{field}' => $hlabel));
  529. $isError = "errorElement";
  530. }
  531. elseif ($min and (!in_array($type, $numeric_types)) and $utf8len < $min)
  532. {
  533. $zem_contact_error[] = gTxt('zem_contact_min_warning', array('{field}' => $hlabel, '{value}' => $min));
  534. $isError = "errorElement";
  535. }
  536. elseif ($max and (!in_array($type, $numeric_types)) and $utf8len > $max)
  537. {
  538. $zem_contact_error[] = gTxt('zem_contact_max_warning', array('{field}' => $hlabel, '{value}' => $max));
  539. $isError = "errorElement";
  540. }
  541. elseif ($min and (in_array($type, $numeric_types)) and $value < $min)
  542. {
  543. $zem_contact_error[] = gTxt('zem_contact_minval_warning', array('{field}' => $hlabel, '{value}' => $min));
  544. $isError = "errorElement";
  545. }
  546. elseif ($max and (in_array($type, $numeric_types)) and $value > $max)
  547. {
  548. $zem_contact_error[] = gTxt('zem_contact_maxval_warning', array('{field}' => $hlabel, '{value}' => $max));
  549. $isError = "errorElement";
  550. }
  551. else
  552. {
  553. zem_contact_store($name, $label, $value);
  554. }
  555. }
  556. elseif ($required)
  557. {
  558. $zem_contact_error[] = gTxt('zem_contact_field_missing', array('{field}' => $hlabel));
  559. $isError = "errorElement";
  560. }
  561. }
  562. else
  563. {
  564. $value = $default;
  565. }
  566. $doctype = get_pref('doctype');
  567. $min_att = $max_att = $step_att = '';
  568. if ($doctype !== 'xhtml') {
  569. $min_att = ($min !== '') ? ' min="'.$min.'"' : '';
  570. $max_att = ($max !== '') ? ' max="'.$max.'"' : '';
  571. $step_att = ($step !== '') ? ' step="'.$step.'"' : '';
  572. }
  573. $size = ($size) ? ' size="'.$size.'"' : '';
  574. $maxlength = ($max && !in_array($type, $numeric_types)) ? ' maxlength="'.$max.'"' : '';
  575. $pattern = ($pattern) ? ' pattern="'.$pattern.'"' : '';
  576. $placeholder = ($placeholder) ? ' placeholder="'.$placeholder.'"' : '';
  577. $autofocus = ($autofocus) ? ' autofocus="'.$autofocus.'"' : '';
  578. $autocomplete = ($autocomplete) ? ' autocomplete="'.$autocomplete.'"' : '';
  579. $zemRequired = $required ? 'zemRequired' : '';
  580. return '<label for="'.$name.'" class="'.$class.' '.$zemRequired.$isError.' '.$name.'">'.htmlspecialchars($label).'</label>'.$break.
  581. '<input type="'.$type.'" id="'.$name.'" class="'.$class.' '.$zemRequired.$isError.'" name="'.$name.'" value="'.htmlspecialchars($value).'"'.$size.$maxlength.$pattern.$placeholder.$autofocus.$autocomplete.$min_att.$max_att.$step_att.' />';
  582. }
  583. function zem_contact_textarea($atts)
  584. {
  585. global $zem_contact_error, $zem_contact_submit;
  586. extract(zem_contact_lAtts(array(
  587. 'break' => br,
  588. 'class' => 'zemTextarea',
  589. 'cols' => 58,
  590. 'default' => '',
  591. 'isError' => '',
  592. 'label' => gTxt('zem_contact_message'),
  593. 'max' => 10000,
  594. 'min' => 0,
  595. 'name' => '',
  596. 'required' => 1,
  597. 'placeholder' => '',
  598. 'autofocus' => '',
  599. 'rows' => 8
  600. ), $atts));
  601. $min = intval($min);
  602. $max = intval($max);
  603. $cols = intval($cols);
  604. $rows = intval($rows);
  605. if (empty($name)) $name = zem_contact_label2name($label);
  606. if ($zem_contact_submit)
  607. {
  608. $value = preg_replace('/^\s*[\r\n]/', '', rtrim(ps($name)));
  609. $utf8len = preg_match_all("/./su", ltrim($value), $utf8ar);
  610. $hlabel = htmlspecialchars($label);
  611. if (strlen(ltrim($value)))
  612. {
  613. if (!$utf8len)
  614. {
  615. $zem_contact_error[] = gTxt('zem_contact_invalid_utf8', array('{field}' => $hlabel));
  616. $isError = "errorElement";
  617. }
  618. elseif ($min and $utf8len < $min)
  619. {
  620. $zem_contact_error[] = gTxt('zem_contact_min_warning', array('{field}' => $hlabel, '{value}' => $min));
  621. $isError = "errorElement";
  622. }
  623. elseif ($max and $utf8len > $max)
  624. {
  625. $zem_contact_error[] = gTxt('zem_contact_max_warning', array('{field}' => $hlabel, '{value}' => $max));
  626. $isError = "errorElement";
  627. #$value = join('',array_slice($utf8ar[0],0,$max));
  628. }
  629. else
  630. {
  631. zem_contact_store($name, $label, $value);
  632. }
  633. }
  634. elseif ($required)
  635. {
  636. $zem_contact_error[] = gTxt('zem_contact_field_missing', array('{field}' => $hlabel));
  637. $isError = "errorElement";
  638. }
  639. }
  640. else
  641. {
  642. $value = $default;
  643. }
  644. $zemRequired = $required ? 'zemRequired' : '';
  645. $placeholder = ($placeholder) ? ' placeholder="'.$placeholder.'"' : '';
  646. $autofocus = ($autofocus) ? ' autofocus="'.$autofocus.'"' : '';
  647. return '<label for="'.$name.'" class="'.$class.' '.$zemRequired.$isError.' '.$name.'">'.htmlspecialchars($label).'</label>'.$break.
  648. '<textarea id="'.$name.'" class="'.$class.' '.$zemRequired.$isError.'" name="'.$name.'" cols="'.$cols.'" rows="'.$rows.'"'.$placeholder.$autofocus.'>'.htmlspecialchars($value).'</textarea>';
  649. }
  650. function zem_contact_email($atts)
  651. {
  652. global $zem_contact_error, $zem_contact_submit, $zem_contact_from, $zem_contact_recipient;
  653. extract(zem_contact_lAtts(array(
  654. 'type' => 'email',
  655. 'default' => '',
  656. 'isError' => '',
  657. 'label' => gTxt('zem_contact_email'),
  658. 'max' => 100,
  659. 'min' => 0,
  660. 'name' => '',
  661. 'required' => 1,
  662. 'break' => br,
  663. 'class' => '',
  664. 'size' => '',
  665. 'placeholder' => '',
  666. 'autofocus' => '',
  667. 'autocomplete' => '',
  668. 'send_article' => 0
  669. ), $atts));
  670. if (empty($name)) $name = zem_contact_label2name($label);
  671. $email = $zem_contact_submit ? trim(ps($name)) : $default;
  672. if ($zem_contact_submit and strlen($email))
  673. {
  674. if (!is_valid_email($email))
  675. {
  676. $zem_contact_error[] = gTxt('zem_contact_invalid_email', array('{email}' => htmlspecialchars($email)));
  677. $isError = "errorElement";
  678. }
  679. else
  680. {
  681. preg_match("/@(.+)$/", $email, $match);
  682. $domain = $match[1];
  683. if (is_callable('checkdnsrr') and checkdnsrr('textpattern.com.','A') and !checkdnsrr($domain.'.','MX') and !checkdnsrr($domain.'.','A'))
  684. {
  685. $zem_contact_error[] = gTxt('zem_contact_invalid_host', array('{host}' => htmlspecialchars($domain)));
  686. $isError = "errorElement";
  687. }
  688. else
  689. {
  690. if ($send_article) {
  691. $zem_contact_recipient = $email;
  692. }
  693. else {
  694. $zem_contact_from = $email;
  695. }
  696. }
  697. }
  698. }
  699. return zem_contact_text(array(
  700. 'type' => $type,
  701. 'default' => $email,
  702. 'isError' => $isError,
  703. 'label' => $label,
  704. 'max' => $max,
  705. 'min' => $min,
  706. 'name' => $name,
  707. 'required' => $required,
  708. 'break' => $break,
  709. 'class' => $class,
  710. 'size' => $size,
  711. 'placeholder' => $placeholder,
  712. 'autofocus' => $autofocus,
  713. 'autocomplete' => $autocomplete,
  714. ));
  715. }
  716. function zem_contact_select($atts)
  717. {
  718. global $zem_contact_error, $zem_contact_submit;
  719. extract(zem_contact_lAtts(array(
  720. 'name' => '',
  721. 'break' => ' ',
  722. 'class' => 'zemSelect',
  723. 'delimiter' => ',',
  724. 'isError' => '',
  725. 'label' => gTxt('zem_contact_option'),
  726. 'list' => gTxt('zem_contact_general_inquiry'),
  727. 'required' => 1,
  728. 'selected' => '',
  729. 'autofocus' => '',
  730. ), $atts));
  731. if (empty($name)) $name = zem_contact_label2name($label);
  732. $list = array_map('trim', explode($delimiter, preg_replace('/[\r\n\t\s]+/', ' ',$list)));
  733. if ($zem_contact_submit)
  734. {
  735. $value = trim(ps($name));
  736. if (strlen($value))
  737. {
  738. if (in_array($value, $list))
  739. {
  740. zem_contact_store($name, $label, $value);
  741. }
  742. else
  743. {
  744. $zem_contact_error[] = gTxt('zem_contact_invalid_value', array('{field}' => htmlspecialchars($label), '{value}' => htmlspecialchars($value)));
  745. $isError = "errorElement";
  746. }
  747. }
  748. elseif ($required)
  749. {
  750. $zem_contact_error[] = gTxt('zem_contact_field_missing', array('{field}' => htmlspecialchars($label)));
  751. $isError = "errorElement";
  752. }
  753. }
  754. else
  755. {
  756. $value = $selected;
  757. }
  758. $out = '';
  759. foreach ($list as $item)
  760. {
  761. $out .= n.t.'<option'.($item == $value ? ' selected="selected">' : '>').(strlen($item) ? htmlspecialchars($item) : ' ').'</option>';
  762. }
  763. $zemRequired = $required ? 'zemRequired' : '';
  764. $autofocus = ($autofocus) ? ' autofocus="'.$autofocus.'"' : '';
  765. return '<label for="'.$name.'" class="'.$class.' '.$zemRequired.$isError.' '.$name.'">'.htmlspecialchars($label).'</label>'.$break.
  766. n.'<select id="'.$name.'" name="'.$name.'" class="'.$class.' '.$zemRequired.$isError.'"'.$autofocus.'>'.
  767. $out.
  768. n.'</select>';
  769. }
  770. function zem_contact_checkbox($atts)
  771. {
  772. global $zem_contact_error, $zem_contact_submit;
  773. extract(zem_contact_lAtts(array(
  774. 'break' => ' ',
  775. 'class' => 'zemCheckbox',
  776. 'checked' => 0,
  777. 'isError' => '',
  778. 'label' => gTxt('zem_contact_checkbox'),
  779. 'name' => '',
  780. 'required' => 1,
  781. 'autofocus' => '',
  782. ), $atts));
  783. if (empty($name)) $name = zem_contact_label2name($label);
  784. if ($zem_contact_submit)
  785. {
  786. $value = (bool) ps($name);
  787. if ($required and !$value)
  788. {
  789. $zem_contact_error[] = gTxt('zem_contact_field_missing', array('{field}' => htmlspecialchars($label)));
  790. $isError = "errorElement";
  791. }
  792. else
  793. {
  794. zem_contact_store($name, $label, $value ? gTxt('yes') : gTxt('no'));
  795. }
  796. }
  797. else {
  798. $value = $checked;
  799. }
  800. $zemRequired = $required ? 'zemRequired' : '';
  801. $autofocus = ($autofocus) ? ' autofocus="'.$autofocus.'"' : '';
  802. return '<input type="checkbox" id="'.$name.'" class="'.$class.' '.$zemRequired.$isError.'" name="'.$name.'"'.
  803. ($value ? ' checked="checked"' : '').$autofocus.' />'.$break.
  804. '<label for="'.$name.'" class="'.$class.' '.$zemRequired.$isError.' '.$name.'">'.htmlspecialchars($label).'</label>';
  805. }
  806. function zem_contact_serverinfo($atts)
  807. {
  808. global $zem_contact_submit;
  809. extract(zem_contact_lAtts(array(
  810. 'label' => '',
  811. 'name' => ''
  812. ), $atts));
  813. if (empty($name)) $name = zem_contact_label2name($label);
  814. if (strlen($name) and $zem_contact_submit)
  815. {
  816. if (!$label) $label = $name;
  817. zem_contact_store($name, $label, serverSet($name));
  818. }
  819. }
  820. function zem_contact_secret($atts, $thing = '')
  821. {
  822. global $zem_contact_submit;
  823. extract(zem_contact_lAtts(array(
  824. 'name' => '',
  825. 'label' => gTxt('zem_contact_secret'),
  826. 'value' => ''
  827. ), $atts));
  828. $name = zem_contact_label2name($name ? $name : $label);
  829. if ($zem_contact_submit)
  830. {
  831. if ($thing) $value = trim(parse($thing));
  832. zem_contact_store($name, $label, $value);
  833. }
  834. return '';
  835. }
  836. function zem_contact_radio($atts)
  837. {
  838. global $zem_contact_error, $zem_contact_submit, $zem_contact_values;
  839. extract(zem_contact_lAtts(array(
  840. 'break' => ' ',
  841. 'class' => 'zemRadio',
  842. 'checked' => 0,
  843. 'group' => '',
  844. 'label' => gTxt('zem_contact_option'),
  845. 'name' => '',
  846. 'autofocus' => '',
  847. ), $atts));
  848. static $cur_name = '';
  849. static $cur_group = '';
  850. if (!$name and !$group and !$cur_name and !$cur_group) {
  851. $cur_group = gTxt('zem_contact_radio');
  852. $cur_name = $cur_group;
  853. }
  854. if ($group and !$name and $group != $cur_group) $name = $group;
  855. if ($name) $cur_name = $name;
  856. else $name = $cur_name;
  857. if ($group) $cur_group = $group;
  858. else $group = $cur_group;
  859. $id = 'q'.md5($name.'=>'.$label);
  860. $name = zem_contact_label2name($name);
  861. if ($zem_contact_submit)
  862. {
  863. $is_checked = (ps($name) == $id);
  864. if ($is_checked or $checked and !isset($zem_contact_values[$name]))
  865. {
  866. zem_contact_store($name, $group, $label);
  867. }
  868. }
  869. else
  870. {
  871. $is_checked = $checked;
  872. }
  873. $autofocus = ($autofocus) ? ' autofocus="'.$autofocus.'"' : '';
  874. return '<input value="'.$id.'" type="radio" id="'.$id.'" class="'.$class.' '.$name.'" name="'.$name.'"'.$autofocus.
  875. ( $is_checked ? ' checked="checked" />' : ' />').$break.
  876. '<label for="'.$id.'" class="'.$class.' '.$name.'">'.htmlspecialchars($label).'</label>';
  877. }
  878. function zem_contact_send_article($atts)
  879. {
  880. if (!isset($_REQUEST['zem_contact_send_article'])) {
  881. $linktext = (empty($atts['linktext'])) ? gTxt('zem_contact_send_article') : $atts['linktext'];
  882. $join = (empty($_SERVER['QUERY_STRING'])) ? '?' : '&';
  883. $href = $_SERVER['REQUEST_URI'].$join.'zem_contact_send_article=yes';
  884. return '<a href="'.htmlspecialchars($href).'">'.htmlspecialchars($linktext).'</a>';
  885. }
  886. return;
  887. }
  888. function zem_contact_submit($atts, $thing)
  889. {
  890. extract(zem_contact_lAtts(array(
  891. 'button' => 0,
  892. 'label' => gTxt('zem_contact_send'),
  893. 'class' => 'zemSubmit',
  894. ), $atts));
  895. $label = htmlspecialchars($label);
  896. if ($button or strlen($thing))
  897. {
  898. return '<button type="submit" class="'.$class.'" name="zem_contact_submit" value="'.$label.'">'.($thing ? trim(parse($thing)) : $label).'</button>';
  899. }
  900. else
  901. {
  902. return '<input type="submit" class="'.$class.'" name="zem_contact_submit" value="'.$label.'" />';
  903. }
  904. }
  905. function zem_contact_lAtts($arr, $atts)
  906. {
  907. foreach(array('button', 'copysender', 'checked', 'required', 'send_article', 'show_input', 'show_error') as $key)
  908. {
  909. if (isset($atts[$key]))
  910. {
  911. $atts[$key] = ($atts[$key] === 'yes' or intval($atts[$key])) ? 1 : 0;
  912. }
  913. }
  914. if (isset($atts['break']) and $atts['break'] == 'br') $atts['break'] = '<br />';
  915. return lAtts($arr, $atts);
  916. }
  917. class zemcontact_evaluation
  918. {
  919. var $status;
  920. var $msg;
  921. function zemcontact_evaluation() {
  922. $this->status = 0;
  923. }
  924. function add_zemcontact_status($check) {
  925. $this->status += $check;
  926. }
  927. function get_zemcontact_status() {
  928. return $this->status;
  929. }
  930. function set_zemcontact_msg ($msg) {
  931. $this->msg = $msg;
  932. }
  933. function get_zemcontact_msg() {
  934. return $this->msg;
  935. }
  936. }
  937. function &get_zemcontact_evaluator()
  938. {
  939. static $instance;
  940. if(!isset($instance)) {
  941. $instance = new zemcontact_evaluation();
  942. }
  943. return $instance;
  944. }
  945. function zem_contact_label2name($label)
  946. {
  947. $label = trim($label);
  948. if (strlen($label) == 0) return 'invalid';
  949. if (strlen($label) <= 32 and preg_match('/^[a-zA-Z][A-Za-z0-9:_-]*$/', $label)) return $label;
  950. else return 'q'.md5($label);
  951. }
  952. function zem_contact_store($name, $label, $value)
  953. {
  954. global $zem_contact_form, $zem_contact_labels, $zem_contact_values;
  955. $zem_contact_form[$label] = $value;
  956. $zem_contact_labels[$name] = $label;
  957. $zem_contact_values[$name] = $value;
  958. }
  959. function zem_contact_mailheader($string, $type)
  960. {
  961. global $prefs;
  962. if (!strstr($string,'=?') and !preg_match('/[\x00-\x1F\x7F-\xFF]/', $string)) {
  963. if ("phrase" == $type) {
  964. if (preg_match('/[][()<>@,;:".\x5C]/', $string)) {
  965. $string = '"'. strtr($string, array("\\" => "\\\\", '"' => '\"')) . '"';
  966. }
  967. }
  968. elseif ("text" != $type) {
  969. trigger_error('Unknown encode_mailheader type', E_USER_WARNING);
  970. }
  971. return $string;
  972. }
  973. if ($prefs['override_emailcharset']) {
  974. $start = '=?ISO-8859-1?B?';
  975. $pcre = '/.{1,42}/s';
  976. }
  977. else {
  978. $start = '=?UTF-8?B?';
  979. $pcre = '/.{1,45}(?=[\x00-\x7F\xC0-\xFF]|$)/s';
  980. }
  981. $end = '?=';
  982. $sep = is_windows() ? "\r\n" : "\n";
  983. preg_match_all($pcre, $string, $matches);
  984. return $start . join($end.$sep.' '.$start, array_map('base64_encode',$matches[0])) . $end;
  985. }
  986. function zem_build_msg_array()
  987. {
  988. global $zem_contact_labels, $zem_contact_values;
  989. $msg = array();
  990. $in_lot = false;
  991. foreach ($zem_contact_labels as $name => $label)
  992. {
  993. if (!trim($zem_contact_values[$name]))
  994. {
  995. $in_lot = false;
  996. continue;
  997. }
  998. if ($in_lot && ($zem_contact_values[$name] == '$0.00' || empty($zem_contact_values[$name])))
  999. {
  1000. // pop secret
  1001. array_pop($msg);
  1002. // pop lot
  1003. array_pop($msg);
  1004. // skip cost
  1005. }
  1006. else
  1007. {
  1008. $val = $zem_contact_values[$name];
  1009. if ( (($name=='clubDiscountField' || $name=='caseDiscountField') && ($val=='$0.00' || $val='-$0.00'))
  1010. || ($name=='shippingState' && $val=='Choose') )
  1011. {
  1012. // skip it
  1013. }
  1014. else
  1015. {
  1016. $msg[] = $label.': '.$val;
  1017. }
  1018. }
  1019. $in_lot = strncasecmp($name,'lot-',4) == 0;
  1020. }
  1021. return $msg;
  1022. }
  1023. # --- END PLUGIN CODE ---
  1024. if (0) {
  1025. ?>
  1026. <!--
  1027. # --- BEGIN PLUGIN HELP ---
  1028. <style>
  1029. li code {font-weight: bold;}
  1030. pre {padding: 0.5em 1em; background: #eee; border: 1px dashed #ccc;}
  1031. h1, h2, h3, h3 code {font-family: sans-serif; font-weight: bold;}
  1032. h1, h2, h3 {margin-left: -1em;}
  1033. h2, h3 {margin-top: 2em;}
  1034. h1 {font-size: 3em;}
  1035. h2 {font-size: 2em;}
  1036. h3 {font-size: 1.5em;}
  1037. li a code {font-weight: normal;}
  1038. .required, .warning {color:red;}
  1039. </style>
  1040. <h1 id="top">Zem Contact Reborn</h1>
  1041. <p>Please reports bugs and problems with this plugin in <a href="http://forum.textpattern.com/viewtopic.php?id=23728">this forum thread</a>.</p>
  1042. <h2 id="contents">Contents</h2>
  1043. <ul>
  1044. <li><a href="#features">Features</a>
  1045. <li><a href="#start">Getting started</a>
  1046. <ul>
  1047. <li><a href="#contactform">Contact form</a></li>
  1048. </ul></li>
  1049. <ul>
  1050. <li><a href="#sendarticle">Send article</a></li>
  1051. <li><a href="#tags">Tags</a>
  1052. <li><a href="#zc"> <code>&#60;txp:zem_contact /&#62;</code> </a></li>
  1053. <li><a href="#zc_text"> <code>&#60;txp:zem_contact_text /&#62;</code> </a></li>
  1054. <li><a href="#zc_email"> <code>&#60;txp:zem_contact_email /&#62;</code> </a></li>
  1055. <li><a href="#zc_textarea"> <code>&#60;txp:zem_contact_textarea /&#62;</code> </a></li>
  1056. <li><a href="#zc_submit"> <code>&#60;txp:zem_contact_submit /&#62;</code> </a></li>
  1057. <li><a href="#zc_select"> <code>&#60;txp:zem_contact_select /&#62;</code> </a></li>
  1058. <li><a href="#zc_checkbox"> <code>&#60;txp:zem_contact_checkbox /&#62;</code> </a></li>
  1059. <li><a href="#zc_radio"> <code>&#60;txp:zem_contact_radio /&#62;</code> </a></li>
  1060. <li><a href="#zc_secret"> <code>&#60;txp:zem_contact_secret /&#62;</code> </a></li>
  1061. <li><a href="#zc_server_info"> <code>&#60;txp:zem_contact_serverinfo /&#62;</code> </a></li>
  1062. </ul></li>
  1063. <ul>
  1064. <li><a href="#zc_send_article"> <code>&#60;txp:zem_contact_send_article /&#62;</code> </a></li>
  1065. <li><a href="#advanced">Advanced examples</a>
  1066. <li><a href="#show_error">Separate input and error forms</a></li>
  1067. <li><a href="#subject_form">User selectable subject field</a></li>
  1068. </ul></li>
  1069. <ul>
  1070. <li><a href="#to_form">User selectable recipient, without showing email addresses</a></li>
  1071. </ul></li>
  1072. <li><a href="#styling">Styling</a></li>
  1073. <li><a href="#history">History</a></li>
  1074. <li><a href="#credits">Credits</a></li>
  1075. </ul>
  1076. <ul>
  1077. <li><a href="#api">Plugin <span class="caps"><span class="caps">API</span></span> and callback events</a></li>
  1078. </ul>
  1079. <h2 id="features">Features</h2>
  1080. <ul>
  1081. <li>Arbitrary text fields can be specified, with min/max/required settings for validation.</li>
  1082. <li>Email address validation, including a check for a valid MX record (Unix only).</li>
  1083. <li>Safe escaping of input data.</li>
  1084. <li>UTF-8 safe.</li>
  1085. <li>Accessible form layout, including <code>&#60;label&#62;</code>, <code>&#60;legend&#62;</code> and <code>&#60;fieldset&#62;</code> tags.</li>
  1086. <li>Various classes and ids to allow easy styling of all parts of the form.</li>
  1087. <li>A separate language plug-in to enable easy localisation.</li>
  1088. </ul>
  1089. <ul>
  1090. <li>Spam prevention <span class="caps"><span class="caps">API</span></span> (used by Tranquillo&#8217;s <code>pap_contact_cleaner</code> plugin).</li>
  1091. </ul>
  1092. <p><a href="#top">Back to top</a></p>
  1093. <h2 id="start">Getting started</h2>
  1094. <h3 id="contactform">Contact form</h3>
  1095. <p>The simplest form is shown below, which produces a default form with Name, Email and Message fields. Email will be delivered to recipient@example.com, with the user&#8217;s supplied email as the &#8220;From:&#8221; address.</p>
  1096. <pre><code>&#60;txp:zem_contact to=&#34;recipient@example.com&#34; /&#62;
  1097. </code></pre>
  1098. <p>To specify fields explicitly, use something like this:</p>
  1099. <pre><code>&#60;txp:zem_contact to=&#34;recipient@example.com&#34;&#62;
  1100. &#60;txp:zem_contact_email /&#62;
  1101. &#60;txp:zem_contact_text label=&#34;Phone&#34; min=7 max=15/&#62;
  1102. &#60;txp:zem_contact_textarea label=&#34;Your question&#34; /&#62;
  1103. &#60;txp:zem_contact_submit label=&#34;Send&#34; /&#62;
  1104. &#60;/txp:zem_contact&#62;
  1105. </code></pre>
  1106. <p>Alternatively, place the field specifications in a Textpattern form, and call it like this:</p>
  1107. <pre><code>&#60;txp:zem_contact to=&#34;recipient@example.com&#34; form=&#34;mycontactform&#34; /&#62;
  1108. </code></pre>
  1109. <p><a href="#top">Back to top</a></p>
  1110. <h3 id="sendarticle">Send article</h3>
  1111. <p>Within the context of an individual article, this plugin can be used to send the article (or excerpt, if it exists) to an email address specified by the visitor. This requires at least two tags:
  1112. <ul>
  1113. <li><code>zem_contact</code>, to create form that is initially hidden by setting the <code>send_article</code> attribute.</li>
  1114. </ul>
  1115. <ul>
  1116. <li><code>zem_contact_send_article</code>, to create a &#8216;send article&#8217; link which reveals the aforementioned form when clicked.</li>
  1117. </ul></p>
  1118. <pre><code>&#60;txp:zem_contact send_article=&#34;1&#34; /&#62;
  1119. &#60;txp:zem_contact_send_article /&#62;
  1120. </code></pre>
  1121. <p>By default the form contains fields for your name and email address, the recipient&#8217;s email address and a personal message, but similar to contact forms you can create your own form layout. Some things you need to know:
  1122. <ul>
  1123. <li>Set the <code>send_article</code> attribute to <code>1</code> in the <code>zem_contact</code> tag.</li>
  1124. </ul>
  1125. <ul>
  1126. <li>Use a <code>zem_contact_email</code> tag with the <code>send_article</code> attribute set to <code>1</code>. This field will be used as the recipient email address.</li>
  1127. </ul></p>
  1128. <pre><code>&#60;txp:zem_contact to=&#34;you@example.com&#34; send_article=&#34;1&#34;&#62;
  1129. &#60;txp:zem_contact_email label=&#34;Recipient Email&#34; send_article=&#34;1&#34; /&#62;
  1130. &#60;txp:zem_contact_email label=&#34;Your Email&#34; /&#62;
  1131. &#60;txp:zem_contact_submit label=&#34;Send Article&#34; /&#62;
  1132. &#60;/txp:zem_contact&#62;
  1133. </code>
  1134. <code>&#60;txp:zem_contact_send_article /&#62;
  1135. </code></pre>
  1136. <p><a href="#top">Back to top</a></p>
  1137. <h2 id="tags">Tags</h2>
  1138. <p><a href="#zc"><code>&#60;txp:zem_contact /&#62;</code></a> produces a flexible, customisable email contact form. It is intended for use as an enquiry form for commercial and private sites, and includes several features to help reduce common problems with such forms (invalid email addresses, missing information).</p>
  1139. <p><a href="#zc_send_article"><code>&#60;txp:zem_contact_send_article /&#62;</code></a> can be used to create a &#8220;send article&#8221; link within an article form, connecting it to the contact form.</p>
  1140. <p>All other tags provided by this plugin can only be used inside a <code>&#60;txp:zem_contact&#62;</code> &#8230; <code>&#60;/txp:zem_contact&#62;</code> container tag or in a Textpattern form used as the <code>form</code> attribute in the <code>&#60;txp:zem_contact /&#62;</code> tag.</p>
  1141. <p><a href="#top">Back to top</a></p>
  1142. <h3 id="zc"><code>&#60;txp:zem_contact /&#62;</code></h3>
  1143. <p>May be used as a self-closing or container tag. Place this where you want the input form to go. Status and error messages, if any, will be displayed before the form.</p>
  1144. <h4>Attributes</h4>
  1145. <ul>
  1146. <li><code>to=&#34;email address&#34;</code> <span class="required">required</span><br />
  1147. Recipient email address.</li>
  1148. </ul>
  1149. <ul>
  1150. <li><code>to_form=&#34;form name&#34;</code><br />
  1151. Use specified form (overrides <strong>to</strong> attribute).</li>
  1152. </ul>
  1153. <ul>
  1154. <li><code>from=&#34;email address&#34;</code><br />
  1155. Email address used in the &#8220;From:&#8221; field when sending email. Defaults to the sender&#8217;s email address. If specified, the sender&#8217;s email address will be placed in the &#8220;Reply-To:&#8221; field instead.</li>
  1156. </ul>
  1157. <ul>
  1158. <li><code>from_form=&#34;form name&#34;</code><br />
  1159. Use specified form (overrides <strong>from</strong> attribute).</li>
  1160. </ul>
  1161. <ul>
  1162. <li><code>subject=&#34;subject text&#34;</code><br />
  1163. Subject used when sending an email. Default is the site name.</li>
  1164. </ul>
  1165. <ul>
  1166. <li><code>subject_form=&#34;form name&#34;</code><br />
  1167. Use specified form (overrides <strong>subject</strong> attribute).</li>
  1168. </ul>
  1169. <ul>
  1170. <li><code>thanks=&#34;text&#34;</code><br />
  1171. Message shown after successfully submitting a message. Default is <strong>Thank you, your message has been sent</strong>.</li>
  1172. <li><code>thanks_form=&#34;form name&#34;</code><br />
  1173. Use specified form (overrides <strong>thanks</strong> attribute).</li>
  1174. </ul>
  1175. <ul>
  1176. <li><code>redirect=&#34;URL&#34;</code><br />
  1177. Redirect to specified <span class="caps"><span class="caps">URL</span></span> (overrides <strong>thanks</strong> and <strong>thanks_form</strong> attributes). <span class="caps"><span class="caps">URL</span></span> must be relative to the Textpattern Site <span class="caps"><span class="caps">URL</span></span>. Example: <em>redirect=&#8220;monkey&#8221;</em> would redirect to http://example.com/monkey.</li>
  1178. </ul>
  1179. <ul>
  1180. <li><code>label=&#34;text&#34;</code><br />
  1181. Label for the contact form. If set to an empty string, display of the fieldset and legend tags will be suppressed. Default is <strong>Contact</strong>.</li>
  1182. <li><code>send_article=&#34;boolean&#34;</code><br />
  1183. Whether to use this form to <a href="#article">send an article</a>. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is <strong>0</strong> (no).</li>
  1184. </ul>
  1185. <ul>
  1186. <li><code>copysender=&#34;boolean&#34;</code><br />
  1187. Whether to send a copy of the email to the sender&#8217;s address. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is <strong>0</strong> (no).</li>
  1188. </ul>
  1189. <ul>
  1190. <li><code>form=&#34;form name&#34;</code><br />
  1191. Use specified form, containing the layout of the contact form fields.</li>
  1192. <li><code>show_input=&#34;boolean&#34;</code><br />
  1193. Whether to display the form input fields. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is <strong>1</strong> (yes).</li>
  1194. </ul>
  1195. <ul>
  1196. <li><code>show_error=&#34;boolean&#34;</code><br />
  1197. Whether to display error and status messages. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is <strong>1</strong> (yes).</li>
  1198. </ul>
  1199. <h4>Examples</h4>
  1200. <p>See <a href="#contactform">Getting started</a> and <a href="#advanced">Advanced examples</a>.</p>
  1201. <p><a href="#top">Back to top</a></p>
  1202. <h3 id="zc_text"><code>&#60;txp:zem_contact_text /&#62;</code></h3>
  1203. <p>Creates a text input field and corresponding <code>&#60;label&#62;</code> tag. The input value will be included in the email, preceded by the label.</p>
  1204. <h4>Attributes</h4>
  1205. <ul>
  1206. <li><code>label=&#34;text&#34;</code><br />
  1207. Text label displayed to the user. Default is <strong>Text</strong>.</li>
  1208. <li><code>type=&#34;value&#34;</code><br />
  1209. Field type, as defined by the W3C, such as 'text', 'number', 'range', 'date', etc. Default is 'text'.</li>
  1210. <li><code>name=&#34;value&#34;</code><br />
  1211. Field name, as used in the <span class="caps"><span class="caps">HTML</span></span> input tag.</li>
  1212. <li><code>break=&#34;tag&#34;</code><br />
  1213. Break tag between the label and input field. Default is <code>&#60;br /&#62;</code>. Use <code>break=&#34;&#34;</code> to put the label and input field on the same line.</li>
  1214. <li><code>default=&#34;value&#34;</code><br />
  1215. Default value when no input is provided.</li>
  1216. <li><code>min=&#34;number&#34;</code><br />
  1217. Minimum input length in characters, or minimum field value for numeric types. Default is <strong>0</strong>.</li>
  1218. <li><code>max=&#34;number&#34;</code><br />
  1219. Maximum input length in characters, or maximum field value for numeric types. Default is <strong>100</strong>.</li>
  1220. <li><code>step=&#34;number&#34;</code><br />
  1221. Interval between successive permissible values in numeric input controls.</li>
  1222. <li><code>size=&#34;integer&#34;</code><br />
  1223. Size of the input field as displayed to the user.</li>
  1224. <li><code>class=&#34;classname&#34;</code><br />
  1225. CSS class to apply to the input field. Default is zemText.</li>
  1226. <li><code>placeholder=&#34;text&#34;</code><br />
  1227. Placeholder text to put in the input field.</li>
  1228. <li><code>autofocus=&#34;boolean&#34;</code><br />
  1229. Set the field to receive focus.</li>
  1230. <li><code>autocomplete=&#34;boolean&#34;</code><br />
  1231. Permit the field to offer entries from previously supplied content.</li>
  1232. <li><code>pattern=&#34;regular expression&#34;</code><br />
  1233. Javascript regular expression with which to validate the field contents.</li>
  1234. </ul>
  1235. <ul>
  1236. <li><code>required=&#34;boolean&#34;</code><br />
  1237. Whether this text field must be filled out. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is <strong>1</strong> (yes).</li>
  1238. </ul>
  1239. <h4>Example</h4>
  1240. <pre><code>&#60;txp:zem_contact_text label=&#34;Your name&#34; /&#62;
  1241. </code></pre>
  1242. <p><a href="#top">Back to top</a></p>
  1243. <h3 id="zc_email"><code>&#60;txp:zem_contact_email /&#62;</code></h3>
  1244. <p>Input field for user&#8217;s email address.</p>
  1245. <p>The entered email address will automatically be validated to make sure it is of the form &#8220;abc@xxx.yyy[.zzz]&#8221;. On non-Windows servers, a test will be done to verify that an A or MX record exists for the domain. Neither test prevents spam, but it does help detecting accidental typing errors.</p>
  1246. <h4>Attributes</h4>
  1247. <ul>
  1248. <li><code>label=&#34;text&#34;</code><br />
  1249. Text label displayed to the user. Default is <strong>Email</strong>.</li>
  1250. <li><code>name=&#34;value&#34;</code><br />
  1251. Field name, as used in the <span class="caps"><span class="caps">HTML</span></span> input tag.</li>
  1252. <li><code>type=&#34;value&#34;</code><br />
  1253. Field type, as defined by the W3C. Suitable values are 'text' or 'email'. Default is 'email'.</li>
  1254. <li><code>break=&#34;tag&#34;</code><br />
  1255. Break tag between the label and input field. Default is <code>&#60;br /&#62;</code>. Use <code>break=&#34;&#34;</code> to put the label and input field on the same line.</li>
  1256. <li><code>default=&#34;value&#34;</code><br />
  1257. Default value when no input is provided.</li>
  1258. <li><code>min=&#34;integer&#34;</code><br />
  1259. Minimum input length in characters. Default is <strong>0</strong>.</li>
  1260. <li><code>max=&#34;integer&#34;</code><br />
  1261. Maximum input length in characters. Default is <strong>100</strong>.</li>
  1262. <li><code>size=&#34;integer&#34;</code><br />
  1263. Size of the input field as displayed to the user.</li>
  1264. <li><code>required=&#34;boolean&#34;</code><br />
  1265. Whether this text field must be filled out. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is <strong>1</strong> (yes).</li>
  1266. <li><code>class=&#34;classname&#34;</code><br />
  1267. CSS class to apply to the field. Default is zemText.</li>
  1268. <li><code>placeholder=&#34;text&#34;</code><br />
  1269. Placeholder text to put in the field.</li>
  1270. <li><code>autofocus=&#34;boolean&#34;</code><br />
  1271. Set the field to receive initial focus.</li>
  1272. <li><code>autocomplete=&#34;boolean&#34;</code><br />
  1273. Permit the field to offer entries from previously supplied content.</li>
  1274. </ul>
  1275. <ul>
  1276. <li><code>send_article=&#34;boolean&#34;</code><br />
  1277. Whether this field is used as the recipient email address when using the send_article function. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is <strong>0</strong> (no).</li>
  1278. </ul>
  1279. <h4>Example</h4>
  1280. <pre><code>&#60;txp:zem_contact_email label=&#34;Your email address&#34; /&#62;
  1281. </code></pre>
  1282. <p><a href="#top">Back to top</a></p>
  1283. <h3 id="zc_textarea"><code>&#60;txp:zem_contact_textarea /&#62;</code></h3>
  1284. <p>Creates a textarea.</p>
  1285. <h4>Attributes

Large files files are truncated, but you can click here to view the full file