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