: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
namespace NinjaForms\Includes\Database;
use NinjaForms\Includes\Contracts\SubmissionDataSource as ContractsSubmissionDataSource;
use NinjaForms\Includes\Entities\SingleSubmission;
use NinjaForms\Includes\Entities\SubmissionFilter;
use NinjaForms\Includes\Entities\SubmissionField;
use NinjaForms\Includes\Handlers\DateTimeConverter;
use \NF_Database_Models_Submission;
* Retrieves a CPT Ninja Forms submission by its form id
* CPT indicates NF submissions stored as custom post type
class CptSubmissionDataSource implements ContractsSubmissionDataSource
const TIMESTAMP_FORMAT = 'Y-m-d H:i:s';
* Identifier of where submission is stored
protected $dataSource = 'nf_post';
* Collection of submissions
* @var SingleSubmission[]
protected $submissionCollection = [];
/** @var SubmissionFilter */
protected $submissionFilter;
* Form Id of the primary form
* Form Id used to define fields; other forms can be included in results.
protected $formId = null;
public function retrieveSubmissionMeta(SubmissionFilter $submissionFilter): array
$this->submissionFilter = $submissionFilter;
$this->submissionCollection = [];
if ([] !== $submissionFilter->getNfFormIds()) {
$formIdCollection = $submissionFilter->getNfFormIds();
$this->formId = $formIdCollection[0];
foreach ($formIdCollection as $formIdFromCollection) {
$this->retrieveSubmissionMetaByNfFormId($formIdFromCollection);
if(''!==$this->submissionFilter->getSearchString()){
$this->applySearchCriterion();
return $this->submissionCollection;
public function retrieveSingleSubmission(SingleSubmission $singleSubmission): SingleSubmission
/** @var NF_Database_Models_Submission $nfSub */
$nfSub = $this->getNfSub($singleSubmission);
$subDate = $nfSub->get_sub_date(SingleSubmission::TIMESTAMP_FORMAT);
$singleSubmission->setTimestamp($subDate);
$singleSubmission = $this->fullyPopulateSingleSubmission($singleSubmission, $nfSub);
return $singleSubmission;
public function retrieveSubmissionValues(SingleSubmission $singleSubmission): SingleSubmission
$submissionRecordId = $singleSubmission->getSubmissionRecordId();
if (isset($this->submissionCollection[$submissionRecordId])) {
$nfSub = $this->getNfSub($singleSubmission);
$singleSubmission = $this->fullyPopulateSingleSubmission($singleSubmission, $nfSub);
$this->submissionCollection[$submissionRecordId] = $singleSubmission;
return $singleSubmission;
public function deleteSubmission(SingleSubmission $singleSubmission): ContractsSubmissionDataSource
$submissionId = $singleSubmission->getSubmissionRecordId();
$submission = Ninja_Forms()->form()->get_sub( $submissionId );
if( in_array($submission->get_status(), $status) ){
public function restoreSubmission(SingleSubmission $singleSubmission): ContractsSubmissionDataSource
$submissionId = $singleSubmission->getSubmissionRecordId();
"post_status" => "publish"
public function updateSubmission(SingleSubmission $singleSubmission): ContractsSubmissionDataSource
$submissionId = $singleSubmission->getSubmissionRecordId();
$submission = Ninja_Forms()->form()->get_sub( $submissionId );
$updatedFieldsCollection = [];
/** @var SubmissionField $submissionField */
foreach($singleSubmission->getSubmissionFieldCollection() as $submissionField){
$updatedFieldsCollection[$submissionField->getSlug()]=$submissionField->getValue();
$submission->update_field_values($updatedFieldsCollection)->save();
* Get the NF_Submission for a SingleSubmission entity
protected function getNfSub(SingleSubmission $singleSubmission): NF_Database_Models_Submission
$nfSub = Ninja_Forms()->form()->get_sub($singleSubmission->getSubmissionRecordId(), null);
* Populate submission values, extra values, handlers
* @param SingleSubmission $singleSubmission
* @param NF_Database_Models_Submission $nfSub
* @return SingleSubmission
protected function fullyPopulateSingleSubmission(SingleSubmission $singleSubmission, NF_Database_Models_Submission $nfSub): SingleSubmission
$singleSubmission = $this->populateSubmissionValues($singleSubmission, $nfSub);
$extraValues = $this->retrieveExtraValues($nfSub, $this->getExtraValueHandlers());
$singleSubmission->setExtraValues($extraValues);
$submissionHandlers = \apply_filters('nf_react_table_submission_handlers',[],$singleSubmission);
$singleSubmission->setSubmissionHandlers($submissionHandlers);
return $singleSubmission;
* Populate a single submissin with submitted values
* @param SingleSubmission $singleSubmission
* @param NF_Database_Models_Submission $nfSub
* @return SingleSubmission
protected function populateSubmissionValues(SingleSubmission $singleSubmission,NF_Database_Models_Submission $nfSub): SingleSubmission
$updatedFieldValueCollection = [];
foreach ($singleSubmission->getSubmissionFieldCollection() as $fieldSlug => $submissionField) {
$value = $nfSub->get_field_value($fieldSlug);
$submissionField->setValue($value);
$updatedFieldValueCollection[$fieldSlug] = $submissionField;
$singleSubmission->setSubmissionFieldCollection($updatedFieldValueCollection);
return $singleSubmission;
* Provide a filtered list of extra value keys storing extra data
* Non-core actions can filter the list to provide their keys for retrieval
protected function getExtraValueHandlers( ): array
$return = \apply_filters('nf_react_table_extra_value_keys',$metaboxHandlers);
// Example of adding an extraValue handler
// $metaboxHandlers['nfacds']='\NinjaForms\ActiveCampaign\Admin\MetaboxEntityConstructor';
* Retrieve extraValues as constructed into MetaboxOutputEntities
* @param NF_Database_Models_Submission $nfSub
* @param array $extraValueHandlers
protected function retrieveExtraValues($nfSub, array $extraValueHandlers): array
foreach($extraValueHandlers as $extraValueKey=>$extraValueHandler){
$extraValue =$nfSub->get_extra_value($extraValueKey);
if(\class_exists($extraValueHandler)){
$structured = (new $extraValueHandler())->handle($extraValue, $nfSub);
if(!is_null($structured)){
$return[$extraValueKey]=$structured;
* Apply search string filter to submission collection
* Runs a WP Query to search for all post Ids with both a form Id from the
* submission filter and also a search string in any meta value of the same
* form. It then filters the submission collection to only those
* submissions that meet these additional requirements.
protected function applySearchCriterion(): void
$searchQuery = new \WP_Query(
'relation' => 'AND', // both of below conditions must match
'value' => $this->submissionFilter->getNfFormIds()
'value' => $this->submissionFilter->getSearchString(),
/** @var array $searchCollection Array of submission Ids that match both form Id and search criterion */
foreach ($searchQuery->posts as $post) {
$searchCollection[] = $post->ID;
$this->submissionCollection = \array_intersect_key($this->submissionCollection, \array_flip($searchCollection));
* Retrieve submissions Ids for a given NF form Id
* User Id is conditionally added if submission filter's property is
* explicityly set (-1 is default value, indicating that no filtering on
* WPDB query args is changed from individual args to being passed as array;
* this makes for cleaner structure when sequence of arguments is variable.
protected function retrieveSubmissionMetaByNfFormId(string $formId): void
$startDate = DateTimeConverter::localizeEpochIntoString( $this->submissionFilter->getStartDate());
$endDate = DateTimeConverter::localizeEpochIntoString( $this->submissionFilter->getEndDate());
$userId = $this->submissionFilter->getUserId();
$statuses = $this->submissionFilter->getStatus();
$status = !empty($statuses) && in_array( "trash", $statuses ) ? "trash" : "publish";
$submissionRecordIdQuery = "SELECT p.ID, p.post_author, p.post_date, mm.meta_value AS seq"
." FROM `" . $wpdb->prefix . "posts` AS p"
." INNER JOIN `" . $wpdb->prefix . "postmeta` AS m"
." INNER JOIN `" . $wpdb->prefix . "postmeta` AS mm"
." WHERE p.post_type = 'nf_sub'";
// Filter on post author as submitter's user id
if (!\is_null($userId)) {
$submissionRecordIdQuery .= "AND p.post_author = %d";
$wpdbPrepareArgs[]=$userId;
$submissionRecordIdQuery .= " AND p.post_status = %s";
$wpdbPrepareArgs[]=$status;
// Filter on form id meta key/value
$submissionRecordIdQuery .= " AND m.meta_key = '_form_id' AND m.meta_value = %s";
$wpdbPrepareArgs[]=$formId;
$submissionRecordIdQuery .= " AND mm.meta_key = '_seq_num'";
$submissionRecordIdQuery .= " AND CAST(p.post_modified AS DATETIME) BETWEEN %s AND %s";
$wpdbPrepareArgs[]=$startDate;
$wpdbPrepareArgs[]=$endDate;
$recordCollection = $wpdb->get_results($wpdb->prepare($submissionRecordIdQuery, $wpdbPrepareArgs));
foreach ($recordCollection as $queryObject) {
$submissionRecordId = $queryObject->ID;
$subDate = $queryObject->post_date;
$seq = $queryObject->seq;
$submitterId = $queryObject->post_author;
$this->submissionCollection[$submissionRecordId] = SingleSubmission::fromArray([
'submissionRecordId' => $submissionRecordId,
'dataSource' => $this->dataSource,
'submitterId' => $submitterId
* Boolean to include as per date filter
* true=>include, false=>omit
protected function includeByDateFilter($subDate): bool
$startDateUnix = $this->submissionFilter->getStartDate();
$startDate = (new \DateTime())->setTimestamp($startDateUnix)->format(self::TIMESTAMP_FORMAT);
$endDateUnix = $this->submissionFilter->getEndDate();
$endDate = (new \DateTime())->setTimestamp($endDateUnix)->format(self::TIMESTAMP_FORMAT);
if(''!==$startDate && $subDate<$startDate){
if(''!==$endDate && $subDate>$endDate){
public function getDataSource(): string
return $this->dataSource;