Server : LiteSpeed System : Linux server 3.10.0-1160.90.1.el7.x86_64 #1 SMP Thu May 4 15:21:22 UTC 2023 x86_64 User : alsaif ( 1057) PHP Version : 7.4.33 Disable Function : show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/alsaif/public_html/administrator/components/com_convertforms/models/ |
<?php
/**
* @package Convert Forms
* @version 4.3.3 Pro
*
* @author Tassos Marinos <info@tassos.gr>
* @link https://www.tassos.gr
* @copyright Copyright © 2023 Tassos 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;
use NRFramework\SmartTags;
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)
{
// Replace Smart Tags in field props.
$field->options = SmartTags::getInstance()->replace($field->options);
$field_class = 'input-xlarge';
$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')
];
// Prepare the Fileupload field
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 (isset($data->params[$key]) && 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();
// Prepare the Telephone field
if ($type === 'tel' && $field->options->get('enable_country_selector') === '1')
{
$type = 'TFPhoneControl';
}
// Get hint
$hint = $field->options->get('placeholder', $label);
$fld = new SimpleXMLElement('<field/>');
$fld->addAttribute('name', $key);
$fld->addAttribute('type', $type);
$fld->addAttribute('label', $label);
$fld->addAttribute('hint', $hint);
$fld->addAttribute('description', $field->options->get('description', ''));
$fld->addAttribute('rows', 10); // Used for textarea inputs
$fld->addAttribute('filter', $field->options->get('filter', 'safehtml'));
// Continue preparation for the Telephone field
if ($type === 'TFPhoneControl')
{
$field_class = 'form-control';
$fld->addAttribute('input_class', $field_class);
if (isset($data->params[$key]))
{
$field_value = is_scalar($data->params[$key]) ? $data->params[$key] : json_encode($data->params[$key]);
$fld->addAttribute('value', $field_value);
}
}
$fld->addAttribute('class', $field_class);
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)
{
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(JText::_('COM_CONVERTFORMS_HONEYPOT_ERROR'));
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' => $class->prepareRawValue($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;
}
}