ghiscoding ghiscoding - 4 months ago 51
Javascript Question

How to use VisualCaptcha with AngularJS and slimPHP in RESTful

So I use

AngularJS
on frontend and
SlimPHP
on backend with REST url. I am trying to use VisualCaptch and I followed the instruction on the PHP side and it seems to work on the backend, I made a simple Angular dataService calling the url and display it on the console but I simply don't understand how to connect it with Angular, I downloaded also that portion but when I create the
HTML
and call the
captcha
inside the
<div>
. Well actually let me explain 1 of the problem, it's the url that the Angular is trying to run that is incorrect but I just don't know how to configure it, is there an option for? So let me display the code that I have so far... By the way, I use the
Controller as vm
syntax, so you will not find
$scope
in my code.

HTML:

<div captcha options="vm.captchaOptions"></div>


AngularJS Controller code:

// this work as a dataService call to SlimPHP
dataService.getCaptchaHowMany().then(function(data) {
console.debug(data);
});

// where does my dataService call goes in here???
vm.captchaOptions = {
imgPath: 'vendors/visual-captcha/img/',
captcha: { numberOfImages: 5 },
init: function ( captcha ) {
vm.captcha = captcha;
}
};


SlimPHP Routes, which I know they work

# Captcha Routes
$app->get('/captcha/start/:howMany', function($howMany) use ($app) { CaptchaController::getCaptchaHowMany($app, $howMany); });
$app->get('/captcha/audio(/:type)', function($type = 'mp3') use ($app) { CaptchaController::getCatpchaDiskAudioStreaming($app, $type); });
$app->get('/captcha/image/:index', function($index) use ($app) { CaptchaController::getCatpchaDiskImageStreaming($app, $index); });
$app->post('/captcha/try', function() use ($app) { CaptchaController::postCatchaTryValidate($app); });


When I copied over the example of VisualCaptcha with Angular and I run the code, it shows a bad url call (bad because my SlimPHP Api is in a different location). So the bad url call looks like this:

GET http://localhost/myproject/user/start/5



But to make a proper call to my
SlimPHP
Api routes it should be like this
GET http://localhost/myproject/api/captcha/start/5



So where do I configure the routes for Angular with VisualCaptcha??? I have also attached the result of my proper url, so I think that my backend (SlimPHP) is properly working, it's just to configure the Angular url in the controller that is wrong.
enter image description here


EDIT
I got a bit further now, I found that we can set the
url
inside the
captchaOptions
which now makes a proper call to my
SlimPHP
api urls with the following

vm.captchaOptions = {
imgPath: 'vendors/visual-captcha/img/',
captcha: {
numberOfImages: 5,
url: 'api/captcha'
},
// use init callback to get captcha object
init: function ( captcha ) {
vm.captcha = captcha;
}
};


I now have the issue that it is not displaying the images. Are the images suppose to come from PHP or from Angular on the client side? Which folder should contain all images? I actually get this error message in the console:

HTTP "Content-Type" of "text/html" is not supported. Load of media resource http://localhost/investing/api/captcha/audio?r=353oeaysbjg failed.


I actually don't want audio, though it's giving me this audio error, I simply want to use the images, that's all. Could someone help me out, please!

Answer

After passing 2 days on this, I finally got it working. There is a couple of code change that I had to do.. so here is the list of modifications:

First, I was missing the session variable inside my SlimPHP application:

// Inject Session closure into app
$app->session = function() use( $app ) {
    if ( $namespace = $app->request->params( 'namespace' ) ) {
        $session = new \visualCaptcha\Session( 'visualcaptcha_' . $namespace );
    } else {
        $session = new \visualCaptcha\Session();
    }
    return $session;
};

Then changed the HTML code to work with Bootstrap instead of Foundation, also added 2 button to check if it's filled and if it's valid:

<div class="form-group">
    <label class="control-label col-sm-5">Visual Captcha Messages</label>
    <div class="col-sm-7" id="status-message">
        <div class="alert" ng-class="{ 'alert-danger': (vm.valid === false), 'alert-success': (vm.valid === true) }" role="alert" ng-show="vm.status !== null">
            <div ng-class="{ 'glyphicon glyphicon-remove-sign': (vm.valid === false), 'glyphicon glyphicon-ok-sign': (vm.valid === true) }" ></div>
            {{ vm.status }}
        </div>
    </div>
</div>

<div class="form-group">
    <label class="control-label col-sm-5">Visual Captcha</label>                
    <div class="col-sm-7" captcha options="vm.captchaOptions"></div>
</div>

<button type="button" class="btn" ng-click="vm.isVisualCaptchaFilled()">Check if visualCaptcha is filled</button>
<button type="button" class="btn" ng-click="vm.isVisualCaptchaValid()">Validate Catpcha</button>

then inside my AngularJS controller using the Controller as vm syntax:

vm = this;  
vm.captcha = {};
vm.captchaOptions = {
    imgPath: 'vendors/visual-captcha/img/', // vendors folder on Angular side, only used for "refresh" and "audio" buttons
    captcha: { 
        numberOfImages: 5,
        url: 'api/captcha'                  // url for SlimPHP route calls
    },
    // use init callback to get captcha object
    init: function ( captcha ) {
        vm.captcha = captcha;
    }
};

// vm public functions revealing
vm.isVisualCaptchaFilled = isVisualCaptchaFilled;
vm.isVisualCaptchaValid = isVisualCaptchaValid;

function isVisualCaptchaFilled() {
    if ( vm.captcha.getCaptchaData().valid ) {
        window.alert( 'visualCaptcha is filled!' );
    } else {
        window.alert( 'visualCaptcha is NOT filled!' );
    }
}

function isVisualCaptchaValid() {
    // we will post the captcha image field name and image field value
    var postData = {
        fieldName: vm.captcha.getCaptchaData().name,
        fieldValue: vm.captcha.getCaptchaData().value
    };

    dataService.isVisualCaptchaValid(postData)
        .then(function(data) {
            // Show success/error messages
            if ( data.status === 'noCaptcha' ) {
                vm.valid = false;
                vm.status = 'visualCaptcha was not started!';
            } else if ( data.status === 'validImage' ) {
                vm.valid = true;
                vm.status = 'Image was valid!';
            } else if ( data.status === 'failedImage' ) {
                vm.valid = false;
                vm.status = 'Image was NOT valid!';
            } else if ( data.status === 'validAudio' ) {
                vm.valid = true;
                vm.status = 'Accessibility answer was valid!';
            } else if ( data.status === 'failedAudio' ) {
                vm.valid = false;
                vm.status = 'Accessibility answer was NOT valid!';
            } else if ( data.status === 'failedPost' ) {
                vm.valid = false;
                vm.status = 'No visualCaptcha answer was given!';
            }
        })
        .then(function() {
            vm.captcha.refresh();
        });        
}

then also had to rewrite the try route inside the SlimPHP app:

// Try to validate the captcha
// -----------------------------------------------------------------------------
$app->post( '/try', function() use( $app ) {
    $captcha = new \visualCaptcha\Captcha( $app->session );
    $frontendData = $captcha->getFrontendData();
    $isCaptchaValid = false;
    $namespace = '';
    $status = '';

    //POST variables, they come as a JSON encoded inside the POST
    $jsonPostFields = $app->request();
    $post = json_decode($jsonPostFields->getBody(), $associativeArray = true);

    // Load the namespace into url params, if set
    if ( isset($post['namespace']) ) {
        $namespace = $post['namespace'];
    }
    if ( ! $frontendData ) {
        $status = 'noCaptcha';
    } else {
        // If an image field name was submitted, try to validate it
        if(isset($post["fieldName"]) && $post["fieldName"] === $frontendData['imageFieldName']) {
            $imageAnswer = $post["fieldValue"];
            if ( $captcha->validateImage( $imageAnswer ) ) {
                $isCaptchaValid = true;
                $status = 'validImage';
            } else {
                $status = 'failedImage';
            }
        } else if(isset($post["fieldName"]) && $post["fieldName"] === $frontendData['audioFieldName']) {
            $audioAnswer = $post["fieldValue"];
            if ( $captcha->validateAudio( $audioAnswer ) ) {
                $isCaptchaValid = true;
                $status = 'validAudio';
            } else {
                $status = 'failedAudio';
            }
        } else {
            $status = 'failedPost';
        }
        $howMany = count( $captcha->getImageOptions() );
        $captcha->generate( $howMany );
    }
    echo json_encode(array("isCaptchaValid" => $isCaptchaValid, "status" => $status));
} );

Please notice that using a dataService was only for the testing & development purposes, you should instead validate the VisualCaptcha on the backend just before saving the form which is also on backend. So in theory, you would only have 1 call in Angular which is the form saving call through the dataService, then on the server backend side there would be 2 things that needs to happen (1- captcha validation, 2- saving form)
I also modified the data to post because I don't like the way they have done their example, I never directly post a form inside an AngularJS SPA application, I instead prefer to use a dataService called by an ng-click which is why I had to define my own postData variable.

With all these changes... it finally works on both Images & Audio!!!