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 overide 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 see 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.

Looking for quality PrestaShop Hosting? Look no further than Arvixe Web Hosting!

Tags: , , , , , , , , , | Posted under PrestaShop | RSS 2.0

Author Spotlight

Fabio Porta

Fabio Porta

Fabio has been involved in web development and design since 2005, when launched his first website at the age of 16. He’s now highly skilled in both client and server side development, along with design, and since August 2012 runs a successful website about PrestaShop tutorials and Prestashop Modules called Nemo’s Post Scriptum, at http://nemops.com

Leave a Reply

Your email address will not be published. Required fields are marked *


2 × 6 =

You may use these HTML tags and attributes: <a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>