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.
