1<?php
2
3/* Modern Contact Plugin for Dokuwiki
4 *
5 * Copyright (C) 2008 Bob Baddeley (bobbaddeley.com)
6 * Copyright (C) 2010-2012 Marvin Thomas Rabe (marvinrabe.de)
7 *
8 * This program is free software; you can redistribute it and/or modify it under the terms
9 * of the GNU General Public License as published by the Free Software Foundation; either
10 * version 3 of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
13 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 * See the GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along with this program;
17 * if not, see <http://www.gnu.org/licenses/>. */
18
19/**
20 * Embed a contact form onto any page
21 * @license GNU General Public License 3 <http://www.gnu.org/licenses/>
22 * @author Bob Baddeley <bob@bobbaddeley.com>
23 * @author Marvin Thomas Rabe <mrabe@marvinrabe.de>
24 */
25
26if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
27if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
28require_once(DOKU_PLUGIN.'syntax.php');
29require_once(DOKU_INC.'inc/auth.php');
30require_once(dirname(__file__).'/recaptchalib.php');
31
32class syntax_plugin_moderncontact extends DokuWiki_Syntax_Plugin {
33
34	public static $captcha = false;
35	public static $lastFormId = 1;
36
37	private $formId = 0;
38	private $status = 1;
39	private $statusMessage;
40	private $errorFlags = array();
41
42	/**
43	 * General information about the plugin.
44	 */
45	public function getInfo(){
46		return array(
47			'author' => 'Marvin Thomas Rabe',
48			'email'  => 'mrabe@marvinrabe.de',
49			'date'	 => '2012-06-06',
50			'name'	 => 'Modern Contact Plugin',
51			'desc'	 => 'Creates a contact form to email the webmaster. Secured with recaptcha.',
52			'url'	 => 'https://github.com/marvinrabe/dokuwiki-contact',
53		);
54	}
55
56	/**
57	 * What kind of syntax are we?
58	 */
59	public function getType(){
60		return 'container';
61	}
62
63	/**
64	 * What about paragraphs?
65	 */
66	public function getPType(){
67		return 'block';
68	}
69
70	/**
71 	 * Where to sort in?
72 	 */
73	public function getSort(){
74		return 300;
75	}
76
77	/**
78 	 * Connect pattern to lexer.
79 	 */
80	public function connectTo($mode) {
81		$this->Lexer->addSpecialPattern('\{\{contact>[^}]*\}\}',$mode,'plugin_moderncontact');
82	}
83
84	/**
85	 * Handle the match.
86	 */
87	public function handle($match, $state, $pos, &$handler){
88		if (isset($_REQUEST['comment']))
89		    return false;
90
91		$match = substr($match,10,-2); //strip markup from start and end
92
93		$data = array();
94
95		//handle params
96		$params = explode('|',$match);
97		foreach($params as $param){
98			$splitparam = explode('=',$param);
99			$data[$splitparam[0]] = $splitparam[1];
100		}
101		return $data;
102	}
103
104	/**
105	 * Create output.
106	 */
107	public function render($mode, &$renderer, $data) {
108		if($mode == 'xhtml'){
109			// Define unique form id
110			$this->formId = syntax_plugin_moderncontact::$lastFormId++;
111
112			// Disable cache
113			$renderer->info['cache'] = false;
114			$renderer->doc .= $this->_contact($data);
115			return true;
116		}
117		return false;
118	}
119
120	/**
121	 * Verify and send email content.´
122	 */
123	protected function _send_contact($captcha=false){
124		global $conf;
125		global $auth;
126		$lang = $this->getLang("error");
127
128		require_once(DOKU_INC.'inc/mail.php');
129		$name = $_POST['name'];
130		$email = $_POST['email'];
131		$subject = $_POST['subject'];
132		$comment = $name."\r\n";
133		$comment .= $email."\r\n\n";
134		$comment .= $_POST['content'];
135		if (isset($_REQUEST['to'])){
136			$user = $auth->getUserData($_POST['to']);
137			if (isset($user)) {
138			   $to = $user['mail'];
139			}
140		} else {
141			$to = $this->getConf('default');
142		}
143
144		// name entered?
145		if(strlen($name) < 2)
146			$this->_set_error('name', $lang["name"]);
147
148		// email correctly entered?
149		if(!$this->_check_email_address($email))
150			$this->_set_error('email', $lang["email"]);
151
152		// comment entered?
153		if(strlen($_POST['content']) < 10)
154			$this->_set_error('content', $lang["content"]);
155
156		// checks recaptcha answer
157		if($conf['plugin']['moderncontact']['captcha'] == 1 && $captcha == true) {
158			$resp = recaptcha_check_answer ($conf['plugin']['moderncontact']['recaptchasecret'],
159						$_SERVER["REMOTE_ADDR"],
160						$_POST["recaptcha_challenge_field"],
161						$_POST["recaptcha_response_field"]);
162			if (!$resp->is_valid){
163				$this->_set_error('captcha', $lang["captcha"]);
164			}
165		}
166
167		// A bunch of tests to make sure it's legitimate mail and not spoofed
168		// This should make it not very easy to do injection
169		if (eregi("\r",$name) || eregi("\n",$name) || eregi("MIME-Version: ",$name) || eregi("Content-Type: ",$name)){
170			$this->_set_error('name', $lang["valid_name"]);
171		}
172		if (eregi("\r",$email) || eregi("\n",$email) || eregi("MIME-Version: ",$email || eregi("Content-Type: ",$email))){
173			$this->_set_error('email', $lang["valid_email"]);
174		}
175		if (eregi("\r",$subject) || eregi("\n",$subject) || eregi("MIME-Version: ",$subject) || eregi("Content-Type: ",$subject)){
176			$this->_set_error('subject', $lang["valid_subject"]);
177		}
178		if (eregi("\r",$to) || eregi("\n",$to) || eregi("MIME-Version: ",$to) || eregi("Content-Type: ",$to)){
179			$this->_set_error('to', $lang["valid_to"]);
180		}
181		if (eregi("MIME-Version: ",$comment) || eregi("Content-Type: ",$comment)){
182			$this->_set_error('content', $lang["valid_content"]);
183		}
184
185		// Status has not changed.
186		if($this->status != 0) {
187			// send only if comment is not empty
188			// this should never be the case anyway because the form has
189			// validation to ensure a non-empty comment
190			if (trim($comment, " \t") != ''){
191				if (mail_send($to, $subject, $comment, $email, '', '', 'Reply-to: '.$email)){
192					$this->statusMessage = $this->getLang("success");
193				} else {
194					$this->_set_error('unknown', $lang["unknown"]);
195				}
196				//we're using the included mail_send command because it's
197				//already there and it's easy to use and it works
198			}
199		}
200
201		return true;
202	}
203
204	/**
205	 * Manage error messages.
206	 */
207	protected function _set_error($type, $message) {
208		$this->status = 0;
209		$this->statusMessage .= empty($this->statusMessage)?$message:'<br>'.$message;
210		$this->errorFlags[$type] = true;
211	}
212
213	/**
214	 * Validate email address. From: http://www.ilovejackdaniels.com/php/email-address-validation
215	 */
216	protected function _check_email_address($email) {
217		// First, we check that there's one @ symbol,
218		// and that the lengths are right.
219		if (!ereg("^[^@]{1,64}@[^@]{1,255}$", $email)) {
220			// Email invalid because wrong number of characters
221			// in one section or wrong number of @ symbols.
222			return false;
223		}
224		// Split it into sections to make life easier
225		$email_array = explode("@", $email);
226		$local_array = explode(".", $email_array[0]);
227		for ($i = 0; $i < sizeof($local_array); $i++) {
228			if (!ereg("^(([A-Za-z0-9!#$%&'*+/=?^_`{|}~-][A-Za-z0-9!#$%&'*+/=?^_`{|}~\.-]{0,63})|(\"[^(\\|\")]{0,62}\"))$",
229				$local_array[$i])) {
230					return false;
231			}
232		}
233		// Check if domain is IP. If not,
234		// it should be valid domain name
235		if (!ereg("^\[?[0-9\.]+\]?$", $email_array[1])) {
236			$domain_array = explode(".", $email_array[1]);
237			if (sizeof($domain_array) < 2) {
238				return false; // Not enough parts to domain
239			}
240			for ($i = 0; $i < sizeof($domain_array); $i++) {
241				if (!ereg("^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]+))$",
242					$domain_array[$i])) {
243						return false;
244				}
245			}
246		}
247		return true;
248	}
249
250	/**
251	 * Does the contact form xhtml creation.
252	 */
253	protected function _contact($data){
254		global $conf;
255		global $USERINFO;
256
257		// Is there none captche on the side?
258		$captcha = ($conf['plugin']['moderncontact']['captcha'] == 1 && syntax_plugin_moderncontact::$captcha == false)?true:false;
259
260		$ret = "<form action=\"".$_SERVER['REQUEST_URI']."#form-".$this->formId."\" method=\"POST\"><a name=\"form-".$this->formId."\"></a>";
261		$ret .= "<table class=\"inline\">";
262
263		// Send message and give feedback
264		if (isset($_POST['submit-form-'.$this->formId]))
265			if($this->_send_contact($captcha))
266				$ret .= $this->_show_message();
267
268		// Build table
269		$ret .= $this->_table_row($this->getLang("name"), 'name', 'text', $USERINFO['name']);
270		$ret .= $this->_table_row($this->getLang("email"), 'email', 'text', $USERINFO['mail']);
271		if (!isset($data['subj']))
272			$ret .= $this->_table_row($this->getLang("subject"), 'subject', 'text');
273		$ret .= $this->_table_row($this->getLang("content"), 'content', 'textarea');
274
275		// Captcha
276		if($captcha) {
277			if($this->errorFlags["captcha"]) {
278				$ret .= '<style>#recaptcha_response_field { border: 1px solid #e18484 !important; }</style>';
279			}
280			$ret .= "<tr><td colspan=\"2\">"
281			. "<script type=\"text/javascript\">var RecaptchaOptions = { lang : '".$conf['lang']."', "
282			. "theme : '".$conf['plugin']['moderncontact']['recaptchalayout']."' };</script>"
283			. recaptcha_get_html($conf['plugin']['moderncontact']['recaptchakey'])."</td></tr>";
284			syntax_plugin_moderncontact::$captcha = true;
285		}
286
287		$ret .= "</table><p>";
288		if (isset($data['subj']))
289			$ret .= "<input type=\"hidden\" name=\"subject\" value=\"".$data['subj']."\" />";
290		if (isset($data['to']))
291			$ret .= "<input type=\"hidden\" name=\"to\" value=\"".$data['to']."\" />";
292		$ret .= "<input type=\"hidden\" name=\"do\" value=\"show\" />";
293		$ret .= "<input type=\"submit\" name=\"submit-form-".$this->formId."\" value=\"".$this->getLang("contact")."\" />";
294		$ret .= "</p></form>";
295
296		return $ret;
297	}
298
299	/**
300	 * Show up error messages.
301	 */
302	protected function _show_message() {
303		return '<tr><td colspan="2">'
304		. '<p class="'.(($this->status == 0)?'contact_error':'contact_success').'">'.$this->statusMessage.'</p>'
305		. '</td></tr>';
306	}
307
308	/**
309	 * Renders a table row.
310	 */
311	protected function _table_row($label, $name, $type, $default='') {
312		$value = (isset($_POST['submit-form-'.$this->formId]) && $this->status == 0)?$_POST[$name]:$default;
313		$class = ($this->errorFlags[$name])?'class="error_field"':'';
314		$row = '<tr><td>'.$label.'</td><td>';
315		if($type == 'textarea')
316			$row .= '<textarea name="'.$name.'" wrap="on" cols="40" rows="6" '.$class.'>'.$value.'</textarea>';
317		else
318			$row .= '<input type="'.$type.'" value="'.$value.'" name="'.$name.'" '.$class.'>';
319		$row .= '</td></tr>';
320		return $row;
321	}
322
323}
324