How do you access Gravity Form fields pre-submission? - forms

I'm trying to access the Gravity Forms fields pre-submission to check for gibberish entries. I've tried pretty much everything on StackOverflow and no luck.
The form is id #1. Some of the 1000 things I've tried include:
GFFormsModel::get_form_meta(1);
GFFormsModel::get_leads(1);
$_POST['input_1']
and reading this:
https://docs.gravityforms.com/gform_pre_submission/
How do I do this?

If you want to fail silently for spam entries, I would recommend using the gform_validation filter and, if you determine that the submission is spam, dynamically enable the honey pot. Gravity Forms will then fail the submission silently. The confirmation will be displayed but the entry will not be created.
Here's how my GP Blacklist plugin handles this:
$honeypot_field_id = GFFormDisplay::get_max_field_id( $form ) + 1;
$_POST[ "input_{$honeypot_field_id}" ] = true;
$form['enableHoneypot'] = true;
$result['is_valid'] = true;
EDIT
To actually retrieve the value, the simplest way is to just fetch it from the $_POST (FYI, there are more thorough techniques). Here's a more robust example.
add_filter( 'gform_validation', function( $result ) {
// Get the value of field ID 1
$value = rgpost( 'input_1' );
if( $value == 'gibberish' ) {
// activate honeypot
}
return $result;
} );

Related

MailChimp Campaign Content Update

MailChimp campaign content docs - https://developer.mailchimp.com/documentation/mailchimp/reference/campaigns/content
I'm trying to replace some placeholders in a campaign content with actual values via the API. At first, I thought there might be some syntax errors or internal logic errors like non-unique mc:edits into a mc:repeatable that would get the HTML refused/declined by MailChimp, hence the update not taking place, however, that was not the case.
Tried replacing html with a simple <p>test</p> and it was still not working.
Here are a couple of local logs, I'll use xyz as my campaign id:
2018-02-26 16:26:13 [::1][9804][-][warning][application] calling GET /campaigns/xyz/content []
2018-02-26 16:26:13 [::1][9804][-][warning][application] got both plain_text and html versions of content
2018-02-26 16:26:13 [::1][9804][-][warning][application] calling PUT /campaigns/xyz/content {"html":"<p>test</p>"}
2018-02-26 16:26:14 [::1][9804][-][warning][application] got response [
'plain_text' => 'test' + other MailChimp stuff such as footer, that were appended automatically by MailChimp,
'html' => '<p>test</p>'
]
// calling GET immediately after PUT in order to see if any update occurred
2018-02-26 16:26:14 [::1][9804][-][warning][application] calling GET /campaigns/xyz/content []
2018-02-26 16:26:14 [::1][9804][-][warning][application] got updated html (my "test" paragraph + auto footer from MailChimp) and proper plain_text
Everything looks fine according to these, that means both versions updated as they were supposed to. However, on the next API/MailChimp dashboard request, it displays the old HTML content, preserving the update I've just made in the plain text version only.
No errors, nothing to look into. It could be any internal MailChimp behaviour.
PS: I know about Setting Mailchimp campaign content html not working or MailChimp API v3 campaign content template sections, but none of the answers provided to those are helpful.
PS2: I know I should contact MailChimp, but according to
Our MailChimp Support Team isn't trained at in-depth API troubleshooting. If you need a developer to help you configure something using the API, check out our great Experts Directory, which lists third-party MailChimp experts who can be hired to help out.
they don't provide support for API troubleshooting.
MailChimp doesn't allow updating the campaign's HTML content because the campaign type is based on a template.
In order to update the HTML content, the campaign has to be set to custom HTML instead of a template. You can check the type by sending a GET API request to /campaigns or /campaigns/{campaign_id} and finding the content_type attribute in the response (documentation).
Alternatively, in the dashboard, the type can be determined by editing the design of the email. Anything under 'Code your own' is HTML and templates are of course templates.
I'm not entirely sure why the first response to a PUT request on a template campaign shows the updated content, but changing the content type should let you update as you want to.
Hope this helps!
If anyone's still looking for an answer to this.
I managed to solve the issue several weeks ago without creating the campaign via API, but actually updating it.
I used placeholders like [product_card id=123], 3 cards per block/row, all repeatable, which are wrapped in a class that I named product-card. In the MailChimp dashboard, you may still see the placeholders, but on preview and any form of preview like thumbnail, it will display correctly.
On the server, I crawl through the campaign's content, "detect" section names based on how they seemed to me in MailChimp and update each section with the content that I want.
PHP snippet below, some Yii2 stuff, but mostly plain PHP. I use $preview to display a preview of how the template would look, I know it's not visible in the function.
/**
* #param $id - Id of the campaign
* #param $s - Whether to save or just preview
*
* #return bool
*/
function changeContent($id, $s = false)
{
$mcCampaign = new McCampaign();
$mcCampaign::$itemId = $id;
$content = $this->api->get("/campaigns/{$id}/content");
if (!isset($content['html'])) return false;
$template = $content['html'];
$forgedDom = new \DOMDocument();
$forgedDom->loadHTML($template);
$mcSections = [];
$finder = new \DOMXPath($forgedDom);
$nodes = $finder->query('//td[contains(#class, "product-card")]');
// disabling this shit in order to avoid strict errors
libxml_use_internal_errors(true);
$mcEditId = 1;
$mcEditIndex = 0;
foreach ($nodes as $key => $node) {
/** #var \DOMElement $node */
$textContent = $node->textContent;
if (!preg_match("#\[product_card id=\d+\]#", $textContent)) continue;
$productId = filter_var($textContent, FILTER_SANITIZE_NUMBER_INT);
$node->textContent = false;
$product = Product::findOne($productId);
$productDetails = $product ? $this->renderPartial('/partials/_mc-product', [
'product' => $product
]) : 'Product not found.';
if ($key != 0) {
if ($key % 3 == 0) {
$mcEditId = 1;
$mcEditIndex++;
} else {
$mcEditId++;
}
}
$mcSections["repeat_1:{$mcEditIndex}:product_card_{$mcEditId}"] = $productDetails;
$fragment = $forgedDom->createDocumentFragment();
$fragment->appendXML($productDetails);
$node->appendChild($fragment);
}
libxml_use_internal_errors(false);
$preview = $forgedDom->saveHTML();
// just in case
/* $preview = str_replace(["\n", "\t", "\r"], "", $preview); */
if ($s) {
if (!empty($mcSections)) {
$res = $this->api->put("/campaigns/{$id}/content", [
'template' => [
'id' => *template_id_here*,
'sections' => $mcSections
],
]);
// forcing Mc to rebuild cache
$this->api->get("/campaigns/{$id}/content");
Yii::$app->session->setFlash('success', 'Done.');
return $this->redirect(['campaign/index']);
} else {
Yii::$app->session->setFlash('error', 'Something went wrong.');
}
}
}

Get Line Items in an Invoice logic hook in SuiteCRM

Via a logic hook I'm trying to update fields of my products, after an invoice has been saved.
What I understand so far is, that I need to get the invoice related AOS_Products_Quotes and from there I could get the products, update the required fields and save the products. Does that sound about right?
The logic hook is being triggered but relationships won't load.
function decrement_stocks ( $bean, $event, $arguments) {
//$bean->product_value_c = $bean->$product_unit_price * $bean->product_qty;
$file = 'custom/modules/AOS_Invoices/decrement.txt';
// Get the Invoice ID:
$sInvoiceID = $bean->id;
$oInvoice = new AOS_Invoices();
$oInvoice->retrieve($sInvoiceID);
$oInvoice->load_relationship('aos_invoices_aos_product_quotes');
$aProductQuotes = $oInvoice->aos_invoices_aos_product_quotes->getBeans();
/*
$aLineItemslist = array();
foreach ($oInvoice->aos_invoices_aos_product_quotes->getBeans() as $lineitem) {
$aLineItemslist[$lineitem->id] = $lineitem;
}
*/
$sBean = var_export($bean, true);
$sInvoice = var_export($oInvoice, true);
$sProductQuotes = var_export($aProductQuotes, true);
$current = $sProductQuotes . "\n\n\n------\n\n\n" . $sInvoice . "\n\n\n------\n\n\n" . $sBean;
file_put_contents($file, $current);
}
The invoice is being retrieved just fine. But either load_relationship isn't doing anything ($sInvoice isn't changing with or without it) and $aProductQuotes is Null.
I'm working on SuiteCRM 7.8.3 and tried it on 7.9.1 as well without success. What am I doing wrong?
I'm not familiar with SuiteCRM specifics, however I'd always suggest to check:
Return value of retrieve(): bean or null?
If null, then no bean with the given ID was found.
In such case $oInvoice would stay empty (Your comment suggests that's not the case here though)
Return value of load_relationship(): true (success) or false (failure, check logs)
And I do wonder, why don't you use $bean?
Instead you seem to receive another copy/reference of $bean (and calling it $oInvoice)? Why?
Or did you mean to receive a different type bean that is somehow connected to $bean?
Then its surely doesn't have the same id as $bean, unless you specifically coded it that way.

cakephp form validation for counting multiple textareas as a group

Is it possible to validate a group of form textareas at once? I would like to check that at least 5 out of 15 text areas are notEmpty. Any suggestions on a method for doing this?
If you're going to down vote, explain why.
I've read http://book.cakephp.org/view/150/Custom-Validation-Rules#Adding-your-own-Validation-Methods-152 but it isn't clear to me how I would group multiple field items together and only check for a minimum of 5 notEmpty cases.
Edit: I'm using version 2.3.7
I don't really have any code to show because I'm just trying to do a data validation on a form with many textareas. My form isn't working right now due to other issues. If this was the only problem I could post all the code, but right now it would just confuse matters. I'm looking for a descriptive answer of how to validate a group of fields together.
Attach the validation rule to one textarea
You can do this by attaching the validation rule to any one of the text areas e.g.
class Foo extends AppModel {
public $validate = array(
'textarea_1' => array(
'atLeast5' => array(
'rule' => array('validate5Textareas'),
'message' => 'Please put text in at least 5 of the little boxes'
)
)
);
public function validate5Textareas() {
$filledTextAreas = 0;
// adapt this to match the names/logic of the real form
for ($i = 1; $i <= 15; $i++) {
if (!empty($this->data[$this->alias]['textarea_' . $i])) {
$filledTextAreas++;
}
}
return $filledTextAreas >= 5;
}
}
The $validate array defines a rule such that validate5Textareas is called if textarea_1 is in the data passed to save.
The function validate5Textareas will return true if 5 or more have text in them and false otherwise.

Form not passing through hidden field value correctly in Facebook, but ok elsewhere

I have a script that first checks if the form page is referred from a GET parameter
$page['is_referred'] = isset($_GET['rfr']) ? 1 : 0;
Then that set value is used as the value of a hidden field in a Symfony2 form
$form = $app['form.factory']->createBuilder('form', $data)
//....
->add('referred', 'hidden', array(
'data' => $page['is_referred'],
))
->getForm();
then
if ('POST' === $request->getMethod()) {
$form->bindRequest($request);
if ($form->isValid()) {
//...
$referred = $form->get('referred')->getData();
$msg_referer = "Thanks from Referrer";
$msg_noreferer = "Thanks";
if( 1 == $referred ){
$page['thanks'] = $msg_referer;
}else{
$page['thanks'] = $msg_noreferer;
}
//..
}
}
The hidden field is read okay on the main site when ?rfr is in the url, but when iframed in Facebook and ?rfr is present, the $msg_noreferer message is displayed instead. Any ideas as to why this happens?
EDIT: I should also mention that the field value is correctly set in Facebook when I check the source, but it just doesn't seem to post the data through correctly
I should also mention that the field value is correctly set in Facebook when I check the source, but it just doesn't seem to post the data through correctly
I’d say, the value is posted alright, but the form validation does not accept it, because the hidden field does not get added to the form on validation.
Have you checked this, if the code adding the hidden field to the form is actually being executed when handling the POST request received from the browser …?

Zend Framework email content generation

With Zend_Framework, I wondered what is considered best practice for building up the content to send in a HTML email. In my case the content of the email that is sent is determined by a number of factors, for example the number of returned rows for a specific value in the database. Because of this it makes sense for me that the content is built up within the controller that sends the email which talks to the relevant database models and determines what the content should be. Where i'm not sure this works is that our designers and copyrighters will often want to adjust the copy in emails and this would then require them to make changes to a model or ask me to. Should i be handling this differently? Should i perhaps be storing HTML snippets somewhere containing the different text and then calling these somehow?
EDIT following from the answer by fireeyedboy, would it be acceptable to do something like this. Create a folder inside views called "partials" and use this to store text/html snippets that i can then call in where i need and replace special strings with dynamic values using regexp(or similar).
$nview = new Zend_View();
$nview->setScriptPath(APPLICATION_PATH.'/views/partials/');
$bodytext = $nview->render('response.phtml');
$mail = new Zend_Mail();
$mail->setBodyText($bodytext);
// etc ...
e.g. in this context where two different templates could be used depending on variables returned from a Model:
// within a controller
public function emailAction()
{
$images = new Model_ApplicationImages();
$totimages = count($images->fetchImages($wsid));
$acceptedImages = $images->fetchImages($wsid,'approved');
$accepted = count($acceptedImages);
$rejectedImages = $images->fetchImages($wsid,'rejected');
$rejected = count($rejectedImages);
$response = ($rejected == $totimages)?'rejected':'approved';
$nview = new Zend_View();
$nview->setScriptPath(APPLICATION_PATH.'/views/partials/');
$content = $nview->render($response.'.phtml');
$mail = new Zend_Mail();
$mail->setBodyText($content);
// etc
}
Is there a more elegant way i can/should be doing this?
Not sure if this is best practice, but what I did is extend Zend_Mail with methods like these:
setTemplatePath( $templatePath );
setTemplateHtml( $templateHtml );
setTemplateText( $templateText );
setTemplateArguments( array $templateArguments );
...then at some point in my overwrittensend() I do:
$view = new Zend_View();
$view->setScriptPath( $this->_templatePath );
foreach( $this->_templateArguments as $key => $value )
{
$view->assign( $key, $value );
}
if( null !== $this->_templateText )
{
$bodyText = $view->render( $this->_templateText );
$this->setBodyText( $bodyText );
}
if( null !== $this->_templateHtml )
{
$bodyHtml = $view->render( $this->_templateHtml );
$this->setBodyHtml( $bodyHtml );
}
So to utilize this you would do something like:
$mail = new My_Extended_Zend_Mail();
$mail->setTemplatePath( 'path/to/your/mail/templates' );
$mail->setTemplateHtml( 'mail.html.phtml' );
$mail->setTemplateText( 'mail.text.phtml' );
$mail->setTemplateArguments(
'someModel' => $someFunkyModel,
/* etc, you get the point */
)
$mail->send();
In other words, with this you can let your designers and copywriters simply edit views (templates) like they are used to already. Hope this helps and has inspired you to come up with something funky that suits your needs.
PS:
Since you mention arbitrary data rows, you can, for instance, utilize the partialLoop view helper that comes with ZF for this. But you probably were aware of this already?
PPS:
I actually agree with chelmertz' comment about not extending Zend_Mail but wrapping it in my own component.