558 lines
20 KiB
PHP
558 lines
20 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @package Convert Forms
|
|
* @version 3.2.12 Free
|
|
*
|
|
* @author Tassos Marinos <info@tassos.gr>
|
|
* @link http://www.tassos.gr
|
|
* @copyright Copyright © 2020 Tassos Marinos All Rights Reserved
|
|
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
|
|
*/
|
|
|
|
use Joomla\Registry\Registry;
|
|
use Joomla\String\StringHelper;
|
|
use Joomla\Filter\InputFilter;
|
|
use ConvertForms\Form;
|
|
use ConvertForms\Helper;
|
|
use ConvertForms\FieldsHelper;
|
|
|
|
defined('_JEXEC') or die('Restricted access');
|
|
|
|
// import Joomla modelform library
|
|
jimport('joomla.application.component.modeladmin');
|
|
|
|
/**
|
|
* Conversion Model Class
|
|
*/
|
|
class ConvertFormsModelConversion extends JModelAdmin
|
|
{
|
|
/**
|
|
* The database object
|
|
*
|
|
* @var object
|
|
*/
|
|
private $db;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param array $config An optional associative array of configuration settings.
|
|
*
|
|
* @see JModelLegacy
|
|
* @since 1.6
|
|
*/
|
|
public function __construct($config = array())
|
|
{
|
|
parent::__construct($config);
|
|
$this->db = JFactory::getDbo();
|
|
|
|
// Make sure our plugins are loaded. Without this line, the PDF plugin won't be able to catch the onContentAfterSave event.
|
|
JPluginHelper::importPlugin('convertformstools');
|
|
}
|
|
|
|
/**
|
|
* Returns a reference to the a Table object, always creating it.
|
|
*
|
|
* @param type The table type to instantiate
|
|
* @param string A prefix for the table class name. Optional.
|
|
* @param array Configuration array for model. Optional.
|
|
* @return JTable A database object
|
|
* @since 2.5
|
|
*/
|
|
public function getTable($type = 'Conversion', $prefix = 'ConvertFormsTable', $config = array())
|
|
{
|
|
return JTable::getInstance($type, $prefix, $config);
|
|
}
|
|
|
|
/**
|
|
* Allows preprocessing of the JForm object.
|
|
*
|
|
* @param JForm $form The form object
|
|
* @param array $data The data to be merged into the form object
|
|
* @param string $group The plugin group to be executed
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 3.6.1
|
|
*/
|
|
protected function preprocessForm(Joomla\CMS\Form\Form $form, $data, $group = 'content')
|
|
{
|
|
if (!isset($data->params))
|
|
{
|
|
return parent::preprocessForm($form, $data, $group);
|
|
}
|
|
|
|
// Ensure the form binds the data regardless the letter case of the field's name.
|
|
// We should not rely on the field's name. Instead we need to switch over to field's IDs instead.
|
|
$data->params = array_change_key_case($data->params);
|
|
|
|
// @todo - What we do here is somehow a joke. We should consider moving the logic of preparing each field to the respective field class in the namespace.
|
|
$data_ = clone $data;
|
|
|
|
$this->prepare($data_);
|
|
|
|
// Add form custom fields to form
|
|
$fields = [];
|
|
|
|
foreach ($data_->prepared_fields as $key => $field)
|
|
{
|
|
$type = $field->options->get('type');
|
|
|
|
// Map of fields types that need to be transformed in order to be recognized by the XML parser.
|
|
$transformFields = [
|
|
'hidden' => 'text',
|
|
'currency' => 'NR_Currencies',
|
|
'country' => 'NR_Geo',
|
|
'checkbox' => 'checkboxes',
|
|
'dropdown' => 'list',
|
|
'fileupload' => 'textlist',
|
|
'confirm' => $field->options->get('confirm_type')
|
|
];
|
|
|
|
if ($type == 'fileupload')
|
|
{
|
|
$limit_files = $field->options->get('limit_files');
|
|
|
|
if (!isset($limit_files) || (isset($limit_files) && $limit_files == '1'))
|
|
{
|
|
$transformFields['fileupload'] = 'text';
|
|
|
|
// In case the previous multiple field is turn into a single field, we need to transform the value from array to string too.
|
|
if (is_array($data->params[$key]))
|
|
{
|
|
$data->params[$key] = implode(',', $data->params[$key]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (array_key_exists($type, $transformFields))
|
|
{
|
|
$type = $transformFields[$type];
|
|
}
|
|
|
|
// Radio fields doesn't accept Array as a value and we need to transform it into a string.
|
|
if (in_array($type, ['radio']))
|
|
{
|
|
if (isset($data->params[$key]))
|
|
{
|
|
$data->params[$key] = implode('', (array) $data->params[$key]);
|
|
}
|
|
}
|
|
|
|
// Create the field
|
|
$label = $field->class->getLabel();
|
|
|
|
$fld = new SimpleXMLElement('<field/>');
|
|
$fld->addAttribute('name', $key);
|
|
$fld->addAttribute('type', $type);
|
|
$fld->addAttribute('label', $label);
|
|
$fld->addAttribute('hint', $field->options->get('placeholder', $label));
|
|
$fld->addAttribute('description', $fld->attributes()->hint);
|
|
$fld->addAttribute('class', 'input-xlarge');
|
|
$fld->addAttribute('rows', 10); // Used for textarea inputs
|
|
$fld->addAttribute('filter', $field->options->get('filter', 'safehtml'));
|
|
|
|
if ($type == 'editor')
|
|
{
|
|
$fld->addAttribute('editor', $field->options->get('editor'));
|
|
}
|
|
|
|
// Define options to list-based fields
|
|
if (in_array($type, ['list', 'radio', 'checkboxes']) && $choices = $field->class->getOptions())
|
|
{
|
|
if ($type == 'list' && $hint = $field->options->get('placeholder'))
|
|
{
|
|
array_unshift($choices, [
|
|
'label' => trim($hint),
|
|
'value' => '',
|
|
'selected' => true
|
|
]);
|
|
}
|
|
|
|
foreach ($choices as $choice)
|
|
{
|
|
$option = $fld->addChild('option', htmlspecialchars($choice['label']));
|
|
$option->addAttribute('value', strip_tags($choice['value']));
|
|
}
|
|
}
|
|
|
|
// Get field's XML
|
|
$fields[] = str_replace('<?xml version="1.0"?>', '', $fld->asXml());
|
|
}
|
|
|
|
$form->setField(new SimpleXMLElement('
|
|
<fieldset name="params">
|
|
<fields name="params">
|
|
' . implode('', $fields) . '
|
|
</fields>
|
|
</fieldset>
|
|
'));
|
|
|
|
parent::preprocessForm($form, $data, $group);
|
|
}
|
|
|
|
/**
|
|
* Method to get the record form.
|
|
*
|
|
* @param array $data Data for the form.
|
|
* @param boolean $loadData True if the form is to load its own data (default case), false if not.
|
|
* @return mixed A JForm object on success, false on failure
|
|
* @since 2.5
|
|
*/
|
|
public function getForm($data = array(), $loadData = true)
|
|
{
|
|
// Get the form.
|
|
$form = $this->loadForm('com_convertforms.conversion', 'conversion', array('control' => 'jform', 'load_data' => $loadData));
|
|
|
|
if (empty($form))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return $form;
|
|
}
|
|
|
|
/**
|
|
* Method to get the data that should be injected in the form.
|
|
*
|
|
* @return mixed The data for the form.
|
|
*/
|
|
protected function loadFormData()
|
|
{
|
|
// Check the session for previously entered form data.
|
|
$data = JFactory::getApplication()->getUserState('com_convertforms.edit.conversion.data', array());
|
|
|
|
if (empty($data))
|
|
{
|
|
$data = $this->getItem(null, false);
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Validate data before saving
|
|
*
|
|
* @param object $form The form to validate
|
|
* @param object $data The data to validate
|
|
* @param string $group
|
|
*
|
|
* @return array The validated data
|
|
*/
|
|
public function validate($form, $data, $group = null)
|
|
{
|
|
// Validate conversion edited via the backend
|
|
if (JFactory::getApplication()->isClient('administrator'))
|
|
{
|
|
return parent::validate($form, $data, $group);
|
|
}
|
|
|
|
// Make sure we have a valid Form data
|
|
if (!isset($data['cf']) || empty($data['cf']))
|
|
{
|
|
throw new Exception('No submission data found');
|
|
}
|
|
|
|
// Make sure we have a valid Form ID passed
|
|
if (!isset($data['cf']['form_id']) || !$formid = (int) $data['cf']['form_id'])
|
|
{
|
|
throw new Exception('Form ID is either missing or invalid');
|
|
}
|
|
|
|
// Let the user manipulate the post data before saved into the database.
|
|
// @todo - Move PHP Scripts logic to a separate plugin.
|
|
$payload = ['post' => &$data['cf']];
|
|
Form::runPHPScript($formid, 'formprocess', $payload);
|
|
|
|
// Get form from payload or load a new instance
|
|
$form = isset($payload['form']) ? $payload['form'] : Form::load($formid);
|
|
|
|
$error_message = null;
|
|
$result = JFactory::getApplication()->triggerEvent('onConvertFormsSubmissionValidate', [&$data['cf'], &$error_message, $form]);
|
|
|
|
if (in_array(false, $result, true))
|
|
{
|
|
throw new \Exception(is_null($error_message) ? 'Error' : $error_message);
|
|
}
|
|
|
|
// Honeypot check
|
|
if (isset($data['cf']['hnpt']) && !empty($data['cf']['hnpt']))
|
|
{
|
|
throw new Exception('Honeypot field triggered');
|
|
die();
|
|
}
|
|
|
|
// Make sure the right form is loaded
|
|
if (is_null($form['id']))
|
|
{
|
|
throw new Exception('Unknown Form');
|
|
}
|
|
|
|
// Initialize the object that is going to be saved in the database
|
|
$newData = [
|
|
'form_id' => $formid,
|
|
'campaign_id' => (int) $form['params']['campaign'],
|
|
'state' => isset($form['params']['submission_state']) ? $form['params']['submission_state'] : 1
|
|
];
|
|
|
|
$overrides = isset($data['overrides']) ? json_decode($data['overrides']) : [];
|
|
|
|
// Let's validate submitted data
|
|
foreach ($form['fields'] as $key => $form_field)
|
|
{
|
|
$field_name = isset($form_field['name']) ? $form_field['name'] : null;
|
|
$field_class = FieldsHelper::getFieldClass($form_field['type'], $form_field, $data);
|
|
$user_value = (!is_null($field_name) && isset($data['cf'][$field_name])) ? $data['cf'][$field_name] : null;
|
|
|
|
// Check if the field must be ignored and not be validated. Eg: hidden by conditional logic.
|
|
if (isset($overrides->ignore) && is_array($overrides->ignore) && in_array($form_field['key'], $overrides->ignore))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Validate and Filter user value. If an error occurs the submission aborts with an exception shown in the form
|
|
$field_class->validate($user_value);
|
|
|
|
// Skip unknown fields or fields with an empty value
|
|
if (!$field_name || $user_value == '')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$newData['params'][$field_name] = $user_value;
|
|
}
|
|
|
|
return $newData;
|
|
}
|
|
|
|
/**
|
|
* Create a new conversion based on the post data.
|
|
*
|
|
* @return object The new conversion row object
|
|
*/
|
|
public function createConversion($data)
|
|
{
|
|
JPluginHelper::importPlugin('convertforms');
|
|
JPluginHelper::importPlugin('convertformstools');
|
|
JPluginHelper::importPlugin('system');
|
|
|
|
// Validate data
|
|
$data = $this->validate(null, $data);
|
|
|
|
/**
|
|
* This event is rather useful for the following reasons:
|
|
*
|
|
* 1. It allows us to make modifications to the submission after it passes the validation checks.
|
|
* 2. It allows us to access and modify submission properties such as 'state' and 'form_id'. Eg: Store all submissions unpublished by default.
|
|
*
|
|
* We may support this event in the PHP Scripts section too.
|
|
*/
|
|
JFactory::getApplication()->triggerEvent('onConvertFormsSubmissionBeforeSave', [&$data]);
|
|
|
|
$form_data = Form::load($data['form_id']);
|
|
|
|
$submission = null;
|
|
|
|
// If we are not saving the data to the database, mock the submission in order to be used as expected.
|
|
if (isset($form_data['params']['save_data_to_db']) && $form_data['params']['save_data_to_db'] == '0')
|
|
{
|
|
$submission = (object) array_merge($form_data, $data);
|
|
$submission->id = 'unsaved_submission_' . uniqid();
|
|
$submission->user_id = 0;
|
|
$submission->created = JFactory::getDate()->toSql();
|
|
$submission->modified = JFactory::getDbo()->getNullDate();
|
|
$this->prepareItem($submission);
|
|
|
|
JPluginHelper::importPlugin('convertforms');
|
|
JPluginHelper::importPlugin('convertformstools');
|
|
|
|
JFactory::getApplication()->triggerEvent('onConvertFormsConversionAfterSave', [$submission, $this, 1]);
|
|
}
|
|
else
|
|
{
|
|
// JSON_UNESCAPED_UNICODE encodes multibyte unicode characters literally.
|
|
// Without: Τάσος => \u03a4\u03ac\u03c3\u03bf\u03c2
|
|
// With: Τάσος => Τάσος
|
|
$data['params'] = json_encode($data['params'], JSON_UNESCAPED_UNICODE);
|
|
|
|
// Everything seems fine. Let's save data to the database.
|
|
if (!$this->save($data))
|
|
{
|
|
throw new Exception($this->getError());
|
|
}
|
|
|
|
$submission = $this->getItem();
|
|
}
|
|
|
|
// Run user's PHP script after the form has been processed, stored into the database and all addons have run.
|
|
// @todo - Move PHP Scripts logic into a separate plugin.
|
|
$payload = ['submission' => &$submission];
|
|
Form::runPHPScript($data['form_id'], 'afterformsubmission', $payload);
|
|
|
|
/**
|
|
* Why this event was created:
|
|
*
|
|
* - Due to the fact that we cannot hook into "onConvertFormsSubmissionAfterSave" and manipulate the $submission object.
|
|
* If we try to do so, the updated $submission wont find its way to the other listeners hooked to this event.
|
|
*
|
|
* When does this event run:
|
|
*
|
|
* - Right after the submission has been saved to the database.
|
|
* - Before other plugins hook to run their own code after the submission has been processed.
|
|
*
|
|
* When it should be used:
|
|
*
|
|
* - When we want to modify the submission object just before other listeners hook into "onConvertFormsSubmissionAfterSave"
|
|
* such as Email Notifications so they will get the updated $submission object.
|
|
*
|
|
* Example Use Case:
|
|
*
|
|
* - If we want to rename the uploaded files and use add the submission id to the filename, we must listen to this event,
|
|
* update the $submission object, rename the file, update the database and emails will use the updated file data.
|
|
*/
|
|
JFactory::getApplication()->triggerEvent('onConvertFormsSubmissionAfterSavePrepare', [&$submission]);
|
|
|
|
/**
|
|
* When does this event run:
|
|
*
|
|
* - At the end of the submission process, after the $submission object has been finalized and its ready to be given
|
|
* out to plugins/code that hook to this event.
|
|
*
|
|
* When it should be used:
|
|
*
|
|
* - We dont want to modify the $submission data and plan to execute our code after the form has been submitted
|
|
* successfully.
|
|
*
|
|
* Example Use Case:
|
|
* - Send Email Notifications
|
|
* - Submit data to Campaigns
|
|
*/
|
|
JFactory::getApplication()->triggerEvent('onConvertFormsSubmissionAfterSave', [&$submission]);
|
|
|
|
return $submission;
|
|
}
|
|
|
|
/**
|
|
* Get a conversion item
|
|
*
|
|
* @param interger $pk The conversion row primary key
|
|
* @param bool $prepare Whether to prepare the submission fields.
|
|
*
|
|
* @return object The conversion object
|
|
*/
|
|
public function getItem($pk = null, $prepare = true)
|
|
{
|
|
if (!$item = parent::getItem($pk))
|
|
{
|
|
return;
|
|
}
|
|
|
|
$this->prepareItem($item, $prepare);
|
|
|
|
return $item;
|
|
}
|
|
|
|
/**
|
|
* Prepares the submission item.
|
|
*
|
|
* @param object $item The submission item.
|
|
* @param bool $prepare Whether to prepare the submission fields.
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function prepareItem(&$item, $prepare = true)
|
|
{
|
|
JPluginHelper::importPlugin('convertformstools');
|
|
|
|
// There's no need to change the timezone on the backend as the date properties are mainly used in the Calendar fields
|
|
// which are already modifying the timezone offset with the filter="user_utc" property.
|
|
if (JFactory::getApplication()->isClient('site'))
|
|
{
|
|
$item->created = Helper::formatDate($item->created);
|
|
$item->modified = Helper::formatDate($item->modified);
|
|
}
|
|
|
|
if ($item->user_id)
|
|
{
|
|
$item->user_name = JFactory::getUser($item->user_id)->name;
|
|
}
|
|
|
|
// Load Form & Campaign Model
|
|
JModelLegacy::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_convertforms/models', 'ConvertFormsModel');
|
|
|
|
$modelForm = JModelLegacy::getInstance('Form', 'ConvertFormsModel', ['ignore_request' => true]);
|
|
$modelCampaign = JModelLegacy::getInstance('Campaign', 'ConvertFormsModel', ['ignore_request' => true]);
|
|
|
|
$item->form = $modelForm->getItem($item->form_id);
|
|
$item->campaign = $modelCampaign->getItem($item->campaign_id);
|
|
|
|
// On J4 we get infinite loop if we inject the prepared fields to $data.
|
|
// Thus, we don't prepare the submission on this method in order to not pollute the $data object.
|
|
// Instead we use the preprocessForm() method with a cloned object.
|
|
if ($prepare)
|
|
{
|
|
$this->prepare($item);
|
|
}
|
|
|
|
return $item;
|
|
}
|
|
|
|
/**
|
|
* Prepares the submission by adding the submitted fields values.
|
|
*
|
|
* @param object $submission
|
|
*
|
|
* @return void
|
|
*/
|
|
public function prepare(&$submission)
|
|
{
|
|
if (!$submission || is_null($submission->id))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Note: Form may be already available in the $submission object.
|
|
if (!$form = Form::load($submission->form_id, false, true))
|
|
{
|
|
return;
|
|
}
|
|
|
|
$fields = [];
|
|
|
|
// Make sure we're manipualating an array.
|
|
$submission_params = array_change_key_case((array) $submission->params);
|
|
|
|
foreach ($form['fields'] as $field)
|
|
{
|
|
// Skip fields with no name like reCAPTCHA, HTML e.t.c
|
|
if (!isset($field['name']))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$field_name = strtolower($field['name']);
|
|
$submitted_value = isset($submission_params[$field_name]) ? $submission_params[$field_name] : '';
|
|
|
|
// Make sure the type of field is valid
|
|
if (!$class = FieldsHelper::getFieldClass($field['type'], $field))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$prepared_field = (object) [
|
|
'options' => new Registry($field),
|
|
'class' => $class,
|
|
'label' => isset($field['label']) && !empty($field['label']) ? JText::_($field['label']) : $field['name'],
|
|
'value' => $class->prepareValue($submitted_value),
|
|
'value_html' => $class->prepareValueHTML($submitted_value),
|
|
'value_raw' => $submitted_value
|
|
];
|
|
|
|
$fields[$field_name] = $prepared_field;
|
|
}
|
|
|
|
// @todo - Stop using 'prepare_fields' property and switch over to the standard 'fields' property.
|
|
$submission->prepared_fields = $fields;
|
|
}
|
|
} |