CakePHP deep (multiple related models) validation? - associations

I have a model structure that is:
Organization belongsTo Address belongsTo CountryCode
So, Organization has foreign keys: mailing_address_id and physical_address_id
Address has foreign key: country_code_id
In the Organization model, relationship is defined as:
public $belongsTo = array(
'MailingAddress' => array('className'=>'Address', 'foreignKey'=>'mailing_address_id')
, 'PhysicalAddress' => array('className'=>'Address', 'foreignKey'=>'physical_address_id')
);
This appears to be working great - validation is functioning properly, etc.
In the Address model, relationship is defined as:
public $belongsTo = array(
'CountryCode' => array('className'=>'CountryCode', 'foreignKey'=>'country_code_id')
);
In my OrganizationsController, in a function to create a new organization, I'm testing validation using this code:
if($this->Organization->saveAll(
$data, array('validate'=>'only')
)) {
// Validates
$this->DBG('Org validated.');
} else {
// does not validate
$this->DBG('Org NOT NOT NOT validated.'.print_r($this->Organization->invalidFields(),true));
}
The $data array looks like this going into validation.
2015-06-08 21:03:38 Debug: Array
(
[Organization] => Array
(
[name] => Test Organization
)
[MailingAddress] => Array
(
[line1] => 100 Main Street
[line2] =>
[city] => Houston
[state] => TX
[postal_code] => 77002
[CountryCode] => Array
(
[name] => United St
)
)
[PhysicalAddress] => Array
(
[line1] => 100 Main Street
[line2] =>
[city] => Houston
[state] => TX
[postal_code] => 77002
[CountryCode] => Array
(
[name] => United St
)
)
)
The country code SHOULD NOT validate with the rules I have set in the CountryCode model:
public $validate = array(
'name' => array(
'nonemptyRule' => array(
'rule' => 'notEmpty'
,'required' => 'create'
,'message' => 'Must be provided.'
)
,'dupeRule' => array(
'rule' => array('isUnique', array('name','code'), false)
,'message' => 'Duplicate'
)
)
,'code' => array(
'rule' => 'notEmpty'
,'required' => 'create'
,'message' => 'Must be provided.'
)
);
However, validation PASSES on Organization->saveAll.
Also, if I attempt to access the CountryCode model from the OrganizationController, it's not loaded.
As in:
$this->Organization->MailingAddress->CountryCode->invalidate('name','Invalid!');
In that case, I get an error that CountryCode is null.
Any ideas why CountryCode wouldn't be validating or loaded?
Is validation supposed to work two steps away?

It turns out, there IS a deep option when validating (and saving). It's documented here with the saveAll options:
http://book.cakephp.org/2.0/en/models/saving-your-data.html
So, the validation block in the question works perfectly well if you include the deep option like so:
if($this->Organization->saveAll(
$data, array('validate'=>'only', 'deep'=>true)
)) {
// Validates
$this->DBG('Org validated.');
} else {
// does not validate
$this->DBG('Org NOT NOT NOT validated.'.print_r($this->Organization->invalidFields(),true));
}

Related

Laravel cast json to array

The client attribute is a json filed in the database, which I caste as array in my model. However by retuning the Model to an array, the client attribute remains as json string:
$provider = TokenCacheProvider::all([
'name', 'auth_url', 'token_url', 'auth_endpoint', 'client'
])
->keyBy('name')
->toArray();
output:
Array
(
[name] => azure_ad
[auth_url] => /oauth2/v2.0/authorize
[token_url] => /oauth2/v2.0/token
[auth_endpoint] => https://login.microsoftonline.com/
[client] => {"tenant":"some value","client_id":"some value","client_secret":"some value"}
)
I would expect and output like this:
Array
(
[name] => azure_ad
[auth_url] => /oauth2/v2.0/authorize
[token_url] => /oauth2/v2.0/token
[auth_endpoint] => https://login.microsoftonline.com/
[client] => Array
(
[tenant] => 'some value'
[client_id] => 'some value'
[client_secret] => 'some value'
[scope] => 'some value'
)
)
My Model has the corresponding $casts:
protected $casts = [
'client' => 'array',
];
Edit
This comes a little bit closer to what I need, but it returns only the clients attribute 😵‍💫
$provider = TokenCacheProvider::all([
'name', 'auth_url', 'token_url', 'auth_endpoint', 'client'
])
->keyBy('name')
->map(function ($item) {
return json_decode($item->client);
})
->toArray();
Resolved 😎
$provider = TokenCacheProvider::all([
'name', 'auth_url', 'token_url', 'auth_endpoint', 'client'
])
->keyBy('name')
->map(function ($item) {
return collect([
'auth_url' =>$item->auth_url,
'token_url' =>$item->token_url,
'auth_endpoint' =>$item->auth_endpoint,
'client' =>json_decode($item->client,true)
]);
})
->toArray();
Not sure if this is the most beautiful solution but it does exactly what I want.
Output:
Array
(
[auth_url] => /oauth2/v2.0/authorize
[token_url] => /oauth2/v2.0/token
[auth_endpoint] => https://login.microsoftonline.com/
[client] => Array
(
[tenant] => some values
[client_id] => some values
[client_secret] => some values
[scope] => https://graph.microsoft.com/.default
)
)
Feedback and enhancements are highly welcome!
Cheers

Dynamically pull values from nested form using Gravity Forms & Gravity Wiz Nested Forms

So I've been going through a restructure build of an entire site, and part of that involved switching from Formidable Forms to Gravity Forms. We did this because we wanted to use the Nested Form feature, so that we could automate multiple attendees without having to create a new form for each.
Here's the problem - on our old site that did have a separate form per attendee via Formidable, we had a code using the Canvas API to send name + email info to Canvas and automatically register users for the online courses this company offers. In trying to convert sections of this code to work with my nested forms, I'm running into a snag:
The main issue is that the value is being spit out as all of the information from the nested form entry, not by name/ email/ etc.
The info is being spit out twice, perhaps because of the way the forms are structured? There are a couple calculations happening in the forms/ nested forms so I'm chalking it up to that.
[1] => WC_Meta_Data Object
(
[current_data:protected] => Array
(
[id] => 212
[key] => Attendee Registration
[value] =>
Name
Test Name
Email Address
courses#email.com
Cell Phone
(333) 333-3333
Would you like to receive text message reminders for this registration?
No
Post-class notification is required for the following states, please identify if you will be using this class to fulfill any state license requirements:
N/A
You'll receive a hard copy and digital certificate upon course completion. Additional options are available here:
All live classes include a hard copy manual and regulations. To join our effort to save paper, please also add any of the following options to take your books home:
)
[data:protected] => Array
(
[id] => 212
[key] => Attendee Registration
[value] =>
Name
Test Name
Email Address
courses#email.com
Cell Phone
(333) 333-3333
Would you like to receive text message reminders for this registration?
No
Post-class notification is required for the following states, please identify if you will be using this class to fulfill any state license requirements:
N/A
You'll receive a hard copy and digital certificate upon course completion. Additional options are available here:
All live classes include a hard copy manual and regulations. To join our effort to save paper, please also add any of the following options to take your books home:
)
)
Also: I was playing around with grabbing the ID of the main entry via [_gravity_form_linked_entry_id], and grabbing the nested info from that via [_gravity_form_lead].
The best I was able to get from that was this... so yeah kind of lost on how to progress here if anyone has any pointers! Thanks so much!
[data:protected] => Array
(
[id] => 211
[key] => _gravity_forms_history
[value] => Array
(
[_gravity_form_cart_item_key] => 72201a9586fb30895b8fb5cac2a796b9
[_gravity_form_linked_entry_id] => 125
[_gravity_form_lead] => Array
(
[form_id] => 1
[source_url] => https://chcv2.flywheelstaging.com/product/air-monitoring-specialist-live/
[ip] => 75.151.95.41
[42.1] => Course Price
[42.2] => $580.00
[42.3] => 1
[21] => 122
[40.1] => Add-On Fees
[40.2] => $0.00
[40.3] => 1
)
[_gravity_form_data] => Array
(
[id] => 1
[bulk_id] => 0
[display_title] =>
[display_description] =>
[disable_woocommerce_price] => no
[price_before] =>
[price_after] =>
[disable_calculations] => no
[disable_label_subtotal] => yes
[disable_label_options] => yes
[disable_label_total] => no
[disable_anchor] => no
[label_subtotal] => Course Fee
[label_options] => Additional Attendees + Selected Options
[label_total] => Attendee Registration + Add-Ons:
[use_ajax] => no
[enable_cart_edit] => no
[enable_cart_edit_remove] => no
[keep_cart_entries] => no
[send_notifications] => no
[enable_cart_quantity_management] => stock
[cart_quantity_field] =>
[update_payment_details] => yes
[display_totals_location] => after
[structured_data_override] => no
[structured_data_low_price] =>
[structured_data_high_price] =>
[structured_data_override_type] => overwrite
)
)
)
Update: Here's how I've incorporated the code from Rochelle's comment below, where I'm getting an error
add_action( 'woocommerce_thankyou', 'canvas_enroll', 20, 2 );
function canvas_enroll($orders) {
$query = new WC_Order_Query( array(
'orderby' => 'date',
'order' => 'DESC',
'return' => 'ids',
) );
$orders = $query->get_orders();
foreach($orders as $order){
foreach ($order->get_items() as $item_id => $item_data) {
if(isset($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]["form_id"]) && !empty($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]["form_id"])){
$linked_entry=$item_data->get_meta( '_gravity_forms_history')["_gravity_form_linked_entry_id"];
$entry_id = $linked_entry;
$entry = GFAPI::get_entry( $entry_id );//id of Parent Gravity Form
if(isset($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['2']) && !empty($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['2'])){
$linked_nested_value=$item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['2'];
$nested_value_array = preg_split ("/\,/", $linked_nested_value); //array of child entries
$child_entry_amt = substr_count($linked_nested_value, ",") + 1;
if ($child_entry_amt > 0){
for ($n = 0; $n < $child_entry_amt; $n++) {
$entry_id_nest[$n]=$nested_value_array[$n];
$entry_nest[$n] = GFAPI::get_entry( $entry_id_nest[$n] ); //nested form entry
$name[$n] = $entry_nest[$n]['12.3'].''.$entry_nest[$n]['12.6'];//replace 1.3 and 1.6 with nested field id of name
$email[$n] = $entry_nest[$n]['11']; //2 is the GF nested field id of email
}
}
}
}
}
}
}
Finally got this figured out! Something that was super helpful was to echo the item meta data ($value, in my case) for that to display all the ids and such, that's how I was able to figure out that I needed 21 in that ID for the child entries.
I'm not really sure why I had to switch to wc_get_order instead of wc_order_query, but it solved the errors I was getting.
function canvas_enroll($order_id) {
$order = wc_get_order( $order_id);
$order_id = array(
'orderby' => 'date',
'order' => 'DESC',
'return' => 'ids',
);
if(!empty($order) && isset($order)){
// Loop through order line items
foreach( $order->get_items() as $key => $value ){
// get order item data (in an unprotected array)
if(isset($value->get_meta( '_gravity_forms_history')["_gravity_form_lead"]["form_id"]) && !empty($value->get_meta( '_gravity_forms_history')["_gravity_form_lead"]["form_id"])){
$linked_entry=$value->get_meta( '_gravity_forms_history')["_gravity_form_linked_entry_id"];
$entry_id = $linked_entry;
$entry = GFAPI::get_entry( $entry_id );//id of Parent Gravity Form
if(isset($value->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['21']) && !empty($value->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['21'])) { //21 was the id for my child form
$linked_nested_value = $value->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['21'];
$nested_value_array = preg_split ("/\,/", $linked_nested_value); //array of child entries
$child_entry_amt = substr_count($linked_nested_value, ",") + 1;
if ($child_entry_amt > 0){
for ($n = 0; $n < $child_entry_amt; $n++) {
$entry_id_nest[$n]=$nested_value_array[$n];
$entry_nest[$n] = GFAPI::get_entry( $entry_id_nest[$n] ); //nested form entry
$firstname[$n] = $entry_nest[$n]['12.3'];//replace 12.3 with nested field id of first name
$lastname[$n] = $entry_nest[$n]['12.6'];//replace 12.6 with nested field id of last name
$email[$n] = $entry_nest[$n]['11']; //replace 11 with nested field id of email
}
}
}
}
}
}
}
I'm just going to paste the code I used for another project that I needed to pull the same type of data in case it puts you on the right track. You'd have to replace the numbers with the ids from your forms:
$query = new WC_Order_Query( array(
'orderby' => 'date',
'order' => 'DESC',
'return' => 'ids',
) );
$orders = $query->get_orders();
foreach($orders as $order){
foreach ($order->get_items() as $item_id => $item_data) {
if(isset($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]["form_id"]) && !empty($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]["form_id"])){
$linked_entry=$item_data->get_meta( '_gravity_forms_history')["_gravity_form_linked_entry_id"];
$entry_id = $linked_entry;
$entry = GFAPI::get_entry( $entry_id );//id of Parent Gravity Form
if(isset($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['2']) && !empty($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['2'])){
$linked_nested_value=$item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['2'];
$nested_value_array = preg_split ("/\,/", $linked_nested_value); //array of child entries
$child_entry_amt = substr_count($linked_nested_value, ",") + 1;
if ($child_entry_amt > 0){
for ($n = 0; $n < $child_entry_amt; $n++) {
$entry_id_nest[$n]=$nested_value_array[$n];
$entry_nest[$n] = GFAPI::get_entry( $entry_id_nest[$n] ); //nested form entry
$name[$n] = $entry_nest[$n]['1.3'].''.$entry_nest[$n]['1.6'];//replace 1.3 and 1.6 with nested field id of name
$email[$n] = $entry_nest[$n]['2']; //2 is the GF nested field id of email
}
}
}
}
}
}
Ok. Try this first to see if it changes anything:
$query = new WC_Order_Query( array(
'orderby' => 'date',
'order' => 'DESC',
'return' => 'ids',
) );
$orders = $query->get_orders();
foreach($orders as $order){
if(!empty($order) && isset($order)){
foreach ($order->get_items() as $item_id => $item_data) {
if(isset($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]["form_id"]) && !empty($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]["form_id"])){
$linked_entry=$item_data->get_meta( '_gravity_forms_history')["_gravity_form_linked_entry_id"];
$entry_id = $linked_entry;
$entry = GFAPI::get_entry( $entry_id );//id of Parent Gravity Form
if(isset($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['2']) && !empty($item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['2'])){
$linked_nested_value=$item_data->get_meta( '_gravity_forms_history')["_gravity_form_lead"]['2'];
$nested_value_array = preg_split ("/\,/", $linked_nested_value); //array of child entries
$child_entry_amt = substr_count($linked_nested_value, ",") + 1;
if ($child_entry_amt > 0){
for ($n = 0; $n < $child_entry_amt; $n++) {
$entry_id_nest[$n]=$nested_value_array[$n];
$entry_nest[$n] = GFAPI::get_entry( $entry_id_nest[$n] ); //nested form entry
$name[$n] = $entry_nest[$n]['1.3'].''.$entry_nest[$n]['1.6'];//replace 1.3 and 1.6 with nested field id of name
$email[$n] = $entry_nest[$n]['2']; //2 is the GF nested field id of email
}
}
}
}
}
}
}

Parsing cybersourse Response in PHP

I Need to parse the below array and retrieve values;status,id.
Below is the PHP Array
Array ( [0] => CyberSource\Model\PtsV2PaymentsPost201Response Object ( [container:protected] =>
Array ( [links] => CyberSource\Model\PtsV2PaymentsPost201ResponseLinks Object ( [container:protected] =>
Array ( [self] => CyberSource\Model\PtsV2PaymentsPost201ResponseLinksSelf
Object ( [container:protected] => Array ( [href] => /pts/v2/payments/621258854395 [method] => GET ) )
[reversal] => [capture] => [customer] => [paymentInstrument] => [shippingAddress] => [instrumentIdentifier] => ) )
[id] => 621258854395
[submitTimeUtc] => 2021-05-17T13:40:55Z
[status] => AUTHORIZED
[reconciliationId] => 621258854395
[errorInformation] =>...............
Below is my php code
$parseApiResponse = new CyberSource\Model\PtsV2PaymentsPost201Response($apiResponse);
print_r("Status"." ".$parseApiResponse->getStatus());
Please assist in resolving this.
Want to do a similar thing as I am able to do in java as below
String status = result.getStatus();
I hope this helps someone. It took me a while to figure this out. The original poster was so close. It's just...
print_r("Status"." ".$apiResponse[0]->getStatus());
No need to instantiate a new "PtsV2PaymentsPost201Response()". The response element ([0]) has already done that when you call, "createPayment()", etc.
lib/Model/PtsV2PaymentsPost201Response.php lists all the available getters. getId(), getStatus(), getLinks(),etc.
$parseApiResponse = new CyberSource\Model\PtsV2PaymentsPost201Response($apiResponse);
$result = \GuzzleHttp\json_decode( $parseApiResponse[0] , true );
return = $result['links'];

WP_List_Table not properly handling returned data?

So following the WPEngineer guide on WP_List_Table (excellent guide by the way), I managed to put together a basic table for the backend of a plugin I'm working on. Using an 'example' array, it works great. Problem I'm running into is that I can't figure out for the life of me how to replace that sample data with actual data! Adding the query where I thought it was supposed to be results in the query variable having the correct data, but the table still returns no contents. The referenced pastebin is what I have so far... any thoughts?
http://pastebin.com/f0DCacfF
CORRECTION: It IS pulling the data (if I manually add a row to the database, the table count gets updated), but it's displaying a blank table.
NOTE: It seems that the sample data is an array, whereas $wpdb->get_results is returning as a stdClass Object.
Sample data setup:
var $api_key_list = array(
array( 'id' => 1,'userid' => 'Quarter Share', 'key' => 'Nathan Lowell', 'desc' => '978-0982514542' )
);
Sample data return:
Array ( [0] => Array ( [id] => 1 [userid] => 1 [key] => 098f6bcd4621d373cade4e832627b4f6 [desc] => Test API key ) )
Query setup:
$api_key_list_query = "SELECT * from $wpapi_db_table_name";
$this->api_key_list = $wpdb->get_results($api_key_list_query);
Query return:
Array ( [0] => stdClass Object ( [id] => 1 [userid] => 1 [key] => 098f6bcd4621d373cade4e832627b4f6 [desc] => Test API key ) [1] => stdClass Object ( [id] => 2 [userid] => 1 [key] => 098f6bcd4621d373cade4e832627b4f6 [desc] => Test API key 2 ) )
Adding the following solved my problem.
$this->api_key_list = array();
$i = 0;
foreach($api_key_list_return as $obj) {
foreach($obj as $key => $val) {
$this->api_key_list[$i][$key] = $val;
}
$i++;
}

Why are Symfony2 validation propertyPath valus in square brackets?

I am validating some values:
$collectionConstraint = new Collection(array(
'email' => array(
new NotBlank(),
new Email(),
),
'password' => array(
new NotBlank(),
new MinLength(array('limit' => 6)),
new MaxLength(array('limit' => 25)),
),
));
$data = array('email' => $this->getRequest()->get('email'), 'password' => $this->getRequest()->get('password'));
$errors = $this->get('validator')->validateValue($data, $collectionConstraint);
But for some reason the fields (propertyPath) are stored with square brackets - I'd like to understand why Sf does that. I have to manually remove all the brackets which seems absurd so I think I am missing some functionality somewhere.
Dump of $errors:
Symfony\Component\Validator\ConstraintViolationList Object
(
[violations:protected] => Array
(
[0] => Symfony\Component\Validator\ConstraintViolation Object
(
[messageTemplate:protected] => This value should not be blank
[messageParameters:protected] => Array
(
)
[root:protected] => Array
(
[email] =>
[password] =>
)
[propertyPath:protected] => [email]
[invalidValue:protected] =>
)
[1] => Symfony\Component\Validator\ConstraintViolation Object
(
[messageTemplate:protected] => This value should not be blank
[messageParameters:protected] => Array
(
)
[root:protected] => Array
(
[email] =>
[password] =>
)
[propertyPath:protected] => [password]
[invalidValue:protected] =>
)
)
)
Even the toString function is useless.
"[email]: This value should not be blank","[password]: This value should not be blank"
Property paths can map either to properties or to indices. Consider a class OptionBag which implements \ArrayAccess and a method getSize().
The property path size refers to $optionBag->getSize()
The property path [size] refers to $optionBag['size']
In your case, you validate an array. Since array elements are also accessed by index, the resulting property path in the violation contains squared brackets.
Update:
You don't have to manually remove the squared brackets. You can use Symfony's PropertyAccess component to map errors to an array with the same structure as your data, for example:
$collectionConstraint = new Collection(array(
'email' => array(
new NotBlank(),
new Email(),
),
'password' => array(
new NotBlank(),
new MinLength(array('limit' => 6)),
new MaxLength(array('limit' => 25)),
),
));
$data = array(
'email' => $this->getRequest()->get('email'),
'password' => $this->getRequest()->get('password')
);
$violations = $this->get('validator')->validateValue($data, $collectionConstraint);
$errors = array();
$accessor = $this->get('property_accessor');
foreach ($violations as $violation) {
$accessor->setValue($errors, $violation->getPropertyPath(), $violation->getMessage());
}
=> array(
'email' => 'This value should not be blank.',
'password' => 'This value should have 6 characters or more.',
)
This also works with multi-dimensional data arrays. There the property paths will be something like [author][name]. The PropertyAccessor will insert the error messages in the same location in the $errors array, i.e. $errors['author']['name'] = 'Message'.
PropertyAccessor's setValue is no real help because it cannot handle multiple violations for a single field. For instance, a field might be shorter than a constraint length and also contain illegal characters. For this, we would have two error messages.
I had to create my own code:
$messages = [];
foreach ($violations as $violation) {
$field = substr($violation->getPropertyPath(), 1, -1);
$messages[] = [$field => $violation->getMessage()];
}
$output = [
'name' => array_unique(array_column($messages, 'name')),
'email' => array_unique(array_column($messages, 'email')),
];
return $output;
We manually strip the [] characters from the property path and create an
array of arrays of fields and corresponding messages. Later we transform the
array to group the messages by fields.
$session = $request->getSession();
$session->getFlashBag()->setAll($messages);
In the controller, we add the messages to the flash bag.

Resources