<?php # eForm 1.4.4.7 - Electronic Form Snippet # Original created by: Raymond Irving 15-Dec-2004. # Extended by: Jelle Jager (TobyL) September 2006 # ----------------------------------------------------- # # # Captcha image support - thanks to Djamoer # Multi checkbox, radio, select support - thanks to Djamoer # Form Parser and extended validation - by Jelle Jager # # see docs/eform.htm for installation and usage information # # VERSION HISTORY # Work around for setting required class on check & radio labels # fixed bug: If eform attibute is set on multiple check boxes only the last # value is set in values list # For a full version history see the eform_history.htm file in the docs directory # # Some more fixes and problems: # FIXED: reg expression failed for select and textarea boxes which have regex special # characters in their name attribute. eg name="multipleSelection[]" # FIXED: validation of multiple values with #LIST & #SELECT stopped after 1st value # Caused by repeating $v variable naming (overwriting $v array) # e.g. # <select name="multipleSelection[]" multiple="multiple" eform="::1::"/> # <option value="1">1</option> # <option value="2">2</option> # <option value="3">3</option> # </select> # would only have the first selected value validated! # # bugfix: &jScript parameter doesn't accept chunks, only a link to a JS file if more than one chunk is declared (eg &jScript=`chunk1,chunk2) # bugfix: &protectSubmit creates hash for all fields instead of fields declared in &protectSubmit # bugfix: Auto respond email didn't honour the &sendAsText parameter # bugfix: The #FUNCTION validation rule for select boxes never calls the function # bugfix: Validation css class isn't being added to labels. # # SECURITY FIX: add additional sanitization to fields after stripping slashes to avoid remote tag execution ## #---------------------------------------------------------- # Modify ZeRo(http://www.petit-power.com) $Revision: 58 $ # $Date: 2009-07-27 16:31:59 +0900 (??, 01 3 2009) $ #---------------------------------------------------------- $GLOBALS['optionsName'] = 'eform'; //name of pseudo attribute used for format settings $GLOBALS['efPostBack'] = false; //tidying up some casing errors in parameters if(isset($eformOnValidate)) $eFormOnValidate = $eformOnValidate; if(isset($eformOnBeforeMailSent)) $eFormOnBeforeMailSent = $eformOnBeforeMailSent; if(isset($eformOnMailSent)) $eFormOnMailSent = $eformOnMailSent; if(isset($eformOnValidate)) $eFormOnValidate = $eformOnValidate; if(isset($eformOnBeforeFormMerge)) $eFormOnBeforeFormMerge = $eformOnBeforeFormMerge; if(isset($eformOnBeforeFormParse)) $eFormOnBeforeFormParse = $eformOnBeforeFormParse; //for sottwell :) if(isset($eFormCSS)) $cssStyle = $eFormCSS; # Snippet customize settings $from = (isset($from)) ? $from : $modx->config['emailsender']; $formid = (isset($formid)) ? $formid : ''; $vericode = (isset($vericode)) ? $vericode: ''; $params = array ( // Snippet Path 'snipPath' => $snipPath, //includes $snip_dir 'snipFolder' => $snip_dir, // eForm Params 'vericode' => $vericode, 'formid' => $formid, 'from' => $from, 'fromname' => isset($fromname)? $fromname:$modx->config['site_name'], 'to' => isset($to)? $to:$modx->config['emailsender'], 'cc' => isset($cc)? $cc:'', 'bcc' => isset($bcc)? $bcc:'', 'subject' => isset($subject)? $subject:'', 'ccsender' => isset($ccsender)? $ccsender:0, 'sendirect' => isset($sendirect)? $sendirect:0, 'mselector' => isset($mailselector)? $mailselector:0, 'mobile' => isset($mobile)? $mobile:'', 'mobiletext' => isset($mobiletext)? $mobiletext:'', 'autosender' => isset($autosender)? $autosender:$from, 'autotext' => isset($automessage)? $automessage:'', 'category' => isset($category)? $category:0, 'keywords' => isset($keywords)? $keywords:'', 'gid' => isset($gotoid)? $gotoid:$modx->documentIdentifier, 'noemail' => isset($noemail)? ($noemail):false, 'saveform' => isset($saveform)? ($saveform? true:false):true, 'tpl' => isset($tpl)? $tpl:'', 'report' => isset($report)? $report:'', 'allowhtml' => isset($allowhtml)? $allowhtml:0, //Added by JJ 'replyto' => isset($replyto)? $replyto:'', 'language' => isset($language)? $language:$modx->config['manager_language'], 'thankyou' => isset($thankyou)? $thankyou:'', 'isDebug' => isset($debug)? $debug:0, 'reportAbuse' => isset($reportAbuse)? $reportAbuse:false, 'disclaimer' => isset($disclaimer)?$disclaimer:'', 'sendAsHtml' => isset($sendAsHtml)?$sendAsHtml:false, 'sendAsText' => isset($sendAsText)?$sendAsText:false, 'sessionVars' => isset($sessionVars)?$sessionVars:false, 'postOverides' => isset($postOverides)?$postOverides:0, 'eFormOnBeforeMailSent' => isset($eFormOnBeforeMailSent)?$eFormOnBeforeMailSent:'', 'eFormOnMailSent' => isset($eFormOnMailSent)?$eFormOnMailSent:'', 'eFormOnValidate' => isset($eFormOnValidate)?$eFormOnValidate:'', 'eFormOnBeforeFormMerge' => isset($eFormOnBeforeFormMerge)?$eFormOnBeforeFormMerge:'', 'eFormOnBeforeFormParse' => isset($eFormOnBeforeFormParse)?$eFormOnBeforeFormParse:'', 'cssStyle' => isset($cssStyle)?$cssStyle:'', 'jScript' => isset($jScript)?$jScript:'', 'submitLimit' => (isset($submitLimit) && is_numeric($submitLimit))?$submitLimit*60:0, 'protectSubmit' => isset($protectSubmit)?$protectSubmit:1, 'requiredClass' => isset($requiredClass)?$requiredClass:"required", 'invalidClass' => isset($invalidClass)?$invalidClass:"invalid", 'runSnippet' => ( isset($runSnippet) && !is_numeric($runSnippet) )?$runSnippet:'', 'autoSenderName' => isset($autoSenderName)?$autoSenderName:'', 'version' => isset($version) ? $version : '1.4.4.7' ); // pixelchutes PHx workaround foreach( $params as $key=>$val ) $params[ $key ] = str_replace( array('((','))'), array('[+','+]'), $val ); function eForm($modx,$params) { global $_lang; global $debugText; global $formats,$fields,$efPostBack; $fields = array(); //reset fields array - needed in case of multiple forms // define some variables used as array index $_dfnMaxlength = 6; extract($params,EXTR_SKIP); // extract params into variables #include default language file include_once($snipPath."lang/english.inc.php"); #include other language file if set. $form_language = isset($language) ? $language : $modx->config['manager_language']; if($form_language!="english" && $form_language!='') { if(file_exists("{$snipPath}lang/{$form_language}.inc.php")) include_once("{$snipPath}lang/{$form_language}.inc.php"); else if( $isDebug ) $debugText .= "<strong>Language file '{$form_language}.inc.php' not found!</strong><br />"; //always in english! } # add debug warning - moved again... if( $isDebug ) $debugText .= $_lang['ef_debug_warning']; # check for valid form key - moved to below fetching form template to allow id coming from form template $nomail = $noemail; //adjust variable name confusion # activate nomail if missing $to if (!$to) $nomail = 1; # load templates if($tpl==$modx->documentIdentifier) return $_lang['ef_is_own_id']."'$tpl'"; //required if(empty($tpl)) $tpl = get_default_tpl(); elseif( $tmp=efLoadTemplate($tpl) ) $tpl = $tmp; else return $_lang['ef_no_doc'] . " '$tpl'"; # check for valid form key if ($formid=='') $formid = 'eform'; // try to get formid from <form> tag id preg_match('/<form[^>]*?id=[\'"]([^\'"]*?)[\'"]/i',$tpl,$matches); $form_id = isset($matches[1])?$matches[1]:''; //check for <input type='hidden name='formid'...> if( !preg_match('/<input[^>]*?name=[\'"]formid[\'"]/i',$tpl) ){ //insert hidden formid field $tpl = str_replace('</form>',"<input type=\"hidden\" name=\"formid\" value=\"$formid\" /></form>",$tpl); } $validFormId = (isset($_POST['formid']) && $formid==$_POST['formid'])?1:0; # check if postback mode $efPostBack = ($validFormId && count($_POST)>0)? true:false; //retain old variable? if($efPostBack){ if(empty($report)) $report = get_default_report(); else { $report = (($tmp=efLoadTemplate($report))!==false)?$tmp:$_lang['ef_no_doc'] . " '$report'"; } if($thankyou) $thankyou = (($tmp=efLoadTemplate($thankyou))!==false )?$tmp:$_lang['ef_no_doc'] . " '$thankyou'"; if($autotext) $autotext = (($tmp=efLoadTemplate($autotext))!==false )?$tmp:$_lang['ef_no_doc'] . " '$autotext'"; } //these will be added to the HEAD section of the document when the form is displayed! if($cssStyle){ $cssStyle = ( strpos($cssStyle,',') && strpos($cssStyle,'<style')===false ) ? explode(',',$cssStyle) : array($cssStyle); foreach( $cssStyle as $tmp ) $startupSource[]= array($tmp,'css'); } if($jScript){ $jScript = ( strpos($jScript,',') && strpos($jScript,'<script')===false ) ? explode(',',$jScript) : array($jScript); foreach( $jScript as $tmp ) $startupSource[]= array($tmp,'javascript'); } #New in 1.4.4 - run snippet to include 'event' functions if( strlen($runSnippet)>0 ){ $modx->runSnippet($runSnippet, array('formid'=>$formid)); //Sadly we cannot know if the snippet fails or if it exists as modx->runsnippet's return value //is ambiguous } # invoke onBeforeFormParse event set by another script if ($eFormOnBeforeFormParse) { if( $isDebug && !function_exists($eFormOnBeforeFormParse)) $fields['debug'] .= "eFormOnBeforeFormParse event: Could not find the function " . $eFormOnBeforeFormParse; else{ $templates = array('tpl'=>$tpl,'report'=>$report,'thankyou'=>$thankyou,'autotext'=>$autotext); if( $eFormOnBeforeFormParse($fields,$templates)===false ) return ""; elseif(is_array($templates)) extract($templates); // extract back into original variables } } # parse form for formats and generate placeholders $tpl = eFormParseTemplate($tpl,$isDebug); if ($efPostBack) { foreach($formats as $k => $discard) if(!isset($fields[$k])) $fields[$k] = ""; // store dummy value inside $fields $disclaimer = (($tmp=efLoadTemplate($disclaimer))!==false )? $tmp:''; //error message containers $vMsg = $rMsg = $rClass = array(); # get user post back data foreach ($_POST as $name => $value){ if (is_array($value)){ //remove empty values $fields[$name] = array_filter($value, create_function('$v','return (!empty($v));')); } else { $value = htmlspecialchars($value, ENT_QUOTES, $modx->config['modx_charset']); if ($allowhtml || $formats[$name][2]=='html') { $fields[$name] = stripslashes($value); } else { $fields[$name] = strip_tags(stripslashes($value)); } } } # get uploaded files foreach ($_FILES as $name => $value){ $fields[$name] = $value; } # check vericode if($vericode) { //add support for captcha code - thanks to Djamoer $code = $_SESSION['veriword'] ? $_SESSION['veriword'] : $_SESSION['eForm.VeriCode']; if($fields['vericode']!=$code) { $vMsg[count($vMsg)]=$_lang['ef_failed_vericode']; $rClass['vericode']=$invalidClass; //added in 1.4.4 } } # sanitize the values with slashes stripped to avoid remote execution of Snippets modx_sanitize_gpc($fields); # validate fields foreach($fields as $name => $value) { $fld = isset($formats[$name]) ? $formats[$name] : ''; if ($fld) { $desc = $fld[1]; $datatype = $fld[2]; $isRequired = $fld[3]; if ($isRequired==1 && $value=="" && $datatype!="file"){ $rMsg[count($rMsg)]="$desc"; $rClass[$name]=$requiredClass; }elseif( !empty($fld[5]) && $value!="" && $datatype!="file" ) { $value = validateField($value,$fld,$vMsg,$isDebug); if($value===false) $rClass[$name]=$invalidClass; //if returned value is not of type boolean replace value... elseif($value!==true) $fields[$name]=$value; //replace value. }else{ //value check switch ($datatype){ case "integer": case "float": if (strlen($value)>0 && !is_numeric($value)){ $vMsg[count($vMsg)]=$desc . $_lang["ef_invalid_number"]; $rClass[$name]=$invalidClass; } break; case "date": if(strlen($value)==0) break; //corrected by xwisdom for php version differences $rt = strtotime($value); //php 5.1.0+ returns false while < 5.1.0 returns -1 if ($rt===false||$rt===-1){ $vMsg[count($vMsg)]=$desc . $_lang["ef_invalid_date"]; $rClass[$name]=$invalidClass; } break; case "email": //stricter email validation - udated to allow + in local name part if (strlen($value)>0 && !preg_match( '/^(?:[a-z0-9+_-]+?\.)*?[a-z0-9_+-]+?@(?:[a-z0-9_-]+?\.)*?[a-z0-9_-]+?\.[a-z0-9]{2,5}$/i', $value) ){ $vMsg[count($vMsg)] = isset($formats[$name][4]) ? $formats[$name][4] : $desc . $_lang["ef_invalid_email"]; $rClass[$name]=$invalidClass; } break; case "file": if ($_FILES[$name]['error']==1 || $_FILES[$name]['error']==2){ $vMsg[count($vMsg)]=$desc . $_lang['ef_upload_exceeded']; $rClass[$name]=$invalidClass; }elseif ($isRequired==1 && ($_FILES[$name] && $_FILES[$name]['type']=='')){ $rMsg[count($rMsg)]=$desc; $rClass[$name]=$requiredClass; }elseif ($_FILES[$name]['tmp_name']){ if( substr($fld[5],0,5)!="#LIST" || validateField($_FILES[$name]['name'],$fld,$vMsg,$isDebug) ) $attachments[count($attachments)] = $_FILES[$name]['tmp_name']; else $rClass[$name]=$invalidClass; } break; case "html": case "checkbox": case "string": default: break; } }//end required test } } // Changed in 1.4.4.5 - now expects 4 parameters if ($eFormOnValidate) { if( $isDebug && !function_exists($eFormOnValidate)) $fields['debug'] .= "eformOnValidate event: Could not find the function " . $eFormOnValidate; else if ($eFormOnValidate($fields,$vMsg,$rMsg,$rClass)===false) return; } if(count($vMsg)>0 || count($rMsg)>0){ //New in 1.4.2 - classes are set in labels and form elements for invalid fields foreach($rClass as $n => $class){ $fields[$n.'_class'] = $fields[$n.'_class']?$fields[$n.'_class'].' '. $class:$class; $fields[$n.'_vClass'] = isset($fields[$n.'_vClass']) ? $fields[$n.'_vClass'].' '. $class:$class; //work around for checkboxes if( isset($formats[$n][6] )){ //have separate id's for check and option tags - set classes as well foreach( explode(',',$formats[$n][6]) as $id) $fields[$id.'_vClass'] = $fields[$id.'_vClass'] ? $fields[$id.'_vClass'].' '. $class : $class; } } //add debugging info to fields array if($isDebug){ ksort($fields); if($isDebug>1){ $debugText .= "<br /><strong>Formats array:</strong><pre>". var_export($formats,true).'</pre>'; $debugText .= "<br /><strong>Fields array:</strong><pre>". var_export($fields,true).'</pre>'; $debugText .= "<br /><strong>Classes parsed :</strong><pre>" . var_export($rClass,true) ."</pre>"; } $debugText .= "<br /><strong>eForm configuration:</strong><pre>\n". var_export($params,true).'</pre>'; $fields['debug']=$debugText; } #set validation message $tmp = (count($rMsg)>0)?str_replace("{fields}", implode(", ",$rMsg),$_lang['ef_required_message']):""; $tmp .= implode("<br />",$vMsg); if(!strstr($tpl,'[+validationmessage+]')) $modx->setPlaceholder('validationmessage',str_replace('[+ef_wrapper+]', $tmp, $_lang['ef_validation_message'])); else { if(!isset($fields['validationmessage'])) $fields['validationmessage'] = ''; $fields['validationmessage'] .= str_replace('[+ef_wrapper+]', $tmp, $_lang['ef_validation_message']); } } else { # format report fields foreach($fields as $name => $value) { $fld = $formats[$name]; if ($fld) { $datatype = $fld[2]; switch ($datatype) { case "integer": $value = number_format($value); break; case "float": $localeInfo = localeconv(); $th_sep = empty($_lang['ef_thousands_separator'])?$localeInfo['thousands_sep']:$_lang['ef_thousands_separator']; $dec_point= $localeInfo['decimal_point']; $debugText .= 'Locale<pre>'.var_export($localeInfo,true).'</pre>'; $value = number_format($value, 2, $dec_point, $th_sep); break; case "date": $format_string = isset($_lang['ef_date_format']) ? $_lang['ef_date_format'] : '%d-%b-%Y %H:%M:%S'; $value = ($value)? strftime($format_string,strtotime($value)):""; $value=str_replace("00:00:00","",$value);// remove trailing zero time values break; case "html": // convert \n to <br /> if(!$sendAsText ) $value = preg_replace('#(\n<br[ /]*?>|<br[ /]*?>\n|\n)#i','<br />',$value); break; case "file": // set file name if($value['type']!="" && $value['type']!=""){ $value = $value["name"]; $patharray = explode(((strpos($value,"/")===false)? "\\":"/"), $value); $value = $patharray[count($patharray)-1]; } else { $value = ""; } break; } $fields[$name] = $value; } } # set postdate $fields['postdate'] = strftime($modx->toDateFormat(null, 'formatOnly') . " %H:%M:%S",time()); //check against email injection and replace suspect content if( hasMailHeaders($fields) ){ //send email to webmaster?? if ($reportAbuse){ //set in snippet configuration tab $body = $_lang['ef_mail_abuse_message']; $body .="<table>"; foreach($fields as $key => $value) $body .= "<tr><td>$key</td><td><pre>$value</pre></td></tr>"; $body .="</table>"; include_once $modx->config['base_path'] . 'manager/includes/controls/modxmailer.inc.php'; $mail = new MODxMailer(); # send abuse alert $mail->IsHTML($isHtml); $mail->From = $modx->config['emailsender']; $mail->FromName = $modx->config['site_name']; $mail->Subject = $_lang['ef_mail_abuse_subject']; $mail->Body = $body; AddAddressToMailer($mail,"to",$modx->config['emailsender']); $mail->Send(); //ignore mail errors in this case } //return empty form with error message //register css and/or javascript if( isset($startupSource) ) efRegisterStartupBlock($startupSource); return formMerge($tpl,array('validationmessage'=> $_lang['ef_mail_abuse_error'])); } # added in 1.4.2 - Limit the time between form submissions if($submitLimit>0){ if( time()<$submitLimit+$_SESSION[$formid.'_limit'] ){ return formMerge($_lang['ef_submit_time_limit'],$fields); } else unset($_SESSION[$formid.'_limit'], $_SESSION[$formid.'_hash']); //time expired } # invoke OnBeforeMailSent event set by another script if ($eFormOnBeforeMailSent) { if( $isDebug && !function_exists($eFormOnBeforeMailSent)) $fields['debug'] .= "eFormOnBeforeMailSent event: Could not find the function " . $eFormOnBeforeMailSent; elseif ($eFormOnBeforeMailSent($fields)===false) { if( isset($fields['validationmessage']) && !empty($fields['validationmessage']) ){ //register css and/or javascript if( isset($startupSource) ) efRegisterStartupBlock($startupSource); return formMerge($tpl,$fields); } else return; } } if( $protectSubmit ){ $hash = ''; # create a hash of key data if(!is_numeric($protectSubmit)) { //supplied field names $protectSubmit = (strpos($protectSubmit,','))? explode(',',$protectSubmit):array($protectSubmit); foreach($protectSubmit as $fld) $hash .= isset($fields[$fld]) ? $fields[$fld] : ''; } else //all required fields { foreach($formats as $fld) { $hash .= ($fld[3]==1) ? $fields[$fld[0]] : ''; } } if($hash) $hash = md5($hash); if( $isDebug ) $debugText .= "<strong>SESSION HASH</strong>:".$_SESSION[$formid.'_hash']."<br />"."<b>FORM HASH</b>:".$hash."<br />"; # check if already succesfully submitted with same data if( isset($_SESSION[$formid.'_hash']) && $_SESSION[$formid.'_hash'] == $hash && $hash!='' ) return formMerge($_lang['ef_multiple_submit'],$fields); } $fields['disclaimer'] = ($disclaimer)? formMerge($disclaimer,$fields):""; if(isset($fields['subject'])) { $subject = $fields['subject']; } else { $subject = ($subject) ? formMerge($subject,$fields) : "from {$fromname}"; } $fields['subject'] = $subject; //make subject available in report & thank you page $report = ($report)? formMerge($report,$fields):""; $keywords = ($keywords)? formMerge($keywords,$fields):""; $from = ($from)? formMerge($from,$fields):""; $fromname = ($from)? formMerge($fromname,$fields):""; $to = formMerge($to,$fields); if(empty($to) || !strpos($to,'@')) $nomail=1; if(!$nomail){ # check for mail selector field - select an email from to list if ($mselector && $fields[$mselector]) { $i = (int)$fields[$mselector]; $ar = explode(",",$to); if ($i>0) $i--; if ($ar[$i]) $to = $ar[$i]; else $to = $ar[0]; } //set reply-to address //$replyto snippet parameter must contain email or fieldname if(!strstr($replyto,'@')) $replyto = ( $fields[$replyto] && strstr($fields[$replyto],'@') )?$fields[$replyto]:$from; # include PHP Mailer include_once $modx->config['base_path'] . 'manager/includes/controls/modxmailer.inc.php'; $mail = new MODxMailer(); # send form //defaults to html so only test sendasText $isHtml = ($sendAsText==1 || strstr($sendAsText,'report'))?false:true; if(!$noemail) { if($sendirect) $to = $fields['email']; $mail = new MODxMailer(); $mail->IsHTML($isHtml); $mail->From = $from; $mail->FromName = $fromname; $mail->Subject = $subject; $mail->Body = $report; AddAddressToMailer($mail,"replyto",$replyto); AddAddressToMailer($mail,"to",$to); AddAddressToMailer($mail,"cc",$cc); AddAddressToMailer($mail,"bcc",$bcc); AttachFilesToMailer($mail,$attachments); if(!$mail->Send()) return 'Main mail: ' . $_lang['ef_mail_error'] . $mail->ErrorInfo; } # send user a copy of the report if($ccsender && $fields['email']) { $mail = new MODxMailer(); $mail->IsHTML($isHtml); $mail->From = $from; $mail->FromName = $fromname; $mail->Subject = $subject; $mail->Body = $report; AddAddressToMailer($mail,'to',$fields['email']); AttachFilesToMailer($mail,$attachments); if(!$mail->Send()) return 'CCSender: ' . $_lang['ef_mail_error'] . $mail->ErrorInfo; } # send auto-respond email //defaults to html so only test sendasText $isHtml = ($sendAsText==1 || strstr($sendAsText,'autotext')) ? false:true; if ($autotext && $fields['email']!='') { $autotext = formMerge($autotext,$fields); $mail = new MODxMailer(); $mail->IsHTML($isHtml); $mail->From = ($autosender)? $autosender:$from; $mail->FromName = ($autoSenderName)?$autoSenderName:$fromname; $mail->Subject = $subject; $mail->Body = $autotext; AddAddressToMailer($mail,'to',$fields['email']); if(!$mail->Send()) return 'AutoText: ' . $_lang['ef_mail_error'] . $mail->ErrorInfo; } //defaults to text - test for sendAsHtml $isHTML = ($sendAsHTML==1 || strstr($sendAsHtml,'mobile'))?true:false; # send mobile email if ($mobile && $mobiletext) { $mobiletext = formMerge($mobiletext,$fields); $mail = new MODxMailer(); $mail->IsHTML($isHtml); $mail->From = $from; $mail->FromName = $fromname; $mail->Subject = $subject; $mail->Body = $mobiletext; AddAddressToMailer($mail,'to',$mobile); $mail->Send(); } }//end test nomail # added in 1.4.2 - Protection against multiple submit with same form data if($protectSubmit) $_SESSION[$formid.'_hash'] = $hash; //hash is set earlier # added in 1.4.2 - Limit the time between form submissions if($submitLimit>0) $_SESSION[$formid.'_limit'] = time(); # invoke OnMailSent event set by another script if ($eFormOnMailSent) { if( $isDebug && !function_exists($eFormOnMailSent) ) $fields['debug'] .= "eFormOnMailSent event: Could not find the function" . $eFormOnMailSent; else if ($eFormOnMailSent($fields)===false) return; } if($isDebug){ $debugText .="<strong>Mail Headers:</strong><br />From: $from ($fromname)<br />Reply-to:$replyto<br />To: $to<br />Subject: $subject<br />CC: $cc<br /> BCC:$bcc<br />"; if($isDebug>1){ $debugText .= "<br /><strong>Formats array:</strong><pre>". var_export($formats,true).'</pre>'; $debugText .= "<br /><strong>Fields array:</strong><pre>". var_export($fields,true).'</pre>'; } $fields['debug'] = $debugText; } # show or redirect to thank you page if ($gid==$modx->documentIdentifier){ if(!empty($thankyou) ){ if($isDebug && !strstr($thankyou,'[+debug+]')) $thankyou .= '[+debug+]'; if( isset($startupSource) ) efRegisterStartupBlock($startupSource,true); //skip scripts if( $sendAsText ){ foreach($formats as $key => $fmt) if($fmt[2]=='html') $fields[$key] = str_replace("\n",'<br />',$fields[$key]); } $thankyou = formMerge($thankyou,$fields); return $thankyou; }else{ return $_lang['ef_thankyou_message']; } } else { $url = $modx->makeURL($gid); $modx->sendRedirect($url); } return; // stop here } }else{ //not postback //add debugging info to fields array if($isDebug){ $debugText .= "<br /><strong>eForm configuration:</strong><pre>". var_export($params,true).'</pre>'; $fields['debug']=$debugText; } //strip the eform attribute $regExpr = "#eform=([\"'])[^\\1]*?\\1#si"; $tpl = preg_replace($regExpr,'',$tpl); } // set vericode if($vericode) { $_SESSION['eForm.VeriCode'] = $fields['vericode'] = substr(uniqid(''),-5); $fields['verimageurl'] = $modx->config['base_url'].'captcha.php?rand='.mt_rand(); } # get SESSION data - thanks to sottwell if($sessionVars){ $sessionVars = (strpos($sessionVars,',',0))?explode(',',$sessionVars):array($sessionVars); foreach( $sessionVars as $varName ){ if( empty($varName) ) continue; $varName = trim($varName); if( isset($_SESSION[$varName]) && !empty($_SESSION[$varName]) ) $fields[$varName] = ( isset($fields[$varName]) && $postOverides )?$fields[$varName]:$_SESSION[$varName]; } } # invoke OnBeforeFormMerge event set by another script if ($eFormOnBeforeFormMerge) { if( $isDebug && !function_exists($eFormOnBeforeFormMerge)) $fields['debug'] .= "eFormOnBeforeFormMerge event: Could not find the function " . $eFormOnBeforeFormMerge; else if ($eFormOnBeforeFormMerge($fields)===false) return; } # build form if($isDebug && !$fields['debug']) $fields['debug'] = $debugText; if($isDebug && !strstr($tpl,'[+debug+]')) $tpl .= '[+debug+]'; //register css and/or javascript if( isset($startupSource) ) efRegisterStartupBlock($startupSource); return formMerge($tpl,$fields); } # Form Merge function formMerge($docText, $docFields, $vClasses='') { global $modx,$formats; global $lastitems; if(!$docText) return ''; preg_match_all('~\[\+(.*?)\+\]~', $docText, $matches); for($i=0;$i<count($matches[1]);$i++) { $name = $matches[1][$i]; if(strpos($name,':')!==false) list($listName,$listValue) = explode(':',$name); else $listName = $name; $value = isset($docFields[$listName])? $docFields[$listName]:''; // support for multi checkbox, radio and select - Djamoer if(is_array($value)) $value=implode(', ', $value); $fld = (isset($formats[$name])) ? $formats[$name] : ''; if (empty($fld)){ // listbox, checkbox, radio select $colonPost = strpos($name, ':'); $listName = substr($name, 0, $colonPost); $listValue = substr($name, $colonPost+1); $datatype = isset($formats[$listName][2]) ? $formats[$listName][2] : ''; if(isset($docFields[$listName]) && is_array($docFields[$listName])) { if($datatype=="listbox" && in_array($listValue, $docFields[$listName])) $docText = str_replace("[+$listName:$listValue+]","selected='selected'",$docText); if(($datatype=="checkbox"||$datatype=="radio") && in_array($listValue, $docFields[$listName])) $docText = str_replace("[+$listName:$listValue+]","checked='checked'",$docText); } else { if($datatype=="listbox" && isset($docFields[$listName]) && $listValue==$docFields[$listName]) $docText = str_replace("[+$listName:$listValue+]","selected='selected'",$docText); if(($datatype=="checkbox"||$datatype=="radio") && $listValue==$docFields[$listName]) $docText = str_replace("[+$listName:$listValue+]","checked='checked'",$docText); } } if(strpos($name,":")===false) $docText = str_replace("[+$name+]",$value,$docText); else { // this might be a listbox item. // we'll remove this field later $lastitems[count($lastitems)]="[+$name+]"; } } $lastitems[count($lastitems)] = "class=\"\""; //removal off empty class attributes $docText = str_replace($lastitems,"",$docText); $docText = $modx->mergeDocumentContent($docText); $docText = $modx->mergeSettingsContent($docText); $docText = $modx->mergeChunkContent($docText); $docText = $modx->evalSnippets($docText); return $docText; } # Adds Addresses to Mailer function AddAddressToMailer(&$mail,$type,$addr) { $a = explode(',', $addr); foreach($a as $_) { if ($type=='to') $mail->AddAddress($_); elseif ($type=='cc') $mail->AddCC($_); elseif ($type=='bcc') $mail->AddBCC($_); elseif ($type=='replyto') $mail->AddReplyTo($_); } } # Attach Files to Mailer function AttachFilesToMailer(&$mail,&$attachFiles) { if(count($attachFiles)>0){ foreach($attachFiles as $attachFile){ if(!file_exists($attachFile)) continue; $FileName = $attachFile; $contentType = "application/octetstream"; if (is_uploaded_file($attachFile)){ foreach($_FILES as $n => $v){ if($_FILES[$n]['tmp_name']==$attachFile) { $FileName = $_FILES[$n]['name']; $contentType = $_FILES[$n]['type']; } } } $patharray = explode(((strpos($FileName,"/")===false)? "\\":"/"), $FileName); $FileName = $patharray[count($patharray)-1]; $mail->AddAttachment($attachFile,$FileName,"base64",$contentType); } } } /*--- Form Parser stuff----------------------*/ function eFormParseTemplate($tpl, $isDebug=false ){ global $modx,$formats,$optionsName,$_lang,$debugText,$fields,$validFormId; global $efPostBack; $formats =""; //clear formats so values don't persist through multiple snippet calls $labels = ""; $regExpr = "#(<label[^>]*?>)(.*?)</label>#si";; preg_match_all($regExpr,$tpl,$matches); foreach($matches[1] as $key => $fld){ $attr = attr2array($fld); if(isset($attr['for'])){ $name = substr($attr['for'],1,-1); //add class to fields array $fields[$name."_vClass"] = isset($attr['class'])?substr($attr['class'],1,-1):''; $labels[$name] = strip_tags($matches[2][$key]); //create placeholder for class $attr['class'] = '"[+'.$name.'_vClass+]"'; $newTag = buildTagPlaceholder('label',$attr,$name); $tpl = str_replace($fld,$newTag,$tpl); } } //retrieve all the form fields $regExpr = "#(<(input|select|textarea)[^>]*?>)#si"; preg_match_all($regExpr,$tpl,$matches); $fieldTypes = $matches[2]; $fieldTags = $matches[1]; for($i=0;$i<count($fieldTypes);$i++){ $type = $fieldTypes[$i]; //get array of html attributes $tagAttributes = attr2array($fieldTags[$i]); //attribute values are stored including quotes //strip quotes as well as any brackets to get the raw name $name = str_replace(array("'",'"','[',']'),'',$tagAttributes['name']); #skip vericode field - updated in 1.4.4 #special case. We need to set the class placeholder but forget about the rest if($name=="vericode"){ if(isset($tagAttributes['class'])){ $fields[$name.'_class'] = substr($tagAttributes['class'],1,-1); } $tagAttributes['class'] = '"[+'.$name.'_class+]"'; $tagAttributes['value'] = ''; $newTag = buildTagPlaceholder('input',$tagAttributes,$name); $tpl = str_replace($fieldTags[$i],$newTag,$tpl); continue; } //store the field options if (isset($tagAttributes[$optionsName])){ //split to max of 5 so validation rule can contain ':' $formats[$name] = explode(":",stripTagQuotes($tagAttributes[$optionsName]),5) ; array_unshift($formats[$name],$name); }else{ if(!isset($formats[$name])) $formats[$name]=array($name,'','',0); } //added for 1.4 - use label if it is defined if(empty($formats[$name][1])) $formats[$name][1]=(isset($labels[$name])) ? $labels[$name] : $name; if(isset($id)) $formats[6] = $id; //added in 1.4.4.1 unset($tagAttributes[$optionsName]); //added in 1.4.2 - add placeholder to class attribute if(isset($tagAttributes['class'])){ $fields[$name.'_class'] = substr($tagAttributes['class'],1,-1); } $tagAttributes['class'] = '"[+'.$name.'_class+]"'; switch($type){ case "select": //replace with 'cleaned' tag and added placeholder $newTag = buildTagPlaceholder('select',$tagAttributes,$name); $tpl = str_replace($fieldTags[$i],$newTag,$tpl); if($formats[$name]) $formats[$name][2]='listbox'; //Get the whole select block with option tags //escape any regex characters! $regExp = "#<select [^><]*?name=".preg_quote($tagAttributes['name'],'#')."[^>]*?".">(.*?)</select>#si"; preg_match($regExp,$tpl,$matches); $optionTags = $matches[1]; $select = $newSelect = $matches[0]; //get separate option tags and split them up preg_match_all("#(<option [^>]*?>)#si",$optionTags,$matches); $validValues = array(); foreach($matches[1] as $option){ $attr = attr2array($option); //* debug */ print __LINE__.': <pre>'.print_r($attr,true) .'</pre><br />'; $value = substr($attr['value'],1,-1); //strip outer quotes if( trim($value)!='' ) $validValues[] = $value; $newTag = buildTagPlaceholder('option',$attr,$name); $newSelect = str_replace($option,$newTag,$newSelect); //if no postback, retain any checked values if(!$efPostBack && !empty($attr['selected'])) $fields[$name][]=$value; } //replace complete select block $tpl = str_replace($select,$newSelect,$tpl); //add valid values to formats... (extension to $formats) if($formats[$name] && !isset($formats[$name][5])){ $formats[$name][4] = $_lang['ef_failed_default']; //convert commas in values to something else ! $formats[$name][5]= "#LIST " . implode(",",str_replace(',',',',$validValues)); } break; case "textarea": // add support for maxlength attribute for textarea // attribute get's stripped form form // if( isset($tagAttributes['maxlength']) ){ $formats[$name][$_dfnMaxlength] == $tagAttributes['maxlength']; unset($tagAttributes['maxlength']); } $newTag = buildTagPlaceholder($type,$tagAttributes,$name); $regExp = "#<textarea [^>]*?name=" . $tagAttributes["name"] . "[^>]*?" . ">(.*?)</textarea>#si"; preg_match($regExp,$tpl,$matches); //if nothing Posted retain the content between start/end tags $placeholderValue = ($efPostBack)?"[+$name+]":$matches[1]; $tpl = str_replace($matches[0],$newTag.$placeholderValue."</textarea>",$tpl); break; default: //all the rest, ie. "input" $newTag = buildTagPlaceholder($type,$tagAttributes,$name); $fieldType = stripTagQuotes($tagAttributes['type']); //validate on maxlength... if( $fieldType=='text' && isset($tagAttributes['maxlength']) && $tagAttributes['maxlength'] ){ $formats[$name][$_dfnMaxlength] == $tagAttributes['maxlength']; } if($formats[$name] && !$formats[$name][2]) $formats[$name][2]=($fieldType=='text')?"string":$fieldType; //populate automatic validation values for hidden, checkbox and radio fields if($fieldType=='hidden'){ if(!$isDebug) $formats[$name][1] = "[undefined]"; //do not want to disclose hidden field names if(!isset($formats[$name][4])) $formats[$name][4]= $_lang['ef_tamper_attempt']; if(!isset($formats[$name][5])) $formats[$name][5]= "#VALUE ". stripTagQuotes($tagAttributes['value']); }elseif($fieldType=='checkbox' || $fieldType=='radio'){ $formats[$name][4]= $_lang['ef_failed_default']; $formats[$name][5] .= isset($formats[$name][5])?",":"#LIST "; //convert embedded comma's in values! $formats[$name][5] .= str_replace(',',',',stripTagQuotes($tagAttributes['value'])); //store the id as well //if no postback, retain any checked values if(!$efPostBack && !empty($tagAttributes['checked'])) $fields[$name][]=stripTagQuotes($tagAttributes['value']); // $formats[$name][6] .= ( isset($formats[$name][6])?",":"").stripTagQuotes($tagAttributes['id']); }elseif(empty($fields[$name])){ //plain old text input field //retain default value set in form template if not already set in code $fields[$name] = (isset($tagAttributes['value'])) ? stripTagQuotes($tagAttributes['value']) : ''; } $tpl = str_replace($fieldTags[$i],$newTag,$tpl); break; } } if($isDebug>2) $debugText .= "<strong>Parsed template</strong><p style=\"border:1px solid black;padding:2px;\">" . str_replace("\n",'<br />',str_replace('+','+',htmlspecialchars($tpl)))."</p><hr>"; return $tpl; } function stripTagQuotes($value){ return substr($value,1,-1); } function buildTagPlaceholder($tag,$attributes,$name){ $type = (isset($attributes["type"])) ? stripTagQuotes($attributes["type"]) : ''; $quotedValue = (isset($attributes['value'])) ? $attributes['value'] : ''; $val = stripTagQuotes($quotedValue); $t = ''; foreach ($attributes as $k => $v) $t .= ($k!='value' && $k!='checked' && $k!='selected')?" $k=$v":""; switch($tag){ case "select": return "<$tag$t>"; //only the start tag mind you case "option": return "<$tag$t value=".$quotedValue." [+$name:$val+]>"; case "input": switch($type){ case 'radio': case 'checkbox': return "<input$t value=".$quotedValue." [+$name:$val+] />"; case 'text': if($name=='vericode') return "<input$t value=\"\" />"; //else pass on to next case 'password': return "<input$t value=\"[+$name+]\" />"; default: //leave as is - no placeholder return "<input$t value=".$quotedValue." />"; } case "file": //no placeholder! case "textarea": //placeholder needs to be added in calling code return "<$tag$t>"; case "label": return "<$tag$t>"; default: return "<input$t value=\"[+$name+]\" />"; } // switch return ""; //if we've arrived here we're in trouble } function attr2array($tag){ $expr = "#([a-z0-9_]*?)=(([\"'])[^\\3]*?\\3)#si"; preg_match_all($expr,$tag,$matches); foreach($matches[1] as $i => $key) $rt[$key]= $matches[2][$i]; if(isset($rt)) return $rt; } function validateField($value,$fld,&$vMsg,$isDebug=false){ global $modx,$_lang, $debugText,$fields; $output = true; $desc = $fld[1]; $fldMsg = trim($fld[4]); if(empty($fld[5])) return $output; //if no rule value validates list($cmd,$param) = explode(" ",trim($fld[5]),2); $cmd = strtoupper(trim($cmd)); if (substr($cmd,0,1)!='#'){ $vMsg[count($vMsg)] = "$desc »" . $_lang['ef_error_validation_rule']; return false; } $valList = (is_array($value))?$value:array($value); //init vars $errMsg=''; unset($vlist); for($i=0;$i<count($valList);$i++){ $value = $valList[$i]; //re-using $value, destroying original!! switch ($cmd) { //really only used internally for hidden fields case "#VALUE": if($value!=$param) $errMsg = $_lang['ef_failed_default']; break; case "#RANGE": if(!isset($vlist)) $vlist = explode(',',strtolower(trim($param))); //cached //the crude way first - will have to refine this foreach($vlist as $p){ if( strpos($p,'~')>0) $range = explode('~',$p); else $range = array($p,$p); //yes,.. I know - cheating :) if($isDebug && (!is_numeric($range[0]) || !is_numeric($range[1])) ) $modx->messageQuit('Error in validating form field!', '',$false,E_USER_WARNING,__FILE__,'','#RANGE rule contains non-numeric values: '.$fld[5],__LINE__); sort($range); if( $value>=$range[0] && $value<=$range[1] ) break 2; //valid } $errMsg = $_lang['ef_failed_range']; break; case "#LIST": // List of comma separated values (not case sensitive) //added in 1.4 - file upload filetype test //FS#960 - removed trimming of $param - values with leading or trailing spaces would always fail validation if($fld[2]=='file')$value = substr($value,strrpos($value,'.')+1); //file extension if(!isset($vlist)){ $vlist = explode(',',strtolower($param)); //cached foreach($vlist as $k =>$v ) $vlist[$k]=str_replace(',',',',$v); } //changes to make sure embedded commma's in values are recognized if( $isDebug && count($vlist)==1 && empty($vlist[0]) ){ //if debugging bail out big time $modx->messageQuit('Error in validating form field!', '',$false,E_USER_WARNING,__FILE__,'','#LIST rule declared but no list values supplied: '.$fld[5],__LINE__); }elseif(!in_array(strtolower($value),$vlist)) $errMsg = ($fld[2]=='file')? $_lang["ef_failed_upload"]: $_lang['ef_failed_list']; break; case "#SELECT": //validates against a list of values from the cms database #cache all this if( !isset($vlist) ) { $rt = array(); $param = str_replace('{DBASE}',$modx->db->config['dbase'],$param); $param = str_replace('{PREFIX}',$modx->db->config['table_prefix'],$param); //added in 1.4 if( preg_match("/{([^}]*?)\}/",$param,$matches) ){ $tmp = $modx->db->escape(formMerge('[+'.$matches[1].'+]',$fields)); $param = str_replace('{'.$matches[1].'}',$tmp,$param); } $rs = $modx->db->query("SELECT $param;"); //select value from 1st field in records only (not case sensitive) while( $v = $modx->db->getValue($rs) ) $vlist[]=strtolower($v); } if(!is_array($vlist)){ //WARNING! if not debugging (and query fails) error is ignored, and value will validate!! //version 1.4 - replaced fatal error with friendly debug message when debugging $debugText .= ($isDebug)? "'<strong>$fld[1]</strong>' ".$_lang['ef_sql_no_result'].'<br />':''; }elseif(!in_array(strtolower($value),$vlist)){ $errMsg = $_lang['ef_failed_list']; } break; case "#EVAL": // php code should return true or false $tmp = $cmd; //just in case eval destroys cmd if( eval($param)===false ) $errMsg = $_lang['ef_failed_eval']; if($isDebug) $debugText .= "<strong>$fld[1]</strong>: ".$_lang['ef_eval_deprecated']." $param"; $cmd = $tmp; break; //added in 1.4 case "#FUNCTION": $tmp = $cmd; //just in case function destroys cmd if( function_exists($param) ) if( !@$param($value) ) $errMsg = $_lang['ef_failed_eval']; else if($isDebug) $debugText .= "<strong>$fld[1]</strong>: ".$_lang['ef_no_function']." $param"; $cmd = $tmp; break; case "#REGEX": if( !$tmp=preg_match($param,$value) ) $errMsg = $_lang['ef_failed_ereg']; if($isDebug && $tmp===false ) $debugText .= "<strong>$fld[1]</strong>: ".$_lang['ef_regex_error']." $param"; break; case "#FILTER": $valList[$i] = filterEformValue($value,$param); break; default: $errMsg = $_lang['ef_error_validation_rule']; }//end switch if($isDebug) { $debugText .="'<strong>$fld[1]</strong>' "; $debugText .= (empty($errMsg))?'passed':'<span style="color:red;">Failed</span>'; $debugText .= " using rule: $cmd ".$param.', (input='.htmlspecialchars($valList[$i]).")<br />\n"; } if(!empty($errMsg)){ $errMsg = ($fldMsg)?"$desc » $fldMsg":"$desc » $errMsg"; $vMsg[count($vMsg)] = $errMsg; $output=false; break; //quit testing more values } }//end for //make sure we have correct return value in case of #filter $valList = (!is_array($value))?implode('',$valList):$valList; return ($cmd=="#FILTER")?$valList:$output; }//end validateField function filterEformValue($value,$param){ list($cmd,$param) = explode(" ",trim($param),2); $cmd = trim($cmd); switch(strtoupper($cmd)){ case "#REGEX": list($src,$dst) = explode("||",$param,2); $value = ( $v = preg_replace($src,$dst,$value) )?$v:$value; break; case "#LIST": $param = explode("||",$param,2); $src = strpos($param[0],',')?explode(',',$param[0]):$param[0]; $dst = strpos($param[1],',')?explode(',',$param[1]):$param[1]; $value = str_replace($src,$dst,$value); break; case "#EVAL": $value = ($v = @eval($param))?$v:$value; break; } return $value; } function efLoadTemplate($key){ global $modx; $tpl = ''; if(substr($key, 0, 5) == '@FILE') { $path = substr($tpl, 6); $path = trim($path); $path = ltrim($path,'/'); $path = MODX_BASE_PATH . $path; $tpl = file_get_contents($path); } elseif(substr($key, 0, 5) == '@CODE') { $tpl = substr($key, 6); } elseif( is_numeric($key) ) { //get from document id //try unpublished docs first $tpl = ( $doc=$modx->getDocument($key,'content',0) )? $doc['content'] :false; if(!$tpl ) $tpl = ( $doc=$modx->getDocument($key,'content',1) )? $doc['content'] : false; } elseif($key) { $tpl = ( $doc=$modx->getChunk($key) )? $doc : false; //try snippet if chunk is not found if(!$tpl) $tpl = ( $doc=$modx->runSnippet($key) )? $doc : false; } return $tpl; } # registers css and/or javascript to modx class function efRegisterStartupBlock($src_array,$noScript=false){ global $modx; if(!array($src_array)) return; foreach($src_array as $item){ list($src,$type) = $item; //skip scripts if told to do so if($type=='javascript' && $noScript) continue; //try to load from document or chunk if( $tmp = efLoadtemplate($src) ) $src = $tmp; $src=trim($src); if ( $type=='css' ) $modx->regClientCSS($src); elseif ( $type == 'javascript' ) $modx->regClientStartupScript($src); else $modx->regClientStartupHTMLBlock($src); }//end foreach } /** * adapted from http://php.mirrors.ilisys.com.au/manual/en/ref.mail.php * If any field contains newline followed by email-header specific string it is replaced by a harmless substitute * (we hope) If any subsitutiosn are made the function returns true * Currently tests for * Content-Transfer-Encoding: * MIME-Version: * content-type: * to: * cc: * bcc: */ function hasMailHeaders( &$fields ){ $injectionAttempt = false; $re = "/(%0A|%0D|\n+|\r+)(Content-Transfer-Encoding:|MIME-Version:|content-type:|to:|cc:|bcc:)/i"; foreach($fields as $fld => $text){ if( ($match = preg_replace($re,'\\[\2]\\', $text))!=$text ){ $injectionAttempt = true; $fields[$fld]=$match; //replace with 'disabled' version } } return ($injectionAttempt)?true:false; } function get_default_tpl() { $tpl = <<< EOT <p class="error">[+validationmessage+]</p> <form method="post" action="[~[*id*]~]"> <input name="formid" type="hidden" value="eform" /> Name : <input name="name" class="text" type="text" size="30" eform="Your Name::1:" /><br /> Email : <input name="email" class="text" type="text" size="30" eform="Email Address:email:1" /><br /> Message : <textarea name="message" rows="4" cols="20" eform="Message:textarea:1"></textarea><br /> <input type="submit" name="contact" class="button" value="send" /> </form> EOT; return $tpl; } function get_default_report() { $tpl = <<< EOT Name : [+name+] Email : [+email+] Message : [+message+] EOT; return $tpl; }