Skip to content

Instantly share code, notes, and snippets.

@dskurth
Last active January 12, 2024 04:07
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save dskurth/d56744de4ae076c417f59b2a459eaf4b to your computer and use it in GitHub Desktop.
Save dskurth/d56744de4ae076c417f59b2a459eaf4b to your computer and use it in GitHub Desktop.
CBM2 - USING CUSTOM TABLES TO READ AND STORE DATA
<?php
////////////////////////////////////////////////////////////////////////////////////////
//
// This a demo version of how to use CMB2 with WordPress Table of your own design
// It assumes you have put together a custom post and are integrating this development tool
// for form management.
//
// PROS
// All the tools for easy data managment for forms with all the data input tools, such as required fields, repeatable fields and all data is stored within the
// cache of the system, rather than being stored to disk. It is quick and easy to bring up basic forms.
//
// CONS
// A high level documentation exists and the support group makes great efforts to help, but there are lots of assumptions you have to figure out
// for yourself. It is not always simple straight forward coding. Especially when it comes to standard HTML type controls such as checkboxes and HTML5 dates.
// If you like fancy written code that is cool, because you can do it...this sample will not be for you.
//
//
// Hopefully this code set examplewill help that process.
// I used the following snippet heavily, for figuring out what the parameters did and did not contain. The DebugLog
// call is a custom function that I wrote for this process. You should replace it with any print utitily you perfer to use.
//
// $results = print_r($object_id, true);
// DebugLog('DATA DUMP $object_id: ' . $results);
//
// DISCOVERIES
// 1. The call back function 'cmb2_override_meta_remove', does not work.
// 2. All the control is done through the use of add_filter('filter type', 'your function', 10, 2); Where the last number '2' is the number of parameters passed to the function.
// 3. The cmb2_override_meta_save is called only once on the clicking of the Update button.
// 4. The cmb2_override_meta_value is called repeatly. One time for every field you are using, plus one. If accessing your database and you have 30 fields, this will hit your
// database 31 times. There for I have written some code to use past database reads and use that UNTIL the last read (fieldcnt +1). You will need to write this code in the
// The cmb2_override_meta_value function call or let it hit your database.
//
// ASSUMPTIONS
// This piece of code assumes the following:
//
// 1. you are only using this work tool for ONE
// 2. repeatable records
// 3. Independent table
// 4. This code "builds" the form based on a static array of fields.
// 5. The database is designed to be uses a data storage AND logical tool for knowing what records to delete and add
//
// If you are using this tool, within a theme or plugin
// for more than one type of metabox, then the callbacks shown here will have
// to be unique and probably should follow the designers thinking for inserting an identifcation name
// within the call back function for clarity.
//
// This code demonstrates 2 filters:
// cmb2_override_meta_value : A filter to overide the codes default desire to store all inputed data to the postmeta table in serialized form.
// cmb2_override_meta_save
//
//
//
///////////////////////////////////////////////////////////////////////////////////////
// Return if this code has been called directly
if ( ! defined( 'ABSPATH' ) ) {return; }
//-----------------------------------------------------------------
// ENTRIES OF CUSTOM POST
//
//-----------------------------------------------------------------
function mmd_form()
{
$prefix = 'mmd'; // Custom Post Name
$cmb = new_cmb2_box( array(
'id' => 'mmd_records_test',
'title' => __( 'Test Form', 'mmd' ),
'object_types' => $prefix, // Post type
'context' => 'normal',
'priority' => 'high',
'show_names' => true, // Show field names on the left
) );
$ListId = get_the_ID(); // ListId is the same as the PostId
// Get the data to output
$input_fields = GetTemplateFields();
$StateArray = GetStateNames();
$CountryArray = GetSupportedCountries();
//-------------------------------------------------------------------
// Group Asssignment
$group_field_id = $cmb->add_field( array(
'id' => 'MMDListsRecord',
'type' => 'group',
'description' => __( 'Individual Directory Listings', 'mmd' ),
'options' => array(
'group_title' => __( 'Record {#}', 'mmd' ), // since version 1.1.4, {#} gets replaced by row number
'add_button' => __( 'Add Another Record', 'mmd' ),
'remove_button' => __( 'Remove Record', 'mmd' ),
'sortable' => true,
),
) );
//--------------------- Build the template
for($i=0; $i<sizeof($input_fields); $i++)
{
$Field = $input_fields[$i];
$FieldDesc = $Field['desc']; // For defintions of array field types, the the input_fields Table
$FieldId = $Field['id'];
$FieldType = $Field['type'];
// Using a method that builds a form, based on a table. As such, you will not see hard code attributes. The reasoning behind this is
// that each field may have unique requriements
// if even their type is the same. Therefore we "build" the attributes based on what the table settings indicate are necessary
// for the particular field. In general, this work package (CMB2) uses the assumption of if it is required the string settings
// is "there", if not, then the string does not exist, even if it is set to blank, some fields, like readonly, will make the field readonly.
// It is simular to the old HTML input scripting.
$Attributes=array();
if($Field['required'] == 1 && $Field['readonly'] == 1)
$Attributes = array('required' => 'required', 'readonly' => 'readonly');
if($Field['required'] == 0 && $Field['readonly'] == 1)
$Attributes = array('readonly' => 'readonly');
if($Field['required'] == 1 && $Field['readonly'] == 0)
$Attributes = array('required' => 'required');
switch($Field['type'])
{
case 'text':
$cmb->add_group_field( $group_field_id, array(
'name' => $FieldDesc,
'id' => $FieldId,
'type' => 'text',
'on_front' => false,
'attributes' => $Attributes,
'default_cb' => 'SetTextBox',
) );
break;
case 'text_small':
$cmb->add_group_field( $group_field_id, array(
'name' => $FieldDesc,
'id' => $FieldId,
'type' => 'text_small',
'on_front' => false,
'attributes' => $Attributes
) );
break;
case 'textarea':
$cmb->add_group_field( $group_field_id, array(
'name' => $FieldDesc,
'id' => $FieldId,
'type' => 'textarea_small',
'on_front' => false,
'attributes' => $Attributes
) );
break;
case 'select_cnt':
$SupportedCountries = mmd_lists_GetSupportedCountries();
$cmb->add_group_field( $group_field_id, array(
'name' => $FieldDesc,
'id' => $FieldId,
'type' => 'select',
'on_front' => false,
'attributes' => $Attributes,
'options' => $SupportedCountries,
) );
break;
case 'select_st':
$StateArray = mmd_lists_GetStateNames();
$cmb->add_group_field( $group_field_id, array(
'name' => $FieldDesc,
'id' => $FieldId,
'type' => 'select',
'on_front' => false,
'attributes' => $Attributes,
'options' => $StateArray,
) );
break;
case 'date':
$cmb->add_group_field( $group_field_id, array(
'name' => $FieldDesc,
'id' => $FieldId,
'on_front' => false,
'type' => 'text_date',
'attributes' =>$Attributes,
//'default_cb' => 'time' // Only works for a complete blank time.
) );
break;
case 'checkbox':
$cmb->add_group_field( $group_field_id, array(
'name' => $FieldDesc,
'id' => $FieldId,
'on_front' => false,
'type' => 'checkbox',
'attributes' =>$Attributes,
'default' => '',
) );
break;
}
unset ($Attributes); // Clear out the data for the next round
} //for($i=0; $i<sizeof($input_fields); $i++)
} // function mmd_listings_add_manual_form()
add_action( 'cmb2_admin_init', 'mmd_form' );
///////////////////////////////////////////////////////////////////////////////////
// Draw Data
///////////////////////////////////////////////////////////////////////////////////
add_filter('cmb2_override_MMDListsRecord_meta_value', 'mmd_get_methods_custom_data', 10, 2);
function mmd_get_methods_custom_data( $dont_override, $PostId )
{
$DatabaseData = array();
$DatabaseData = GetDataBaseRecords($PostId); // YOu will have to write your own version of this function
$TotalRecords = $DatabaseData[0];
$Records = array(); // Data to be returned back to the form
$DBIndex = 1; // The database record starts with record 0 as the total number of records. So the data starts at record 1.
for($RecordIndex=0; $RecordIndex < $TotalRecords; $RecordIndex++)
{
$RowData = $DatabaseData[$DBIndex];
// CBM2 assumes that checkboxes values are on/off. In particular only "on" or nothing there. To fix the checkbox situation
// from a classic indicator of 1 or 0, this code checks for the database state and outputs the string information based on CBM2 requirement.
// Fix the checkboxes from a 0/1 to on or off
$FeaturedCkBox = '';
if($RowData['Featured'] == 1)
$FeaturedCkBox = 'on';
//CBM2 has a ton of trouble with default date/time indicators as used as default in a MySql table. it displays 0000-00-00 00:00:00 with slashes
// To fix the problem, this example sets the date to a "forever" state of a 1,000 years in the future.
if(strcmp($RowData['PaymentDate'], '0000-00-00 00:00:00')==0)
$RowData['PaymentDate'] = '2025-01-01 00:00:00'; // Can not go above about 5 years or the scripts do not know what to do.
// This is where you actually update the CMB2 fiels.
// Each array, within the master array, is a repeatable record with the form.
$Record[$RecordIndex] = array(
'title' => $RowData['BusinessName'],
'address' => $RowData['BusinessAddress'],
'city' => $RowData['BusinessCity'],
'state' => $RowData['BusinessState'],
'postcode' => $RowData['BusinessZip'],
'country' => $RowData['BusinessCountry'],
'phone' => $RowData['BusinessPhone'],
'email' => $RowData['Email'],
'paymentdate' => $RowData['PaymentDate'],
'featured' => $FeaturedCkBox,
'dbid' => $RowData['id'],
);
$Records[] = $Record[$RecordIndex]; // CBM2 works off of a zero based record set.
$DBIndex +=1;
}
return $Records;
}
///////////////////////////////////////////////////////////////
// Save the data back to the database
//
// A crazy thing with the CMB2 system is that, when it deletes
// a record in it's cache, it does not send a message to the
// delete call back. Instead, it just delete the information array
// for the cache. SO, a safe is really a two operation function.
// First you have to find the records from the fields that are missing
// This is where a single table design works nicely.
// During the draw process, you place the database record id number in the form, either by hidden value
// or for display in the form, in a readonly state. Then when you save the data, you have a map
// of all the records that are still being stored and which records needs to be deleted from the table.
// containing the unqiue database id.
//
//////////////////////////////////////////////////////////////
add_filter('cmb2_override_MMDListsRecord_meta_save', 'save_custom_data', 10, 2);
function save_custom_data($data, $values )
{
$PostId = get_the_ID();
$DatabaseData = GetDataBaseRecords($PostId); // Array of records
$FieldData = $values['value']; // Access to the data contained within the fields.
//-----------------------------------------------
// First check if any record(s) were deleted
//array_diff ($DatabaseData,
$FieldBaseAvaiableRecords = array();
$DBBaseAvaiableRecords = array();
$DBDeleteRecords = array();
$FieldData = $values['value'];
// Store all records by db id into an array
for($i=0; $i<$DatabaseData[0]; $i++)
$DBBaseAvaiableRecords[$i] = $DatabaseData[$i+1]['id']; // Database records start at 0
// STore all records by db id that remain
$RecordId=0;
while(1==1)
{
$Record = $FieldData[$RecordId];
if (empty($Record))
break;
if(strlen( $Record['dbid'])>0) // No id means new entry
$FieldBaseAvaiableRecords[$RecordId] = $Record['dbid'];
$RecordId ++;
} //while(1==1)
// Are there any records to be deleted? There will be more in the database, than in the fields
// Yes, find out what
$DeletedRecords=array_diff($DBBaseAvaiableRecords, $FieldBaseAvaiableRecords);
if(sizeof($DeletedRecords)>0)
{
$DeleteRecordKeys = array_keys($DeletedRecords);
for($i=0; $i<sizeof($DeletedRecords); $i++)
{
$Key = $DeleteRecordKeys[$i];
$FinalDeleteList[$i] = $DeletedRecords[$Key];
}
delete_records($PostId, $FinalDeleteList);
}
//---------------------------------------
// Save all the field arra
$RecordId=0;
while(1==1)
{
$Record = $FieldData[$RecordId];
if (empty($Record))
break; // Stop when there are no more records in the cache
$DBRecordSet['ListId'] = $PostId;
// These are th eonly records save by manual entry
$DBRecordSet['Email'] = $Record['email'];
$DBRecordSet['BusinessName'] = $Record['title'];
$DBRecordSet['BusinessAddress'] = $Record['address'];
$DBRecordSet['BusinessCity'] = $Record['city'];
$DBRecordSet['BusinessState'] = $Record['state'];
$DBRecordSet['BusinessZip'] = $Record['postcode'];
$DBRecordSet['BusinessCountry'] = $Record['country'];
$DBRecordSet['BusinessPhone'] = $Record['phone'];
$DBRecordSet['PaymentDate'] = $Record['paymentdate'];
$DBRecordSet['Featured'] = 0;
if( array_key_exists( 'featured', $Record ) )
{
if(strlen($Record['featured']) > 0)
$DBRecordSet['Featured'] = 1;
}
if(strlen( $Record['dbid'])>0)
UpdateRecord($PostId, $Record['dbid'], $DBRecordSet); // This record already exists, update it.
else
NewRecord($PostId, $DBRecordSet); // New record, insert it.
$RecordId ++;
} //while(1==1)
return true;
}
// THIS FUNCTION NEVER APPEARS TO BE CALLED, EVEN WITH A REMOVE
add_filter('cmb2_override_MMDListsRecord_meta_remove', 'remove_custom_data', 10, 2);
function remove_custom_data($data, $values)
{
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// TEMPLATE CARD DATA
/////////////////////////////////////////////////////////////////////////////////////////////////
function GetTemplateFields()
{
$input_fields = array(
array( 'id'=>'title', 'type'=>'text', 'desc'=>'Title: ', 'required'=>1, 'readonly'=> 0, 'PH'=>'Business Name' ),
array( 'id'=>'address', 'type'=>'text', 'desc'=>'Address: ', 'required'=>1, 'readonly'=> 0, 'PH'=>'Business Address' ),
array( 'id'=>'city', 'type'=>'text', 'desc'=>'City: ', 'required'=>1, 'readonly'=> 0, 'PH'=>'Business City' ),
array( 'id'=>'state', 'type'=>'select_st', 'desc'=>'State: ', 'required'=>1, 'readonly'=> 0, 'PH'=>'' ),
array( 'id'=>'country', 'type'=>'select_cnt', 'desc'=>'Country: ', 'required'=>1, 'readonly'=> 0, 'PH'=>'' ),
array( 'id'=>'postcode', 'type'=>'text_small', 'desc'=>'Post/Zip Code: ','required'=>0, 'readonly'=> 0, 'PH'=>'Post Code' ),
array( 'id'=>'phone', 'type'=>'text', 'desc'=>'Phone: ', 'required'=>0, 'readonly'=> 0, 'PH'=>'Business Phone' ),
array( 'id'=>'email', 'type'=>'text', 'desc'=>'Email: ', 'required'=>1, 'readonly'=> 0, 'PH'=>'Business Email' ),
array( 'id'=>'paymentdate', 'type'=>'date', 'desc'=>'Expire Date: ', 'required'=>0, 'readonly'=> 0, 'PH'=>'' ),
array( 'id'=>'featured', 'type'=>'checkbox', 'desc'=>'Featured', 'required'=>0, 'readonly'=> 0, 'PH'=>'' ),
array( 'id'=>'dbid', 'type'=>'text_small', 'desc'=>'DBId: ', 'required'=>0, 'readonly'=> 1, 'PH'=>'' ),
);
return $input_fields;
}
function GetStateNames()
{
$StateArray = array('PICK A STATE'=> __( 'PICK A STATE', 'mmd' ),
'Alabama'=> __( 'Alabama', 'mmd' ),
'Alaska'=> __( 'Alaska', 'mmd' ),
'Arizona'=> __( 'Arizona', 'mmd' ),
'Arkansas'=> __( 'Arkansas', 'mmd' ),
'California'=> __( 'California', 'mmd' ),
'Colorado'=> __( 'Colorado', 'mmd' ),
'Connecticut'=> __( 'Connecticut', 'mmd' ),
'Delaware'=> __( 'Delaware', 'mmd' ),
'District of Columbia'=> __( 'District of Columbia', 'mmd' ),
'Florida'=> __( 'Florida', 'mmd' ),
'Georgia'=> __( 'Georgia', 'mmd' ),
'Hawaii'=> __( 'Hawaii', 'mmd' ),
'Idaho'=> __( 'Idaho', 'mmd' ),
'Illinois'=> __( 'Illinois', 'mmd' ),
'Indiana'=> __( 'Indiana', 'mmd' ),
'Iowa'=> __( 'Iowa', 'mmd' ),
'Kansas'=> __( 'Kansas', 'mmd' ),
'Kentucky'=> __( 'Kentucky', 'mmd' ),
'Louisiana'=> __( 'Louisiana', 'mmd' ),
'Maine'=> __( 'Maine', 'mmd' ),
'Maryland'=> __( 'Maryland', 'mmd' ),
'Massachusetts'=> __( 'Massachusetts', 'mmd' ),
'Michigan' => __( 'Michigan', 'mmd' ),
'Minnesota'=> __( 'Minnesota', 'mmd' ),
'Mississippi'=> __( 'Mississippi', 'mmd' ),
'Missouri'=> __( 'Missouri', 'mmd' ),
'Montana'=> __( 'Montana', 'mmd' ),
'Nebraska'=> __( 'Nebraska', 'mmd' ),
'Nevada'=> __( 'Nevada', 'mmd' ),
'New Hampshire'=> __( 'New Hampshire', 'mmd' ),
'New Jersey'=> __( 'New Jersey', 'mmd' ),
'New Mexico'=> __( 'New Mexico', 'mmd' ),
'New York'=> __( 'New York', 'cmb2' ),
'North Carolina'=> __( 'North Carolina', 'mmd' ),
'North Dakota'=> __( 'North Dakota', 'mmd' ),
'Ohio'=> __( 'Ohio', 'mmd' ),
'Oklahoma'=> __( 'Oklahoma', 'mmd' ),
'Oregon'=> __( 'Oregon', 'mmd' ),
'Pennsylvania'=> __( 'Pennsylvania', 'mmd' ),
'Rhode Island'=> __( 'Rhode Island', 'mmd' ),
'South Carolina'=> __( 'South Carolina', 'mmd' ),
'South Dakota'=> __( 'South Dakota', 'mmd' ),
'Tennessee'=> __( 'Tennessee', 'mmd' ),
'Texas'=> __( 'Texas', 'mmd' ),
'Utah'=> __( 'Utah', 'mmd' ),
'Vermont'=> __( 'Vermont', 'mmd' ),
'Virginia'=> __( 'Virginia', 'mmd' ),
'Washington'=> __( 'Washington', 'mmd' ),
'West Virginia'=> __( 'West Virginia', 'mmd' ),
'Wisconsin'=> __( 'Wisconsin', 'mmd' ),
'Wyoming'=> __( 'Wyoming', 'mmd' ),
'--'=> __( '--', 'mmd' ),
'Nunavut'=> __( 'Nunavut', 'mmd' ),
'Quebec'=> __( 'Quebec', 'mmd' ),
'Northwest Territories'=> __( 'Northwest Territories', 'mmd' ),
'Ontario'=> __( 'Ontario', 'mmd' ),
'British Columbia'=> __( 'British Columbia', 'mmd' ),
'Alberta'=> __( 'Alberta', 'mmd' ),
'Saskatchewan'=> __( 'Saskatchewan', 'mmd' ),
'Manitoba'=> __( 'Manitoba', 'mmd' ),
'Yukon'=> __( 'Yukon', 'mmd' ),
'Newfoundland and Labrador'=> __( 'Newfoundland and Labrador', 'mmd' ),
'New Brunswick'=> __( 'New Brunswick', 'mmd' ),
'Nova Scotia'=> __( 'Nova Scotia', 'mmd' ),
'Prince Edward Island'=> __( 'Prince Edward Island', 'mmd' ), );
return $StateArray;
}
function GetSupportedCountries()
{
$CountryArray = array('United States' => __( 'United States', 'mmd' ),
'Canada' => __( 'MONTHLY DIRECTORY LISTING', 'mmd' ));
return $CountryArray;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment