<?php if ( ! defined( 'ABSPATH' ) ) exit;
* Class NF_Database_Models_Form
final class NF_Database_Models_Form extends NF_Abstracts_Model
protected $_type = 'form';
protected $_table_name = 'nf3_forms';
protected $_meta_table_name = 'nf3_form_meta';
protected $_columns = array(
protected static $imported_form_id;
public function __construct( $db, $id = '' )
add_action( 'ninja_forms_before_import_form', array( $this, 'import_form_backwards_compatibility' ) );
parent::__construct( $db, $id );
$fields = Ninja_Forms()->form( $this->_id )->get_fields();
foreach( $fields as $field ){
$actions = Ninja_Forms()->form( $this->_id )->get_actions();
foreach( $actions as $action ){
$chunked_option_flag = 'nf_form_' . $this->_id . '_chunks';
$chunked_option_value = get_option( $chunked_option_flag );
// if there is nf_form_x_chunks option, we need to delete those
if( $chunked_option_value ) {
// if we have chunk'd it, get the list of chunks
$form_chunks = explode( ',', $chunked_option_value );
//get the option value of each chunk and concat them into the form
foreach( $form_chunks as $chunk ){
delete_option( $chunked_option_flag );
$this->delete_submissions();
WPN_Helper::delete_nf_cache( $this->_id );
do_action( 'ninja_forms_after_form_delete', $this->_id );
private function delete_submissions( ) {
// SQL for getting 250 subs at a time
$sub_sql = "SELECT id FROM `" . $wpdb->prefix . "posts` AS p
LEFT JOIN `" . $wpdb->prefix . "postmeta` AS m ON p.id = m.post_id
WHERE p.post_type = 'nf_sub' AND m.meta_key = '_form_id'
AND m.meta_value = %s LIMIT " . $max_cnt;
while ($post_result <= $max_cnt ) {
$subs = $wpdb->get_col( $wpdb->prepare( $sub_sql, $this->_id ),0 );
// if we are out of subs, then stop
if( 0 === count( $subs ) ) break;
// otherwise, let's delete the postmeta as well
$delete_meta_query = "DELETE FROM `" . $wpdb->prefix . "postmeta` WHERE post_id IN ( [IN] )";
$delete_meta_query = $this->prepare_in( $delete_meta_query, $subs );
$meta_result = $wpdb->query( $delete_meta_query );
if ( $meta_result > 0 ) {
// now we actually delete the posts(nf_sub)
$delete_post_query = "DELETE FROM `" . $wpdb->prefix . "posts` WHERE id IN ( [IN] )";
$delete_post_query = $this->prepare_in( $delete_post_query, $subs );
$post_result = $wpdb->query( $delete_post_query );
$total_subs_deleted = $total_subs_deleted + $post_result;
private function prepare_in( $sql, $vals ) {
$not_in_count = substr_count( $sql, '[IN]' );
if ( $not_in_count > 0 ) {
$args = array( str_replace( '[IN]', implode( ', ', array_fill( 0, count( $vals ), '%d' ) ), str_replace( '%', '%%', $sql ) ) );
// This will populate ALL the [IN]'s with the $vals, assuming you have more than one [IN] in the sql
for ( $i=0; $i < substr_count( $sql, '[IN]' ); $i++ ) {
$args = array_merge( $args, $vals );
$sql = call_user_func_array( array( $wpdb, 'prepare' ), array_merge( $args ) );
public static function get_next_sub_seq( $form_id )
// TODO: Leverage form cache.
$last_seq_num = $wpdb->get_var( $wpdb->prepare(
'SELECT value FROM ' . $wpdb->prefix . 'nf3_form_meta WHERE `key` = "_seq_num" AND `parent_id` = %s'
$wpdb->update( $wpdb->prefix . 'nf3_form_meta', array( 'value' => $last_seq_num + 1,
'meta_value' => $last_seq_num + 1, 'meta_key' => '_seq_num' )
, array( 'key' => '_seq_num', 'parent_id'
$wpdb->update( $wpdb->prefix . 'nf3_forms', array( 'seq_num' => $last_seq_num + 1 ), array( 'id' => $form_id ) );
$wpdb->insert( $wpdb->prefix . 'nf3_form_meta',
array( 'key' => '_seq_num',
'value' => $last_seq_num + 1,
'meta_key' => '_seq_num',
'meta_value' => $last_seq_num + 1
$wpdb->update( $wpdb->prefix . 'nf3_forms', array( 'seq_num' => $last_seq_num + 1 ), array( 'id' => $form_id ) );
public static function import( array $import, $id = '', $is_conversion )
$import = apply_filters( 'ninja_forms_before_import_form', $import );
$form = Ninja_Forms()->form( $id )->get();
$form->update_settings( $import[ 'settings' ] );
$form->update_setting( 'created_at', current_time( 'mysql' ) );
$form_id = $form->get_id();
'settings' => $form->get_settings()
$update_process = Ninja_Forms()->background_process( 'update-fields' );
foreach( $import[ 'fields' ] as $settings ){
$field_id = $settings[ 'id' ];
$field = Ninja_Forms()->form($form_id)->field( $field_id )->get();
unset( $settings[ 'id' ] );
$settings[ 'created_at' ] = current_time( 'mysql' );
$field = Ninja_Forms()->form($form_id)->field()->get();
* If this is the default contact form,
* ensure that we properly save the fields
* to avoid the loss of settings when the cache is disabled.
$field->update_settings( $settings );
$settings[ 'parent_id' ] = $form_id;
array_push( $form_cache[ 'fields' ], array(
'id' => $field->get_id(),
$update_process->push_to_queue(array(
'id' => $field->get_id(),
$update_process->save()->dispatch();
foreach( $import[ 'actions' ] as $settings ){
$action = Ninja_Forms()->form($form_id)->action()->get();
$settings[ 'created_at' ] = current_time( 'mysql' );
$action->update_settings( $settings )->save();
array_push( $form_cache[ 'actions' ], array(
'id' => $action->get_id(),
WPN_Helper::update_nf_cache( $form_id, $form_cache );
add_action( 'admin_notices', array( 'NF_Database_Models_Form', 'import_admin_notice' ) );
self::$imported_form_id = $form_id;
public static function import_admin_notice()
Ninja_Forms()->template( 'admin-notice-form-import.html.php', array( 'form_id'=> self::$imported_form_id ) );
* This static method is called to duplicate a form using the form ID.
* To duplicate a form we:
* Check to see if we've ran stage one of the db update process.
* Use SQL to insert a copy of our form and form meta.
* Grab all fields for a specific form.
* Loop over those fields and insert fields and field meta.
* Run ->update_settings() and ->save() on the form model.
* Call our WPN_Helper method to build a form cache.
* @param int $form_id ID of the form being duplicated.
* @return $new_form_id ID of our form duplicate.
public static function duplicate( $form_id )
* Check to see if we've got new field columns.
* We do this here instead of the get_sql_queries() method so that we don't hit the db multiple times.
$sql = "SHOW COLUMNS FROM {$wpdb->prefix}nf3_fields LIKE 'field_key'";
$results = $wpdb->get_results( $sql );
// If we don't have the field_key column, we need to remove our new columns.
if ( empty ( $results ) ) {
$db_stage_one_complete = false;
$db_stage_one_complete = true;
// Duplicate the Form Object.
$wpdb->query( $wpdb->prepare(
INSERT INTO {$wpdb->prefix}nf3_forms ( `title` )
SELECT CONCAT( `title`, ' - ', %s )
FROM {$wpdb->prefix}nf3_forms
", esc_html__( 'copy', 'ninja-forms' ), $form_id
$new_form_id = $wpdb->insert_id;
$blacklist = apply_filters( 'ninja_forms_excluded_duplicate_form_settings', $blacklist );
$blacklist = "'" . implode( "','", $blacklist ) . "'";
// Duplicate the Form Meta.
$wpdb->query( $wpdb->prepare(
INSERT INTO {$wpdb->prefix}nf3_form_meta ( `parent_id`, `key`, `value` )
SELECT %d, `key`, `value`
FROM {$wpdb->prefix}nf3_form_meta
AND `key` NOT IN({$blacklist});
", $new_form_id, $form_id
// Get the fields to duplicate
$old_fields = $wpdb->get_results( $wpdb->prepare(
FROM {$wpdb->prefix}nf3_fields
// Get our field and field_meta table column names and values.
$fields_sql = self::get_sql_queries( 'field_table_columns', $db_stage_one_complete );
$field_meta_sql = self::get_sql_queries( 'field_meta_table_columns', $db_stage_one_complete );
foreach( $old_fields as $old_field ){
// Duplicate the Field Object.
$wpdb->query( $wpdb->prepare(
INSERT INTO {$wpdb->prefix}nf3_fields ( {$fields_sql[ 'insert' ]} )
SELECT {$fields_sql[ 'select' ]}
FROM {$wpdb->prefix}nf3_fields
", $new_form_id, $old_field->id
$new_field_id = $wpdb->insert_id;
// Duplicate the Field Meta.
$wpdb->query( $wpdb->prepare(
INSERT INTO {$wpdb->prefix}nf3_field_meta ( {$field_meta_sql[ 'insert' ]} )
SELECT {$field_meta_sql[ 'select' ]}
FROM {$wpdb->prefix}nf3_field_meta
", $new_field_id, $old_field->id
// Duplicate the Actions.
// Get the actions to duplicate
$old_actions = $wpdb->get_results( $wpdb->prepare(
FROM {$wpdb->prefix}nf3_actions
// Get our action and action_meta table columns and values.
$actions_sql = self::get_sql_queries( 'action_table_columns', $db_stage_one_complete );
$actions_meta_sql = self::get_sql_queries( 'action_meta_table_columns', $db_stage_one_complete );
foreach( $old_actions as $old_action ){
// Duplicate the Action Object.
$wpdb->query( $wpdb->prepare(
INSERT INTO {$wpdb->prefix}nf3_actions ( {$actions_sql[ 'insert' ]} )
SELECT {$actions_sql[ 'select' ]}
FROM {$wpdb->prefix}nf3_actions
", $new_form_id, $old_action->id
$new_action_id = $wpdb->insert_id;
// Duplicate the Action Meta.
$wpdb->query( $wpdb->prepare(
INSERT INTO {$wpdb->prefix}nf3_action_meta ( {$actions_meta_sql[ 'insert' ]} )
SELECT {$actions_meta_sql[ 'select' ]}
FROM {$wpdb->prefix}nf3_action_meta
", $new_action_id, $old_action->id
* In order for our new form and form_meta fields to populate on
* duplicate we need to update_settings and save
$new_form = Ninja_Forms()->form( $new_form_id )->get();
$new_form->update_settings( $new_form->get_settings() );
* Build a cache for this new form.
WPN_Helper::build_nf_cache( $new_form_id );
* When duplicating a form, we need to build specific SQL queries.
* This is a fairly repetative task, so we've extrapolated the code to its own function.
* @param string $table_name Name of the table we want to update.
* @return array Associative array like: ['insert' => "`column1`, "`column2`", etc", 'select' => "`column1`, etc."]
private static function get_sql_queries( $table_name, $db_stage_one_complete = true )
* These arrays contain the columns in our database.
* Later, if the user hasn't ran stage one of our db update process, we'll remove the items that aren't supported.
'field_table_columns' => array(
'personally_identifiable',
'field_meta_table_columns' => array(
'action_table_columns' => array(
'action_meta_table_columns' => array(
// If we haven't been passed a table name or the table name is invalid, return false.
if ( empty( $table_name ) || ! isset( $db_columns[ $table_name ] ) ) {return false;}
// If we have not completed stage one of our db update, then we unset new db columns.
if ( ! $db_stage_one_complete ) {
$db_columns[ 'field_table_columns' ] = array_diff(
$db_columns[ 'field_table_columns' ],
'personally_identifiable'
$db_columns[ 'field_meta_table_columns' ] = array_diff(
$db_columns[ 'field_meta_table_columns'],
$db_columns[ 'action_table_columns' ] = array_diff(
$db_columns[ 'action_table_columns' ],
$db_columns[ 'action_meta_table_columns' ] = array_diff(
$db_columns[ 'action_meta_table_columns' ],
* $sql_insert is a var that tracks which table columns we want to insert.