Page Menu
Home
In-Portal Phabricator
Search
Configure Global Search
Log In
Files
F804435
forms_eh.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Wed, Feb 26, 11:05 AM
Size
18 KB
Mime Type
text/x-php
Expires
Fri, Feb 28, 11:05 AM (21 h, 49 m)
Engine
blob
Format
Raw Data
Handle
576983
Attached To
rINP In-Portal
forms_eh.php
View Options
<?php
/**
* @version $Id: forms_eh.php 15065 2012-01-17 15:11:29Z alex $
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined
(
'FULL_PATH'
)
or
die
(
'restricted access!'
);
class
FormsEventHandler
extends
kDBEventHandler
{
/**
* Allows to override standard permission mapping
*
* @return void
* @access protected
* @see kEventHandler::$permMapping
*/
protected
function
mapPermissions
()
{
parent
::
mapPermissions
();
$permissions
=
Array
(
// user can view any form on front-end
'OnItemBuild'
=>
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
()
{
$reset_event
=
new
kEvent
(
'adm:OnResetSections'
);
$this
->
Application
->
HandleEvent
(
$reset_event
);
$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
)
{
$object
=&
$event
->
getObject
();
/* @var $object kDBItem */
$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
,
$this
->
getRequestProtectedFields
(
$field_values
));
if
(
$object
->
Validate
()
)
{
$event
->
redirect
=
$this
->
Application
->
GetVar
(
'success_template'
);
$this
->
Application
->
EmailEventAdmin
(
$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
->
EmailEventUser
(
$this
->
Application
->
GetVar
(
'email_event'
),
null
,
$send_params
);
if
(
$field_values
[
'MailingList'
]
)
{
$this
->
Application
->
StoreVar
(
'SubscriberEmail'
,
$field_values
[
'Email'
]);
$this
->
Application
->
HandleEvent
(
$sub_event
,
'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
)
{
$object
=&
$event
->
getObject
();
/* @var $object kDBItem */
$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
);
}
$pop3_helper
=&
$this
->
Application
->
makeClass
(
'POP3Helper'
,
Array
(
$connection_info
,
10
));
/* @var $pop3_helper POP3Helper */
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
)
{
$object
=&
$event
->
getObject
();
/* @var $object kDBItem */
$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
)
{
$object
=&
$event
->
getObject
();
/* @var $object kDBItem */
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
);
$mailbox_helper
=&
$this
->
Application
->
recallObject
(
'MailboxHelper'
);
/* @var $mailbox_helper 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
)
{
$mailbox_helper
=&
$this
->
Application
->
recallObject
(
'MailboxHelper'
);
/* @var $mailbox_helper 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
$mailbox_helper
=&
$this
->
Application
->
recallObject
(
'MailboxHelper'
);
/* @var $mailbox_helper 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
$mime_decode_helper
=&
$this
->
Application
->
recallObject
(
'MimeDecodeHelper'
);
/* @var $mime_decode_helper 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'
);
$form_submission
=&
$this
->
Application
->
recallObject
(
'formsubs.-item'
,
null
,
Array
(
'skip_autoload'
=>
true
));
/* @var $form_submission kDBItem */
// 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
;
}
$reply_to
=&
$this
->
Application
->
recallObject
(
'submission-log.-reply-to'
,
null
,
Array
(
'skip_autoload'
=>
true
));
/* @var $reply_to kDBItem */
$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
;
}
$reply
=&
$this
->
Application
->
recallObject
(
'submission-log.-reply'
,
null
,
Array
(
'skip_autoload'
=>
true
));
/* @var $reply kDBItem */
$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
();
}
}
Event Timeline
Log In to Comment