Adding new fields to the Prestashop contact form

In this tutorial, we will see how to enhance the default Prestashop contact form by adding new fields, so that it can accommodate all our needs (present and future).

Introduction

Adding new fields to the Prestashop contact form is by far a lot harder than simply throwing new inputs in the form itself. With that only, these fields would never be actually added to the thread in the database. In order to do this properly, we need to edit:

  • The contact-form.tpl template file, located in the theme folder
  • The database, to add the new field
  • The CustomerThread class (we will use an ovveride for it)
  • ContactController.php, to retrieve new data from the form
  • Message.tpl template file for the back office, to ensure new data is shown to employees as well!

 

Step 1 – The contact form

Let’s get started by editing the actual contact form. Reach your theme’s folder and open up contact-form.tpl. There is no mandatory place to add the new field to (as long as it is inside the form); I will do it right above the message textarea. As always, I am using the default theme.

Locate the following code (might be different if you are using a custom theme):

<p class="textarea">
	<label for="message">{l s='Message'}</label>
	<textarea id="message" name="message" rows="15" cols="10">{if isset($message)}{$message|escape:'htmlall':'UTF-8'|stripslashes}{/if}</textarea>
</p>

Right before this, add:

<p class="text">
	<label for="extrafield">{l s='Extra field'}</label>
	{if isset($customerThread.extrafield)}
		<input type="text" id="extrafield" name="extrafield" value="{$customerThread.extrafield|escape:'htmlall':'UTF-8'}" readonly="readonly" />
	{else}
		<input type="text" id="extrafield" name="extrafield" value="" />
	{/if}
</p>		

And our new field is there. Of course, it can be anything, form a custom select box, to a radio button, textarea, etc. I chose a text input.

The field is there, but our database doesn’t know. Let’s tell it! Login to the database using your favorite tool (my choice is always phpMyAdmin).Locate the ps_customer_thread table (as always, your prefix might be other than ps_).

Add a new TEXT column with no limitations. Again, if you chose something other than a text input, like a radio or checkbox, you might want to use TINYINT instead, to add only 1 or 0.

HEY! Why aren’t we doing this for the ps_customer_message?

Because I chose to add one single value for the whole thread. In this tutorial I will cover how to add fields to the thread, not single messages. But no fear, the process is quite similar, requiring you to edit the CustomerMessage class instead, and relative table.

Step 2 – Class and Controller

Our new field is there, ready to be filled in, and so is the database column. But the CustomerThread object doesn’t know about the new field, so, we need to setup a class override to account for the new field.

Go to override/classes/ and create a new file called CustomerThread.php. Open it up and fill it with the following code:



<?php

class CustomerThread extends CustomerThreadCore
{
    public $extrafield;

	public static $definition = array(
		'table' => 'customer_thread',
		'primary' => 'id_customer_thread',
		'fields' => array(
			'id_lang' => 	array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
			'id_contact' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
			'id_shop' => 	array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
			'id_customer' =>array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
			'id_order' => 	array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
			'id_product' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
			'email' => 		array('type' => self::TYPE_STRING, 'validate' => 'isEmail', 'size' => 254),
			'token' => 		array('type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true),
			'status' => 	array('type' => self::TYPE_STRING),
			'date_add' => 	array('type' => self::TYPE_DATE, 'validate' => 'isDate'),
			'date_upd' => 	array('type' => self::TYPE_DATE, 'validate' => 'isDate'),
			'extrafield' => 	array('type' => self::TYPE_STRING, 'validate' => 'isGenericName'),
		),
	);


}

TIP: If you are new to overrides, you might want to have a look at my other tutorial on how to properly extend Prestashop objects.

Explanation: What we did is a simple redefinition of the fields this objects accept. First, we declared our new extrafield as a public variable, and then added it to the definitions list. We set it as a string type, and a simple generic validation. Be sure you check out the correct type and validation every time you add a new field, or you might end up with unexpected results!

At this point, we also need to amend the controller that is responsible of adding such data and sending out emails. We can do the same, and use an override for this as well, but this time we need to copy and paste a whole method from the original one.

 

Go to override/controllers/front/ and create a new file named ContactController.php, the open up the original file with the same name, located in controllers/front. copy the whole _postProcess method and paste it inside the newly created file, so it looks like this:



<?php

class ContactController extends ContactControllerCore
{
	public function postProcess()
	{
		if (Tools::isSubmit('submitMessage'))
		{
			$fileAttachment = null;
			if (isset($_FILES['fileUpload']['name']) && !empty($_FILES['fileUpload']['name']) && !empty($_FILES['fileUpload']['tmp_name']))
			{
				$extension = array('.txt', '.rtf', '.doc', '.docx', '.pdf', '.zip', '.png', '.jpeg', '.gif', '.jpg');
				$filename = uniqid().substr($_FILES['fileUpload']['name'], -5);
				$fileAttachment['content'] = file_get_contents($_FILES['fileUpload']['tmp_name']);
				$fileAttachment['name'] = $_FILES['fileUpload']['name'];
				$fileAttachment['mime'] = $_FILES['fileUpload']['type'];
			}
			$message = Tools::getValue('message'); // Html entities is not usefull, iscleanHtml check there is no bad html tags.
			if (!($from = trim(Tools::getValue('from'))) || !Validate::isEmail($from))
				$this->errors[] = Tools::displayError('Invalid email address.');
			else if (!$message)
				$this->errors[] = Tools::displayError('The message cannot be blank.');
			else if (!Validate::isCleanHtml($message))
				$this->errors[] = Tools::displayError('Invalid message');
			else if (!($id_contact = (int)(Tools::getValue('id_contact'))) || !(Validate::isLoadedObject($contact = new Contact($id_contact, $this->context->language->id))))
				$this->errors[] = Tools::displayError('Please select a subject from the list provided. ');
			else if (!empty($_FILES['fileUpload']['name']) && $_FILES['fileUpload']['error'] != 0)
				$this->errors[] = Tools::displayError('An error occurred during the file-upload process.');
			else if (!empty($_FILES['fileUpload']['name']) && !in_array(substr($_FILES['fileUpload']['name'], -4), $extension) && !in_array(substr($_FILES['fileUpload']['name'], -5), $extension))
				$this->errors[] = Tools::displayError('Bad file extension');
			else
			{
				$customer = $this->context->customer;
				if (!$customer->id)
					$customer->getByEmail($from);

				$contact = new Contact($id_contact, $this->context->language->id);

				if (!((
						($id_customer_thread = (int)Tools::getValue('id_customer_thread'))
						&& (int)Db::getInstance()->getValue('
						SELECT cm.id_customer_thread FROM '._DB_PREFIX_.'customer_thread cm
						WHERE cm.id_customer_thread = '.(int)$id_customer_thread.' AND cm.id_shop = '.(int)$this->context->shop->id.' AND token = \''.pSQL(Tools::getValue('token')).'\'')
					) || (
						$id_customer_thread = CustomerThread::getIdCustomerThreadByEmailAndIdOrder($from, (int)Tools::getValue('id_order'))
					)))
				{
					$fields = Db::getInstance()->executeS('
					SELECT cm.id_customer_thread, cm.id_contact, cm.id_customer, cm.id_order, cm.id_product, cm.email
					FROM '._DB_PREFIX_.'customer_thread cm
					WHERE email = \''.pSQL($from).'\' AND cm.id_shop = '.(int)$this->context->shop->id.' AND ('.
						($customer->id ? 'id_customer = '.(int)($customer->id).' OR ' : '').'
						id_order = '.(int)(Tools::getValue('id_order')).')');
					$score = 0;
					foreach ($fields as $key => $row)
					{
						$tmp = 0;
						if ((int)$row['id_customer'] && $row['id_customer'] != $customer->id && $row['email'] != $from)
							continue;
						if ($row['id_order'] != 0 && Tools::getValue('id_order') != $row['id_order'])
							continue;
						if ($row['email'] == $from)
							$tmp += 4;
						if ($row['id_contact'] == $id_contact)
							$tmp++;
						if (Tools::getValue('id_product') != 0 && $row['id_product'] == Tools::getValue('id_product'))
							$tmp += 2;
						if ($tmp >= 5 && $tmp >= $score)
						{
							$score = $tmp;
							$id_customer_thread = $row['id_customer_thread'];
						}
					}
				}
				$old_message = Db::getInstance()->getValue('
					SELECT cm.message FROM '._DB_PREFIX_.'customer_message cm
					LEFT JOIN '._DB_PREFIX_.'customer_thread cc on (cm.id_customer_thread = cc.id_customer_thread)
					WHERE cc.id_customer_thread = '.(int)($id_customer_thread).' AND cc.id_shop = '.(int)$this->context->shop->id.'
					ORDER BY cm.date_add DESC');
				if ($old_message == $message)
				{
					$this->context->smarty->assign('alreadySent', 1);
					$contact->email = '';
					$contact->customer_service = 0;
				}

				if ($contact->customer_service)
				{
					if ((int)$id_customer_thread)
					{
						$ct = new CustomerThread($id_customer_thread);
						$ct->status = 'open';
						$ct->id_lang = (int)$this->context->language->id;
						$ct->id_contact = (int)($id_contact);
						if ($id_order = (int)Tools::getValue('id_order'))
							$ct->id_order = $id_order;
						if ($id_product = (int)Tools::getValue('id_product'))
							$ct->id_product = $id_product;
						$ct->update();
					}
					else
					{
						$ct = new CustomerThread();
						if (isset($customer->id))
							$ct->id_customer = (int)($customer->id);
						$ct->id_shop = (int)$this->context->shop->id;
						if ($id_order = (int)Tools::getValue('id_order'))
							$ct->id_order = $id_order;
						if ($id_product = (int)Tools::getValue('id_product'))
							$ct->id_product = $id_product;
						$ct->id_contact = (int)($id_contact);
						$ct->id_lang = (int)$this->context->language->id;
						$ct->email = $from;
						$ct->status = 'open';
						$ct->token = Tools::passwdGen(12);

						$ct->add();
					}

					if ($ct->id)
					{
						$cm = new CustomerMessage();
						$cm->id_customer_thread = $ct->id;
						$cm->message = Tools::htmlentitiesUTF8($message);
						if (isset($filename) && rename($_FILES['fileUpload']['tmp_name'], _PS_MODULE_DIR_.'../upload/'.$filename))
							$cm->file_name = $filename;
						$cm->ip_address = ip2long($_SERVER['REMOTE_ADDR']);
						$cm->user_agent = $_SERVER['HTTP_USER_AGENT'];
						if (!$cm->add())
							$this->errors[] = Tools::displayError('An error occurred while sending the message.');
					}
					else
						$this->errors[] = Tools::displayError('An error occurred while sending the message.');
				}

				if (!count($this->errors))
				{
					$var_list = array(
									'{order_name}' => '-',
									'{attached_file}' => '-',
									'{message}' => Tools::nl2br(stripslashes($message)),
									'{email}' =>  $from,
								);

					if (isset($filename))
						$var_list['{attached_file}'] = $_FILES['fileUpload']['name'];

					$id_order = (int)Tools::getValue('id_order');
					
					if (isset($ct) && Validate::isLoadedObject($ct))
					{
						if ($ct->id_order)
							$id_order = $ct->id_order;
						$subject = sprintf(Mail::l('Your message has been correctly sent #ct%1$s #tc%2$s'), $ct->id, $ct->token);
					}
					else
						$subject = Mail::l('Your message has been correctly sent');

					if ($id_order)
					{
						$order = new Order((int)$id_order);
						$var_list['{order_name}'] = $order->getUniqReference();
						$var_list['{id_order}'] = $id_order;
					}
					
					if (empty($contact->email))
						Mail::Send($this->context->language->id, 'contact_form', $subject, $var_list, $from, null, null, null, $fileAttachment);
					else
					{					
						if (!Mail::Send($this->context->language->id, 'contact', Mail::l('Message from contact form').' [no_sync]',
							$var_list, $contact->email, $contact->name, $from, ($customer->id ? $customer->firstname.' '.$customer->lastname : ''),
									$fileAttachment) ||
								!Mail::Send($this->context->language->id, 'contact_form', $subject, $var_list, $from, null, $contact->email, $contact->name, $fileAttachment))
									$this->errors[] = Tools::displayError('An error occurred while sending the message.');
					}
				}
				
				if (count($this->errors) > 1)
					array_unique($this->errors);
				else
					$this->context->smarty->assign('confirmation', 1);
			}
		}
	}

}


Inside this file, locate the following block of code:


if ((int)$id_customer_thread)
{
	$ct = new CustomerThread($id_customer_thread);
	$ct->status = 'open';
	$ct->id_lang = (int)$this->context->language->id;
	$ct->id_contact = (int)($id_contact);
	if ($id_order = (int)Tools::getValue('id_order'))
		$ct->id_order = $id_order;
	if ($id_product = (int)Tools::getValue('id_product'))
		$ct->id_product = $id_product;
	$ct->update();
}
else
{
	$ct = new CustomerThread();
	if (isset($customer->id))
		$ct->id_customer = (int)($customer->id);
	$ct->id_shop = (int)$this->context->shop->id;
	if ($id_order = (int)Tools::getValue('id_order'))
		$ct->id_order = $id_order;
	if ($id_product = (int)Tools::getValue('id_product'))
		$ct->id_product = $id_product;
	$ct->id_contact = (int)($id_contact);
	$ct->id_lang = (int)$this->context->language->id;
	$ct->email = $from;
	$ct->status = 'open';
	$ct->token = Tools::passwdGen(12);
	$ct->add();
}

This is responsible for creating new threads, or update current ones. Thus, the first block will update an existing customer thread, whilst the “else” one creates a new one. So, let’s tell the object it has an extrafield in both cases:


if ((int)$id_customer_thread)
{
	$ct = new CustomerThread($id_customer_thread);
	$ct->status = 'open';
	$ct->id_lang = (int)$this->context->language->id;
	$ct->id_contact = (int)($id_contact);
	if ($id_order = (int)Tools::getValue('id_order'))
		$ct->id_order = $id_order;
	if ($id_product = (int)Tools::getValue('id_product'))
		$ct->id_product = $id_product;
	$ct->extrafield = Tools::getValue('extrafield');
	$ct->update();
}
else
{
	$ct = new CustomerThread();
	if (isset($customer->id))
		$ct->id_customer = (int)($customer->id);
	$ct->id_shop = (int)$this->context->shop->id;
	if ($id_order = (int)Tools::getValue('id_order'))
		$ct->id_order = $id_order;
	if ($id_product = (int)Tools::getValue('id_product'))
		$ct->id_product = $id_product;
	$ct->id_contact = (int)($id_contact);
	$ct->id_lang = (int)$this->context->language->id;
	$ct->email = $from;
	$ct->status = 'open';
	$ct->token = Tools::passwdGen(12);
	$ct->extrafield = Tools::getValue('extrafield');
	$ct->add();
}

As you can see, we added $ct->extrafield = Tools::getValue(‘extrafield’); to both cases, just before the entries are inserted or updated.

As a last step, go to cache/ in your root folder, and delete class_index.php so that our changes take place. Now do a quick test run. If everything went smoothly, you should se the new value appear in the database.

Step 3 – Adding email variables and back office texts

We’re almost there! The system is already working, but it needs to be fine-tuned. First, in the very same ContactController, locate this block of code:


$var_list = array(
				'{order_name}' => '-',
				'{attached_file}' => '-',
				'{message}' => Tools::nl2br(stripslashes($message)),
				'{email}' =>  $from,

			);

These are the variables assigned to both the emails which will be sent out (to the customer and store owner). Let’s add our variable there!


$var_list = array(
				'{order_name}' => '-',
				'{attached_file}' => '-',
				'{message}' => Tools::nl2br(stripslashes($message)),
				'{email}' =>  $from,
				'{extrafield}' =>  (isset($ct) && $ct->extrafield) ? $ct->extrafield : ''
			);

Note: as you can see, we are first checking that the $ct variable is set, as this will not happen if a customer thread already exists!

Lastly, let’s open up the 2 email templates being sent out: contact and contact_form. You can find them in the mails/ folder, inside one of the subfolder named as your shop’s languages’ ISOs. FInd a suitable place for the new field and add

Extrafield: <strong>{extrafield}</strong>

Again, run a quick test run to ensure everything works!

email_with_new_field

 

Now, as a really last step, we will add the field to the back office as well. Again, following the maintainability principle, we will not directly modify the core files, but use overrides.

Go to your admin folder, then themes\default\template\controllers\customer_threads, and copy message.tpl. Now back to override/controllers/admin/templates, create a new folder names exactly customer_threads and paste message.tpl inside.

As always, feel free to choose any spot to add the new content. I will add it right before this:

		<dl>
			<dt>{l s='Thread ID:'}</dt>
			<dd>{$message.id_customer_thread}</dd>
		</dl>

So, before that, add

		<dl>
			<dt>{l s='Extrafield:'}</dt>
			<dd>{$message.extrafield}</dd>
		</dl>

Save & reach the admin screen, you should see the new text field appearing!

Conclusion

As you can see, a lot needs to be done for a simple addition. However, after doing it the first time it will be like second nature! Therefore, next time you’ll need to add an extra field to the contact form, you’ll already have most things settled.

You like the tuts and want to say "thank you"? Well, you can always feel free to donate:

  • Carl

    Beauty Nemo! You’re the man!

  • Carl

    Hey Florian,

    Just wanted to say this helped me out greatly! Thanks for the help! Cheers.

  • namelessphantom

    Does it works for more than 1 extra field?
    Because i keep trying and i only get 1 of the 3 extra fields i need sent into my email

  • jubilee

    How to add new fields to contact form in prestashop 1.6.0.9 (Latest Version)?

    • NemoPS

      This should work on the latest version too, only the tpl part will differ a bit

  • Julien

    Hi,

    Thank you for this article !

    I followed all the instructions above, but it does not work. (Prestashop 1.5.6)

    > I get email, and the information in BO without the new entry “Phone”
    > I find in my database sql three inputs instead of one… (IMG eg.)

    We can see 3 inputs (One with nothing, One with “phone” and one with mail)

    Could you help me please ?

    Jules

    • NemoPS

      Hard to tell what’s going on! Did you add the field to the override? And made sure you refreshed the index? Can’t see any image :(

      • Julien

        Hi,

        I tested with a new version 1.5.6.2 and still nothing…
        And I can see 3 entries in my sql base. I don’t understand :(

        Do you really sure that work with these prestashop version ?

        Thank you :)

        Jules

        • NemoPS

          No idea really :( In any case yes, it’s supposed to work on that version

        • Julien

          Ok :(
          Thank you Nemo

        • Julien

          Nemo,
          The name of TEXT column must to be the same than the name of field ? extra field ?

        • Virginia Ortiz Terrón

          Que error te dio me esta pasando lo mismo que a ti y no encuentro la solución

  • Ivan Leon

    Hi Nemo, I dont know if I’m using the wrong location of the file but I cannot find the code you said in the contact-form.tpl on prestashop 1.6.0.9 using the default template.

    • NemoPS

      Actually you can add the code where you prefer, you can move it to the spot you like, just make sure it’s between 2 other elements (the tpl is different from 1.5)

  • Florian

    Hi Fabio, I am new with Prestashop and I followed your tutorial above to add 3 fields (Name/Business Name/Phone) for my contact form.

    Form is done properly and database receive apparently data (I can see that it actually create threads), however when I logged on the back-office I cannot see these additionnal fields and neither from the automatic email.

    I am running PrestaShop 1.6.
    Contact form is here: http://dev-cfd.cfdesign.com.au/index.php?controller=contact

    I wonder what I actually missed?

    I would really appreciate any help from anyone here ;)

    Let me know.

    Florian

    • Ricardo

      Hi Florian, did you find any solution? I have the same problem : everything is working ok but I can’t see the new field in my back – office. Many Thanks.

    • http://iswebs.com.pl Igor

      Hi, Florian

      I wanted too use this tutorial in Prestashop 1.6.0.9 but I have problem with mails.

      firstly I just created 2 extra fields , they are in:

      – contact.tpl
      – CustomerThread.php
      – ContactController.php
      – contact-form.tpl
      – In mails:
      > contact.html
      > contact-form.html

      Just as it is in this tutorial

      E-mail was sended correctly but when I see mail in my mail-program, fields are empty ( didn’t downloaded fields content )

      Could you send me yours files ?
      I want them to see and create my own

      my mail -> info@iswebs.com.pl

  • Fernando

    I’m having the same problem. Have you solved?

  • http://www.fmeextensions.com/ Roberto Carlo

    The tutorial is good one but only for developers, i think as a merchant i cant use coding stuff to make sure everything is steup ok, for aotumated solution i would suggest see the following prestashop forum thread, prestashop.com/forums/topic/322838-module-prestashop-custom-registration-fieldsattributes/?hl=%2Bregistration

  • Newbie

    Awesome tutorial. I was able to add the field without knowing prestashop or coding. Can you tell me how to make the new field as a mandatory field (am using it for phone number)? It would be great if there is also an error message when user does not enter this field.

  • Pankaj Nath

    Great Tutorial. Explained very nicely.
    I have only one doubt , how are we suppose to validate the extrafield ??

    • NemoPS

      Well it depends on the content, you can use Validate::isString if you want to make sure it doesn’t contain html or js

      • Pankaj Nath

        Am using the field to capture user Contact Number.
        So I want to validate 3 thing on this:
        1) Field shouldn’t be empty
        2) Field should be numberic only
        3) The number entered shouldn’t exceed 10 digit and has to me more than 8.
        Can you please suggest how to achieve that?

  • http://www.makarandmane.com/services/ Makarand Mane

    Hi I am new to Prestashop ,

    I am getting error for regular form like Please select a subject from the list provided.

    project url : http://www.technobratz.in/fryersmate/en/contact-us

    please help

  • http://firebranddesigns.net Patrick

    All this is required just to add an extra field to a contact form?! Remind me again what is so great about Prestashop…

    • http://www.aplicit.com Vincent Piton

      great post, thank you !

  • jimmy

    Hi, I have followed the instruction closely and the basic function is working perfectly. However I’ve encounter a small problem that is when the page/form reloads due to missing required fields, the entered values of the new custom fields are not saved.

    This is one of my new custom field, but somehow even though some text is entered in this field, isset($customerThread.contactname) is still false. If the form is successfully submitted, this field does show up properly in the database. Could you please help me solve this issue?

    {if isset($customerThread.contactname)}

    {else}

    {/if}

  • http://www.surfinbird.com.ar guido

    hey. great tutorial, but i can’t see the new data on my database. i mean i see the new fields created on the table customer_thread, i complete all the fields in the contact-form but when i put OK, those fields are not updated in my database
    what am i doing wrong?
    thanks!

  • Amazing

    Hi,

    great tutorial, thanks, everything works fine.

    I just can’t figure out one thing.
    If user fills in all fields except one, say, he didn’t choose the subject, the page is refreshed and error note appears. So after page is refreshed, the values of native fields are saved (message, e-mail), but extrafield gets empty after each page refresh.

    I tried to put corresponding value of the extrafield like that:
    {if isset($extrafield)}{$extrafield|escape:’htmlall':’UTF-8’|stripslashes}{/if}

    It works for $message, but it doesn’t work for $extrafield.

    It seems like $extrafield is not set, because in SMARTY_DEBUG, I can see $message variable, but there is not any $extrafield.

    so, is it possible to display values of extrafields after page refresh?

    • http://nemops.com Nemo

      Hi!
      This is an interesting question. I’ll try to figure it out and post a solution the sooner!
      Thanks for addressing the issue!

      • http://msuleman.com suleman

        Thanks for your quick response!!

      • Amazing

        Hello!

        How are yout?
        Any news about this issue?

  • http://msuleman.com suleman

    I checked, when someone try to send an email its storing extrafield value more then 5 times mean loop that.. How i can debug please!

  • http://msuleman.com suleman

    I am facing issue, in last step on admin side i am not able to get database stored value very strange to me..

  • http://albertosuarezcaballero.es Alberto

    Thanks for the tuto mate, made things a lot easier ;)

  • http://www.secondskin.co.za Michael

    Is this future proof or if a new prestashop version comes out – will I have to manually edit all the files again?

    • http://nemops.com Nemo

      It depends.
      If overrides can be left untouched in new versions (say, in other 1.5.x) yes, it is. For 1.6, it’s hard to tell! :)

Store Top Sales

You like the tuts and want to say "thank you"? Well, you can always feel free to donate: