Array ('self' => true), ); $this->permMapping = array_merge($this->permMapping, $permissions); } function OnCreateSubmissionNodes($event) { if (defined('IS_INSTALL') && IS_INSTALL) { // skip any processing, because Forms table doesn't exists until install is finished return ; } $forms = $this->getForms(); if (!$forms) { return ; } $form_subsection = Array( 'parent' => 'in-portal:forms', 'icon' => 'form_submission', 'label' => '', 'url' => Array('t' => 'submissions/submissions_list', 'pass' => 'm,form'), 'permissions' => Array('view', 'add', 'edit', 'delete'), 'priority' => 1, 'type' => stTREE, ); $priority = 1; $sections = $this->Application->getUnitOption($event->Prefix, 'Sections'); foreach ($forms as $form_id => $form_name) { $this->Application->Phrases->AddCachedPhrase('form_sub_label_'.$form_id, $form_name); $this->Application->Phrases->AddCachedPhrase('la_description_in-portal:submissions:'.$form_id, $form_name.' Submissions'); $form_subsection['label'] = 'form_sub_label_'.$form_id; $form_subsection['url']['form_id'] = $form_id; $form_subsection['priority'] = $priority++; $sections['in-portal:submissions:'.$form_id] = $form_subsection; } $this->Application->setUnitOption($event->Prefix, 'Sections', $sections); } function getForms() { $cache_key = 'forms[%FormSerial%]'; $forms = $this->Application->getCache($cache_key); if ($forms === false) { $this->Conn->nextQueryCachable = true; $sql = 'SELECT Title, FormId FROM ' . TABLE_PREFIX . 'Forms ORDER BY Title ASC'; $forms = $this->Conn->GetCol($sql, 'FormId'); $this->Application->setCache($cache_key, $forms); } return $forms; } /** * Saves content of temp table into live and * redirects to event' default redirect (normally grid template) * * @param kEvent $event * @return void * @access protected */ protected function OnSave(kEvent $event) { parent::OnSave($event); if ( $event->status == kEvent::erSUCCESS ) { $this->OnCreateFormFields($event); $this->_deleteSectionCache(); } } /** * Deletes all selected items. * Automatically recurse into sub-items using temp handler, and deletes sub-items * by calling its Delete method if sub-item has AutoDelete set to true in its config file * * @param kEvent $event * @return void * @access protected */ protected function OnMassDelete(kEvent $event) { parent::OnMassDelete($event); if ( $event->status == kEvent::erSUCCESS ) { $this->_deleteSectionCache(); } } function _deleteSectionCache() { $this->Application->HandleEvent(new kEvent('adm:OnResetSections')); $this->Application->StoreVar('RefreshStructureTree', 1); } /** * Dynamically fills custom data config * * @param kEvent $event */ function OnCreateFormFields($event) { $cur_fields = $this->Conn->Query('DESCRIBE '.TABLE_PREFIX.'FormSubmissions', 'Field'); $cur_fields = array_keys($cur_fields); // keep all fields, that are not created on the fly (includes ones, that are added during customizations) foreach ($cur_fields as $field_index => $field_name) { if (!preg_match('/^fld_[\d]+/', $field_name)) { unset($cur_fields[$field_index]); } } $desired_fields = $this->Conn->GetCol('SELECT CONCAT(\'fld_\', FormFieldId) FROM '.TABLE_PREFIX.'FormFields ORDER BY FormFieldId'); $sql = array(); $fields_to_add = array_diff($desired_fields, $cur_fields); foreach ($fields_to_add as $field) { $field_expression = $field.' Text NULL'; $sql[] = 'ADD COLUMN '.$field_expression; } $fields_to_drop = array_diff($cur_fields, $desired_fields); foreach ($fields_to_drop as $field) { $sql[] = 'DROP COLUMN '.$field; } if ($sql) { $query = 'ALTER TABLE '.TABLE_PREFIX.'FormSubmissions '.implode(', ', $sql); $this->Conn->Query($query); } } /** * Enter description here... * * @param kEvent $event * @return void * @access protected */ protected function OnFormSubmit($event) { /** @var kDBItem $object */ $object = $event->getObject(); $fields = explode(',',$this->Application->GetVar('fields')); $required_fields = explode(',', $this->Application->GetVar('required_fields')); $fields_params = $this->Application->GetVar('fields_params'); $virtual_fields = $this->Application->getUnitOption($event->Prefix, 'VirtualFields'); foreach ($fields as $field) { $virtual_fields[$field] = Array (); if ( in_array($field, $required_fields) ) { $virtual_fields[$field]['required'] = 1; } $params = getArrayValue($fields_params, $field); if ( $params !== false ) { if ( getArrayValue($params, 'Type') == 'email' ) { $virtual_fields[$field]['formatter'] = 'kFormatter'; $virtual_fields[$field]['regexp'] = '/^(' . REGEX_EMAIL_USER . '@' . REGEX_EMAIL_DOMAIN . ')$/i'; $virtual_fields[$field]['error_msgs'] = Array ('invalid_format' => '!la_invalid_email!'); } if ( getArrayValue($params, 'Type') == 'file' ) { $virtual_fields[$field]['formatter'] = 'kUploadFormatter'; $virtual_fields[$field]['upload_dir'] = '/uploads/sketches/'; } } } $object->SetVirtualFields($virtual_fields); $field_values = $this->getSubmittedFields($event); $checkboxes = explode(',', $this->Application->GetVar('checkbox_fields')); // MailingList,In-Link,In-Newz,In-Bulletin foreach ($checkboxes as $checkbox) { if (isset($field_values[$checkbox])) { $field_values[$checkbox] = 1; } else { $field_values[$checkbox] = '0'; } } $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); if ( $object->Validate() ) { $event->redirect = $this->Application->GetVar('success_template'); $this->Application->emailAdmin($this->Application->GetVar('email_event')); $send_params = Array ( 'to_email' => $field_values[$this->Application->GetVar('email_field')], 'to_name' => $field_values[$this->Application->GetVar('name_field')] ); $this->Application->emailUser($this->Application->GetVar('email_event'), null, $send_params); if ( $field_values['MailingList'] ) { $this->Application->StoreVar('SubscriberEmail', $field_values['Email']); $this->Application->HandleEvent(new kEvent('u:OnSubscribeUser', Array ('no_unsubscribe' => 1))); } } else { $event->status = kEvent::erFAIL; } } /** * Don't use security image, when form requires login * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemCreate(kEvent $event) { parent::OnBeforeItemCreate($event); $this->itemChanged($event); } /** * Don't use security image, when form requires login * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemUpdate(kEvent $event) { parent::OnBeforeItemUpdate($event); $this->itemChanged($event); } /** * Occurs before item is changed * * @param kEvent $event */ function itemChanged($event) { $this->_validatePopSettings($event); $this->_disableSecurityImage($event); $this->_setRequired($event); } /** * Validates POP3 settings (performs test connect) * * @param kEvent $event */ function _validatePopSettings($event) { /** @var kDBItem $object */ $object = $event->getObject(); $modes = Array ('Reply', 'Bounce'); $fields = Array ('Server', 'Port', 'Username', 'Password'); $changed_fields = array_keys( $object->GetChangedFields() ); foreach ($modes as $mode) { $set = true; $changed = false; foreach ($fields as $field) { $value = $object->GetDBField($mode . $field); if (strlen( trim($value) ) == 0) { $set = false; break; } if (!$changed && in_array($mode . $field, $changed_fields)) { $changed = true; } } if ($set && $changed) { // fields are set and at least on of them is changed $connection_info = Array (); foreach ($fields as $field) { $connection_info[ strtolower($field) ] = $object->GetDBField($mode . $field); } /** @var POP3Helper $pop3_helper */ $pop3_helper = $this->Application->makeClass('POP3Helper', Array ($connection_info, 10)); switch ( $pop3_helper->initMailbox(true) ) { case 'socket': $object->SetError($mode . 'Server', 'connection_failed'); break; case 'login': $object->SetError($mode . 'Username', 'login_failed'); break; case 'list': $object->SetError($mode . 'Server', 'message_listing_failed'); break; } } } } /** * Makes email communication fields required, when form uses email communication * * @param kEvent $event */ function _setRequired($event) { /** @var kDBItem $object */ $object = $event->getObject(); $required = $object->GetDBField('EnableEmailCommunication'); $fields = Array ( 'ReplyFromName', 'ReplyFromEmail', 'ReplyServer', 'ReplyPort', 'ReplyUsername', 'ReplyPassword', ); if ($required && $object->GetDBField('BounceEmail')) { $bounce_fields = Array ('BounceEmail', 'BounceServer', 'BouncePort', 'BounceUsername', 'BouncePassword'); $fields = array_merge($fields, $bounce_fields); } $object->setRequired($fields, $required); } /** * Don't use security image, when form requires login * * @param kEvent $event */ function _disableSecurityImage($event) { /** @var kDBItem $object */ $object = $event->getObject(); if ($object->GetDBField('RequireLogin')) { $object->SetDBField('UseSecurityImage', 0); } } /** * Queries pop3 server about new incoming mail * * @param kEvent $event */ function OnProcessReplies($event) { $this->_processMailbox($event, false); } /** * Queries pop3 server about new incoming mail * * @param kEvent $event */ function OnProcessBouncedReplies($event) { $this->_processMailbox($event, true); } /** * Queries pop3 server about new incoming mail * * @param kEvent $event * @param bool $bounce_mode */ function _processMailbox($event, $bounce_mode = false) { $this->Application->SetVar('client_mode', 1); $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); $table_name = $this->Application->getUnitOption($event->Prefix, 'TableName'); $sql = 'SELECT * FROM ' . $table_name . ' WHERE EnableEmailCommunication = 1'; $forms = $this->Conn->Query($sql, $id_field); /** @var MailboxHelper $mailbox_helper */ $mailbox_helper = $this->Application->recallObject('MailboxHelper'); $field_prefix = $bounce_mode ? 'Bounce' : 'Reply'; foreach ($forms as $form_id => $form_info) { $recipient_email = $bounce_mode ? $form_info['BounceEmail'] : $form_info['ReplyFromEmail']; if (!$recipient_email) { continue; } $mailbox_helper->process( Array ( 'server' => $form_info[$field_prefix . 'Server'], 'port' => $form_info[$field_prefix . 'Port'], 'username' => $form_info[$field_prefix . 'Username'], 'password' => $form_info[$field_prefix . 'Password'] ), Array (&$this, 'isValidRecipient'), Array (&$this, 'processEmail'), Array ( 'recipient_email' => $recipient_email, 'bounce_mode' => $bounce_mode, 'form_info' => $form_info, ) ); } } function isValidRecipient($params) { /** @var MailboxHelper $mailbox_helper */ $mailbox_helper = $this->Application->recallObject('MailboxHelper'); $recipients = $mailbox_helper->getRecipients(); $recipient_email = $params['recipient_email']; $emails_found = preg_match_all('/((' . REGEX_EMAIL_USER . ')(@' . REGEX_EMAIL_DOMAIN . '))/i', $recipients, $all_emails); if (is_array($all_emails)) { for ($i = 0; $i < $emails_found; $i++) { if ($all_emails[1][$i] == $recipient_email) { // only read messages, that are addresses to submission reply email return true; } } } // If this is a forwarded message - we drop all the other aliases and deliver only to the x-forward to address; if (preg_match('/((' . REGEX_EMAIL_USER . ')(@' . REGEX_EMAIL_DOMAIN . '))/i', $mailbox_helper->headers['x-forward-to'], $get_to_email)) { if ($get_to_email[1] == $recipient_email) { // only read messages, that are addresses to submission reply email return true; } } return false; } function processEmail($params, &$fields_hash) { if ($params['bounce_mode']) { // mark original message as bounced /** @var MailboxHelper $mailbox_helper */ $mailbox_helper = $this->Application->recallObject('MailboxHelper'); if (!array_key_exists('attachments', $mailbox_helper->parsedMessage)) { // for now only parse bounces based on attachments, skip other bounce types return false; } for ($i = 0; $i < count($mailbox_helper->parsedMessage['attachments']); $i++) { $attachment =& $mailbox_helper->parsedMessage['attachments'][$i]; switch ($attachment['headers']['content-type']) { case 'message/delivery-status': // save as BounceInfo /** @var MimeDecodeHelper $mime_decode_helper */ $mime_decode_helper = $this->Application->recallObject('MimeDecodeHelper'); $charset = $mailbox_helper->parsedMessage[ $fields_hash['MessageType'] ][0]['charset']; $fields_hash['Message'] = $mime_decode_helper->convertEncoding($charset, $attachment['data']); break; case 'message/rfc822': // undelivered message $fields_hash['Subject'] = $attachment['filename2'] ? $attachment['filename2'] : $attachment['filename']; break; } } } if (!preg_match('/^(.*) #verify(.*)$/', $fields_hash['Subject'], $regs)) { // incorrect subject, no verification code $form_info = $params['form_info']; if ($form_info['ProcessUnmatchedEmails'] && ($fields_hash['FromEmail'] != $params['recipient_email'])) { // it's requested to convert unmatched emails to new submissions $form_id = $form_info['FormId']; $this->Application->SetVar('form_id', $form_id); $sql = 'SELECT ' . $this->Application->getUnitOption('formsubs', 'IDField') . ' FROM ' . $this->Application->getUnitOption('formsubs', 'TableName') . ' WHERE MessageId = ' . $this->Conn->qstr($fields_hash['MessageId']); $found = $this->Conn->GetOne($sql); if ($found) { // don't process same message twice return false; } $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'FormFields WHERE (FormId = ' . $form_info['FormId'] . ') AND (EmailCommunicationRole > 0)'; $form_fields = $this->Conn->Query($sql, 'EmailCommunicationRole'); // what roles are filled from what fields $role_mapping = Array ( SubmissionFormField::COMMUNICATION_ROLE_EMAIL => 'FromEmail', SubmissionFormField::COMMUNICATION_ROLE_NAME => 'FromName', SubmissionFormField::COMMUNICATION_ROLE_SUBJECT => 'Subject', SubmissionFormField::COMMUNICATION_ROLE_BODY => 'Message', ); $submission_fields = Array (); foreach ($role_mapping as $role => $email_field) { if (array_key_exists($role, $form_fields)) { $submission_fields[ 'fld_' . $form_fields[$role]['FormFieldId'] ] = $fields_hash[$email_field]; } } if ($submission_fields) { // remove object, because it's linked to single form upon creation forever $this->Application->removeObject('formsubs.-item'); /** @var kDBItem $form_submission */ $form_submission = $this->Application->recallObject('formsubs.-item', null, Array ('skip_autoload' => true)); // in case that other non-role mapped fields are required $form_submission->IgnoreValidation = true; $form_submission->SetDBFieldsFromHash($submission_fields); $form_submission->SetDBField('FormId', $form_id); $form_submission->SetDBField('MessageId', $fields_hash['MessageId']); $form_submission->SetDBField('SubmissionTime_date', adodb_mktime()); $form_submission->SetDBField('SubmissionTime_time', adodb_mktime()); $form_submission->SetDBField('ReferrerURL', $this->Application->Phrase('la_Text_Email')); return $form_submission->Create(); } } return false; } $sql = 'SELECT ' . $this->Application->getUnitOption('submission-log', 'IDField') . ' FROM ' . $this->Application->getUnitOption('submission-log', 'TableName') . ' WHERE MessageId = ' . $this->Conn->qstr($fields_hash['MessageId']); $found = $this->Conn->GetOne($sql); if ($found) { // don't process same message twice return false; } /** @var kDBItem $reply_to */ $reply_to = $this->Application->recallObject('submission-log.-reply-to', null, Array ('skip_autoload' => true)); $reply_to->Load($regs[2], 'VerifyCode'); if (!$reply_to->isLoaded()) { // fake verification code OR feedback, containing submission log was deleted return false; } if ($params['bounce_mode']) { // mark original message as bounced $reply_to->SetDBField('BounceInfo', $fields_hash['Message']); $reply_to->SetDBField('BounceDate_date', TIMENOW); $reply_to->SetDBField('BounceDate_time', TIMENOW); $reply_to->SetDBField('SentStatus', SUBMISSION_LOG_BOUNCE); $reply_to->Update(); return true; } /** @var kDBItem $reply */ $reply = $this->Application->recallObject('submission-log.-reply', null, Array ('skip_autoload' => true)); $reply->SetDBFieldsFromHash($fields_hash); $reply->SetDBField('ReplyTo', $reply_to->GetID()); $reply->SetDBField('FormSubmissionId', $reply_to->GetDBField('FormSubmissionId')); $reply->SetDBField('ToEmail', $params['recipient_email']); $reply->SetDBField('Subject', $regs[1]); // save subject without verification code $reply->SetDBField('SentStatus', SUBMISSION_LOG_SENT); return $reply->Create(); } }