Yii2 Restful API UploadedFile Hack

ok, here a little hack that i did because i couldn't get the tutorial one working on the following link

http://www.yiiframework.com/doc-2.0/guide-input-file-upload.html

with the following code

namespace app\controllers;

use Yii;
use yii\web\Controller;
use app\models\UploadForm;
use yii\web\UploadedFile;

class SiteController extends Controller
{
    public function actionUpload()
    {
        $model = new UploadForm();

        if (Yii::$app->request->isPost) {
            $model->imageFile = UploadedFile::getInstance($model, 'imageFile');
            if ($model->upload()) {
                // file is uploaded successfully
                return;
            }
        }

        return $this->render('upload', ['model' => $model]);
    }
}

Sadly, no matter how hard you try, $model->imageFile will always equal to null. If you are working on an Upload API, and you are trying to get your image or whatever upload. You might soon figure that this is an impossible task. But if you try to return $_FILES['imageFile'], it does show you some hot stuff which you have uploaded but the given code just doesn't work! Hence, I have a little hack to FORCE it to work for me at least.

By digging the code and logic within yii\web\UploadedFile i came down with the solution below,

namespace app\controllers;

use Yii;
use yii\web\Controller;
use app\models\UploadForm;
use yii\web\UploadedFile;

class SiteController extends Controller
{
    public function actionUpload()
    {
        $model = new UploadForm();

        if (Yii::$app->request->isPost) {
                        $file = $_FILES['imageFile'];
                        $model->imageFile = new UploadedFile( [
                                'name' => $file['name'],
                                'tempName' => $file['tmp_name'],
                                'type' => $file['type'],
                                'size' => $file['size'],
                                'error' => $file['error'],
                                ]);
            if ($model->upload()) {
                // file is uploaded successfully
                return;
            }
        }

        return $this->render('upload', ['model' => $model]);
    }
}

Its exactly the same as what is given just that i manually created the instances instead of using the method getInstance since getInstanceByName also doesn't work. Well, hopefully this help some poor folk out there. Good Luck!

Easily Integrate PayPal IPN in Yii Framework

Wonder how to integrate paypal IPN in Yii framework? Are you searching for Yii extension to integrate paypal IPN into your application? Well, i used to do that until i got fed up with reading all the documentation all excessive code dated by 2012 that hasn't been update for months! So i decided to just integrate paypal ipn alone into my Yii framework and guess what, it was so dead simple that it simply doesn't need any extension to began with.

PayPal IPN Tutorial

Before i began writing this tutorial, you should really understand what is paypal ipn is and why you are integrating it. Paypal IPN refers PayPal's Instant Payment Notification (IPN) which is a service provides by paypal to instantly notify your application whenever there is a payment made to you. And Micah Carrick had explained it perfectly on how to integrate it on a PHP level.

Coding Paypal IPN in Yii Framework

Here we go, firstly, you will need to get the code PHP-PayPal-IPN from github. The library provides you with a simple integration to Paypal IPN. Download the code and placed into your project/protected/vendors/PHP-PayPal-IPN and rename the ipnlistener.php to IpnListener.php.

Next, you will need to create a controller to listen to incoming ipn request and paste the following code into your listener method.

{
class PaymentController extends Controller{
        public function actionListen(){
                // intantiate the IPN listener
                Yii::import("application.vendors.PHP-PayPal-IPN.*", true);
                $listener = new IpnListener();

                // tell the IPN listener to use the PayPal test sandbox
                $listener->use_sandbox = true;

                // try to process the IPN POST
                try {
                        $listener->requirePostMethod();
                        $verified = $listener->processIpn();
                } catch (Exception $e) {
                        // error_log($e->getMessage());
                        Yii::log("IPN FAILED! returned an error: $e",CLogger::LEVEL_ERROR);
                        Yii::app()->end();
                }

                // TODO: Handle IPN Response here
                if ($verified) {
                        $model = new Transaction('search');
                        $model->attributes=$_POST;
                        $model->save();
                        // TODO: Implement additional fraud checks and MySQL storage
                        mail('[email protected]', 'Valid IPN', $listener->getTextReport());
                } else {
                        // manually investigate the invalid IPN
                        mail('[email protected]', 'Invalid IPN', $listener->getTextReport());
                }
        }

}

Do take note that you will need to give your method public access in order for paypal ipn services to reach it. Now, take note that the model i am using here belongs to my own, you can create your own and save every single data passed in by paypal to your database like what i did but do make sure that the naming convention are the same for both paypal $POST request and your own table fields.

Test Paypal IPN

In order to test your Paypal IPN request, you will need to create a sandbox paypal account and use its Instant Payment Notification (IPN) Simulator as show below,

Screen Shot 2014-06-26 at 2.52.33 AM

All you need to do is to scroll it all the way down and click "Send IPN". And you should be able to test your Paypal ipn listener! And guess what? You don't even need to create any sandbox paypal account to create a paypal IPN listener!

Yii renderPartial duplication solution

Its been almost a year since i found something interested to write since im busy working and didn't really get the time to write some useful stuff. Today i came across a well known issue in Yii solution that caused duplicate js request whenever we are doing ajax stuff with renderPartial. Here's a small js script that will save our asses.

        $.ajaxSetup({
                global: true,
                dataFilter: function(data,type){
                        //  only 'text' and 'html' dataType should be filtered
                        if (type && type != "html" && type != "text")
                        {
                                return data;
                        }
         
                        var selector = 'script[src]';
         
                        // get loaded scripts from DOM the first time we execute.
                        if (!$._loadedScripts) 
                        {
                                $._loadedScripts = {};
                                $._dataHolder = $(document.createElement('div'));
 
                                var loadedScripts = $(document).find(selector);
 
                                //fetching scripts from the DOM
                                for (var i = 0, len = loadedScripts.length; i < len; i++) 
                                {
                                        $._loadedScripts[loadedScripts[i].src] = 1;
                                }
                        }
                 
                        //$._dataHolder.html(data) does not work
                        $._dataHolder[0].innerHTML = data;
         
                        // iterate over new scripts and remove if source is already in DOM:
                        var incomingScripts = $($._dataHolder).find(selector);
                        for (var i = 0, len = incomingScripts.length; i < len; i++)
                        {
                                
                                if ($._loadedScripts[incomingScripts[i].src] && incomingScripts[i].src.indexOf('search') == -1)
                                {
                                        $(incomingScripts[i]).remove();
                                }
                                else
                                {
                                        $._loadedScripts[incomingScripts[i].src] = 1;
                                }
                        }
                        return $._dataHolder[0].innerHTML;
                }
        });

Credit goes to Yii forum contributor! but not all scenario will work, still, its good enough at least

Yii CClientScript Disable RegisterScript

I though this might be useful since its not widely spread yet. There will be times when you want to disable some scripts on CClientScript so that your ajax or JSON will print properly. In this case, depending on what you want to disable, these methods might be helpful.

Disable JavaScript or CSS files in CClientScript

Well, you if want to disable JavaScript or CSS files that were included via registerCssFile or registerScriptFile you can fire the following code into your filer or anywhere your action is being fired

        $cs = Yii::app()->clientScript;
        $cs->scriptMap['jquery.js'] = false;
        $cs->scriptMap['bootstrap.min.css'] = false;
        $cs->scriptMap['bootstrap.min.js']  = false;
        $cs->scriptMap['bootstrap-yii.css'] = false;

scriptMap will disable the file mentioned (example jquery,js) and replace with 'false', this is similar to how you would go about optimizing js and css scripts on Yii as well 😉

Removing CSS or JavaScript code in CClientScript

this will be a little bit tougher than you would imagine since the array 'scripts' which suppose to be available isn't really 'public' but you can still remove the code via the following,

Yii::app()->clientScript->registerScript('mykey', "jQuery('{$selector}').{$name}({$options});");

basically, to remove the custom script i have wrote above, i will fire something like this

Yii::app()->clientScript->registerScript('mykey', false);

basically i am overwriting whatever i have registered and this will remove the custom script that i have written. Neat isn't it 😉

Remove every single custom scripts on CClientScript

if the above isn't what you were looking for, may be you are like me who manage to remove custom scripts using the above method but the script tags still appear, in this case, i fire the following code to stop my headache

        $cs = Yii::app()->clientScript;
        $cs->reset();

since we can't make 'hasScripts' to be false (which is the reason why the script tags still appear), we will reset all the calling scripts instead. but this won't always work as neat as you would want to but its a good solution so far.

Disable JavaScripts entirely on CClientScript

Well, its so irritating that i wanted everything to be disable on cclientscript, in this case, use the following,

        $cs = Yii::app()->clientScript;
        $cs->reset();
        $cs->enableJavaScript = false;

This will ensure everything is disabled.

This should be useful for people working on Ajax, restful and JSON implementation on Yii 😉

Disable Yii Log on Action Controller

Well, i found something interesting yesterday that might be useful for Yii developer when they deal with restful api or JSON output with Yii framework. Sometimes it is good to disable Log output in order to return proper JSON or restful api calls. In that case, you can try to do the following,

        foreach (Yii::app()->log->routes as $route)
        {   
                if ($route instanceof CWebLogRoute || $route instanceof CFileLogRoute || $route instanceof YiiDebugToolbarRoute)
                {   
                        $route->enabled = false;
                }   
        } 

However, if you still seeing your JSON or Restful api output being 'unclean', this is most likely caused by preload options on your config.php file. Preload will always run earlier than whatever you have defined in your action controller. In order to bypass this, the only way is to dive into these extensions and provide certain validation to prevent it from running unnecessary. Sometimes, you will see facebook api appending javascript and meta tag into your json call, in that case, you can add in a new 'enabled' property on your facebook extension and disabled upon your action controller initialization. You can place the following code below to shut up facebook entirely upon running your action controller method.

Yii::app()->facebook->enabled = false;

the enabled property have to be written by you as a new property on facebook extension class file. And the other condition to prevent certain method to run will also have to be written by you since Yii doesn't provide a more graceful method to disable output before preload or extensions that are not managed by them