субота, 2 квітня 2011 р.

sfDoctrineGuard email login

В авторизації є помилка. Навіть коли вказано, що можна авторизвуатись через e-mail, Symfony буде перевіряти лише username. Solution:

http://trac.symfony-project.org/ticket/9494

тобто в файлі sfGuardUserValidator.class.php в методі doClean() треба

$user = $this->getTable()->retrieveByUsername($username);
замінити на:

$user = $this->getTable()->$method($username);
Читати далі >

вівторок, 15 березня 2011 р.

Symfony admin many-to-many safe delete - Безпечне видалення записів many-to-many

Безпечне видалення записів many-to-many в Symfony admin.

В згенерованому файлі modules/mymodule/actions.сlass.php слід перезвантажити метод executeDelete:
  public function executeDelete(sfWebRequest $request)
  {
    $request->checkCSRFProtection();

    $this->dispatcher->notify(new sfEvent($this, 'admin.delete_object', array('object' => $this->getRoute()->getObject())));

    if(count($this->getRoute()->getObject()->getDetail()) > 0) {
        $this->getUser()->setFlash('error', 'Не вдалось видалити. У вас є деталі цієї підкатегорії. Видаліть спочатку їх.');
        $this->redirect('@subcategory');
    }


    if ($this->getRoute()->getObject()->delete())
    {
      $this->getUser()->setFlash('notice', 'The item was deleted successfully.');
    }

    $this->redirect('@subcategory');
  }
В цьому випадку, якщо у вас будуть деталі цієї підкатегорії, замість помилки ви отримаєте флеш-повідомлення. Таким само варто перезавантажити і метод batchDelete().
Читати далі >

четвер, 13 січня 2011 р.

Валідація форм на сторонах сервера та клієнта в Symfony Framework (server and client side form validation in Symfony Framework)

Не зовсім хочеться починати саме з цієї статті, але вона вже чекала на публікацію надто довго і розмістити її тут стало ділом принципу.

Мова йтиме про php фреймворк Symfony і таку тривіальну задачу як валідація форм. Забігаючи наперед, скажу що вирішення цієї задачі є далеко не єдиною панацеєю, а швидше розширеною реалізацією стандартних рішень. Код прикладів є далеким від ідеалу, але при бажанні це можна досить швидко виправити.

В цьому пості розглянемо 2 приклади валідації: з використанням плагінів jQuery notifyBar та jQuery validation.


Приклад 1 (валідація на стороні сервера):

Нехай в schema.yml маємо наступну структуру БД:

Question:
  actAs:
    Timestampable: ~
  columns:
    title: string(250)
    body: text(5000)
    author_mail: string(100)
    author_name: string(100)
    answer: text(5000)

В контроллері для виводу передаємо екзмпляр форми QuestionsForm:
public function executeIndex(sfWebRequest $request) {
    $this->questions = Doctrine::getTable('Question')->getQuestions(); 
    $this->form = new QuestionsForm(); }
У вигляді IndexSuccess.php підключаємо таблиці стилів для вигляду форми і плагіна notifyBar та викликаємо партіал, передаючи йому екземпляр форми $form, отриманий з контроллера:

<link rel="stylesheet" href="<?php echo stylesheet_path('questions.css'); ?>">

<div id="question">
<?php include_partial('qform', array('form' => $form)); ?>
</div>

В партіалі _qform.php відображаємо форму та підключаємо Javascript з використаням слота questform:
<div class="gform">
<form id="qform" action="<?php echo url_for('@postquestion') ?>" method="POST" name="register">

<?php echo $form->renderGlobalErrors() ?>
<?php echo $form->renderHiddenFields() ?>
<br/>
<div>
<div class="form_label">
<div id="name_lbl">Ваше ім'я:&nbsp;<span class="err_lbl"></span></div>
<div id="email_lbl">Ваш e-mail:&nbsp;<span class="err_lbl"></span></div>
</div>
<input type="text" name="questions[author_name]" id="qfname" class="form_inputtext"/>
<input type="text" name="questions[author_mail]" id="qfemail" class="form_inputtext"/>
<p class="form_label" id="title_lbl">Запитання:&nbsp;
<span class="err_lbl"></span>
</p>
<input type="text" name="questions[title]" id="qftitle" class="form_inputtext"/>
<p class="form_label" id="full_lbl">Розгорнуте запитання:&nbsp;
<span class="err_lbl"></span>
</p>
<textarea type="text" class="form_textarea" name="questions[body]" id="qfbody"></textarea>
</div>
<div id="qcaptcha"> <?php echo $form['captcha'] ?> </div>
<p class="captcha_label err_lbl"><span></span></p>
</form>
<div class="qbtholder">
<div class="grid_3">
<div class="fbtn" id="qsendlink"><a href="#">Надіслати запитання</a> </div>
</div>
<div class="grid_3">
<div class="fbtn qclose" id="qclose"><a href="#">Закрити</a></div>
</div>
</div>
<div class="clear"></div>
</div>
<noscript>
<input type="submit" value="Надіслати" />
</noscript> 

<?php slot('questform') ?>
<script src="<?php echo javascript_path('notify.js'); ?>"></script>
<script src="<?php echo javascript_path('jquery.easing-1.3.pack.js'); ?>"></script>
<script src="<?php echo javascript_path('main.js'); ?>"></script>
<script src="<?php echo javascript_path('question.js'); ?>"></script>
<?php end_slot(); ?>
Далі в контроллері actions.class.php приймаємо та обробляємо запит:

public function executeCreate(sfWebRequest $request) {
$this->forward404If(!$request->isXmlHttpRequest(), 'Не надіслано');
$this->form = new QuestionsForm();
$rez = $this->processQuestionForm($request, $this->form);

if ($rez[0]) {
return $this->renderText(
json_encode(array(
'haserror' => false,
'message' => 'Запитання прийнято',
)
));
}

return $this->renderText(
json_encode(array(
'haserror' => true,
'message' => 'Помилка, спробуйте ще раз',
'errfield' => $rez[1],
'saveerr' => $rez[2],
)
)
);
}

protected function processQuestionForm(sfWebRequest $request, sfForm $form) {
$rez = array();
$captcha = array(
'recaptcha_challenge_field' => $request->getParameter('recaptcha_challenge_field'),
'recaptcha_response_field' => $request->getParameter('recaptcha_response_field')
);

$form->bind($request->getParameter($form->getName()), array('captcha' => $captcha));
if ($form->isValid()) {
$question = new Question();
$question->author_mail = $form->getValue('author_mail');
$question->author_name = $form->getValue('author_name');
$question->title = $form->getValue('title');
$question->body = $form->getValue('body');

if ($question->trySave()) {
$rez[0] = true;
}

else
$rez[0] = false;
$rez[1] = '';
$rez[2] = 'Помилка при збереженні, спробуйте пізніше';
return $rez;
}

else {
$errors = array();
foreach ($form->getErrorSchema() as $field => $error) {
$errors[$field] = $error->getMessage();
}

$rez[0] = false;
$rez[1] = $errors;
$rez[2] = '';
return $rez;
}
}

При наявності помилок, вони будуть передані до вигляду завдяки цьому коду:
foreach ($form->getErrorSchema() as $field => $error) {
$errors[$field] = $error->getMessage();
}


Повідомлення помилок для getMessage(); можна налаштувати в класі форми, наприклад:

$this->setValidators(array(
'title'=>new sfValidatorString(array('required' => false, 'max_length' => 255), array(
'required'=>'Будь ласка, введіть заголовок новини',
'invalid'=>'Неправильний формат поля',
'max_length' => 'Значення поля перевищує 255 символів'
)),

'body'=>new sfValidatorString(array('required' => false), array(
'required'=>'Будь ласка, введіть заголовок новини',
'invalid'=>'Неправильний формат поля',
'max_length' => 'Значення поля перевищує 255 символів'
)), 
))

Залишилось вивести помилки з допомогою плагіна.
В файлі main.js перевантажуємо методи плагіна для тривалішого виводу помилок:

function msg(txt, type){
type = typeof(type) != 'undefined' ? type : 'error';
$.notifyBar({
html: txt,
cls: type, //"success"
delay: 2000,
animationSpeed: "normal"
});
}

function errMsg(txt, type){
type = typeof(type) != 'undefined' ? type : 'error';
$.notifyBar({
html: txt,
cls: type, //"success"
delay: 3000,
animationSpeed: "normal"
});
}


Та, власне, в файлі question.js пишемо функції для відправки форми з допомогою AJAX, її валідації на стороні сервера та виводу помилок:

$(document).ready(function() {
$("#qlink").click(function() {
$("#question form")[0].reset();
clearErrors();
$("#question").fadeIn("slow");
return false;
});


function clearErrors() {
$('#name_lbl').children().text('');
$('#email_lbl').children().text('');
$('#title_lbl').children().text('');
$('#full_lbl').children().text('');
$('.captcha_label').children().text('');
$('#qfname').removeClass("error_border");
$('#qfemail').removeClass("error_border");
$('#qftitle').removeClass("error_border");
$('#qfbody').removeClass("error_border");
$('#recaptcha_response_field').removeClass("error_border");
}

$("#qsendlink").live('click', function(){
$("#__notifyBar").remove();
clearErrors();

var i = $("#question form").serialize();
$.post(
'questions/post',
i,
function(rez){
if (!rez.haserror)
{
msg(rez.message, "success");
$("#question").hide();
$("#question form")[0].reset();
Recaptcha.reload();
}

else {
var saveerr = '';
if(rez.saveerr != null)
{
saveerr = rez.saveerr;
}

if(rez.errfield['author_name'] != null)
{
$('#name_lbl').children().text(rez.errfield['author_name']);
$('#qfname').addClass("error_border");
}

if(rez.errfield['author_mail'] != null)
{
$('#email_lbl').children().text(rez.errfield['author_mail']);
$('#qfemail').addClass("error_border");
}

if(rez.errfield['title'] != null)
{
$('#title_lbl').children().text(rez.errfield['title']);
$('#qftitle').addClass("error_border");
}

if(rez.errfield['body'] != null)
{
$('#full_lbl').children().text(rez.errfield['body']);
$('#qfbody').addClass("error_border");
}

if(rez.errfield['captcha'] != null)
{
$('.captcha_label').children().text(rez.errfield['captcha']);
$('#recaptcha_response_field').addClass("error_border");
}

errMsg(rez.message + saveerr);
}
},
'json'
);

return false;
});

$("#qclose").click(function(){
$("#__notifyBar").remove();
$("#question").fadeOut("100");
clearErrors();
Recaptcha.reload();
$("#question form")[0].reset();
return false;
})

return false;
}); 
Приклад 2 (валідація на стороні сервера та клієнта):

Для виведення форми використовується плагін fancybox.
Нехай маємо вигляд usefulSuccess.php. В ньому розміщуємо лінк для форми та підключаємо партіaл:
<a href="#qrequest" id="askq" class="inline">Задать вопрос</a><?php include_partial('ask') ?>
В партіалі _ask.php відображаємо саму форму:
<div style="display:none" id="clcont">
<div id="qrequest">
<h3 class="request_header">Вопрос</h3>
<form action="/" method="post" id="request_form" class="qform">
<p class="topfield">
<label for="r_name" >Имя*</label>
<input type="text" name="r_name" id="r_name" class="required" style="width: 220px;"/>
</p>
<p>
<label for="r_phone">Телефон*</label>
<input type="text" name="r_phone" id="r_phone" class="required" style="width: 220px;"/>
</p>
<p>
<label for="r_email">Email</label>
<input type="text" name="r_email" id="r_email" class="email" style="width: 220px;"/>
</p>
<p style="vertical-align: top; position: relative;">
<label for="r_question" style="position: absolute; top: 10px;">Вопрос*</label>
<label for="r_question"></label>
<span>
<textarea cols="42" rows="8" name="r_question" id="r_question" class="required" style="width: 360px;"></textarea>
</span>
</p>
<div class="clear"></div>

<button type="submit" id="request_submit" class="submit" >Отправить</button>
</form>

</div>
</div>
Валідація базуватиметься на основі класів полів вводу, наприклад для поля:

<input type="text" name="r_email" id="r_email" class="email" style="width: 220px;"/> 

буде використано валідатор email. Окрім цього, плагін validate містить наступні валідатори: required, remote, email, url, date, dateISO, number, digits, creditcard, equalTo, accept, maxlength, minlength, rangelength, range, max, min.
Також при необхідності є можливсіть валідації з використанням регулярних виразів.

Налашутвання повідомлень момилок проводиться в масиві:

messages: {
required: "Это поле должно быть заполнено.",
email: "Неверный формат адреса.")
}

Він розташований в файлі плагіна jquery.validate.js
На завершення потрібно в партіалі _ask.php додати слот для відображення, перевірки і виведення помилок:

<?php slot('js') ?>
<script type="text/javascript" src="<?php echo javascript_path('jquery.validate.js'); ?>"></script>
<script type="text/javascript">

$(document).ready(function(){
$("a.inline").fancybox();

var validator = $("#request_form").validate({
errorElement: "p",
focusCleanup: true,
blockFocusCleanup: false,

submitHandler: function() {
var i = $("#qrequest form").serialize();

$.post(
'pages/ask',
i,
function(rez){
if (rez.haserror == 'no')
{
$("#clcont").hide();
$("#request_form")[0].reset();
$.fancybox.close();
}

else {
$("#clcont").hide();
$.fancybox.close();
alert ('Не удалось отослать вопрос. Попробуйте позже, пожалуйста.');
}

},
'json'
);

return false;
}
});

$("#fancybox-close").live('click', function(){
$("#request_form")[0].reset();
validator.resetForm();
return false;
})

$(document).keyup(function(e)
{
switch(e.which)
{
case 27:
$("#request_form")[0].reset();
validator.resetForm();
return false;
}
});

});
</script>
<?php end_slot() ?>

В рядках
errorElement: "p",
focusCleanup: true,
blockFocusCleanup: false, 

описано куди та як виводити помилки валідації.

Для відображення помилок використовується клас error, згодом його можна описати на власний розсуд:

.error {color: #ee0101;}

Форма надсилається ajax-ом в контроллер. Код контроллера в цьому випадку матиме приблизно наступний вигляд:

public function executeAsk(sfWebRequest $request) {

if ($request->isXmlHttpRequest() || $request->getParameter('r_email') !='') {

// Заповнення даних для надсилання
$toSendVals = array(
'r_name' => $request->getParameter('r_name'),
'r_phone' => $request->getParameter('r_phone'),
'r_question' => $request->getParameter('r_question'),
'r_email' => $request->getParameter('r_email'));

//Створення партіала тіла листа (слід винести в окремий файл)
$body = "
<table>
<tr>
<td>Имя</td>
<td>".$toSendVals['r_name']."</td>
</tr>
<tr>
<td>Телефон</td>
<td>".$toSendVals['r_phone']."</td>
</tr>
<tr>
<td>Email</td>
<td>".$toSendVals['r_email']."</td>
</tr>
<tr>
<td>Вопрос</td>
<td>".$toSendVals['r_question']."</td>
</tr>
</table>
";

try {
//Конструктор повідомлення
$message = $this->getMailer()->compose(sfConfig::get('app_from_mail'), sfConfig::get('app_manager_mail'), 'Вопрос из сайта', $body);

$message->setContentType("text/html");
//Надсилання і перевірка результатів if (!$this->getMailer()->send($message)) { return $this->renderText(json_encode(array('haserror' => 'yes', 'senderror' => 'yes'))); } } catch (Exception $e) { return $this->renderText(json_encode(array('haserror' => 'yes', 'senderror' => 'no', 'error'=> $e->getMessage()))); } return $this->renderText(json_encode(array('haserror' => 'no'))); } return $this->renderText(json_encode(array('haserror' => 'yes'))); }

Результат відправки перевіряється прапорцем haserror. Для наочності в прикладі вивід помилки надсилання виводиться з допомогою рядка:

alert ('Не удалось отослать вопрос. Попробуйте позже, пожалуйста.'); 

Цей вивід можна обробляти вже на власний смак, наприклад, з використанням плагіна noifyBar з пиркладу 1. Також шаблон листа в котнроллері слід розмістити в окремому партіалі і завантажувати його при виклиці відповідного екшена.

Другий приклад є універсальнішим, бо у випадку, якщо якимось чином невірно введені дані пройдуть валідацію на стороні клієнта, вони будуть перехоплені вже на серверній стороні (при умові, що для цього буде застосована фаладація форм з прикладу 1). В даному прикладі така валідація не застосовувалась в зв'язку з її непотрібністю.
Читати далі >

Спроба повноформатного блогу №1

Це моя перша спроба вести повноформатний блог. На разі я маю власний мікроблог на twitter. Планую розміщувати тут різні корисні посилання, частини коду які можуть знадобитись іншим людям ну і можливо ще щось, що саме поки і сам не знаю. Поки не гарантую абсолютну якість коду і його 100% правильність, але старатимусь, щоб все було максимально класно. Читати далі >