PageRenderTime 74ms CodeModel.GetById 26ms RepoModel.GetById 0ms 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
  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</h4>
  1286. <ul>
  1287. <li><code>label=&#34;text&#34;</code><br />
  1288. Text label displayed to the user. Default is <strong>Message</strong>.</li>
  1289. <li><code>name=&#34;value&#34;</code><br />
  1290. Field name, as used in the <span class="caps"><span class="caps">HTML</span></span> input tag.</li>
  1291. <li><code>break=&#34;tag&#34;</code><br />
  1292. 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>
  1293. <li><code>default=&#34;value&#34;</code><br />
  1294. Default value when no input is provided.</li>
  1295. <li><code>cols=&#34;integer&#34;</code><br />
  1296. Column width. Default is <strong>58</strong>.</li>
  1297. <li><code>rows=&#34;integer&#34;</code><br />
  1298. Row height. Default is <strong>8</strong>.</li>
  1299. <li><code>min=&#34;integer&#34;</code><br />
  1300. Minimum input length in characters. Default is <strong>0</strong>.</li>
  1301. <li><code>max=&#34;integer&#34;</code><br />
  1302. Maximum input length in characters. Default is <strong>10000</strong>.</li>
  1303. <li><code>class=&#34;classname&#34;</code><br />
  1304. CSS class to apply to the textarea. Default is zemTextarea.</li>
  1305. <li><code>placeholder=&#34;text&#34;</code><br />
  1306. Placeholder text to put in the textarea.</li>
  1307. <li><code>autofocus=&#34;boolean&#34;</code><br />
  1308. Set the textarea to receive initial focus.</li>
  1309. </ul>
  1310. <ul>
  1311. <li><code>required=&#34;boolean&#34;</code><br />
  1312. 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>
  1313. </ul>
  1314. <h4>Example</h4>
  1315. <p>Textarea that is 40 chars wide, 10 lines high, with a customized label:</p>
  1316. <pre><code>&#60;txp:zem_contact_textarea cols=&#34;40&#34; rows=&#34;10&#34; label=&#34;Your question&#34; /&#62;
  1317. </code></pre>
  1318. <p><a href="#top">Back to top</a></p>
  1319. <h3 id="zc_submit"><code>&#60;txp:zem_contact_submit /&#62;</code></h3>
  1320. <p>Creates a submit button.<br />
  1321. When used as a container tag, a &#8220;button&#8221; element will be used instead of an &#8220;input&#8221; element.</p>
  1322. <h4>Attributes:</h4>
  1323. <ul>
  1324. <li><code>label=&#34;text&#34;</code><br />
  1325. Text shown on the submit button. Default is &#8220;Send&#8221;.</li>
  1326. <li><code>class=&#34;classname&#34;</code><br />
  1327. CSS class to apply to the button. Default is zemSubmit</li>
  1328. </ul>
  1329. <ul>
  1330. <li><code>button=&#34;boolean&#34;</code><br />
  1331. <em>Deprecated. Use a container tag if you want a button element.</em></li>
  1332. </ul>
  1333. <h4>Examples</h4>
  1334. <p>Standard submit button:</p>
  1335. <pre><code>&#60;txp:zem_contact_submit /&#62;
  1336. </code></pre>
  1337. <p>Submit button with your own text:</p>
  1338. <pre><code>&#60;txp:zem_contact_submit label=&#34;Send&#34; /&#62;
  1339. </code></pre>
  1340. <p>Usage as a container tag, which allows you to use Textpattern tags and <span class="caps">HTML</span> markup in the submit button:</p>
  1341. <pre><code>&#60;txp:zem_contact_submit&#62;&#60;strong&#62;Send&#60;/strong&#62; question&#60;/txp:zem_contact_submit&#62;
  1342. </code></pre>
  1343. <pre><code>&#60;txp:zem_contact_submit&#62;&#60;img src=&#34;path/to/img.png&#34; alt=&#34;submit&#34;&#62;&#60;/txp:zem_contact_submit&#62;
  1344. </code></pre>
  1345. <p><a href="#top">Back to top</a></p>
  1346. <h3 id="zc_select"><code>&#60;txp:zem_contact_select /&#62;</code></h3>
  1347. <p>Creates a drop-down selection list.</p>
  1348. <h4>Attributes</h4>
  1349. <ul>
  1350. <li><code>list=&#34;comma-separated values&#34;</code> <span class="required">required</span><br />
  1351. List of items to show in the select box.</li>
  1352. <li><code>selected=&#34;value&#34;</code><br />
  1353. List item that is selected by default.</li>
  1354. <li><code>label=&#34;text&#34;</code><br />
  1355. Text label displayed to the user. Default is <strong>Option</strong>.</li>
  1356. <li><code>name=&#34;value&#34;</code><br />
  1357. Field name, as used in the <span class="caps"><span class="caps">HTML</span></span> input tag.</li>
  1358. <li><code>break=&#34;tag&#34;</code><br />
  1359. 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>
  1360. <li><code>delimiter=&#34;character&#34;</code><br />
  1361. Separator character used in the <strong>list</strong> attribute. Default is <strong>,</strong> (comma).</li>
  1362. <li><code>class=&#34;classname&#34;</code><br />
  1363. CSS class to apply to the field. Default is zemSelect.</li>
  1364. <li><code>autofocus=&#34;boolean&#34;</code><br />
  1365. Set the field to receive initial focus.</li>
  1366. </ul>
  1367. <ul>
  1368. <li><code>required=&#34;boolean&#34;</code><br />
  1369. Whether a non-empty option must be selected. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is <strong>1</strong> (yes).</li>
  1370. </ul>
  1371. <h4>Examples</h4>
  1372. <p>Select list labeled &#8216;Department&#8217;, containing three options and a blank option (due to the comma before &#8216;Marketing&#8217;) shown by default, forcing the user to make a selection.</p>
  1373. <pre><code>&#60;txp:zem_contact_select label=&#34;Department&#34; list=&#34;,Marketing,Sales,Support&#34; /&#62;
  1374. </code></pre>
  1375. <p>Select list containing three options with &#8216;Marketing&#8217; selected by default.</p>
  1376. <pre><code>&#60;txp:zem_contact_select list=&#34;Marketing,Sales,Support&#34; selected=&#34;Marketing&#34; /&#62;
  1377. </code></pre>
  1378. <p><a href="#top">Back to top</a></p>
  1379. <h3 id="zc_checkbox"><code>&#60;txp:zem_contact_checkbox /&#62;</code></h3>
  1380. <p>Creates a check box.</p>
  1381. <h4>Attributes</h4>
  1382. <ul>
  1383. <li><code>label=&#34;text&#34;</code><br />
  1384. Text label displayed to the user. Default is <strong>Checkbox</strong>.</li>
  1385. <li><code>name=&#34;value&#34;</code><br />
  1386. Field name, as used in the <span class="caps"><span class="caps">HTML</span></span> input tag.</li>
  1387. <li><code>break=&#34;tag&#34;</code><br />
  1388. 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>
  1389. <li><code>checked=&#34;boolean&#34;</code><br />
  1390. Whether this box is checked when first displayed. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is &#8220;0&#8221; (no).</li>
  1391. <li><code>class=&#34;classname&#34;</code><br />
  1392. CSS class to apply to the field. Default is zemCheckbox.</li>
  1393. <li><code>autofocus=&#34;boolean&#34;</code><br />
  1394. Set the field to receive initial focus.</li>
  1395. </ul>
  1396. <ul>
  1397. <li><code>required=&#34;boolean&#34;</code><br />
  1398. Whether this checkbox must be filled out. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is <strong>1</strong> (yes).</li>
  1399. </ul>
  1400. <h4>Examples</h4>
  1401. <p>Shrink-wrap agreement which must be checked by the user before the email will be sent.</p>
  1402. <pre><code>&#60;txp:zem_contact_checkbox label=&#34;I accept the terms and conditions&#34; /&#62;
  1403. </code></pre>
  1404. <p>Optional checkboxes:</p>
  1405. <pre><code>Select which operating systems are you familiar with:&#60;br /&#62;
  1406. &#60;txp:zem_contact_checkbox label=&#34;Windows&#34; required=&#34;0&#34; /&#62;&#60;br /&#62;
  1407. &#60;txp:zem_contact_checkbox label=&#34;Unix/Linux/BSD&#34; required=&#34;0&#34; /&#62;&#60;br /&#62;
  1408. &#60;txp:zem_contact_checkbox label=&#34;MacOS&#34; required=&#34;0&#34; /&#62;&#60;br /&#62;
  1409. </code></pre>
  1410. <p><a href="#top">Back to top</a></p>
  1411. <h3 id="zc_radio"><code>&#60;txp:zem_contact_radio /&#62;</code></h3>
  1412. <p>Creates a radio button.</p>
  1413. <h4>Attributes</h4>
  1414. <ul>
  1415. <li><code>group=&#34;text&#34;</code> <span class="required">required</span><br />
  1416. Text used in the email to describe this group of radio buttons. This attribute value is remembered for subsequent radio buttons, so you only have to set it on the first radio button of a group. Default is <strong>Radio</strong>.</li>
  1417. <li><code>label=&#34;text&#34;</code> <span class="required">required</span><br />
  1418. Text label displayed to the user as radio button option.</li>
  1419. <li><code>name=&#34;value&#34;</code><br />
  1420. Field name, as used in the <span class="caps"><span class="caps">HTML</span></span> input tag. This attribute value is remembered for subsequent radio buttons, so you only have to set it on the first radio button of a group. If it hasn&#8217;t been set at all, it will be derived from the <code>group</code> attribute.</li>
  1421. <li><code>break=&#34;tag&#34;</code><br />
  1422. Break tag between the label and field. Default is a space.</li>
  1423. <li><code>class=&#34;classname&#34;</code><br />
  1424. CSS class to apply to the field. Default is zemRadio.</li>
  1425. <li><code>autofocus=&#34;boolean&#34;</code><br />
  1426. Set the field to receive initial focus.</li>
  1427. </ul>
  1428. <ul>
  1429. <li><code>checked=&#34;boolean&#34;</code><br />
  1430. Whether this radio option is checked when the form is first displayed. Available values: <strong>1</strong> (yes), <strong>0</strong> (no). Default is <strong>0</strong> (no).</li>
  1431. </ul>
  1432. <h4>Example</h4>
  1433. <p>Group mutually exclusive radio buttons by setting the <code>group</code> attribute on the first radio button in a group. Only the chosen radio button from each group will be used in the email message. The message will be output in the form <strong>group: label</strong> for each of the chosen radio buttons.</p>
  1434. <pre><code>&#60;txp:zem_contact_radio label=&#34;Medium&#34; group=&#34;I like my steak&#34; /&#62;
  1435. &#60;txp:zem_contact_radio label=&#34;Rare&#34; /&#62;
  1436. &#60;txp:zem_contact_radio label=&#34;Well done&#34; /&#62;
  1437. </code>
  1438. <code>&#60;txp:zem_contact_radio label=&#34;Wine&#34; group=&#34;With a glass of&#34; /&#62;
  1439. &#60;txp:zem_contact_radio label=&#34;Beer&#34; /&#62;
  1440. &#60;txp:zem_contact_radio label=&#34;Water&#34; /&#62;
  1441. </code></pre>
  1442. <p><a href="#top">Back to top</a></p>
  1443. <h3 id="zc_secret"><code>&#60;txp:zem_contact_secret /&#62;</code></h3>
  1444. <p>This tag has no effect on the form or <span class="caps">HTML</span> output, but will include additional information in the email. It can be used as a self-closing tag or as a container tag.</p>
  1445. <h4>Attributes</h4>
  1446. <ul>
  1447. <li><code>name=&#34;text&#34;</code><br />
  1448. Used internally. Set this only if you have multiple &#8216;secret&#8217; form elements with identical labels.</li>
  1449. <li><code>label=&#34;text&#34;</code><br />
  1450. Used to identify the field in the email. Default is <strong>Secret</strong>.</li>
  1451. </ul>
  1452. <ul>
  1453. <li><code>value=&#34;value&#34;</code><br />
  1454. Some text you want to add to the email.</li>
  1455. </ul>
  1456. <h4>Examples</h4>
  1457. <p>Usage as a self-closing tag</p>
  1458. <pre><code>&#60;txp:zem_contact_secret value=&#34;The answer is 42&#34; /&#62;
  1459. </code></pre>
  1460. <p>Usage as a container tag</p>
  1461. <pre><code>&#60;txp:zem_contact_secret label=&#34;Dear user&#34;&#62;
  1462. Please provide a useful example for this tag!
  1463. &#60;/txp:zem_contact_secret&#62;
  1464. </code></pre>
  1465. <p><a href="#top">Back to top</a></p>
  1466. <h3 id="zc_serverinfo"><code>&#60;txp:zem_contact_serverinfo /&#62;</code></h3>
  1467. <p>This tag has no effect on the form or <span class="caps">HTML</span> output, but will include additional information in the email based on the <span class="caps">PHP</span> $_SERVER variable.</p>
  1468. <h4>Attributes</h4>
  1469. <ul>
  1470. <li><code>name=&#34;value&#34;</code> <span class="required">required</span><br />
  1471. Name of the server variable. See the <a href="http://php.net/manual/reserved.variables.php#reserved.variables.server"><span class="caps">PHP</span> manual</a> for a full list.</li>
  1472. </ul>
  1473. <ul>
  1474. <li><code>label=&#34;text&#34;</code><br />
  1475. Used to identify the field in the email. Defaults to the value of the <strong>name</strong> attribute.</li>
  1476. </ul>
  1477. <h4>Examples</h4>
  1478. <p>Add the IP address of the visitor to the email</p>
  1479. <pre><code>&#60;txp:zem_contact_serverinfo name=&#34;REMOTE_ADDR&#34; label=&#34;IP number&#34; /&#62;
  1480. </code></pre>
  1481. <p>Add the name of the visitor&#8217;s browser to the email</p>
  1482. <pre><code>&#60;txp:zem_contact_serverinfo name=&#34;HTTP_USER_AGENT&#34; label=&#34;Browser&#34; /&#62;
  1483. </code></pre>
  1484. <p><a href="#top">Back to top</a></p>
  1485. <h3 id="zc_send_article"><code>&#60;txp:zem_contact_send_article /&#62;</code></h3>
  1486. <p>Use this tag in your individual article form, where you want the &#8220;send article&#8221; link to be displayed.</p>
  1487. <h4>Attributes:</h4>
  1488. <ul>
  1489. <li><code>linktext=&#34;text&#34;</code><br />
  1490. Text displayed for the link. Default is <strong>send article</strong></li>
  1491. </ul>
  1492. <h4>Examples:</h4>
  1493. <p>See <a href="#sendarticle">Getting started</a></p>
  1494. <p><a href="#top">Back to top</a></p>
  1495. <h2 id="advanced">Advanced examples</h2>
  1496. <h3 id="show_error">Separate input and error forms</h3>
  1497. <p>Using <code>show_input</code> and <code>show_error</code> to display the form and error messages on different parts of a page. A form is used to make sure the contents of both forms are identical, otherwise they would be seen as two independent forms. The first form only shows errors (no input), the second form only shows the input fields (no errors).</p>
  1498. <pre><code>&#60;div id=&#34;error&#34;&#62;
  1499. &#60;txp:zem_contact form=&#34;contact_form&#34; show_input=&#34;0&#34; /&#62;
  1500. &#60;/div&#62;
  1501. </code>
  1502. <code>&#60;div id=&#34;inputform&#34;&#62;
  1503. &#60;txp:zem_contact form=&#34;contact_form&#34; show_error=&#34;0&#34; /&#62;
  1504. &#60;/div&#62;
  1505. </code></pre>
  1506. <p>Apart from the <code>show_error</code> and <code>show_input</code> attributes, all other attributes must be 100% identical in both forms, otherwise they would be seen as two unrelated forms.</p>
  1507. <p><a href="#top">Back to top</a></p>
  1508. <h3 id="subject_form">User selectable subject field</h3>
  1509. <p>Specify the <code>subject_form</code> attribute and create a form which includes a <code>zem_contact_select</code> tag:</p>
  1510. <pre><code>&#60;txp:zem_contact to=&#34;you@example.com&#34; subject_form=&#34;my_subject_form&#34; /&#62;
  1511. &#60;txp:zem_contact_text label=&#34;Name&#34; /&#62;&#60;br /&#62;
  1512. &#60;txp:zem_contact_email /&#62;&#60;br /&#62;
  1513. &#60;txp:zem_contact_select label=&#34;Choose Subject&#34; list=&#34;,Question,Feedback&#34; /&#62;&#60;br /&#62;
  1514. &#60;txp:zem_contact_textarea label=&#34;Message&#34; /&#62;&#60;br /&#62;
  1515. &#60;/txp:zem_contact&#62;
  1516. </code></pre>
  1517. <p>Create a Textpattern form called &#8220;my_subject_form&#8221;, containing:</p>
  1518. <pre><code>&#60;txp:php&#62;
  1519. global $zem_contact_form;
  1520. echo $zem_contact_form[&#39;Choose Subject&#39;];
  1521. &#60;/txp:php&#62;
  1522. </code></pre>
  1523. <p>The <code>label</code> used in the <code>zem_contact_select</code> tag must be identical to the corresponding variable in the <code>subject_form</code>. Here we used <code>Choose subject</code>.</p>
  1524. <p>If you&#8217;d prefer to add a common prefix for all subjects, use a <code>subject_form</code> containing:</p>
  1525. <pre><code>&#60;txp:php&#62;
  1526. global $zem_contact_form;
  1527. echo &#39;My common prefix - &#39; . $zem_contact_form[&#39;Choose Subject&#39;];
  1528. &#60;/txp:php&#62;
  1529. </code></pre>
  1530. <p><a href="#top">Back to top</a></p>
  1531. <h3 id="to_form">User selectable recipient, without showing email address</h3>
  1532. <p>Specify the <code>to_form</code> attribute and create a form which includes a <code>zem_contact_select</code> tag:</p>
  1533. <pre><code>&#60;txp:zem_contact to_form=&#34;my_zem_contact_to_form&#34;&#62;
  1534. &#60;txp:zem_contact_text label=&#34;Name&#34; /&#62;&#60;br /&#62;
  1535. &#60;txp:zem_contact_email /&#62;&#60;br /&#62;
  1536. &#60;txp:zem_contact_select label=&#34;Department&#34; list=&#34;,Support,Sales&#34; /&#62;&#60;br /&#62;
  1537. &#60;txp:zem_contact_textarea label=&#34;Message&#34; /&#62;&#60;br /&#62;
  1538. &#60;/txp:zem_contact&#62;
  1539. </code></pre>
  1540. <p>Create a Textpattern form called &#8220;my_zem_contact_to_form&#8221;, containing:</p>
  1541. <pre><code>&#60;txp:php&#62;
  1542. global $zem_contact_form;
  1543. switch($zem_contact_form[&#39;Department&#39;])
  1544. {
  1545. case &#39;Support&#39;:
  1546. echo &#39;crew@example.com&#39;;
  1547. break;
  1548. case &#39;Sales&#39;:
  1549. echo &#39;showmethemoney@example.com&#39;;
  1550. break;
  1551. default:
  1552. echo &#39;someone@example.com&#39;;
  1553. }
  1554. &#60;/txp:php&#62;
  1555. </code></pre>
  1556. <p>The <code>label</code> used in the <code>zem_contact_select</code> tag must be identical to the corresponsing variable in the <code>to_form</code>. Here we used <code>Department</code>.</p>
  1557. <p>A &#8216;default&#8217; email address in the <code>to_form</code> is specified to ensure that a valid email address is used in cases where you add or change a select/radio option and forget to update the <code>to_form</code>.</p>
  1558. <p class="warning">Never use tags like <code>zem_contact_text</code>, <code>zem_contact_email</code> or <code>zem_contact_textarea</code> for setting the recipient address, otherwise your form can be abused to send spam to any email address!</p>
  1559. <p><a href="#top">Back to top</a></p>
  1560. <h2 id="styling">Styling</h2>
  1561. <p>The form itself has a class <strong>zemContactForm</strong> set on the <code>FORM</code> <span class="caps">HTML</span> tag.</p>
  1562. <p>The list of error messages (if any) has a class <strong>zemError</strong> set on the <code>UL</code> <span class="caps">HTML</span> tag that encloses the list of errors.</p>
  1563. <p>All form elements and corresponding labels have the following classes (or ids set):
  1564. <ol>
  1565. <li>One of <strong>zemText</strong>, <strong>zemTextarea</strong>, <strong>zemSelect</strong>, <strong>zemRadio</strong>, <strong>zemCheckbox</strong>, <strong>zemSubmit</strong>. It should be obvious which class is used for which form element (and corresponding label).</li>
  1566. <li><strong>zemRequired</strong> or <strong>errorElement</strong> or <strong>zemRequirederrorElement</strong>, depending on whether the form element is required, an error was found in whatever the visitor entered&#8230; or both.</li>
  1567. </ol>
  1568. <ol>
  1569. <li>An individual &#8220;id&#8221; or &#8220;class&#8221; set to the value of the <code>name</code> attribute of the corresponding tag. When styling forms based on this class, you should explicitly set the <code>name</code> attribute because automatically generated names may change in newer zem_contact_reborn versions.</li>
  1570. </ol></p>
  1571. <p><a href="#top">Back to top</a></p>
  1572. <h2 id="history">History</h2>
  1573. <p>Only the changes that may affect people who upgrade are detailed below.<br />
  1574. To save space, links to forum topics that detail all the changes in each version have been added.</p>
  1575. <ul>
  1576. <li>10 sep 2013: <strong>version 4.5.0.0</strong>
  1577. <ul>
  1578. <li>HTML 5 attributes added: <code>placeholder</code>, <code>autofocus</code>, <code>autocomplete</code>, <code>type</code>, <code>pattern</code></li>
  1579. <li>CSS <code>class</code> attribute allows overriding built-in class names</li>
  1580. <li>Textpack replaces zem_contact_lang plugin</li>
  1581. <li>explode() replaces deprecated split()</li>
  1582. </ul></li>
  1583. </ul>
  1584. <ul>
  1585. <li>14 feb 2007: <strong>version 4.0.3.19</strong> <a href="http://forum.textpattern.com/viewtopic.php?id=21144">changelog</a>
  1586. <ul>
  1587. <li><a href="#sendarticle">send_article</a> functionality revised, requiring changes when upgrading from earlier versions</li>
  1588. <li>New language strings: &#8216;send_article&#8217; and &#8216;recipient&#8217; (replaces &#8216;receiver&#8217;)</li>
  1589. <li>Sets of radio buttons require the new <a href="#zc_radio">group</a> attribute</li>
  1590. <li>Yes/No values deprecated in favor for the <span class="caps"><span class="caps">TXP</span></span> standard 1/0 values (yes/no still work in this version)</li>
  1591. </ul></li>
  1592. </ul>
  1593. <ul>
  1594. <li>20 nov 2006: <strong>version 4.0.3.18</strong> <a href="http://forum.textpattern.com/viewtopic.php?id=19823">changelog</a>
  1595. <ul>
  1596. <li>IDs &#8216;zemContactForm&#8217; and &#8216;zemSubmit&#8217; have changed to classes to allow multiple forms per page</li>
  1597. </ul></li>
  1598. <ul>
  1599. <li>New language strings: &#8216;form_used&#8217;, &#8216;invalid_utf8&#8217;, &#8216;max_warning&#8217;, &#8216;name&#8217;, &#8216;refresh&#8217;, &#8216;secret&#8217;</li>
  1600. </ul></li>
  1601. <li>12 mar 2006: <strong>version 4.0.3.17</strong> <a href="http://forum.textpattern.com/viewtopic.php?id=13416">changelog</a></li>
  1602. <li>11 feb 2006: <strong>version .16</strong></li>
  1603. <li>06 feb 2006: <strong>version .15</strong>
  1604. <li>03 feb 2006: <strong>version .14</strong></li>
  1605. <ul>
  1606. <li>Requires separate zem_contact_lang plugin</li>
  1607. </ul></li>
  1608. <li>29 jan 2006: <strong>version .12</strong></li>
  1609. <li>27 jan 2006: <strong>version .11</strong></li>
  1610. <li>23 jan 2006: <strong>version .09 and .10</strong></li>
  1611. <li>23 jan 2006: <strong>version .08</strong></li>
  1612. <li>17 jan 2006: <strong>version .07</strong></li>
  1613. <li>16 jan 2006: <strong>version .05 and .06</strong></li>
  1614. <li>15 jan 2006: <strong>version .04</strong></li>
  1615. <li>10 jan 2006: <strong>version .03</strong></li>
  1616. </ul>
  1617. <ul>
  1618. <li>19 dec 2005: <strong>version .02</strong></li>
  1619. </ul>
  1620. <p><a href="#top">Back to top</a></p>
  1621. <h2 id="credits">Credits</h2>
  1622. <ul>
  1623. <li><strong>zem</strong> wrote the zem_contact 0.6 plugin on which this plugin was initially based.</li>
  1624. <li><strong>Mary</strong> completely revised the plugin code.</li>
  1625. <li><strong>Stuart</strong> Turned it into a plugin, added a revised help text and additional code. Maintained all plugin versions till 4.0.3.17.</li>
  1626. <li><strong>wet</strong> added the zem_contact_radio tag.</li>
  1627. <li><strong>Tranquillo</strong> added the anti-spam <span class="caps"><span class="caps">API</span></span> and zem_contact_send_article functionality.</li>
  1628. <li><strong>aslsw66</strong>, <strong>jdykast</strong> and others (?) provided additional code</li>
  1629. <li><strong>Ruud</strong> cleaned up and audited the code to weed out bugs and completely revised the help text. Maintainer of versions 4.0.3.18 and up.</li>
  1630. </ul>
  1631. <ul>
  1632. <li>Supported and tested to destruction by the Textpattern community.</li>
  1633. </ul>
  1634. <p><a href="#top">Back to top</a></p>
  1635. <h2 id="api">Zem Contact Reborn&#8217;s <span class="caps">API</span></h2>
  1636. <p>The plugin <span class="caps">API</span> of zem contact, developed by Tranquillo, is similar to the comments <span class="caps">API</span> of Textpattern, which is explained in the Textbook <a href="http://textpattern.net/wiki/index.php?title=Plugin_Development_Topics">Plugin Development Topics</a> and <a href="http://textpattern.net/wiki/index.php?title=Combat_Comment_Spam">Combat Comment Spam</a>.</p>
  1637. <p>Two callback events exist in zem_contact_reborn:
  1638. <ul>
  1639. <li><code>zemcontact.submit</code> is called after the form is submitted and the values are checked if empty or valid email addresses, but before the mail is sent.</li>
  1640. </ul>
  1641. <ul>
  1642. <li><code>zemcontact.form</code> let&#8217;s you insert content in the contact form as displayed to the visitor.</li>
  1643. </ul></p>
  1644. <p>For reference here are the commands that will be interesting to plugin developers:</p>
  1645. <pre><code>// This will call your function before the form is submitted
  1646. // So you can analyse the submitted data
  1647. register_callback(&#39;abc_myfunction&#39;,&#39;zemcontact.submit&#39;);
  1648. </code>
  1649. <code>// This will call your function and add the output (use return $mystuff)
  1650. // to the contact-form.
  1651. register_callback(&#39;abc_myotherfunction2&#39;,&#39;zemcontact.form&#39;);
  1652. </code>
  1653. <code>// To get hold of the form-variables you can use
  1654. global zem_contact_form;
  1655. </code>
  1656. <code>// With the following two lines you can tell zem_contact if your
  1657. // plugin found spam
  1658. $evaluator =&#38; get_zemcontact_evaluator();
  1659. </code>
  1660. <code>// The passed value must be non-zero to mark the content as spam.
  1661. // Value must be a number between 0 and 1.
  1662. $evaluator -&#62; add_zemcontact_status(1);
  1663. </code></pre>
  1664. <p>Multiple plugins can be active at the same time and each of them can mark the submitted content as spam and prevent the form from being submitted.</p>
  1665. <p><strong>An example of a plug-in connecting to Zem Contact Reborn&#8217;s API:</strong></p>
  1666. <pre><code>register_callback(&#39;pap_zemcontact_form&#39;,&#39;zemcontact.form&#39;);
  1667. register_callback(&#39;pap_zemcontact_submit&#39;,&#39;zemcontact.submit&#39;);
  1668. </code>
  1669. <code>function pap_zemcontact_form() {
  1670. $field = &#39;&#60;div style=&#34;display:none&#34;&#62;&#39;.
  1671. finput(&#39;text&#39;,&#39;phone&#39;,ps(&#39;phone&#39;),&#39;&#39;,&#39;&#39;,&#39;&#39;,&#39;&#39;,&#39;&#39;,&#39;phone&#39;).&#39;&#60;br /&#62;&#39;.
  1672. finput(&#39;text&#39;,&#39;mail&#39;,ps(&#39;mail&#39;),&#39;&#39;,&#39;&#39;,&#39;&#39;,&#39;&#39;,&#39;&#39;,&#39;mail&#39;).&#39;&#60;/div&#62;&#39;;
  1673. return $field;&#60;/code&#62;
  1674. }
  1675. </code>
  1676. <code>function pap_zemcontact_submit() {
  1677. $checking_mail_field = trim(ps(&#39;mail&#39;));
  1678. $checking_phone_field = trim(ps(&#39;phone&#39;));
  1679. </code>
  1680. <code> $evaluation =&#38; get_zemcontact_evaluator();
  1681. </code>
  1682. <code> // If the hidden fields are filled out, the contact form won&#39;t be submitted!
  1683. if ($checking_mail_field != &#39;&#39; or $checking_phone_field != &#39;&#39;) {
  1684. $evaluation -&#62; add_zemcontact_status(1);
  1685. }
  1686. return;
  1687. }
  1688. </code></pre>
  1689. <p><a href="#top">Back to top</a></p>
  1690. # --- END PLUGIN HELP ---
  1691. -->
  1692. <?php
  1693. }
  1694. ?>