PluginProbe
Contact Forms by Cimatti / 2.3.0
Contact Forms by Cimatti v2.3.0
2.3.6 2.3.5 2.3.0 2.2.32 2.2.4 2.2.0 2.1.2 2.1.1 trunk 1.0 1.1 1.2 1.2.1 1.3 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 All 62 releases
contact-forms / PFBC / Form.php

Form.php in Contact Forms by Cimatti 2.3.0, at PFBC/Form.php

470 lines 15.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * PFBC (PHP Form Builder Class) - Third-party library
4 * @package PFBC
5 */
6 if ( ! defined( 'ABSPATH' ) ) exit;
7
8 // phpcs:disable WordPress.Security.EscapeOutput, WordPress.NamingConventions.PrefixAllGlobals, PluginCheck.CodeAnalysis.Heredoc, WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput, WordPress.WP.AlternativeFunctions, WordPress.WP.EnqueuedResources -- Third-party library
9
10 /*This project's namespace structure is leveraged to autoload requested classes at runtime.*/
11 function PFBC_Load($class) {
12 $file = dirname(__FILE__) . "/" . str_replace("_", DIRECTORY_SEPARATOR, $class) . ".php";
13 if(is_file($file))
14 include_once $file;
15 }
16 spl_autoload_register("PFBC_Load");
17 if(in_array("__autoload",spl_autoload_functions()))
18 spl_autoload_register("__autoload");
19
20 class Form extends Base {
21 protected $elements = array();
22 protected $prefix = "http";
23 protected $values = array();
24 protected $widthSuffix = "px";
25
26 protected $ajax;
27 protected $ajaxCallback;
28 protected $attributes;
29 protected $error;
30 /*jQueryUI themes can be previewed at http://jqueryui.com/themeroller/.*/
31 protected $jQueryUITheme = "smoothness";
32 protected $resourcesPath;
33 /*Prevents various automated from being automatically applied. Current options for this array
34 included jQuery, jQueryUI, jQueryUIButtons, focus, and style.*/
35 protected $prevent = array();
36 protected $view;
37 protected $width;
38
39 public function __construct($id = "pfbc", $width = "") {
40 $this->configure(array(
41 "width" => $width,
42 "action" => basename($_SERVER["SCRIPT_NAME"]),
43 "id" => preg_replace("/\W/", "-", $id),
44 "method" => "post"
45 ));
46
47 if(isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on")
48 $this->prefix = "https";
49
50 /*The Standard view class is applied by default and will be used unless a different view is
51 specified in the form's configure method*/
52 if(empty($this->view))
53 $this->view = new View_Standard;
54
55 if(empty($this->error))
56 $this->error = new Error_Standard;
57
58 /*The resourcePath property is used to identify where third-party resources needed by the
59 project are located. This property will automatically be set properly if the PFBC directory
60 is uploaded within the server's document root. If symbolic links are used to reference the PFBC
61 directory, you may need to set this property in the form's configure method or directly in this
62 constructor.*/
63 $path = dirname(__FILE__) . "/Resources";
64 if(strpos($path, $_SERVER["DOCUMENT_ROOT"]) !== false)
65 $this->resourcesPath = substr($path, strlen($_SERVER["DOCUMENT_ROOT"]));
66 else
67 $this->resourcesPath = "/PFBC/Resources";
68 }
69
70 /*When a form is serialized and stored in the session, this function prevents any non-essential
71 information from being included.*/
72 public function __sleep() {
73 return array("attributes", "elements", "error");
74 }
75
76 public function addElement(Element $element) {
77 $element->setForm($this);
78 //If the element doesn't have a specified id, a generic identifier is applied.
79 $id = $element->getID();
80 if(empty($id))
81 $element->setID($this->attributes["id"] . "-element-" . sizeof($this->elements));
82 $this->elements[] = $element;
83
84 /*For ease-of-use, the form tag's encytype attribute is automatically set if the File element
85 class is added.*/
86 if($element instanceof Element_File)
87 $this->attributes["enctype"] = "multipart/form-data";
88 }
89
90 /*Values that have been set through the setValues method, either manually by the developer
91 or after validation errors, are applied to elements within this method.*/
92 protected function applyValues() {
93 foreach($this->elements as $element) {
94 $name = $element->getName();
95 if(isset($this->values[$name]))
96 $element->setValue($this->values[$name]);
97 elseif(substr($name, -2) == "[]" && isset($this->values[substr($name, 0, -2)]))
98 $element->setValue($this->values[substr($name, 0, -2)]);
99 }
100 }
101
102 /*Errors from session are applied to elements for accessibility (ARIA attributes).
103 This ensures elements have errors set before rendering, enabling proper aria-invalid and aria-describedby attributes.*/
104 protected function applyErrors() {
105 $errors = $this->getErrors();
106 if(!empty($errors)) {
107 foreach($this->elements as $element) {
108 $name = $element->getName();
109 if(substr($name, -2) == "[]")
110 $name = substr($name, 0, -2);
111
112 if(isset($errors[$name]) && is_array($errors[$name])) {
113 $element->setErrors($errors[$name]);
114 }
115 }
116 }
117 }
118
119 public static function clearErrors($id = "pfbc") {
120 if(!empty($_SESSION["pfbc"][$id]["errors"]))
121 unset($_SESSION["pfbc"][$id]["errors"]);
122 }
123
124 public static function clearValues($id = "pfbc") {
125 if(!empty($_SESSION["pfbc"][$id]["values"]))
126 unset($_SESSION["pfbc"][$id]["values"]);
127 }
128
129 /*This method parses the form's width property into a numeric width value and a width suffix - either px or %.
130 These values are used by the form's concrete view class.*/
131 public function formatWidthProperties() {
132 if(!empty($this->width)) {
133 if(substr($this->width, -1) == "%") {
134 $this->width = substr($this->width, 0, -1);
135 $this->widthSuffix = "%";
136 }
137 elseif(substr($this->width, -2) == "px")
138 $this->width = substr($this->width, 0, -2);
139 }
140 else {
141 /*If the form's width property is empty, 100% will be assumed.*/
142 $this->width = 100;
143 $this->widthSuffix = "%";
144 }
145 }
146
147 public function getAjax() {
148 return $this->ajax;
149 }
150
151 public function getElements() {
152 return $this->elements;
153 }
154
155 public function getError() {
156 return $this->error;
157 }
158
159 public function getId() {
160 return $this->attributes["id"];
161 }
162
163 public function getJQueryUIButtons() {
164 return $this->jQueryUIButtons;
165 }
166
167 public function getPrevent() {
168 return $this->prevent;
169 }
170
171 public function getResourcesPath() {
172 return $this->resourcesPath;
173 }
174
175 public function getErrors() {
176 $errors = array();
177 if(session_id() == "")
178 $errors[""] = array("Error: The pfbc project requires an active session to function properly. Simply add session_start() to your script before any output has been sent to the browser.");
179 else {
180 $errors = array();
181 $id = $this->attributes["id"];
182 if(!empty($_SESSION["pfbc"][$id]["errors"])) {
183 $errors = $_SESSION["pfbc"][$id]["errors"];
184 }
185 }
186
187 return $errors;
188 }
189
190 public static function getSessionValues($id = "pfbc") {
191 $values = array();
192 if(!empty($_SESSION["pfbc"][$id]["values"]))
193 $values = $_SESSION["pfbc"][$id]["values"];
194 return $values;
195 }
196
197 public function getWidth() {
198 return $this->width;
199 }
200
201 public function getWidthSuffix() {
202 return $this->widthSuffix;
203 }
204
205 public static function isValid($id = "pfbc", $clearValues = true) {
206 $valid = true;
207 /*The form's instance is recovered (unserialized) from the session.*/
208 $form = self::recover($id);
209 if(!empty($form)) {
210 if($_SERVER["REQUEST_METHOD"] == "POST")
211 $data = $_POST;
212 else
213 $data = $_GET;
214
215 /*Any values/errors stored in the session for this form are cleared.*/
216 self::clearValues($id);
217 self::clearErrors($id);
218
219 /*Each element's value is saved in the session and checked against any validation rules applied
220 to the element.*/
221 if(!empty($form->elements)) {
222 foreach($form->elements as $element) {
223 $name = $element->getName();
224 if(substr($name, -2) == "[]")
225 $name = substr($name, 0, -2);
226
227 /*The File element must be handled differently b/c it uses the $_FILES superglobal and
228 not $_GET or $_POST.*/
229 if($element instanceof Element_File)
230 $data[$name] = $_FILES[$name]["name"];
231
232 if(isset($data[$name])) {
233 $value = $data[$name];
234 if(is_array($value)) {
235 $valueSize = sizeof($value);
236 for($v = 0; $v < $valueSize; ++$v)
237 $value[$v] = stripslashes($value[$v]);
238 }
239 else
240 $value = stripslashes($value);
241 self::setSessionValue($id, $name, $value);
242 }
243 else
244 $value = null;
245
246 /*If a validation error is found, the error message is saved in the session along with
247 the element's name.*/
248 if(!$element->isValid($value)) {
249 self::setError($id, $element->getErrors(), $name);
250 $valid = false;
251 }
252 }
253 }
254
255 /*If no validation errors were found, the form's session values are cleared.*/
256 if($valid) {
257 if($clearValues)
258 self::clearValues($id);
259 self::clearErrors($id);
260 }
261 }
262 else
263 $valid = false;
264
265 return $valid;
266 }
267
268 /*This method restores the serialized form instance.*/
269 protected static function recover($id) {
270 if(!empty($_SESSION["pfbc"][$id]["form"]))
271 return unserialize($_SESSION["pfbc"][$id]["form"]);
272 else
273 return "";
274 }
275
276 public function render($returnHTML = false) {
277 $this->view->setForm($this);
278 $this->error->setForm($this);
279
280 /*When validation errors occur, the form's submitted values are saved in a session
281 array, which allows them to be pre-populated when the user is redirected to the form.*/
282 $values = self::getSessionValues($this->attributes["id"]);
283 if(!empty($values))
284 $this->setValues($values);
285 $this->applyValues();
286
287 /*Apply validation errors to elements for accessibility (ARIA attributes)*/
288 $this->applyErrors();
289
290 $this->formatWidthProperties();
291
292 if($returnHTML)
293 ob_start();
294
295 $this->renderCSS();
296 $this->view->render();
297 $this->renderJS();
298
299 /*The form's instance is serialized and saved in a session variable for use during validation.*/
300 $this->save();
301
302 if($returnHTML) {
303 $html = ob_get_contents();
304 ob_end_clean();
305 return $html;
306 }
307 }
308
309 /*When ajax is used to submit the form's data, validation errors need to be manually sent back to the
310 form using json.*/
311 public static function renderAjaxErrorResponse($id = "pfbc") {
312 $form = self::recover($id);
313 if(!empty($form))
314 $form->error->renderAjaxErrorResponse();
315 }
316
317 protected function renderCSS() {
318 $this->renderCSSFiles();
319
320 echo '<style type="text/css">';
321 $this->view->renderCSS();
322 $this->error->renderCSS();
323 foreach($this->elements as $element)
324 $element->renderCSS();
325 echo '</style>';
326 }
327
328 protected function renderCSSFiles() {
329 $urls = array();
330 if(!in_array("jQueryUI", $this->prevent))
331 $urls[] = $this->prefix . "://ajax.googleapis.com/ajax/libs/jqueryui/1/themes/" . $this->jQueryUITheme . "/jquery-ui.css";
332 foreach($this->elements as $element) {
333 $elementUrls = $element->getCSSFiles();
334 if(is_array($elementUrls))
335 $urls = array_merge($urls, $elementUrls);
336 }
337
338 /*This section prevents duplicate css files from being loaded.*/
339 if(!empty($urls)) {
340 $urls = array_values(array_unique($urls));
341 foreach($urls as $url)
342 echo '<link type="text/css" rel="stylesheet" href="', $url, '"/>';
343 }
344 }
345
346 protected function renderJS() {
347 $this->renderJSFiles();
348
349 echo '<script type="text/javascript">';
350 $this->view->renderJS();
351 foreach($this->elements as $element)
352 $element->renderJS();
353
354 $id = $this->attributes["id"];
355
356 echo 'jQuery(document).ready(function() {';
357
358 /*When the form is submitted, disable all submit buttons to prevent duplicate submissions.*/
359 echo 'jQuery("#', $id, '").bind("submit", function() {';
360 if(!in_array("jQueryUIButtons", $this->prevent)) {
361 echo 'jQuery(this).find("button[type=submit]").button("disable");';
362 echo 'jQuery(this).find("button[type=submit] span.ui-button-text").css("padding-right", "2.1em").append("<img class=\"pfbc-loading\" src=\"', $this->resourcesPath, '/loading.gif\"/>");';
363 }
364 else
365 echo 'jQuery(this).find("button[type=submit]").attr("disabled", "disabled");';
366 echo '});';
367
368 /*jQuery is used to set the focus of the form's initial element.*/
369 if(!in_array("focus", $this->prevent))
370 echo 'jQuery("#', $id, ' :input:visible:enabled:first").focus();';
371
372 $this->view->jQueryDocumentReady();
373 foreach($this->elements as $element)
374 $element->jQueryDocumentReady();
375
376 /*For ajax, an anonymous onsubmit javascript function is bound to the form using jQuery. jQuery's
377 serialize function is used to grab each element's name/value pair.*/
378 if(!empty($this->ajax)) {
379 echo 'jQuery("#', $id, '").bind("submit", function() {';
380 $this->error->clear();
381 echo <<<JS
382 jQuery.ajax({
383 url: "{$this->attributes["action"]}",
384 type: "{$this->attributes["method"]}",
385 data: jQuery("#$id").serialize(),
386 success: function(response) {
387 if(response != undefined && typeof response == "object" && response.errors) {
388 JS;
389 $this->error->applyAjaxErrorResponse();
390 echo <<<JS
391 jQuery("html, body").animate({ scrollTop: jQuery("#$id").offset().top }, 500 );
392 }
393 else {
394 JS;
395 /*A callback function can be specified to handle any post submission events.*/
396 if(!empty($this->ajaxCallback))
397 echo $this->ajaxCallback, "(response);";
398
399 echo '}';
400
401 if(!in_array("jQueryUIButtons", $this->prevent)) {
402 echo 'jQuery("#', $id, ' button[type=submit] span.ui-button-text").css("padding-right", "1em").find("img").remove();';
403 echo 'jQuery("#', $id, ' button[type=submit]").button("enable");';
404 }
405 else
406 echo 'jQuery("#', $id, '").find("button[type=submit]").removeAttr("disabled");';
407
408 echo <<<JS
409 }
410 });
411 return false;
412 });
413
414 JS;
415 }
416
417 echo <<<JS
418 });
419 </script>
420 JS;
421 }
422
423 protected function renderJSFiles() {
424 $urls = array();
425 if(!in_array("jQuery", $this->prevent))
426 $urls[] = $this->prefix . "://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js";
427 if(!in_array("jQueryUI", $this->prevent))
428 $urls[] = $this->prefix . "://ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min.js";
429 foreach($this->elements as $element) {
430 $elementUrls = $element->getJSFiles();
431 if(is_array($elementUrls))
432 $urls = array_merge($urls, $elementUrls);
433 }
434
435 /*This section prevents duplicate css files from being loaded.*/
436 if(!empty($urls)) {
437 $urls = array_values(array_unique($urls));
438 foreach($urls as $url)
439 echo '<script type="text/javascript" src="', $url, '"></script>';
440 }
441 }
442
443 /*The save method serialized the form's instance and saves it in the session.*/
444 protected function save() {
445 $_SESSION["pfbc"][$this->attributes["id"]]["form"] = serialize($this);
446 }
447
448 /*Valldation errors are saved in the session after the form submission, and will be displayed to the user
449 when redirected back to the form.*/
450 public static function setError($id, $errors, $element = "") {
451 if(!is_array($errors))
452 $errors = array($errors);
453 if(empty($_SESSION["pfbc"][$id]["errors"][$element]))
454 $_SESSION["pfbc"][$id]["errors"][$element] = array();
455
456 foreach($errors as $error)
457 $_SESSION["pfbc"][$id]["errors"][$element][] = $error;
458 }
459
460 public static function setSessionValue($id, $element, $value) {
461 $_SESSION["pfbc"][$id]["values"][$element] = $value;
462 }
463
464 /*An associative array is used to pre-populate form elements. The keys of this array correspond with
465 the element names.*/
466 public function setValues(array $values) {
467 $this->values = array_merge($this->values, $values);
468 }
469 }
470