ZF2 - Show just one error on forms - forms

I can't seem to get ZF2 to show just one error message for failed form validation messages.
For example, an EmailAddress validator can pass back up to 7 messages and typically shows the following if the user has made a typo:
oli.meffff' is not a valid hostname for the email address
The input appears to be a DNS hostname but cannot match TLD against known list
The input appears to be a local network name but local network names are not allowed
How can I override the error to show something a little more friendly, such as "Please enter a valid email address" instead of specifics like the above?

OK, managed to come up with a solution for this. Instead of using the same string as the error for all validator failures as Sam suggested above, I have overridden the error messages in the InputFilter for the elements and then used a custom form error view helper to show only the first message.
Here is the helper:
<?php
namespace Application\Form\View\Helper;
use Traversable;
use \Zend\Form\ElementInterface;
use \Zend\Form\Exception;
class FormElementSingleErrors extends \Zend\Form\View\Helper\FormElementErrors
{
/**
* Render validation errors for the provided $element
*
* #param ElementInterface $element
* #param array $attributes
* #throws Exception\DomainException
* #return string
*/
public function render(ElementInterface $element, array $attributes = array())
{
$messages = $element->getMessages();
if (empty($messages)) {
return '';
}
if (!is_array($messages) && !$messages instanceof Traversable) {
throw new Exception\DomainException(sprintf(
'%s expects that $element->getMessages() will return an array or Traversable; received "%s"',
__METHOD__,
(is_object($messages) ? get_class($messages) : gettype($messages))
));
}
// We only want a single message
$messages = array(current($messages));
// Prepare attributes for opening tag
$attributes = array_merge($this->attributes, $attributes);
$attributes = $this->createAttributesString($attributes);
if (!empty($attributes)) {
$attributes = ' ' . $attributes;
}
// Flatten message array
$escapeHtml = $this->getEscapeHtmlHelper();
$messagesToPrint = array();
array_walk_recursive($messages, function ($item) use (&$messagesToPrint, $escapeHtml) {
$messagesToPrint[] = $escapeHtml($item);
});
if (empty($messagesToPrint)) {
return '';
}
// Generate markup
$markup = sprintf($this->getMessageOpenFormat(), $attributes);
$markup .= implode($this->getMessageSeparatorString(), $messagesToPrint);
$markup .= $this->getMessageCloseString();
return $markup;
}
}
It's just an extension of FormElementErrors with the render function overridden to include this:
// We only want a single message
$messages = array(current($messages));
I then insert the helper into my application using the solution I posted to my issue here.

Related

How to extend date picker viewhelper of EXT:powermail?

I need to add a custom validator to the datepicker field. By default, this field comes without any validators.
I've already made the validator settings visible in the TCA of tx_powermail_domain_model_field and added my custom validator as usual.
Now I need the attributes data-parsley-customXXX and data-parsley-error-message added to the HTML input field which is usually done via the the viewhelper in ValidationDataAttributeViewHelper.php:
https://github.com/einpraegsam/powermail/blob/develop/Classes/ViewHelpers/Validation/ValidationDataAttributeViewHelper.php#L342
https://github.com/einpraegsam/powermail/blob/develop/Classes/ViewHelpers/Validation/ValidationDataAttributeViewHelper.php#L348
This is the code I need to extend:
https://github.com/einpraegsam/powermail/blob/develop/Classes/ViewHelpers/Validation/DatepickerDataAttributeViewHelper.php#L32
I found a solution for my problem. As suggested in the comment it's possible to extend the Viewhelper:
ext_localconf.php:
$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][\In2code\Powermail\ViewHelpers\Validation\DatepickerDataAttributeViewHelper::class] = [
'className' => \Vendor\MyExt\Powermail\ViewHelpers\Validation\DatepickerDataAttributeViewHelper::class
];
myext/Classes/Powermail/ViewHelpers/Validation/DatepickerDataAttributeViewHelper.php
<?php
declare(strict_types=1);
namespace Vendor\MyExt\Powermail\ViewHelpers\Validation;
use In2code\Powermail\Domain\Model\Field;
use In2code\Powermail\Utility\LocalizationUtility;
class DatepickerDataAttributeViewHelper extends \In2code\Powermail\ViewHelpers\Validation\DatepickerDataAttributeViewHelper
{
/**
* Returns Data Attribute Array Datepicker settings (FE + BE)
*
* #return array for data attributes
*/
public function render(): array
{
/** #var Field $field */
$field = $this->arguments['field'];
$additionalAttributes = $this->arguments['additionalAttributes'];
$value = $this->arguments['value'];
$additionalAttributes['data-datepicker-force'] =
$this->settings['misc']['datepicker']['forceJavaScriptDatePicker'];
$additionalAttributes['data-datepicker-settings'] = $this->getDatepickerSettings($field);
$additionalAttributes['data-datepicker-months'] = $this->getMonthNames();
$additionalAttributes['data-datepicker-days'] = $this->getDayNames();
$additionalAttributes['data-datepicker-format'] = $this->getFormat($field);
if ($value) {
$additionalAttributes['data-date-value'] = $value;
}
if ($field->getValidation() && $this->isClientValidationEnabled()) {
$value = 1;
if ($field->getValidationConfiguration()) {
$value = $field->getValidationConfiguration();
}
$additionalAttributes['data-parsley-custom' . $field->getValidation()] = $value;
$additionalAttributes['data-parsley-error-message'] =
LocalizationUtility::translate('validationerror_validation.' . $field->getValidation());
}
$this->addMandatoryAttributes($additionalAttributes, $field);
return $additionalAttributes;
}
}

Creating an attachment for MailMessage

From my extbase 6.2 extension I want to send different e-mails.
In a controller class I wrote a mail function that looks like this:
(notice no #param for $attachment - see my question at the end)
/**
*
* #param string $to
* #param string $subject
* #param string $email_prefix
* #param string $msg
* #param string $email_suffix
*/
public function mailAction($to, $subject, $email_prefix, $msg, $email_suffix, $attachment = null) {
try {
$from = \TYPO3\CMS\Core\Utility\MailUtility::getSystemFrom();
$body = $email_prefix
. PHP_EOL . PHP_EOL
. $msg
. PHP_EOL . PHP_EOL
. $email_suffix;
$htmlBody = nl2br($body);
$toEmail = $to;
$mail = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Mail\\MailMessage');
$mail->setFrom($from)
->setTo(array($toEmail))
->setSubject($subject)
->setBody($htmlBody, 'text/html');
$mail->addPart($body, 'text/plain');
if ($attachment) {
$mail->attach($attachment);
}
if (empty($toEmail) || strpos($toEmail, '#') === FALSE) {
$this->addFlashMessage('Die Mail konnte nicht verschickt werden! Keine Email-Adresse des Empfängers', '', \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR
);
} else {
if ($mail->send()) {
$this->addFlashMessage('Die Mail für wurde verschickt!', '');
} else {
$this->addFlashMessage('Die Mail konnte nicht verschickt werden!', '', \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR
);
}
}
$this->redirect('list');
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
}
}
In a function that calls the mail function I tried creating an attachment like this but it failed saying: Fatal error: Class 'Swift_Attachment' not found in.../...Controller.php
$attachment = \Swift_Attachment::newInstance()
->setFilename('Termine.html')
->setContentType('text/html')
->setBody($emailView->render());
Then I call the mail function like this:
$this->redirect('mail', null, null, array(
$to,
$subject,
$email_prefix,
$msg,
$email_suffix,
$attachment));
My questions:
How can I successfully create an object of type Swift_Attachment in a controller of my extbase extension (without creating a MailMessage object beforehand and creating the attachment inside of it)?
What should I put after #param as the type of my $attachment variable in my mail function for this to work?
-- EDIT --
Ok, so apparently no one does that because it's not meant to be.
I now used Rene's approach combining it with Dimitri's scalable answer for multiple attachments. My #param is now array because I have to create the actual attachment after instantiating MailMessage - thanks!
In my extension for 6.2.25 ist works without any including:
$email->attach(\Swift_Attachment::newInstance(
$emailView->render(),
'Termine.html',
'text/html'
));
So you should check why your autoload of classes don't work. Have you tried to clear the cache complete?
To your second question: the correct param declaration should be:
#param \Swift_Mime_Attachment $attachment
But I wouldn't make an redirect, but an $this->forward. You don't need an redirection on client side for this. If this action is only called by an other action I also recommend to make it an protected function an call it directly from your action:
$this->sendMail($to, $subject, $email_prefix, $msg, $email_suffix, $attachment)
-- EDIT --
I recommend to use bypass the attachment information to the function to create the attachment object after the SwitftMailer was initialized:
/**
*
* #param string $to
* #param string $subject
* #param string $email_prefix
* #param string $msg
* #param string $email_suffix
* #param array $attachment
*/
public function mailAction($to, $subject, $email_prefix, $msg, $email_suffix, $attachment = null) {
...
if (is_array($attachment) && array_key_exist('content', $attachment) && array_key_exist('filename', $attachment) && array_key_exist('mime', $attachment)) {
$mail->attach(\Swift_Attachment::newInstance($attachment['content'], $attachment['filename'], $attachment['mime']));
}
...
}
In \TYPO3\CMS\Core\Mail\MailMessage there is a require_once for the swiftmailer classes; they don't seem to be autoloaded. Maybe you can pass the attachment as rendered HTML and create the Swift_Attachment object after instantiating the MailMessage object?
If the solution in 1. works it would be a simple string.
As already stated by Jigal van Hemert you can only create the attachment objects after you create the MailMessage object because the class is not autoloaded. I would just pass the attachment as a filepath to your mail function and it should be handled there and not outside.
if ($attachment) {
$email->attach(\Swift_Attachment::fromPath($attachment));
}
In my opinion it makes more sense if you can pass multiple files instead of one, so the $attachment should be an $attachments array
if(count($attachments)) {
foreach ($attachments as $name => $file) {
if(file_exists($file)) {
if(trim($name) && !is_numeric($name)) {
$email->attach(\Swift_Attachment::fromPath($file)->setFilename($name));
} else {
$email->attach(\Swift_Attachment::fromPath($file));
}
}
}
}

Is there a way to remove the return-path that kohana is setting in emails?

Our site uses Kohana and php and we are using sendgrid to send transactional emails. With gmail we're having a ton of spam issues and we only send opt-in emails and have a high open rate. One of the potential issues is that our emails seem to have TWO return-paths in the header:
Is being set by us in Kohana
is being inserted by sendgrid.
Sendgrid says that when they send a message, they take over that 'envelope from' for the sake of handling Bounce management. But we can't figure out a way to have Kohana not insert this. Any suggestions? CODE EXAMPLE:
Kohana uses Swift to send mails. How we send them now is below. We've tried removing reply-to via
$message->headers->set('reply-to', '');
but it doesn't appear to work. Funny enough, setting it to a non-empty value alters it, but there doesn't seem to be a way to get rid of it altogether.
Full code for this function:
/**
* Send an email message.
*
* #param string|array recipient email (and name), or an array of To, Cc, Bcc names
* #param string|array sender email (and name)
* #param string message subject
* #param string message body
* #param boolean send email as HTML
* #param string Reply To address. Optional, default null, which defaults to From address
* #return integer number of emails sent
*/
public static function send($category, $to, $from, $subject, $message, $html = FALSE, $replyto = null)
{
// Connect to SwiftMailer
(email::$mail === NULL) and email::connect();
// Determine the message type
$html = ($html === TRUE) ? 'text/html' : 'text/plain';
// Append mixpanel tracking pixel to html emails
if ($html) {
$mixpanel_token = FD_DEV_MODE ? "08c59f4e26aa718a1038459af75aa559" : "d863dc1a3a6242dceee1435c0a50e5b7";
$json_array = '{ "event": "e-mail opened", "properties": { "distinct_id": "' . $to . '", "token": "' . $mixpanel_token . '", "time": ' . time() . ', "campaign": "' . $category . '"}}';
$message .= '<img src="http://api.mixpanel.com/track/?data=' . base64_encode($json_array) . '&ip=1&img=1"></img>';
}
// Create the message
$message = new Swift_Message($subject, $message, $html, '8bit', 'utf-8');
// Adding header for SendGrid, added by David Murray
$message->headers->set('X-SMTPAPI', '{"category" : "' . $category . '"}');
if (is_string($to))
{
// Single recipient
$recipients = new Swift_Address($to);
}
elseif (is_array($to))
{
if (isset($to[0]) AND isset($to[1]))
{
// Create To: address set
$to = array('to' => $to);
}
// Create a list of recipients
$recipients = new Swift_RecipientList;
foreach ($to as $method => $set)
{
if ( ! in_array($method, array('to', 'cc', 'bcc')))
{
// Use To: by default
$method = 'to';
}
// Create method name
$method = 'add'.ucfirst($method);
if (is_array($set))
{
// Add a recipient with name
$recipients->$method($set[0], $set[1]);
}
else
{
// Add a recipient without name
$recipients->$method($set);
}
}
}
if (is_string($from))
{
// From without a name
$from = new Swift_Address($from);
}
elseif (is_array($from))
{
// From with a name
$from = new Swift_Address($from[0], $from[1]);
}
// Reply To support, not standard in Swift, added by Soham
if (!$replyto) $replyto = $from;
$message->setReplyTo($replyto);
return email::$mail->send($message, $recipients, $from);
}
This is not really a Kohana question but more of a Swiftmailer question, since Swiftmailer comes not standard with the Kohana Framework. According to the Swiftmailer docs you can set/get the Return-Path explicitly:
$message->setReturnPath('bounces#address.tld');
Hopes this helps!
i just wanna say thank you for providing this solution for me, indirectly..
// Adding header for SendGrid, added by David Murray
$message->headers->set('X-SMTPAPI', '{"category" : "INSERT CATEGORY HERE"}');
X-SMTPAPI usage documentation from Sendgrid's Website is sucks..

Zend Framework: Paypal

I have been attempting to implement a paypal functionality into my application by following the example here: http://www.alexventure.com/2011/04/02/zend-framework-and-paypal-api-part-2-of-2/
This is my paymentAction in my controller.
public function paymentAction()
{
$auth= Zend_Auth::getInstance();
$user= $auth->getIdentity();
$username = $user->username;
$cart = new Application_Model_DbTable_Cart();
$select = $cart->select()
->from(array('c' => 'cart'))
->join(array('p' => 'product'), 'p.productid = c.productid')
->where('username = ?', $username)
->setIntegrityCheck(false);
$fetch = $cart->fetchAll($select)->toArray();
$paypal = new My_Paypal_Client;
$amount = 0.0;
foreach($fetch as $item) {
$amount = $amount + ($item['price']*$item['quantity']);
}
$returnURL = 'http://www.google.com';
$cancelURL = 'http://www.yahoo.com';
$currency_code = 'USD';
$reply = $paypal->ecSetExpressCheckout(
$amount,
$returnURL,
$cancelURL,
$currency_code
);
if ($reply->isSuccessfull())
{
$replyData = $paypal->parse($reply->getBody());
if ($replyData->ACK == 'SUCCESS' || $replyData->ACK == 'SUCCESSWITHWARNING')
{
$token = $replyData->TOKEN;
$_SESSION['CHECKOUT_AMOUNT'] = $amount;
header(
'Location: ' .
$paypal->api_expresscheckout_uri .
'?&cmd=_express-checkout&token=' . $token
);
}
}
else
{
throw new Exception('ECSetExpressCheckout: We failed to get a successfull response from PayPal.');
}
}
However, this is the error that returns.
Message: No valid URI has been passed to the client
Where did i go wrong? I would be happy to provide code from other areas of my application if needed. Thanks.
Zend_Http_Client::request() has not received a valid instance of Zend_Uri_Http.
Here's where the error occurs:
/**
* Send the HTTP request and return an HTTP response object
*
* #param string $method
* #return Zend_Http_Response
* #throws Zend_Http_Client_Exception
*/
public function request($method = null)
{
if (! $this->uri instanceof Zend_Uri_Http) {
/** #see Zend_Http_Client_Exception */
require_once 'Zend/Http/Client/Exception.php';
throw new Zend_Http_Client_Exception('No valid URI has been passed to the client');//Note the exact message.
}//Truncated
The only obvious error I see in the code you provided is :
$paypal = new My_Paypal_Client;//no () at end of declaration
I hope you implemented part one of the tutorial where the constructor is built. Otherwise you may just need to pass a better uri.
[EDIT]
I think your problem is here:
//needs a uri value for Zend_Http_Client to construct
$paypal = new My_Paypal_Client($url);
ecSetExpressCheckout does not construct the http client so it has no idea of where it's requesting the token from.
Alternatively you could just add this line below $paypal and above $reply:
//pass the uri required to construct Zend_Http_Client
$paypal->setUri($url);
I just hope you know what the url shouild be.
Good Luck.

how to build query string in zend framework?

I'm trying to build a query string as following:
Next Page
I want to add an array to query string. For example, array('find_loc'=>'New+York', 'find_name'=>'starbucks')
I expect to get url that looks like http://example.com/1/?find_loc=New+York&find_name=starbucks
What's the best way to do this? I found a similar question that suggested appending the string to the url. Is there a helper for query string?
Simple answer to your question is no.
Here is the class description:
/**
* Helper for making easy links and getting urls that depend on the routes and router
*
* #package Zend_View
* #subpackage Helper
* #copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
* #license http://framework.zend.com/license/new-bsd New BSD License
*/
Helper for making easy links and getting urls that depend on the routes and router
I think the description is clear in it's purpose. Use it for making URLs that depend on the routes and router. So, just append your query strings as recommend in the link you posted in your question.
The following should work for you:
Next Page
The ZF-Router will map the values to the Request object.
In your controller you can access these params with the Response-Object:
$loc = $this->getRequest()->getParam('find_loc');
$name = $this->getRequest()->getParam('find_name);
You can make custom helper:
class My_View_Helper_UrlHttpQuery extends Zend_View_Helper_Abstract
{
public function urlHttpQuery($query)
{
$urlHelper = $this->view->getHelper('url');
$params = func_get_args();
array_shift($params);//removing first argument
$url = call_user_func_array(($urlHelper, 'url'), $params);
if(!is_string($query)) { //allow raw query string
$query = array($query);
$query = http_build_query($query);
}
if(!empty($query) {
$url .= '?' . ltrim('?', $query);
}
return $url;
}
}
After you register this helper with view, you can use it like this Next Page
Working code
/**
* Class Wp_View_Helper_UrlHttpQuery
*/
class Wp_View_Helper_UrlHttpQuery extends Zend_View_Helper_Abstract
{
public function urlHttpQuery($query = array())
{
$urlHelper = $this->view->getHelper('url');
$params = func_get_args();
//removing first argument
array_shift($params);
$url = call_user_func_array(array($urlHelper, 'url'), $params);
if (is_array($query) || is_object($query)) {
$query = http_build_query($query);
}
if (!empty($query)) {
$url .= '?' . ltrim($query, '?');
}
return $url;
}
}
since the upstream code doesn't work