1. Advertising
    y u no do it?

    Advertising (learn more)

    Advertise virtually anything here, with CPM banner ads, CPM email ads and CPC contextual links. You can target relevant areas of the site and show ads based on geographical location of the user if you wish.

    Starts at just $1 per CPM or $0.10 per CPC.

Problem in PHP form

Discussion in 'Programming' started by Kaleem Ullah Hashmi, Jul 15, 2014.

  1. #1
    Hi,

    I have develop a quote form in php and after submission it will e-mail me all the inforamtion correctly but i have problem not getting the information from drop down list and also have browse field so user can upload this file if he wants.

    PHP Code:

    <?php
    
    if(!$_POST) exit;
    
    $email = $_POST['email'];
    
    
    //$error[] = preg_match('/\b[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b/i', $_POST['email']) ? '' : 'INVALID EMAIL ADDRESS';
    if(!eregi("^[a-z0-9]+([_\\.-][a-z0-9]+)*" ."@"."([a-z0-9]+([\.-][a-z0-9]+)*)+"."\\.[a-z]{2,}"."$",$email )){
        $error.="Invalid email address entered";
        $errors=1;
    }
    if($errors==1) echo $error;
    else{
        $values = array ('name','email','company','subject','message');
        $required = array('name','email','company','subject','message');
        
        $your_email = "kuhashmi@gmail.com";
        $email_subject = "New Message: ".$_POST['subject'];
        $email_content = "new message from contact page:\n";
       
        foreach($values as $key => $value){
          if(in_array($value,$required)){
            if ($key != 'subject' && $key != 'company') {
              if( empty($_POST[$value]) ) { echo 'PLEASE FILL IN REQUIRED FIELDS'; exit; }
            }
            $email_content .= $value.': '.$_POST[$value]."\n";
          }
        }
        
        if(@mail($your_email,$email_subject,$email_content)) {
    
    echo "Thanks for your enquiry we will contact you shortly have a nice day. Please check your spam folder also" ;
    
    echo "<script type=\"text/javascript\">";
    echo "setTimeout(function(){window.location=\"http://www.moghulweb.com/contact-us.html\"},2000);";
    echo "</script>";
    }
    }
    ?>
    PHP:
    HTML CODE
    <form action="Quote.php" method="post" class="wpcf7-form" novalidate>                               
                                    <p>Your Name (required)<br />
                                        <input id="name" name="name" class="text" /></p>
                                    <p>Company Name (required)<br />
                                        <input id="email" name="email" class="text" /> </p>
                                        <p>E-mail Address<br />
                                        <input id="company" name="company" class="text" /> </p>
                                    <p>Budget<br />
                                        <input id="subject" name="subject" class="text" /> </p>
                                        <p>Nature of Business<br />
                                        <input id="subject" name="subject" class="text" /> </p>
                                        <p>Type of Website
                                            <select name="type">
                                                  <option>PSD to HTML</option>
                                                  <option>PSD to WP</option>
                                                  <option>PSD to PHP</option>
                                            </select></p>
                                        <p>Number of Pages<br />
                                        <input id="subject" name="subject" class="text" /> </p>
                                        <p>Upload File -(jpg, PDF, DOC, gif)
                                        <label for="file"></label>
    <input type="file" name="file" id="file" /></p>
                                    <p>Company Description<br />
                                        <textarea id="message" name="message"  cols="40" rows="10"></textarea> </p>
                                    <p><input type="submit" value="SUBMIT" /></p>
                                    <div ></div></form>
    HTML:
    can't get value from Select and Browse field.

    Thanks
     
    Kaleem Ullah Hashmi, Jul 15, 2014 IP
  2. Austiner316

    Austiner316 Well-Known Member

    Messages:
    136
    Likes Received:
    0
    Best Answers:
    0
    Trophy Points:
    136
    Digital Goods:
    1
    #2
    To get the value of your select there must be a value inside the option

    <option value="1">PSD TO HTML</option>
     
    Austiner316, Jul 15, 2014 IP
  3. PoPSiCLe

    PoPSiCLe Illustrious Member

    Messages:
    4,623
    Likes Received:
    725
    Best Answers:
    152
    Trophy Points:
    470
    #3
    You're not doing anything with the uploaded file, so it's no wonder you're not getting anything from it. You need to process the file, and put it in as an attachment in the email, if you want it to be sent to you.
    Besides, you should look into the PHP-function for filtering variables. You can use this simple function:
    
    function filtervariable($string,$type,$method) {
    //function for sanitizing variables using PHPs built-in filter methods
      if ($method == 'sanitize') {
      $filtermethod = 'FILTER_SANITIZE_';
      } elseif ($method == 'validate') {
      $filtermethod = 'FILTER_VALIDATE_';
      } else {
      return;
      }
      switch ($type) {
      case 'email':
      case 'string':
      case 'number_int':
      case 'int':  
      case 'special_chars':
      case 'url':
      $filtertype = $filtermethod.strtoupper($type);
      break;
      }
      return filter_var($string, constant($filtertype));
    }
    
    PHP:
    This simple function will take the input, and depending on what you chose, sanitize or validate the input. You use it as follows:
    
    filtervariable($_POST['email'],'email','validate');
    
    PHP:
     
    PoPSiCLe, Jul 15, 2014 IP
  4. deathshadow

    deathshadow Acclaimed Member

    Messages:
    9,732
    Likes Received:
    1,998
    Best Answers:
    253
    Trophy Points:
    515
    #4
    @PoPSiCLe -- that function seems like an unnecessary mess just to "avoid" looking up the correct super-constant. I mean is that REALLY worth doing instead of just:

    filter_var($_POST['email'], FILTER_VALIDATE_EMAIL);

    Without the 'function for nothing'?

    When checking for valid e-mail addresses another user on another (now defunct) forums and I came up with this:
    function isValidEmail($address) {
    	if (filter_var($address,FILTER_VALIDATE_EMAIL)==FALSE) {
    		return false;
    	}
    	/* explode out local and domain */
    	list($local,$domain)=explode('@',$address);
    	
    	$localLength=strlen($local);
    	$domainLength=strlen($domain);
    	
    	return (
    		/* check for proper lengths */
    		($localLength>0 && $localLength<65) &&
    		($domainLength>3 && $domainLength<256) &&
    		(
    			checkdnsrr($domain,'MX') ||
    			checkdnsrr($domain,'A')
    		)
    	);
    }
    Code (markup):
    filter_var only checks for characters, not lengths, so we also want to check that the local name (the part before the @) is between 1 and 64 characters long, and the domain is between 3 and 255 characters in length. It also uses a DNS check to make sure said domain has a MX or at the very least an A record. You'd be shocked how many bots filling out forms don't even use real domain names, so that's a good and easy filter, though the name check can take a bit depending on your server config.

    No, you DON'T! If there is no value the content INSIDE the OPTION is returned as it's value.

    <select name="test">
      <option>Testing 1</option>
      <option>Testing 2</option>
    </select>
    Code (markup):
    If you choose "Testing 1" in a POST form, $_POST['test'] == 'Testing 1' -- the only reason to use VALUE is if the value is DIFFERENT from the content of the OPTION.

    Though it would probably help if the OP actually had properly built labels and wasn't abusing paragraphs on non-paragraph elements, actually had a FIELDSET, wasn't using multiple echo to do the job of just one echo, was as PoPsiCle suggested using filtervariable to check the e-mail, wasn't accessing numeric indexes on an array for no good reason, wasn't wasting time checking a value that ALWAYS exists as a exit condition, actually had different names on ALL the fields, actually had a name on the SELECT (which is the "well there's your problem"), etc, etc...

    Big tip there, even if you didn't come from a POST or GET method form, $_POST, $_GET and $_REQUIRED always exist, so even the first line of code serves no legitimate purpose.

    When making a form I usually like to have a single array that is used to both build the form, AND iterated through for validation. Likewise I'd have a single user-callable PHP file for showing both the form and processing the form, so you can re-send it back to the user if it fails validation. This file would then hand-off to the appropriate sub-files, so we're not loading code we're not going to use -- like having the form present on success, or the mail send present on failure.

    Gimme a while, and I'll gut down my codebase for doing this down to something digestible. I'll have to whip up a file handler though, but that's not a 'big' deal.
     
    Last edited: Jul 16, 2014
    deathshadow, Jul 16, 2014 IP
  5. deathshadow

    deathshadow Acclaimed Member

    Messages:
    9,732
    Likes Received:
    1,998
    Best Answers:
    253
    Trophy Points:
    515
    #5
    Alright, first up I have to thank you -- I've been meaning to rewrite my own form handler, and you gave me the extra little nudge to do so. What follows isn't complete yet (still needs radio handlers and a client-side scripting val), but it's enough for your purposes and for you and others to learn from.

    BUT, this is probably going to take a LONG time in a lot of separate posts to explain, so hang on.... for the TLDR crowd, Golf Tango Foxtrot Oscar now, you're about to get glazed.

    I've uploaded a working demo of what I'm about to explain here:
    http://www.cutcodedown.com/for_others/KaleemUllahHashmi/

    There's a .rar of the whole thing, and the .source files are there to let you read along online. I build most of my PHP to only have a single user-callable file they can access, for the purposes of this demo that would be:

    http://www.cutcodedown.com/for_others/KaleemUllahHashmi/getQuote.php

    The includes found in the subdirectories will either not do anything, or throw an error. In a production environment I'd put in a .htaccess in the subdirectories to trap attempts to access .php files from http and redirect to getQuote.php or the appropriate index.php

    Currently it's set to show you what it WOULD send as a e-mail instead of actually sending one.

    Let's talk directory structure:

    / -- user callable PHP files
    /data -- files that contain data but no processing
    /modules -- files that provide logic/functionality
    /template -- files that create output and formatting

    Now, about that array I mentioned in the previous post -- It is the first thing I'd build. I put all the fields and data and so forth about the form into an array -- this array can be used to build the form AND to validate it. Follow along here:

    http://www.cutcodedown.com/for_others/KaleemUllahHashmi/data/quoteFormFields.source

    the $quoteForm array at the top level has general information about the form -- First is the ID we want on it in the markup, which I also use as a prefix to the ID's of each of the INPUT/TEXTAREA/SELECT inside it. I do this so as to reduce the odds of namespace conflicts. The 'action' and 'method' elements are pretty self explanatory too. The heading is usually made by the template into a H2, 'submit' is the text to go as the submit's value, and the 'mailTo' field is who the mail should be sent to.

    'mailTo' when set to 'TESTING' instead of an actual e-mail address will output to the browser the variables it would have fed to the mail() function instead of actually mailing.

    Finally we have the 'fields' array which -- as the name would imply -- contains our various form fields. The index of each array is used as the NAME attribute and to build the ID. 'Label' is just the label for the form element.

    'type' can be any input TYPE, or 'textarea', 'select', or 'fieldset'. I figure it's easier to just say what it is than have two separate fields for TYPE and tagName. If you set type 'fieldset' you can nest another fieldset inside this one, using the 'fields' attribute. (which goes outside the scope of this example)

    The 'validations' array just contains which validations to perform. In this setup there are the following validations:

    name : type (field type)

    required : boolean (all types) -- user must enter a value

    email : boolean (non-file) - must be a valid e-mail

    compare : string (non-file) - name of another field that this one must match, handy for checking passwords or e-mails are typed correctly.

    allowedExtensions : array of strings (file) - extensions allowed on a file upload

    maxSize : integer (file) -- maximum file upload size

    integer : boolean (non-file) -- value must be an integer

    numeric : boolean (non-file) -- value must be a number

    numMax : number (non-file) -- value must be <= numMax

    numMin : number (non-file) -- value must be >= numMin

    lengthMax : number (non-file) -- value must be <= lengthMax in characters

    lengthMin : number (non-file) -- value must be >= lengthMax in characters

    We only use the first half of that list, I'm just documenting for completeness here.

    For example, many of the fields are set to 'required' => true. If you go through the code and find "compareMail", you can see it uses the 'compare' => 'email' to say that 'email' and 'compareMail' must contain the same value.

    If you look at 'siteType' -- the only select on the page, you'll see two more useful options. 'options' is just an array of possible OPTION. 'useIndexes' says whether or not the indexes to 'options' should be used as the VALUE attribute on the page. The way it's set up right now it will return the text as the value by omitting the VALUE attribute. But if say you wanted it to return something like:

    <option value="HTML">PSD to HTML</option>
    <option value="WP">PSD to WP</option>
    <option value="PHP">PSD to PHP</option>
    Code (markup):
    You'd change that section to read:

    			'useIndexes' => true,
    			'options' => [
    				'HTML' => 'PSD to HTML',
    				'WP' => 'PSD to WP',
    				'PHP' => 'PSD to PHP'
    			],
    Code (markup):
    Pretty simple. and excuse my snarky comment about what a giant nonsensical scam PSD "design" is...

    Some other handy fields can be seen under 'file' -- 'extraText' is just a paragraph to be shown underneath the Input. Useful in this case for showing acceptable file formats -- really this is what HTML 5's "placeholder" should be, instead of it's false simplicity BS.

    Really the structure isn't that complex... and it makes it easier to edit the form AND it's validations from one handy location.

    Now, before we get too deep into functionality, I plugged this into my standard testing template. I usually have an index.template.php -- source here:

    http://www.cutcodedown.com/for_others/KaleemUllahHashmi/template/index.template.source

    in most of my testing scenarios much akin to how my production systems handle it. I set up a couple of handy define to make the code easily able to transform from XHTML to HTML as needed, then make two standard functions -- template_header and template_footer.

    template_header takes the optional parameters of pageTitle, which is used to build the TITLE tag, and $keywords / $description to build the appropriate META... template_footer just outputs everything that would go after the page content.

    I find it really handy to have a standard testing template like this even at the early development stage as it removes a lot of the 'grunt work' -- just include it, call the functions where needed, done.

    Next, let's cover how the array is turned into a form.

    http://www.cutcodedown.com/for_others/KaleemUllahHashmi/template/form.template.source

    form.template.php is a generic form builder -- so you can re-use this array structure and the code to turn it into markup across multiple different pages. This is also why I made it have it's own class.

    I wrote this to be HTML/XHTML aware so it uses both the HTML_XML and HTML_CLOSURE defines setup by the template to make sure we use the proper format for things like 'selected="selected"' vs. just 'selected', as well as ' />' vs. '>'. Basically, DEFINE rocks.

    template_form starts out with some private static values. Things are static as I made this a singleton -- JUST so I can restrict scope; the only legitimate reason to use the singleton model in PHP.

    template_form::process gets passed the form array, so you'd call it in the case of this form as:

    template_form::process($quoteForm);

    It then sets up $selected to the appropriate version for XML or HTML, resets the count of how many required fields there are, and makes a private copy of $formData for use by the sub-methods. (so we aren't passing it on the stack)

    Next we output the opening of the FORM tag. I set multipart and utf-8 manually just to be damned sure it does what we want... then we dump out the heading.

    We hand off to the fieldset processor passing it the topmost 'fields' -- we'll cover that in more depth in a moment.

    Finally we output the submits and hiddens. I put these in a DIV as they really aren't user-interaction fieldsets. The required message is only displayed if we've had required fields.

    The big thing here is the 'fromFrom' hidden INPUT. I store in the $_SESSION and in the form a simple SHA256 hash. This simple security step laughably (seriosuly, it's pathetic) makes about two-thirds the spam-bots out there fall flat on their faces because to save time they don't actually request a new form, they just try re-sending the same one over and over again. By checking that these values match between the session and the submit, we not only kick script-kiddies in the junk it also prevents multiple submits of the same form from accidentally re-sending; a risk when people hit 'back' or refresh the page.

    ... and it is pathetic it works since from a spam point of view this 'check' is a joke. But then, so are most spammers.

    template_form::fieldset accepts the $fields array, and optionally attributes to be shown on the fieldset, and a legend. This again is for the nested fieldset capabilites which we're not using in this example.

    We echo out the fieldset and any attributes, then the legend if present. I always put a SPAN inside LEGEND as the appearance of LEGEND cross-browser is a pain in the ass and doesn't style consistently at ALL. We want LEGEND for semantics particularly on things like fieldsets wrapping a radio button group, but it's a PITA to style. SPAN we know styles consistently, so adding it gets us best of both worlds.

    We iterate throught the fields, starting out by emptying the $classes array and then adding values to it as needed. IF there are errors, let's give it an 'error' class. If it's required, lets give it a 'required' class; and increment the required counter since we're here and store that yet, this element is required for later on.

    A more complete version would probably add classes for every validation, they could then be used as hooks by scripting to do client-side validation if desired.

    The ID gets created from the form ID, an underscore and the fieldname, which then gets plugged into $attributes for use on whichever tags we're using.

    Then $classes gets imploded to put the appropriate spaces between our values.

    If our type is a fieldset, we recursively call ::fieldset with this element's 'fields' -- again, nested fieldsets is outside the scope of this demo.

    Otherwise, we output it's label using a bold asterisk to indicate the field is required, then run SWITCH to figure out how to handle the 'type' of field.

    For a select, we output the tag and it's attributes, then foreach our way through the options to output them. If the value in $_POST matches the index (value) we make it selected. We then optionally include value based on 'useIndexes' and finally output the rest of the OPTION.

    Oops, that code needs some tweaking as it should also check against $content if 'useindexes' is false! Side note, that's another reason I write up a breakdown of all my code even if I'm the only one going to read it -- sometimes you'll notice things you missed!

    With textareas it's a simple matter of dumping out the tag, it's attributes, and if the $_POST value is already set a htmlspecialchars of that.

    Same for input, except the specialchars of $_POST is plugged into value instead of as content. Again, the HTML_CLOSURE defined in the template makes sure we use the correct ending form. Pretty much any value other than textarea or select is made an input, which means you can use the HTML 5 'types' if desired with this.

    Outputting the $_POST value into the form elements is important in that it means if we re-send the form due to a validation failure, everything (except file, which doesn't send the actual value server-side) the user already typed in isn't lost, and is just sent back for them to edit.

    If there's 'extraText' for this field, show it...

    If there's errors, show an errorMessage paragraph, if not show a line-break.

    Close out the fieldset, and poof, there's the form!

    Alright, before we get into validation and sending the mail (I'm going to make those separate posts), let's dig into making this user-callable.

    If you look at getQuote.php's source:
    http://www.cutcodedown.com/for_others/KaleemUllahHashmi/getQuote.source

    You can see the first thing I do is start sessions and regenerate the session ID. I use sessions a LOT... no, a LOT... so the first thing I do in any code that's going to be called directly by the user is start a session. Regenerating the ID at the same time means every page load will get a different ID -- this greatly reduces the odds of what's called a "man in the middle" attack which can be used to hijack a session. It's not a foolproof guarantee against it, but it makes the window in which it can be done narrower.

    I then include the template and our form fields since regardless of how getQuote.php is called, we're going to want that data.

    I've broken the functionality up into a number of separate files so that code that isn't being used isn't loaded. This can really speed up processing particularly if the file isn't in your byte-code cache.

    First we check if we came from the form. If we did, we check if that value matches what's in our session, storing that result. I then immediately destroy that session value so it cannot be re-used again. (preventing that resubmit problem I mentioned above). We then include quoteFormValidate.php if they matched, quoteFormInvalid if they didn't.

    Finally, we include the quoteForm.php if that $_POST value isn't set, since that means we didn't come from a form...

    Let's cover that first. if you look at the source for quoteForm.php:

    http://www.cutcodedown.com/for_others/KaleemUllahHashmi/modules/quoteForm.source

    It's pretty simple. We output the template_header with the text "get a quote", include the form.template.php, call template_form::process($quoteForm); then dump out the footer. Boom, nice formatted form.

    The quoteFormInvalid.php

    http://www.cutcodedown.com/for_others/KaleemUllahHashmi/modules/quoteFormInvalid.source

    Is pretty much the same file, but it outputs an errorBox warning that a possible hacking attempt or resubmit of the same page has occurred.

    quoteFormValidate.php on the other hand:

    http://www.cutcodedown.com/for_others/KaleemUllahHashmi/modules/quoteFormValidate.source

    includes the generic formValidate.php, runs the formValidate (we'll cover that in the next post), and then includes either quoteFormErrors or quoteFormSend depending on if it validated.

    quoteFormErrors:

    http://www.cutcodedown.com/for_others/KaleemUllahHashmi/modules/quoteFormErrors.source

    actually looks pretty darned familiar at this point. Just a different errorBox.

    All of this is just simple conditional logic, nothing too fancy... the fancy part, thats in the two remaining files we've not covered: the 'generic' formValidate.php and quoteFormSend.php

    I'll cover formValidate.php in the next post, and quoteFormSend in the one after.

    ... to be continued.
     
    deathshadow, Jul 17, 2014 IP
  6. deathshadow

    deathshadow Acclaimed Member

    Messages:
    9,732
    Likes Received:
    1,998
    Best Answers:
    253
    Trophy Points:
    515
    #6
    Alright, let's talk formValidate.php

    http://www.cutcodedown.com/for_others/KaleemUllahHashmi/modules/formValidate.source

    I make this a proper class as you might run multiple form validations (unlikely, but hey, why not?). Again I use an object to avoid namespace conflicts. We start out with a public count of how many errors the validator has come across, and some private variables so we don't have to constantly pass values on functions.

    The constructor resets the error counter, and makes a copy of 'fields'. I pass $formData by reference since this routine will be making changes to it. YES, I know PHP automatically passes arrays and objects by reference, but I like to SEE IT for clarity sake. (yeah, ok, I learned Smalltalk and Object Pascal two decades before I ever even heard of PHP, sue me!)

    It then hands off the fields to the ::process method. This calls a few later methods like addError and validation, for now just know that they exist, we'll cover how they work later.

    "process" is self-callable again so that you can have nested fieldsets, which as mentioned before we're not using, but the capability is there.

    It simply foreach's it's way through the fields. I pass the fieldData as a reference so again we can make changes to it's values -- like plugging in errors for the formhandler to show.

    I store a copy of the current $_POST value or false so I can more easily check it in the various methods of this object. It's far easier to do "=== false" or "!== false" than it is to sit there doing isset AND checking it's value.

    I then add to 'fieldData' the error Array as empty.

    We then use 'switch' to check it the field type.

    If it's a fieldset, we recursively call ::process

    if it's a file, I first set 'srcFile' to false. We will use than in the send routine to determine if a file was uploaded or not.

    We then check if $_FILES even contains the index we're looking for. If you have a input of type file it will exist EVEN IF no file was uploaded -- so if this isn't defined, we either have a hacking attempt or one strange-assed browser, so reject it with an error.

    We then check if error is an array -- which would indicate multiple file uploads to one field. Generally we don't want to allow that on file fields as there are a few hacks/attack vectors based on that way of uploading.

    Finally, we check the error state with SWITCH.

    UPLOAD_ERR_OK - the file uploaded properly and we move on to check that the filesize is acceptable. IF not, throw an error, if so we continue on. A lot of people use mime-type for the check... Honestly, I HATE MIME TYPES, WHAT THE **** IS WRONG WITH EXTENSIONS?!? So I pull the extension with pathInfo, check if it's in the allowedExtensions... if not, add an error, if so we call 'setFileData' which means it's ok for the 'send' routine to send the file as an attachment or do whatever other processing you want with it.

    UPLOAD_ERR_NO_FILE - no file was uploaded. If this is required field throw an error, otherwise plod on.

    UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE -- the file exceeded the php/server upload limit.

    otherwise it's an unknown response, so throw an error. This is a BAD thing to actually have happen.

    that's it for files, all other field types get the 'standardValidations' -- which I made a function as more complex handling (radio buttons for example) may make use of those while running other checks.

    Finally we implode the error field if there were errors into nicely formatted markup... and that's the validation.

    hmm... if count is zero maybe that should return FALSE?

    ::setFileData() -- takes and adds to 'fieldData' the originating filename, the temporary filename, and the mime-type. Doing it this way means the send method doesn't have to go digging into $_FILES, simplifying life greatly.

    ::validation simple looks up to see if the element has that validation type, returning it's value if present, false if it's not present or contains a false equivalent value.

    ::addError just puts the error on the fieldData's error array and increments the total error counter.

    ::findField is used by the compare function, but it could be useful for other purposes. It just goes through the fields and any child fieldsets looking for a name match.

    ::standardValidations just runs through the 'validations' array, and looks for a method in this object that matches 'v_' . $validation - if present and callable, that routine is called.

    The rest of the object is our various validation methods:

    ::v_required -- tests if the value is empty, throwing an error if so.

    ::v_email -- if empty, it returns. Otherwise it tests if it's a valid e-mail using the method outlined in my previous post, checking it for valid characters, valid lengths, and a valid domain. If it's valid, it returns -- otherwise we addError.

    ::v_compare -- compares this element's value to the one it's supposed to compare to, if they don't match throw an error including the label for the field it's supposed to match, using the ::findField method to track down said element.

    ::v_integer -- if this field has a value, we check that it's integer. If not, we throw an error.

    ::v_numeric -- same but just checking if it's a number.

    ::v_numMax,
    ::v_numMin,
    ::v_lengthMax,
    ::v_lengthMin -- by this point it should be pretty obvious what's going on with these.

    ... and that's the validation function, which can be reused across all sorts of different forms.

    Next up, the mail sending function. To be continued...
     
    deathshadow, Jul 17, 2014 IP
  7. deathshadow

    deathshadow Acclaimed Member

    Messages:
    9,732
    Likes Received:
    1,998
    Best Answers:
    253
    Trophy Points:
    515
    #7
    Finally, let's review the source for quoteFormSend.php:

    http://www.cutcodedown.com/for_others/KaleemUllahHashmi/modules/quoteFormSend.source

    I start out by defining some values we're going to re-use a lot. To send an attachment as part of a mail we have to make it a multi-part message. To that end we want a reasonably unique hash so people can't send a matching value in a field to send their own headers or other overrides. Normally both uniqId and md5 aren't considered very secure, but for a e-mail section divider even this is overkill... also 'more secure' values are typically too long to use since a divider has to fit inside the "quoted printable" limitation of 76 characters set forth in the e-mail specification. ... and I tell you, that's bitten me in the ass more than once -- then you have new kid jacktards who say my forced formatting of code to a 76 character limit is "an arbitrary and pointless garbage" -- *** 'em!)

    MHASH is just the semi-random hash, BMIX is the divider between our multi-part sections, BALT is the divider between our alternat content sections. Alternate content allows us to send both a plaintext and a HTML version of this mail... something important as HTML is cute if you want it formatted, but a lot of mail SERVERS will reject HTML mails as spam if there's no accompanying plaintext, and other mail clients just flat out refuse to show HTML e-mails.

    MMIX and MALT are just the actual dividers with their trailing line-feeds to make our output a bit easier to deal with and to make sure we don't forget any carriage returns. In e-mail output a single CR/LF pair before a content area is always the start of a new item/value pair, while a CR/LF/CR/LF means end of a major section, typically used right before the actual content of that block. You do NOT want to screw this up as it will mean your mail might be flagged as spam for being malformed, and that's why I use define for this!

    The headerClean() function just strips carriage returns and linefeeds out of the values -- we need this since again, CR/LF has meaning -- allowing those through in header values would make it VERY easy for someone to hijack your e-mail system!

    $mailName is just who it's from. Since your form has company or contact name, I decided to make it smart enough to use the company name if present, the contact name as a fallback. Again, it gets cleaned of CRLF.

    $mailMail is just a cleaned copy of the e-mail. We can pull values straight from post since our validation has already checked them. This does not run the risk of a direct call hack as we also call $quoteForm, so unless called by another PHP file declaring that, this will bomb out with an error before it even TRIES to 'send'.

    $mailSubject just gets that this is a quote request from so-and-so.

    $mailFrom is the same, but includes the e-mail address.

    $mailHeaders is then built normally. The from gets $mailFrom, while 'reply-to' gets just the cleaned e-mail address. We want to set MIME-Version since we're using all sorts of content-type's here, then we set our multipart/mixed heading using our BMIX value. Finally I like to include the PHP mailer version -- I find it makes hotmail and gmail less likely to pitch mails in the spam folder.

    We empty out $mailFields and then go through just auto-populating it off the form, filtering out 'file' types. This gives us a list of fields the user filled out on the form to make generating the plaintext and html parts of our mail easier. I also filter out 'compare' values since they are identical to other fields.

    Finally, we can build our message. We start out with MMIX to say it's the start of a mixed section, then we start a new content-type of multipart/alternative.

    multipart/alternative is simply multiple versions in different formats of the same content. We tell it that BALT is our divider, then we do a MALT to start the first of our alternative sections.

    The plaintext version then just gets text/plain and utf-8, and a safe transfer-encoding type since some systems aren't smart enough to realize that sendmail/postfix/whatever usually sends stuff encoded that way.

    We can then just iterate through all the fields and output it as some nice formatted plaintext with linefeeds and tabs. If it's set, if it's not empty, show it's title, crlf, tab, show it's content, crlf, crlf.

    Then the HTML version. The comment there says it all -- you want it to work reliably across mail clients, Stick to HTML 3.2 -- don't even try the browser specific crap added in 4 tranny, it doesn't work reliably. So, find Mr. Peabody and his boy Sherman, and get ready to write HTML `90's style.

    As before we send a MALT followed by content-type (text/html in this case) and the transfer-encoding. CRLF twice, and you can start the markup.

    I went pretty simple on the HTML, using $mailFrom as the TITLE and H1 before dumping out titles and values in a definition list. Using a DL is questionable semantics, but it works rather nicely here. A table may in fact be more semantically correct, but really if you're using HTML e-mails you're stuck in the worst of 1990's practices, so you might as well just run with it.

    After the HTML mail we do a double CRLF again followed by a MALT. e-mail boundaries MUST appear not only between sections, but also before the first and after the last. You omit that, and it may work, it may not depending entirely on the viewers mail-client.

    Now, we grab hold of the file field. I put it into $file just to make the code clearer. If it's srcFile value is non-false we must actually have a file to send, so we add another CRLF (making two total) before starting a MMIX -- the multipart boundary.

    It then gets a content-type matching the file's mime-type, and sending the original filename along with it. We then set our transfer-encoding to base64 (the only way really to send files as attachments), and make sure it is treated as an attachment with Content-Disposition. As before we double CRLF to indicate the start of the content for this section, then output a chunk_split (breaks into multiple lines) base64_encode version of the file.

    I pull the file contents directly from it's temporary location instead of moving it first. It is usually far, FAR safer from a security standpoint to work with it from that spot on the drive -- where HTTP likely has zero access -- and allowing it to be auto-deleted when PHP execution ends, than it is to try and do anything fancier with it.

    After that we toss in another CRLF and declare a MMIX boundary, which is also the end of our message.

    I then have the 'TESTING' string set up so we can output the resulting mail as a webpage instead of sending it. I ALWAYS output inside <pre> my subject, header and message body so I can visually verify that it is indeed well formed, before I start passing it to mail(). Once you know what to look for, doing this type of testing can save you a lot of headaches of going "why isn't this mail sending" or "why is this mail being rejected" or even "why is this mail going to my spam box"

    ... and in this case, it proved handy to show that yes, the functionality works -- without sending an actual mail.

    If it's not "testing" we assume it's a valid e-mail, so we call mail(); If mail fails, we send a mail system failure message.

    In each of these cases we call template_header to pass an appropriate title, put everything into a 'mailresult' div for formatting purposes, and send a H2 saying what's going on.

    After that we just close the DIV, call template_footer, and we're done.

    .... and that is a complete mailing system. Probably a bit more complex than you were thinking, but really it's a complex topic where things should be as simple as they need to be, and no more so. There's a lot of security and other issues involved, and making your code re-usable pays a lot of benefits long-term.

    So, read through, any questions fire away. Also, if anyone sees any obvious security flubs on my part, I'd love to hear it.
     
    Last edited: Jul 17, 2014
    deathshadow, Jul 17, 2014 IP