"Bare metal" CCaptcha?

Alright, simple question. Got a page with inputs - raw HTML, no special Yii form model. Can I just drop a CCaptcha in as a widget and then run a manual check on the returned POST values?

Your question made me curious, so I took a look at Yii’s implementation, and I believe what you want to do is NOT possible, because the validator for CCaptcha is tied to a model.

See the source code for the CCaptcha validation:

yii/framework/validators/CCaptchaValidator.php

Function: validateAttribute()

Docs: http://www.yiiframework.com/doc/api/1.1/CCaptchaValidator#validateAttribute-detail

The first input to validateAttribute() is an instance of CFormModel.

With a good deal of work, you could re-implement what CCaptchaValidator does, but why re-invent the wheel? Seems like a lot of work, without reward.

// -----------------------------------------------------------------

// UPDATE

// -----------------------------------------------------------------

Someone who used to be more active on the forum wrote to me about this case, and here’s what he said:

CCaptcha and CCaptchaAction can be easily used without the need of CCaptchaValidator. It is the last one who is used to validate the attrbute of a model but you can use the first two, to used ‘manually’ as you wrote. The only thing required is that the validation occur on the controller who rendered the image. Please check the following code of CCaptchaValidator:




if(($captcha=Yii::app()->getController()->createAction($this->captchaAction))===null)

{

   if(strpos($this->captchaAction,'/')!==false) // contains controller or module

   {

      if(($ca=Yii::app()->createController($this->captchaAction))!==null)

      {

         list($controller,$actionID)=$ca;

         $captcha=$controller->createAction($actionID);

      }

   }


   if($captcha===null)

      throw new CException(Yii::t('yii','CCaptchaValidator.action "{id}" is invalid. Unable to find such an action in the current controller.',

	array('{id}'=>$this->captchaAction)));

   }

}



See how it creates the action on the fly based on the controller and then it creates a captcha based on the action? After that, you can validate the value.

There. That’s the end of what my friend said… I can’t claim to understand it fully. I hope you’ll present your final solution when you have one!

:mellow:

Well, I do have it generating a captcha image, based on the controller code in this topic. And I have a text field allowing for entry. That part was pretty easy.

Problem is, I’m not sure what I’m supposed to be checking, or where it is. So I’m left with the equivalent of:


if ($_POST['captchafield'] == $mysteriousValueSomewhere) { ... }

Which I believe is where the “bare metal” part comes in. The widget and the action are being done the normal Yii way, as far as I understand, but without a form model or anything of that sort, I believe the value I’m looking for must be extracted from… wherever, with basic PHP. The basic PHP shouldn’t be hard at all, but the “where the heck is it” problem is a biggie.

Maybe one of the Yii authors can weigh in on this one. To answer it requires a thorough understanding of their Captcha-related classes–an understanding I lack, sorry!

:mellow:

Well, while waiting for that, I’ve got some reverse-engineering to do, it seems :P

I’m curious why you are wanting to do this in the first place? Is it really necessary? Are you sure there’s not a workaround? I find that the encapsulation of things like this into widgets is very convenient. I wrote, for example, a CreditCard widget, which encapsulates everything a form will ever need both for displaying and for validating a credit card. Every one of my payment-related views simply plunks down the CreditCard widget, and voila, I dont’ have to think about it. Isn’t the purpose of a framework to spare us this kind of level of detail?

Still, I understand when getting down to bare metal is the only solution… so can you explain why you believe bare metal is the only way to go ?

Well it’s still a widget, called with, “$this->widget(‘CCaptcha’, array(‘captchaAction’=>’/user/register/captcha’))”. And I have a fair number of widgets of my own, because they are indeed very useful if the same block needs to be used repeatedly (much like a function). What I don’t have are the form models, where the captcha validation would normally take place. I’m doing my own validation in a much faster, much more efficient method directly in the controller, because it just plain makes sense to do so.

Honestly, I’m mostly using the framework because a large chunk of this project was dragged over from a previous abandoned project with a different leader; I could never forgive myself if my actual goal were to avoid the hard work and attention to detail that I’m known for. If it were plausible, I’d be coding this thing in C++, and it’d be quite fast indeed ;)

I see. I haven’t had any performance issues with Yii, but obviously you are, so bare metal becomes a requirement. Hope you’ll post your final sol’n on the forum.

:mellow:

What a coincidence, I have a solution now! The CCaptchaValidator doesn’t do much of anything - its purpose is to locate the captcha action, run the validation function that’s located in the CCaptchaAction, and then spit out an error as needed.

So, streamlining it is simple. You know the action’s name based on this function in the controller:


public function actions()

{

	return array(

		'captcha' => 'CCaptchaAction'

	);

}

So at that point, you can just use one line to grab it:


$captcha = Yii::app()->getController()->createAction('captcha');

Now, you have a CCaptchaAction, which through use of sessions is already loaded with the values you need. That’s it, it already knows the code that’s supposed to match. And that CCaptchaAction has the validate function, into which you feed the input (generally from a POST field), and it will return the boolean you need, like so:


if (!$captcha->validate($_POST['captchafield'], true)) { ... }

There. CCaptcha widget and CCaptchaAction, without needing a form model or CCaptchaValidator.

Coincidental with your persistence. Thanks for posting this simple and elegant sol’n!

:mellow:

Can’t hold my horses without saying this: This is a great post to write a wiki… please do it. ;)