Useful CakePHP shell scripts

Today I want to present some useful shell scripts I often use.
Hopefully you find them useful, as well 🙂

Where to put them

Drop shells in a vendor folder of your choice (either app, core, or plugin), for instance:
/app/vendors/shells/ or like me /app/plugins/tools/vendors/shells/

Password Reset

A quick and easy shell script to reset all passwords for local development.
Usage:

cake pwd_reset [pwd]

The password is optional (will be prompted otherwise)

The code:

<?php

# enhancement for plugin user model
if (!defined('CLASS_USER')) {
    define('CLASS_USER', 'User');
}

/**
 * reset user passwords
 */
class PwdResetShell extends Shell {
    var $tasks = array();
    //var $uses = array('User');

    var $Auth = null;

    /**
     * reset all pwds to a simply pwd (for local development)
     * 2011-08-01 ms
     */
    function main() {
        $components = array('AuthExt', 'Auth');
        foreach ($components as $component) {
            if (App::import('Component', $component)) {
                $component .='Component';
                $this->Auth = new $component();
                break;
            }
        }
        if (!is_object($this->Auth)) {
            $this->out('No Auth Component found');
            die();
        }

        $this->out('Using: '.get_class($this->Auth).' (Abort with STRG+C)');

        if (!empty($this->args[0]) && mb_strlen($this->args[0]) >= 2) {
            $pwToHash = $this->args[0];
        }
        while (empty($pwToHash) || mb_strlen($pwToHash) < 2) {
            $pwToHash = $this->in(__('Password to Hash (2 characters at least)', true));
        }
        $this->hr();
        $this->out('pwd:');
        $this->out($pwToHash);
        $pw = $this->Auth->password($pwToHash);
        $this->hr();
        $this->out('hash:');
        $this->out($pw);

        $this->hr();
        $this->out('resetting...');

        $this->User = ClassRegistry::init(CLASS_USER);
        if (!$this->User->hasField('password')) {
            $this->error(CLASS_USER.' model doesnt have a password field!');
        }
        
        if (method_exists($this->User, 'escapeValue')) {
            $newPwd = $this->User->escapeValue($pw);
        } else {
            $newPwd = '\''.$pw.'\'';
        }
        $this->User->recursive = -1;
        $this->User->updateAll(array('password'=>$newPwd), array('password !='=>$pw));
        $count = $this->User->getAffectedRows();
        $this->out($count.' pwds resetted - DONE');
    }

    function help() {
        $this->out('-- Hash and Reset all user passwords with Auth(Ext) Component --');
    }
}

Useful if you just switched the salt, the hash method or imported users from another DB for testing 🙂

New User

How do you set up an admin account with a new project if you can’t login? Sure, allow(*) or other hacks.
But wouldnt a simple command like cake user be nicer? Check it out:

<?php

if (!defined('CLASS_USER')) {
    define('CLASS_USER', 'User');
}

class UserShell extends Shell {
    var $tasks = array();
    var $uses = array(CLASS_USER);

    function help() {
        $this->out('command: cake user');
    }

    //TODO: refactor (smaller sub-parts)
    function main() {
        if (App::import('Component', 'AuthExt')) {
            $this->Auth = new AuthExtComponent();
        } else {
            App::import('Component', 'Auth');
            $this->Auth = new AuthComponent();
        }

        while (empty($username)) {
            $username = $this->in(__('Username (2 characters at least)', true));
        }
        while (empty($password)) {
            $password = $this->in(__('Password (2 characters at least)', true));
        }

        $schema = $this->User->schema();

        if (isset($this->User->Role) && is_object($this->User->Role)) {
            $roles = $this->User->Role->find('list');

            if (!empty($roles)) {
                $this->out('');
                pr($roles);
            }

            $roleIds = array_keys($roles);
            while (!empty($roles) && empty($role)) {
                $role = $this->in(__('Role', true), $roleIds);
            }
        } elseif (method_exists($this->User, 'roles')) {
            $roles = User::roles();

            if (!empty($roles)) {
                $this->out('');
                pr ($roles);
            }

            $roleIds = array_keys($roles);
            while (!empty($roles) && empty($role)) {
                $role = $this->in(__('Role', true), $roleIds);
            }
        }
        if (empty($roles)) {
            $this->out('No Role found (either no table, or no data)');
            $role = $this->in(__('Please insert a role manually', true));
        }

        $this->out('');
        $pwd = $this->Auth->password($password);

        $data = array('User'=>array(
            'password' => $pwd,
            'active' => 1
        ));
        if (!empty($username)) {
            $data['User']['username'] = $username;
        }
        if (!empty($email)) {
            $data['User']['email'] = $email;
        }
        if (!empty($role)) {
            $data['User']['role_id'] = $role;
        }

        if (!empty($schema['status']) && method_exists('User', 'statuses')) {
            $statuses = User::statuses();
            pr($statuses);
            while(empty($status)) {
                $status = $this->in(__('Please insert a status', true), array_keys($statuses));
            }
            $data['User']['status'] = $status;
        }

        if (!empty($schema['email'])) {
            $provideEmail = $this->in(__('Provide Email? ', true),array('y', 'n'), 'n');
            if ($provideEmail === 'y') {
                $email = $this->in(__('Please insert an email', true));
                $data['User']['email'] = $email;
            }
            if (!empty($schema['email_confirmed'])) {
                $data['User']['email_confirmed'] = 1;
            }
        }

        $this->out('');
        pr ($data);
        $this->out('');
        $this->out('');
        $continue = $this->in(__('Continue? ', true),array('y', 'n'), 'n');
        if ($continue != 'y') {
            $this->error('Not Executed!');
        }

        $this->out('');
        $this->hr();
        if ($this->User->save($data)) {
            $this->out('User inserted! ID: '.$this->User->id);
        } else {
            $this->error('User could not be inserted ('.print_r($this->User->validationErrors, true).')');
        }
    }
}

Remove the closing PHP tags

Since CakePHP1.3 the closing tags are omitted – for a good reason. You should also make sure your app does not contain any of those closing tags in PHP files. With this shell it’s done in seconds:

<?php

/**
 * removes closing php tag (?>) from php files
 * it also makes sure there is no whitespace at the beginning of the file
 */
class PhpTagShell extends Shell {
    var $tasks = array();
    var $uses = array();

    var $autoCorrectAll = false;
    # each report: [0] => found, [1] => corrected
    var $report = array('leading'=>array(0, 0),'trailing'=>array(0, 0));

    function main() {
        if(isset($this->args[0]) && !empty($this->args[0])) {
            $folder = realpath($this->args[0]);
        } else {
            $folder = APP;
        }
        if(is_file($folder)) {
            $r = array($folder);
        } else {
            App::import('Core',array('Folder'));
            $App = new Folder($folder);
            $this->out("Find recursive *.php in [".$folder."] ....");
            $r = $App->findRecursive('.*\.php');
        }

        $folders = array();

        foreach($r as $file) {
            $error = array();
            $action = '';

            $c = file_get_contents($file);
            if(preg_match('/^[\n\r|\n\r|\n|\r|\s]+\<\?php/', $c)) {
                $error[] = 'leading';
            }
            if(preg_match('/\?\>[\n\r|\n\r|\n|\r|\s]*$/', $c)) {
                $error[] = 'trailing';
            }
            if (!empty($error)) {
                foreach($error as $e) {
                    $this->report[$e][0]++;
                }
                $this->out('');
                $this->out('contains '.rtrim(implode($error, ', '), ', ').' whitespaces / php tags: '.$this->shortPath($file));

                if (!$this->autoCorrectAll) {
                    $dirname = dirname($file);

                    if (in_array($dirname, $folders)) {
                        $action = 'y';
                    }

                    while (empty($action)) {
                        //TODO: [r]!
                        $action = $this->in(__('Remove? [y]/[n], [a] for all in this folder, [r] for all below, [*] for all files(!), [q] to quit', true), array('y','n','r','a','q','*'), 'q');
                    }
                } else {
                    $action = 'y';
                }

                if ($action == '*') {
                    $action = 'y';
                    $this->autoCorrectAll = true;

                } elseif ($action == 'a') {
                    $action = 'y';
                    $folders[] = $dirname;
                    $this->out('All: '.$dirname);
                }

                if($action == 'q') {
                    die('Abort... Done');
                } elseif ($action == 'y') {
                    $res = $c;
                    if(in_array('leading', $error)) {
                        $res = preg_replace('/^[\n\r|\n\r|\n|\r|\s]+\<\?php/', '<?php', $res);
                    }
                    if(in_array('trailing', $error)) {
                        $res = preg_replace('/\?\>[\n\r|\n\r|\n|\r|\s]*$/', "\n", $res);
                    }
                    file_put_contents($file, $res);
                    foreach($error as $e) {
                        $this->report[$e][1]++;
                        $this->out('fixed '.$e.' php tag: '.$this->shortPath($file));
                    }
                }
            }
        }

        # report
        $this->out('--------');
        $this->out('found '.$this->report['leading'][0].' leading, '.$this->report['trailing'][0].' trailing ws / php tag');
        $this->out('fixed '.$this->report['leading'][1].' leading, '.$this->report['trailing'][1].' trailing ws / php tag');
    }

}

You an either go through the complete app. Or you can pass a specific path like so:
cake php_tag C:\testfolder (custom folder) or cake php_tag config (/app/config).

UPDATE 2013-02-12 for 2.x

I put everything in my Tools plugin for easier use. It also contains the most recent (bug)fixes. Make sure you use those files instead.
And the path would now be APP/Console/Command/ – but it is better to use the Tools plugin as a whole.
Just call them as

cake Tools.ShellName command

now.

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.