: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
<?php if ( ! defined( 'ABSPATH' ) ) exit;
use NinjaForms\Includes\Admin\Processes\DeleteBatchFile;
use NinjaForms\Includes\Contracts\SubmissionHandler;
use NinjaForms\Includes\Entities\SingleSubmission;
use NinjaForms\Includes\Entities\SubmissionExtraHandlerResponse;
use NinjaForms\Includes\Factories\SubmissionAggregateFactory;
use NinjaForms\Includes\Factories\SubmissionFilterFactory;
* Class NF_Routes_SubmissionsActions
final class NF_Routes_Submissions extends NF_Abstracts_Routes
* Register REST API routes related to submissions actions
* @route "ninja-forms-submissions/export"
* @route 'ninja-forms-submissions/email-action"
function register_routes() {
register_rest_route('ninja-forms-submissions', 'submissions/get', array(
'description' => esc_attr__('Form IDs', 'ninja-forms'),
'validate_callback' => 'rest_validate_request_arg',
'callback' => [ $this, 'get_submissions' ],
'permission_callback' => [ $this, 'get_submissions_permission_callback' ]
register_rest_route('ninja-forms-submissions', 'submissions/delete', array(
'description' => esc_attr__('Submissions', 'ninja-forms'),
'validate_callback' => 'rest_validate_request_arg',
'callback' => [ $this, 'delete_submissions' ],
'permission_callback' => [ $this, 'delete_submissions_permission_callback' ]
register_rest_route('ninja-forms-submissions', 'submissions/update', array(
'description' => esc_attr__('Update single Submission', 'ninja-forms'),
'type' => 'JSON encoded array',
'validate_callback' => 'rest_validate_request_arg',
'callback' => [ $this, 'update_submission' ],
'permission_callback' => [ $this, 'update_submission_permission_callback' ]
register_rest_route('ninja-forms-submissions', 'submissions/restore', array(
'description' => esc_attr__('Update Submission', 'ninja-forms'),
'validate_callback' => 'rest_validate_request_arg',
'description' => esc_attr__('Array of Submissions', 'ninja-forms'),
'validate_callback' => 'rest_validate_request_arg',
'callback' => [ $this, 'restore_submissions' ],
'permission_callback' => [ $this, 'update_submission_permission_callback' ]
register_rest_route('ninja-forms-submissions', 'submissions/handle-extra', array(
'description' => esc_attr__('Update Submission', 'ninja-forms'),
'type' => 'JSON encoded array',
'validate_callback' => 'rest_validate_request_arg',
'description' => esc_attr__('Extra Handler of Submission', 'ninja-forms'),
'validate_callback' => 'rest_validate_request_arg',
'callback' => [ $this, 'handle_extra_submission' ],
'permission_callback' => [ $this, 'handle_extra_submission_permission_callback' ]
register_rest_route('ninja-forms-submissions', 'export', array(
'description' => esc_attr__('Array of Form IDs we want to get the submissions from.', 'ninja-forms'),
'validate_callback' => 'rest_validate_request_arg',
'description' => esc_attr__('strtotime($date) that represents the start date we will retrieve submssions at.', 'ninja-forms'),
'validate_callback' => 'rest_validate_request_arg',
'description' => esc_attr__('strtotime($date) that represents the end date we will retrieve submssions at.', 'ninja-forms'),
'validate_callback' => 'rest_validate_request_arg',
'description' => esc_attr__('Export single submission if a JSON encoded submission is passed', 'ninja-forms'),
'type' => 'JSON encoded array',
'validate_callback' => 'rest_validate_request_arg',
'description' => esc_attr__('Export submissions based on array of submissions IDs', 'ninja-forms'),
'validate_callback' => 'rest_validate_request_arg',
'callback' => [ $this, 'bulk_export_submissions' ],
'permission_callback' => [ $this, 'get_submissions_permission_callback' ],
register_rest_route('ninja-forms-submissions', 'download-all', array(
'description' => esc_attr__('Array of Form IDs we want to get the submissions from.', 'ninja-forms'),
'validate_callback' => 'rest_validate_request_arg',
'callback' => [ $this, 'download_all_submissions' ],
'permission_callback' => [ $this, 'get_submissions_permission_callback' ],
* Delete the temp file created from the `download-all` request
register_rest_route('ninja-forms-submissions', 'delete-download-file', array(
'description' => esc_attr__('File path of the file to delete', 'ninja-forms'),
'validate_callback' => 'rest_validate_request_arg',
'callback' => [ $this, 'delete_download_file' ],
// Uses the same permissions as the `download-all` request
'permission_callback' => [ $this, 'delete_submissions_files_permission_callback' ],
register_rest_route('ninja-forms-submissions', 'set-submissions-settings', array(
'description' => esc_attr__('Setting name in the submissionsSettings array', 'ninja-forms'),
'validate_callback' => 'rest_validate_request_arg',
'description' => esc_attr__('Settings data', 'ninja-forms'),
'validate_callback' => 'rest_validate_request_arg',
'description' => esc_attr__('Form ID of the setting saved', 'ninja-forms'),
'validate_callback' => 'rest_validate_request_arg',
'callback' => [ $this, 'set_submissions_settings' ],
'permission_callback' => [ $this, 'get_submissions_permission_callback' ],
register_rest_route('ninja-forms-submissions', 'get-submissions-settings', array(
'callback' => [ $this, 'get_submissions_settings' ],
'permission_callback' => [ $this, 'get_submissions_permission_callback' ],
register_rest_route('ninja-forms-submissions', 'email-action', array(
'description' => esc_attr__('Submission ID', 'ninja-forms'),
'validate_callback' => 'rest_validate_request_arg',
'description' => esc_attr__('Email Action Settings', 'ninja-forms'),
'validate_callback' => 'rest_validate_request_arg',
'callback' => [ $this, 'trigger_email_action' ],
'permission_callback' => [ $this, 'permission_callback' ],
* Secure endpoint to allow users to read submissions
* Already passed Nonce validation via wp_rest and x_wp_nonce header checked against rest_cookie_check_errors()
public function get_submissions_permission_callback(WP_REST_Request $request) {
// Allow only admin to export personally identifiable data
$permissionLevel = 'manage_options';
$allowed= \current_user_can($permissionLevel);
* Filter permissions for Reading Submissions
* @param bool $allowed Is request authorized?
* @param WP_REST_Request $request The current request
return apply_filters( 'ninja_forms_api_allow_get_submissions', $allowed, $request );
* Secure endpoint to allow users to delete submissions
* Already passed Nonce validation via wp_rest and x_wp_nonce header checked against rest_cookie_check_errors()
public function delete_submissions_permission_callback(WP_REST_Request $request) {
// Allow only admin to export personally identifiable data
$permissionLevel = 'manage_options';
$allowed= \current_user_can($permissionLevel);
* Filter permissions for Reading Submissions
* @param bool $allowed Is request authorized?
* @param WP_REST_Request $request The current request
return apply_filters( 'ninja_forms_api_allow_delete_submissions', $allowed, $request );
* Secure endpoint to allow users to update a submission
* Already passed Nonce validation via wp_rest and x_wp_nonce header checked against rest_cookie_check_errors()
public function update_submission_permission_callback(WP_REST_Request $request) {
// Allow only admin to export personally identifiable data
$permissionLevel = 'manage_options';
$allowed= \current_user_can($permissionLevel);
* Filter permissions for updating a submission
* @param bool $allowed Is request authorized?
* @param WP_REST_Request $request The current request
return apply_filters( 'ninja_forms_api_allow_update_submission', $allowed, $request );
* Secure endpoint to allow users to perform extra handling
* Already passed Nonce validation via wp_rest and x_wp_nonce header checked against rest_cookie_check_errors()
public function handle_extra_submission_permission_callback(WP_REST_Request $request) {
// Allow only admin to export personally identifiable data
$permissionLevel = 'manage_options';
$allowed= \current_user_can($permissionLevel);
* Filter permissions for updating a submission
* @param bool $allowed Is request authorized?
* @param WP_REST_Request $request The current request
return apply_filters( 'ninja_forms_api_allow_handle_extra_submission', $allowed, $request );
* Secure endpoint to allowed users and uploads folder
* Used to delete files from the uploads directory. Tmp file in our case
* Already passed Nonce validation via wp_rest and x_wp_nonce header checked
* against rest_cookie_check_errors()
public function delete_submissions_files_permission_callback(WP_REST_Request $request) {
// Allow users with manage_options capability and only within wp_upload_dir
$permissionLevel = 'manage_options';
$dir = wp_get_upload_dir();
$fileinfo = pathinfo(json_decode($request->get_body())->file_path);
$allowed_dir = $dir['basedir'] . '/ninja-forms-tmp' === $fileinfo['dirname'];
$allowed = \current_user_can($permissionLevel) && $allowed_dir;
* Filter permissions for deleting files from the uploads directory. Tmp file in our case
* @param bool $allowed Is request authorized?
* @param WP_REST_Request $request The current request
return apply_filters( 'ninja_forms_api_allow_delete_current_uploads_file', $allowed, $request );
* Secure endpoint to allowed users
* Security disclosure regarding <=3.5.7 showed that any logged in user
* could export form data, possibly exposing personally identifiable
* information. Permissions changed such that only admin can export
* submission data; a filter enables one to override that permission if
* Already passed Nonce validation via wp_rest and x_wp_nonce header checked
* against rest_cookie_check_errors()
public function permission_callback(WP_REST_Request $request) {
// Allow only admin to export personally identifiable data
$permissionLevel = 'manage_options';
$allowed= \current_user_can($permissionLevel);
* Filter permissions for Triggering Email Actions
* @param bool $allowed Is request authorized?
* @param WP_REST_Request $request The current request
return apply_filters( 'ninja_forms_api_allow_email_action', $allowed, $request );
* Bulk_export_submissions
* @return array of CSVs by form
public function bulk_export_submissions(WP_REST_Request $request)
//Gather data from the request
$data = json_decode($request->get_body());
//TODO organize queries to work with a single method instead of defining a method depending on the data received
if (!empty($data->form_ids) && !empty($data->start_date) && !empty($data->end_date)) { // export by date from multiple forms
$form_ids = explode(",", $data->form_ids);
$start_date = $data->start_date;
$end_date = $data->end_date;
$requestType = 'filterByDates';
} elseif (!empty($data->singleSubmission)) { // export a single submission
$singleSubmissionJson = $data->singleSubmission;
$requestType = 'getSingleSubmission';
} else if (!empty($data->submissions) && !empty($data->form_ids)) { // export multiple submissions from a single form
$form_ids = explode(",", $data->form_ids);
$submissionsIDs = $data->submissions;
$requestType = 'getSubmissions';
return new WP_Error('malformed_request', __('This request is missing data', 'ninja-forms'));
if ('filterByDates' === $requestType) {
foreach ($form_ids as $formId) {
$params = (new SubmissionFilterFactory())->maybeLimitByLoggedInUser()
->setStartDate($start_date)
->setNfFormIds([$formId])
->setStatus(['active', 'publish']);
// construct aggregate within CSV adapter, applying filter to aggregate
$submissionAggregateCsvExportAdapter = (new SubmissionAggregateFactory())->SubmissionAggregateCsvExportAdapter();
$submissionAggregateCsvExportAdapter->submissionAggregate->filterSubmissions($params);
$csvObject = (new NF_Exports_SubmissionCsvExport())->setUseAdminLabels(true)
->setSubmissionAggregateCsvExportAdapter($submissionAggregateCsvExportAdapter);
$csv[$formId] = $csvObject->handle();
} elseif ('getSingleSubmission' === $requestType) {
$singleSubmission = SingleSubmission::fromArray(json_decode($singleSubmissionJson, true));
$formId = $singleSubmission->getFormId();
// construct aggregate within CSV adapter, applying filter to aggregate
$submissionAggregateCsvExportAdapter = (new SubmissionAggregateFactory())->SubmissionAggregateCsvExportAdapter();
$submissionAggregateCsvExportAdapter->submissionAggregate->requestSingleSubmission($singleSubmission);
$csvObject = (new NF_Exports_SubmissionCsvExport())->setUseAdminLabels(true)
->setSubmissionAggregateCsvExportAdapter($submissionAggregateCsvExportAdapter);
$csv[$formId] = $csvObject->handle();
} elseif ('getSubmissions' === $requestType) {
$params = (new SubmissionFilterFactory())->maybeLimitByLoggedInUser()
->setNfFormIds($form_ids)
->setSubmissionsIDs($submissionsIDs);
$submissionAggregateCsvExportAdapter = (new SubmissionAggregateFactory())->SubmissionAggregateCsvExportAdapter();
$submissionAggregateCsvExportAdapter->submissionAggregate->filterSubmissions($params);
$csvObject = (new NF_Exports_SubmissionCsvExport())->setUseAdminLabels(true)->setSubmissionAggregateCsvExportAdapter($submissionAggregateCsvExportAdapter);
$csv[$form_ids[0]] = $csvObject->handle();
* Download all submissions
public function download_all_submissions(WP_REST_Request $request)
//Gather data from the request
$data = json_decode($request->get_body());
if (!empty($data->form_ids)) { // download all submissions
$form_ids = explode(",", $data->form_ids);
return new WP_Error('malformed_request', __('This request is missing data', 'ninja-forms'));
$export = (new NF_Admin_Processes_ExportSubmissions($formId));
$downloadUrl = $export->getFileUrl();