PageRenderTime 24ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/application/controllers/IndexController.php

http://simple-php-contact-form.googlecode.com/
PHP | 368 lines | 201 code | 52 blank | 115 comment | 25 complexity | 0ebcae1d8919584999cc5fb0b342b16b MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0
  1. <?php
  2. /**
  3. * Simple PHP Contact Form
  4. *
  5. * This file is part of the Simple PHP Contact Form which is subject to the New
  6. * BSD License {@see LICENSE} which you would be advised to read before using,
  7. * modifying or redistributing this software.
  8. *
  9. * PHP version 5.2
  10. *
  11. * @category Simple-PHP-Contact-Form
  12. * @package Application_Controllers
  13. * @author jah <jah@jahboite.co.uk>
  14. * @copyright 2010 jah <jah@jahboite.co.uk>
  15. * @license New BSD License {@see LICENSE}
  16. * @version SVN: $Id$
  17. * @link http://code.google.com/p/simple-php-contact-form/
  18. */
  19. /**
  20. * IndexController.
  21. *
  22. * @category Simple-PHP-Contact-Form
  23. * @package Application_Controllers
  24. * @author jah <jah@jahboite.co.uk>
  25. * @copyright 2010 jah <jah@jahboite.co.uk>
  26. * @license New BSD License {@see LICENSE}
  27. * @version Release: @package_version@
  28. * @link http://code.google.com/p/simple-php-contact-form/
  29. */
  30. class IndexController extends Zend_Controller_Action
  31. {
  32. private $_recipientsConfig = null;
  33. private $_formConfig = null;
  34. private $_form = null;
  35. private $_auditor = null;
  36. /**
  37. * Initialises the form at each request of this controller.
  38. * TODO error action doesn't need init - so either move the error action
  39. * or move the init stuff elsewhere.
  40. *
  41. * @return null
  42. */
  43. public function init()
  44. {
  45. // Get the form configuration data.
  46. $bs = $this->getInvokeArg('bootstrap');
  47. $formConfigLoc = $bs->getOption('formconfigloc');
  48. if ($formConfigLoc === null) {
  49. throw new EnterpriseSecurityException(
  50. 'Your request could not be fulfilled.',
  51. 'Contact Application cannot start, cannot find form configuration data!'
  52. );
  53. }
  54. $this->_formConfig = new Zend_Config_Ini(
  55. $formConfigLoc, APPLICATION_ENV, false
  56. );
  57. if (! $this->_formConfig instanceof Zend_Config) {
  58. throw new EnterpriseSecurityException(
  59. 'Your request could not be fulfilled.',
  60. 'Contact Application cannot start, cannot find form configuration data!'
  61. );
  62. }
  63. // Get the recipients configuration data.
  64. $rcptConfigLoc = $bs->getOption('recipientsconfigloc');
  65. if (! empty($rcptConfigLoc)) {
  66. $this->_recipientsConfig = new Zend_Config_Ini(
  67. $rcptConfigLoc, APPLICATION_ENV, false
  68. );
  69. }
  70. if (! $this->_recipientsConfig instanceof Zend_Config) {
  71. $this->_auditor = ESAPI::getAuditor('IndexController');
  72. $this->_auditor->warning(
  73. Auditor::SECURITY,
  74. false,
  75. 'Contact Application cannot find contact recipients data. Form submissions will be logged to file!'
  76. );
  77. }
  78. // get a new form and pass recipient config data to it.
  79. $this->_form = $this->_helper->formLoader('contact', $this->_formConfig);
  80. $this->_form->setRecipientConfiguration($this->_recipientsConfig);
  81. $this->_form->setCaptcha();
  82. }
  83. /**
  84. * The index action will add a form which will be rendered by the view.
  85. * If the form has been previously submitted and the form contained
  86. * validation errors then client will be redirected here and the form
  87. * will be retrieved from the session storage.
  88. *
  89. * @return null
  90. */
  91. public function indexAction()
  92. {
  93. // see if the form has already been submitted. if it was and it failed
  94. // validation then the form will be persisted in the session and can be
  95. // represented to the client. if it passed validation the client can be
  96. // shown a thank you message.
  97. if (Zend_Session::sessionExists()) {
  98. $ns = new Zend_Session_Namespace('Contact');
  99. if ($ns->submission === false) {
  100. $this->view->contactForm = $ns->form;
  101. unset($ns->form);
  102. unset($ns->submission);
  103. return;
  104. } else if ($ns->submission === true) {
  105. unset($ns->submission);
  106. $this->render('thanks');
  107. return;
  108. }
  109. }
  110. // new form
  111. $this->_form->setCSRFToken();
  112. $this->view->contactForm = $this->_form;
  113. }
  114. /**
  115. * The send action is the target for form submission. If the request does
  116. * not contain POST data then the response will be a redirect to the index
  117. * action where the form will be displayed.
  118. * POST data is validated and if successful an email will be sent before
  119. * redirecting the client away from the send action (to a success message).
  120. * If validation fails, the form is persisted in the session and the client
  121. * is redirected away from the send action to display the form with error
  122. * messages.
  123. * Whether or not the form is successfully validated, a user session will be
  124. * started so that the client can be tracked and certain thresholds enforced.
  125. *
  126. * @return null
  127. */
  128. public function sendAction()
  129. {
  130. if (! $this->_request->isPost()) {
  131. $this->_helper->getHelper('redirector')
  132. ->setCode(303)
  133. ->gotoSimple(
  134. 'index', null, null, $this->_request->getParams()
  135. );
  136. }
  137. $ns = new Zend_Session_Namespace('Contact');
  138. $ids = ESAPI::getIntrusionDetector();
  139. // Is the form submission a valid one?
  140. $valid = $this->_form->isValid($this->_request->getPost());
  141. // Check whether a csrf token is being re-used. This should not happen
  142. // often due to the POST-Redirect-GET design of this application.
  143. // If the token is being reused then throw an Intrusion Exception.
  144. if ( isset($ns->history->token)
  145. && in_array($this->_form->token->getValue(), $ns->history->token)
  146. ) {
  147. throw new IntrusionException(
  148. 'Form resubmission is not permitted.',
  149. 'Submitted token is one contained in the history of the current session.'
  150. );
  151. }
  152. if ($valid !== true) {
  153. // Add a formValidationErrors event to the client session.
  154. $ids->addEvent(
  155. 'ValidationErrorEvent',
  156. 'Form submission contained inputs that caused Validation errors.'
  157. );
  158. // There are certain validation errors that require an exception to
  159. // be thrown becuase errors for these elements is assumed to be
  160. // evidence of tampering. Throw IntrusionException if there are
  161. // errors in CSRFToken or recipientsMap.
  162. if ($this->_form->detectedTamper()) {
  163. throw new IntrusionException(
  164. 'The submitted form contained invalid information.',
  165. 'Form submission contained evidence of tampering!'
  166. );
  167. }
  168. // Log a warning about failed validation and include the error
  169. // messages.
  170. $validationErrMsgs = $this->_form->getMessages(null, true);
  171. $vmsgs = '';
  172. foreach ($validationErrMsgs as $elem => $messageArray) {
  173. $vmsgs .= "[{$elem}:";
  174. foreach ($messageArray as $validator => $message) {
  175. $vmsgs .= "{$validator}={$message};";
  176. }
  177. $vmsgs .= "] ";
  178. }
  179. ESAPI::getAuditor('IndexController')->warning(
  180. Auditor::SECURITY,
  181. false,
  182. 'Validation failure messages: ' . $vmsgs
  183. );
  184. // Store the form in the session so that we can perform a redirect
  185. // away from the send action. // TODO expire it soon!
  186. $ns->submission = false;
  187. $ns->form = $this->_form;
  188. $this->_helper->getHelper('redirector')
  189. ->setCode(303)
  190. ->gotoSimple(
  191. 'index', null, null, $this->_request->getParams()
  192. );
  193. return;
  194. }
  195. // We have a valid form!!
  196. // Kill the CSRF cookie.
  197. setcookie(Form_Contact::CSRFCOOKIE, 'expired', 1, '/');
  198. // Add token to map of submitted tokens
  199. if (is_array($ns->history->token)) {
  200. array_unshift($ns->history->token, $this->_form->token->getValue());
  201. } else {
  202. $ns->history->token = array($this->_form->token->getValue());
  203. }
  204. // extract the valid values from the form and send a mail
  205. $validValues = array();
  206. $elements = $this->_form->getElements();
  207. foreach ($elements as $key => $elem) {
  208. $validValues[$key] = $elem->getValue();
  209. }
  210. // attempt to send a mail
  211. $successfulDelivery = $this->_helper->sendMail(
  212. $validValues, $this->_recipientsConfig
  213. );
  214. // If mail delivery was not successful show a
  215. if ($successfulDelivery !== true) {
  216. ESAPI::getAuditor('IndexController')->warning(
  217. Auditor::SECURITY, false,
  218. 'Sending of mail failed - ' . $this->_helper->sendMail->getResponse()
  219. );
  220. $ids->addEvent(
  221. 'MailNotDeliveredEvent',
  222. 'successfully submitted valid contact form but mail was NOT sent.'
  223. );
  224. // TODO an encrypted logfile with failed email messages?
  225. // View helpful error message.
  226. throw new Exception('Successful form submission failed to be sent.');
  227. } else {
  228. $ns->submission = true;
  229. $this->_helper->getHelper('redirector')
  230. ->setCode(303)
  231. ->gotoSimple(
  232. 'index', null, null, $this->_request->getParams()
  233. );
  234. return;
  235. }
  236. }
  237. /**
  238. * The router is set to /action and this segment of the url is overloaded
  239. * so that /someRecipient is also valid. This __call method is invoked
  240. * whenever this segment of the url is not a defined action and in these
  241. * cases, the $actionMethod may be a valid recipient. These sre trapped
  242. * here and, if valid, the request is re-dispatched to the index action to
  243. * which we pass a recipient parameter.
  244. * If a valid recipient is not found then execution is passed to the parent
  245. * __call method.
  246. *
  247. * @param string $actionMethod Url segment.
  248. * @param array $args Request arguments.
  249. *
  250. * @return null
  251. */
  252. public function __call($actionMethod, $args)
  253. {
  254. $logger = ESAPI::getAuditor('IndexController');
  255. // I do not anticipate this happening often...
  256. if (! is_string($actionMethod)) {
  257. return parent::__call($actionMethod, $args);
  258. }
  259. // If there's less than two recipients defined, we don't need to trap
  260. // usernames. ignore them.
  261. if ($this->_recipientsConfig->count() < 2) {
  262. return parent::__call($actionMethod, $args);
  263. }
  264. // Strip the trailing 'Action' from the method name.
  265. $method = null;
  266. $detectedCharacterEncoding = mb_detect_encoding($actionMethod);
  267. $len = mb_strlen($actionMethod, $detectedCharacterEncoding);
  268. if (mb_substr($actionMethod, $len-6, 6, $detectedCharacterEncoding) == 'Action' ) {
  269. $method = mb_substr(
  270. $actionMethod, 0, $len-6, $detectedCharacterEncoding
  271. );
  272. } else {
  273. $method = $actionMethod;
  274. }
  275. // Validate the possible recipient and, if valid, add a 'recipient'
  276. // request param and continue on to the indexAction of this controller.
  277. $recipientValidator = new Custom_Validate_ValidRecipient(
  278. $this->_recipientsConfig
  279. );
  280. if ($recipientValidator->isValid($method)) {
  281. $this->_request->setActionName('index');
  282. $this->_request->setParams(
  283. array(
  284. 'recipient' => $method,
  285. 'action' => 'index'
  286. )
  287. );
  288. $this->_request->setDispatched(false);
  289. return;
  290. }
  291. return parent::__call($actionMethod, $args);
  292. }
  293. /**
  294. * The error controller will redirect the client here and a generic message will
  295. * be displayed. Direct requests for this page will result in a redirect to
  296. * index action.
  297. *
  298. * @return null
  299. */
  300. public function errorAction()
  301. {
  302. if (Zend_Session::sessionExists()) {
  303. $ns = new Zend_Session_Namespace('Contact');
  304. if ($ns->error === true) {
  305. // Just show this view. Simples!
  306. unset($ns->error);
  307. return;
  308. }
  309. }
  310. $this->_helper->getHelper('redirector')
  311. ->setCode(303)
  312. ->gotoSimple(
  313. 'index', null, null, $this->_request->getParams()
  314. );
  315. }
  316. }