<?php if ( ! defined( 'ABSPATH' ) ) exit;
* Class NF_Updates_CacheFieldReconcilliation
* This allows us to make sure that the data being saved to the database is in the fields table.
class NF_Updates_CacheFieldReconcilliation extends NF_Abstracts_RequiredUpdate
private $running = array();
* Non-associatve array of field ids from the cache.
private $field_ids = array();
* columns to retrieve from meta table
private $meta_keys = array(
'personally_identifiable'
* The denominator object for calculating our steps.
* The table names for our database queries.
* @param $data (Array) The data object passed in by the AJAX call.
* @param $running (Array) The array of required updates being run.
public function __construct( $data = array(), $running )
// Build our arguments array.
'slug' => 'CacheFieldReconcilliation',
'class_name' => 'NF_Updates_CacheFieldReconcilliation',
$this->running = $running;
// Call the parent constructor.
parent::__construct( $args );
$this->table = $this->db->prefix . 'nf3_fields';
$this->meta_table = $this->db->prefix . 'nf3_field_meta';
* Function to loop over the batch.
public function process()
// If we've not already started...
if ( ! isset( $this->running[ 0 ][ 'running' ] ) ) {
// Run our startup method.
* Get the next round of fields to update
$this->get_fields_this_step();
* Saves our current location, along with any processing data we may need for the next step.
* If we're done with our step, runs cleanup instead.
* Respond to the AJAX call.
* Function to run any setup steps necessary to begin processing.
public function startup()
// Record that we're processing the update.
$this->running[ 0 ][ 'running' ] = true;
$sql = "SELECT `id` FROM `{$this->table}`";
$fields = $this->db->get_results( $sql, 'ARRAY_A' );
// Record the total number of steps in this batch.
$this->running[ 0 ][ 'steps' ] = ceil(count( $fields ) / $this->divisor);
// Record our current step (defaulted to 0 here).
$this->running[ 0 ][ 'current' ] = 0;
public function get_fields_this_step() {
if( 0 < $this->running[ 0 ][ 'current' ] ) {
$offset = $this->running[ 0 ][ 'current' ] * $this->divisor;
// Get a list of our fields...
$sql = "SELECT `id` FROM `{$this->table}` LIMIT {$offset}, {$this->divisor}";
$this->field_ids = $this->db->get_results( $sql, 'ARRAY_A' );
$this->field_ids = $this->array_squash( $this->field_ids );
// $this->running[ 0 ][ 'fields' ] = $this->field_ids;
* Update field table records with data from field meta
public function update_fields() {
$field_meta = $this->get_field_meta();
$update_query = $this->get_update_query( $field_meta );
$this->query($update_query);
* Get meta data to use for updating
public function get_field_meta() {
if(0 === count($this->field_ids)) return false;
$in_fields = implode( ', ', $this->field_ids );
$meta_keys = "'" . implode( "' , '", $this->meta_keys ) . "'";
$meta_query = $this->get_field_meta_query();
$results = $this->db->get_results( $meta_query, 'ARRAY_A');
$meta_data = $this->format_field_meta($results);
* Construct the query to get meta data
* return String $meta_query
public function get_field_meta_query() {
$in_fields = implode( ', ', $this->field_ids );
$meta_keys = "'" . implode( "' , '", $this->meta_keys ) . "'";
$meta_query = "SELECT `parent_id`, `key`, `meta_key`, `meta_value`, `value` FROM `{$this->meta_table}` WHERE `parent_id` IN ({$in_fields}) AND `key` IN ({$meta_keys}) ORDER BY `parent_id` ASC";
* Format the data into format that helps us construct the insert/update query
* @return Array $meta_data
public function format_field_meta( $metadata ) {
foreach( $metadata as $meta ) {
$parent_id = $meta['parent_id'];
foreach( $meta as $key => $val ) {
if( 'parent_id' !== $key ) {
$meta_data[ $parent_id ][ $meta['key'] ] = $meta['value'];
$meta_data[ $parent_id ][ 'meta_' . $meta['meta_key'] ] = $meta['meta_value'];
* Construct field update query
public function get_update_query( $field_data ) {
if( 0 === count( $field_data) ) return false;
$sql = "INSERT INTO {$this->table}
(`id`, `label`, `key`, `field_label`, `field_key`, `order`, `required`, `default_value`, `label_pos`, `personally_identifiable`)
foreach( $field_data as $field_id => $meta ) {
$sql .= "({$field_id}, '{$this->db->_real_escape($meta['label'])}', '{$this->db->_real_escape($meta['key'])}', '{$this->db->_real_escape($meta['meta_label'])}', '{$this->db->_real_escape($meta['meta_key'])}', {$meta['order']},";
if( isset( $meta[ 'required' ] ) && '' !== $meta[ 'required' ]) {
$sql .= "{$meta['required']},";
if(isset( $meta[ 'meta_default' ] ) ) {
$sql .= "'{$this->db->_real_escape($meta['meta_default'])}',";
if( isset( $meta[ 'meta_label_pos' ] ) ) {
$sql .= "'{$meta['meta_label_pos']}',";
if(isset($meta['personally_identifiable'])) {
$sql .= "{$meta['personally_identifiable']}";
$sql = rtrim( $sql, ',' );
$sql .= "ON DUPLICATE KEY
`label` = VALUES(`label`),
`field_label` = VALUES(`field_label`),
`field_key` = VALUES(`field_key`),
`order` = VALUES(`order`),
`required` = VALUES(`required`),
`required` = VALUES(`required`),
`default_value` = VALUES(`default_value`),
`label_pos` = VALUES(`label_pos`),
`personally_identifiable` = VALUES(`personally_identifiable`)";
* Function to cleanup any lingering temporary elements of a required update after completion.
public function cleanup()
// Remove the current process from the array.
array_shift( $this->running );
// Record to our updates setting that this update is complete.
$this->confirm_complete();
// If we have no updates left to process...
if ( empty( $this->running ) ) {
// Call the parent cleanup method.
* After we've done our processing, but before we get to step cleanup, we need to store process information.
* This method updates our form class var so that it can be passed to the next step.
* If we've completed this step, it calls the cleanup method.
private function end_of_step()
$this->running[ 0 ][ 'current' ] = intval( $this->running[ 0 ][ 'current' ] ) + 1;
// Prepare to output our number of steps and current step.
$this->response[ 'stepsTotal' ] = $this->running[ 0 ][ 'steps' ];
$this->response[ 'currentStep' ] = $this->running[ 0 ][ 'current' ];
if ( $this->divisor > count($this->field_ids)) {
// Run our cleanup method.
// Record our current location in the process.
update_option( 'ninja_forms_doing_required_updates', $this->running );
// Prepare to output the number of updates remaining.
$this->response[ 'updatesRemaining' ] = count( $this->running );
* Function to compress our db results into a more useful format.
* @param $data (Array) The result to be compressed.
* @return (Array) Associative if our data was complex.
* Non-associative if our data was a single item.
* @since UPDATE_VERSION_ON_MERGE
private function array_squash( $data )
// For each item in the array...
foreach ( $data as $row ) {
// If the item has more than 1 attribute...
if ( 1 < count( $row ) ) {
// Assign the data to an associated result.
$response[] = intval($row['id']);
// Unset the id setting, as that will be the key.
unset( $response[ $row[ 'id' ] ][ '' ] );
} // Otherwise... (We only have 1 attribute.)
// Add the id to the stack in a non-associated result.
$response[] = intval( $row[ 'id' ] );