form.php 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. <?php
  2. namespace Grav\Plugin;
  3. use Grav\Common\Plugin;
  4. use Grav\Common\Page\Page;
  5. use Grav\Common\Page\Pages;
  6. use Grav\Common\Grav;
  7. use Grav\Common\Uri;
  8. use Grav\Common\Twig;
  9. use Grav\Plugin\Form;
  10. use RocketTheme\Toolbox\Event\Event;
  11. use RocketTheme\Toolbox\File\File;
  12. use Symfony\Component\Yaml\Yaml;
  13. class FormPlugin extends Plugin
  14. {
  15. /**
  16. * @var bool
  17. */
  18. protected $active = false;
  19. /**
  20. * @var Form
  21. */
  22. protected $form;
  23. /**
  24. * @return array
  25. */
  26. public static function getSubscribedEvents()
  27. {
  28. return [
  29. 'onPageInitialized' => ['onPageInitialized', 0],
  30. 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0],
  31. 'onTwigSiteVariables' => ['onTwigSiteVariables', 0],
  32. 'onFormProcessed' => ['onFormProcessed', 0]
  33. ];
  34. }
  35. /**
  36. * Initialize form if the page has one. Also catches form processing if user posts the form.
  37. */
  38. public function onPageInitialized()
  39. {
  40. /** @var Page $page */
  41. $page = $this->grav['page'];
  42. if (!$page) {
  43. return;
  44. }
  45. $header = $page->header();
  46. if (isset($header->form) && is_array($header->form)) {
  47. $this->active = true;
  48. // Create form.
  49. require_once __DIR__ . '/classes/form.php';
  50. $this->form = new Form($page);
  51. // Handle posting if needed.
  52. if (!empty($_POST)) {
  53. $this->form->post();
  54. }
  55. }
  56. }
  57. /**
  58. * Add current directory to twig lookup paths.
  59. */
  60. public function onTwigTemplatePaths()
  61. {
  62. $this->grav['twig']->twig_paths[] = __DIR__ . '/templates';
  63. }
  64. /**
  65. * Make form accessible from twig.
  66. */
  67. public function onTwigSiteVariables()
  68. {
  69. if (!$this->active) {
  70. return;
  71. }
  72. $this->grav['twig']->twig_vars['form'] = $this->form;
  73. }
  74. /**
  75. * Handle form processing instructions.
  76. *
  77. * @param Event $event
  78. */
  79. public function onFormProcessed(Event $event)
  80. {
  81. $form = $event['form'];
  82. $action = $event['action'];
  83. $params = $event['params'];
  84. if (!$this->active) {
  85. return;
  86. }
  87. if (!$this->validate($form)) {
  88. /** @var Language $l */
  89. $l = $this->grav['language'];
  90. $this->form->message = $l->translate('FORM_PLUGIN.NOT_VALIDATED');
  91. $uri = $this->grav['uri'];
  92. $route = $uri->route();
  93. /** @var Twig $twig */
  94. $twig = $this->grav['twig'];
  95. $twig->twig_vars['form'] = $form;
  96. /** @var Pages $pages */
  97. $pages = $this->grav['pages'];
  98. $page = $pages->dispatch($route, true);
  99. unset($this->grav['page']);
  100. $this->grav['page'] = $page;
  101. return;
  102. }
  103. $this->process($form);
  104. switch ($action) {
  105. case 'captcha':
  106. // Validate the captcha
  107. $query = http_build_query([
  108. 'secret' => $params['recatpcha_secret'],
  109. 'response' => $this->form->value('g-recaptcha-response')
  110. ]);
  111. $url = 'https://www.google.com/recaptcha/api/siteverify?'.$query;
  112. $response = json_decode(file_get_contents($url), true);
  113. if (!isset($response['success']) || $response['success'] !== true) {
  114. throw new \RuntimeException('Error validating the Captcha');
  115. }
  116. break;
  117. case 'message':
  118. $this->form->message = (string) $params;
  119. break;
  120. case 'redirect':
  121. $this->grav->redirect((string) $params);
  122. break;
  123. case 'reset':
  124. if (in_array($params, array(true, 1, '1', 'yes', 'on', 'true'), true)) {
  125. $this->form->reset();
  126. }
  127. break;
  128. case 'display':
  129. $route = (string) $params;
  130. if (!$route || $route[0] != '/') {
  131. /** @var Uri $uri */
  132. $uri = $this->grav['uri'];
  133. $route = $uri->route() . ($route ? '/' . $route : '');
  134. }
  135. /** @var Twig $twig */
  136. $twig = $this->grav['twig'];
  137. $twig->twig_vars['form'] = $form;
  138. /** @var Pages $pages */
  139. $pages = $this->grav['pages'];
  140. $page = $pages->dispatch($route, true);
  141. unset($this->grav['page']);
  142. $this->grav['page'] = $page;
  143. break;
  144. case 'save':
  145. $prefix = !empty($params['fileprefix']) ? $params['fileprefix'] : '';
  146. $format = !empty($params['dateformat']) ? $params['dateformat'] : 'Ymd-His-u';
  147. $ext = !empty($params['extension']) ? '.' . trim($params['extension'], '.') : '.txt';
  148. $filename = !empty($params['filename']) ? $params['filename'] : '';
  149. $operation = !empty($params['operation']) ? $params['operation'] : 'create';
  150. if (!$filename) {
  151. $filename = $prefix . $this->udate($format) . $ext;
  152. }
  153. /** @var Twig $twig */
  154. $twig = $this->grav['twig'];
  155. $vars = array(
  156. 'form' => $this->form
  157. );
  158. $fullFileName = DATA_DIR . $this->form->name . '/' . $filename;
  159. $file = File::instance($fullFileName);
  160. if ($operation == 'create') {
  161. $body = $twig->processString(
  162. !empty($params['body']) ? $params['body'] : '{% include "forms/data.txt.twig" %}',
  163. $vars
  164. );
  165. $file->save($body);
  166. } elseif ($operation == 'add') {
  167. $vars = $vars['form']->value();
  168. foreach ($form->fields as $field) {
  169. if (isset($field['process']) && isset($field['process']['ignore']) && $field['process']['ignore']) {
  170. unset($vars[$field['name']]);
  171. }
  172. }
  173. if (file_exists($fullFileName)) {
  174. $data = Yaml::parse($file->content());
  175. if (count($data) > 0) {
  176. array_unshift($data, $vars);
  177. } else {
  178. $data[] = $vars;
  179. }
  180. } else {
  181. $data[] = $vars;
  182. }
  183. $file->save(Yaml::dump($data));
  184. }
  185. break;
  186. }
  187. }
  188. /**
  189. * Validate a form
  190. *
  191. * @param Form $form
  192. * @return bool
  193. */
  194. protected function validate($form) {
  195. foreach ($form->fields as $field) {
  196. if (isset($field['validate']) && isset($field['validate']['required']) && $field['validate']['required']) {
  197. if (!$form->value($field['name'])) {
  198. return false;
  199. }
  200. }
  201. }
  202. return true;
  203. }
  204. /**
  205. * Process a form
  206. *
  207. * Currently available processing tasks:
  208. *
  209. * - fillWithCurrentDateTime
  210. *
  211. * @param Form $form
  212. * @return bool
  213. */
  214. protected function process($form) {
  215. foreach ($form->fields as $field) {
  216. if (isset($field['process'])) {
  217. if (isset($field['process']['fillWithCurrentDateTime']) && $field['process']['fillWithCurrentDateTime']) {
  218. $form->setValue($field['name'], gmdate('D, d M Y H:i:s', time()));
  219. }
  220. }
  221. }
  222. }
  223. /**
  224. * Create unix timestamp for storing the data into the filesystem.
  225. *
  226. * @param string $format
  227. * @param int $utimestamp
  228. * @return string
  229. */
  230. private function udate($format = 'u', $utimestamp = null)
  231. {
  232. if (is_null($utimestamp)) {
  233. $utimestamp = microtime(true);
  234. }
  235. $timestamp = floor($utimestamp);
  236. $milliseconds = round(($utimestamp - $timestamp) * 1000000);
  237. return date(preg_replace('`(?<!\\\\)u`', \sprintf('%06d', $milliseconds), $format), $timestamp);
  238. }
  239. }