1: <?php
   2:    3:    4:    5:    6:    7:    8:    9:   10:   11:   12:   13:   14:   15:   16: 
  17: 
  18: App::uses('AppShell', 'Console/Command');
  19: App::uses('BakeTask', 'Console/Command/Task');
  20: App::uses('ConnectionManager', 'Model');
  21: App::uses('Model', 'Model');
  22: App::uses('Validation', 'Utility');
  23: 
  24:   25:   26:   27:   28: 
  29: class ModelTask extends BakeTask {
  30: 
  31:   32:   33:   34:   35: 
  36:     public $path = null;
  37: 
  38:   39:   40:   41:   42: 
  43:     public $tasks = array('DbConfig', 'Fixture', 'Test', 'Template');
  44: 
  45:   46:   47:   48:   49: 
  50:     public $skipTables = array('i18n');
  51: 
  52:   53:   54:   55:   56: 
  57:     protected $_tables = array();
  58: 
  59:   60:   61:   62:   63: 
  64:     protected $_modelNames = array();
  65: 
  66:   67:   68:   69:   70: 
  71:     protected $_validations = array();
  72: 
  73:   74:   75:   76:   77: 
  78:     public function initialize() {
  79:         $this->path = current(App::path('Model'));
  80:     }
  81: 
  82:   83:   84:   85:   86: 
  87:     public function execute() {
  88:         parent::execute();
  89: 
  90:         if (empty($this->args)) {
  91:             $this->_interactive();
  92:         }
  93: 
  94:         if (!empty($this->args[0])) {
  95:             $this->interactive = false;
  96:             if (!isset($this->connection)) {
  97:                 $this->connection = 'default';
  98:             }
  99:             if (strtolower($this->args[0]) === 'all') {
 100:                 return $this->all();
 101:             }
 102:             $model = $this->_modelName($this->args[0]);
 103:             $this->listAll($this->connection);
 104:             $useTable = $this->getTable($model);
 105:             $object = $this->_getModelObject($model, $useTable);
 106:             if ($this->bake($object, false)) {
 107:                 if ($this->_checkUnitTest()) {
 108:                     $this->bakeFixture($model, $useTable);
 109:                     $this->bakeTest($model);
 110:                 }
 111:             }
 112:         }
 113:     }
 114: 
 115:  116:  117:  118:  119: 
 120:     public function all() {
 121:         $this->listAll($this->connection, false);
 122:         $unitTestExists = $this->_checkUnitTest();
 123:         foreach ($this->_tables as $table) {
 124:             if (in_array($table, $this->skipTables)) {
 125:                 continue;
 126:             }
 127:             $modelClass = Inflector::classify($table);
 128:             $this->out(__d('cake_console', 'Baking %s', $modelClass));
 129:             $object = $this->_getModelObject($modelClass, $table);
 130:             if ($this->bake($object, false) && $unitTestExists) {
 131:                 $this->bakeFixture($modelClass, $table);
 132:                 $this->bakeTest($modelClass);
 133:             }
 134:         }
 135:     }
 136: 
 137:  138:  139:  140:  141:  142:  143: 
 144:     protected function _getModelObject($className, $table = null) {
 145:         if (!$table) {
 146:             $table = Inflector::tableize($className);
 147:         }
 148:         $object = new Model(array('name' => $className, 'table' => $table, 'ds' => $this->connection));
 149:         $fields = $object->schema(true);
 150:         foreach ($fields as $name => $field) {
 151:             if (isset($field['key']) && $field['key'] === 'primary') {
 152:                 $object->primaryKey = $name;
 153:                 break;
 154:             }
 155:         }
 156:         return $object;
 157:     }
 158: 
 159:  160:  161:  162:  163:  164:  165:  166: 
 167:     public function inOptions($options, $prompt = null, $default = null) {
 168:         $valid = false;
 169:         $max = count($options);
 170:         while (!$valid) {
 171:             $len = strlen(count($options) + 1);
 172:             foreach ($options as $i => $option) {
 173:                 $this->out(sprintf("%${len}d. %s", $i + 1, $option));
 174:             }
 175:             if (empty($prompt)) {
 176:                 $prompt = __d('cake_console', 'Make a selection from the choices above');
 177:             }
 178:             $choice = $this->in($prompt, null, $default);
 179:             if (intval($choice) > 0 && intval($choice) <= $max) {
 180:                 $valid = true;
 181:             }
 182:         }
 183:         return $choice - 1;
 184:     }
 185: 
 186:  187:  188:  189:  190: 
 191:     protected function _interactive() {
 192:         $this->hr();
 193:         $this->out(__d('cake_console', "Bake Model\nPath: %s", $this->getPath()));
 194:         $this->hr();
 195:         $this->interactive = true;
 196: 
 197:         $primaryKey = 'id';
 198:         $validate = $associations = array();
 199: 
 200:         if (empty($this->connection)) {
 201:             $this->connection = $this->DbConfig->getConfig();
 202:         }
 203:         $currentModelName = $this->getName();
 204:         $useTable = $this->getTable($currentModelName);
 205:         $db = ConnectionManager::getDataSource($this->connection);
 206:         $fullTableName = $db->fullTableName($useTable);
 207:         if (!in_array($useTable, $this->_tables)) {
 208:             $prompt = __d('cake_console', "The table %s doesn't exist or could not be automatically detected\ncontinue anyway?", $useTable);
 209:             $continue = $this->in($prompt, array('y', 'n'));
 210:             if (strtolower($continue) === 'n') {
 211:                 return false;
 212:             }
 213:         }
 214: 
 215:         $tempModel = new Model(array('name' => $currentModelName, 'table' => $useTable, 'ds' => $this->connection));
 216: 
 217:         $knownToExist = false;
 218:         try {
 219:             $fields = $tempModel->schema(true);
 220:             $knownToExist = true;
 221:         } catch (Exception $e) {
 222:             $fields = array($tempModel->primaryKey);
 223:         }
 224:         if (!array_key_exists('id', $fields)) {
 225:             $primaryKey = $this->findPrimaryKey($fields);
 226:         }
 227: 
 228:         if ($knownToExist) {
 229:             $displayField = $tempModel->hasField(array('name', 'title'));
 230:             if (!$displayField) {
 231:                 $displayField = $this->findDisplayField($tempModel->schema());
 232:             }
 233: 
 234:             $prompt = __d('cake_console', "Would you like to supply validation criteria \nfor the fields in your model?");
 235:             $wannaDoValidation = $this->in($prompt, array('y', 'n'), 'y');
 236:             if (array_search($useTable, $this->_tables) !== false && strtolower($wannaDoValidation) === 'y') {
 237:                 $validate = $this->doValidation($tempModel);
 238:             }
 239: 
 240:             $prompt = __d('cake_console', "Would you like to define model associations\n(hasMany, hasOne, belongsTo, etc.)?");
 241:             $wannaDoAssoc = $this->in($prompt, array('y', 'n'), 'y');
 242:             if (strtolower($wannaDoAssoc) === 'y') {
 243:                 $associations = $this->doAssociations($tempModel);
 244:             }
 245:         }
 246: 
 247:         $this->out();
 248:         $this->hr();
 249:         $this->out(__d('cake_console', 'The following Model will be created:'));
 250:         $this->hr();
 251:         $this->out(__d('cake_console', "Name:       %s", $currentModelName));
 252: 
 253:         if ($this->connection !== 'default') {
 254:             $this->out(__d('cake_console', "DB Config:  %s", $this->connection));
 255:         }
 256:         if ($fullTableName !== Inflector::tableize($currentModelName)) {
 257:             $this->out(__d('cake_console', 'DB Table:   %s', $fullTableName));
 258:         }
 259:         if ($primaryKey !== 'id') {
 260:             $this->out(__d('cake_console', 'Primary Key: %s', $primaryKey));
 261:         }
 262:         if (!empty($validate)) {
 263:             $this->out(__d('cake_console', 'Validation: %s', print_r($validate, true)));
 264:         }
 265:         if (!empty($associations)) {
 266:             $this->out(__d('cake_console', 'Associations:'));
 267:             $assocKeys = array('belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany');
 268:             foreach ($assocKeys as $assocKey) {
 269:                 $this->_printAssociation($currentModelName, $assocKey, $associations);
 270:             }
 271:         }
 272: 
 273:         $this->hr();
 274:         $looksGood = $this->in(__d('cake_console', 'Look okay?'), array('y', 'n'), 'y');
 275: 
 276:         if (strtolower($looksGood) === 'y') {
 277:             $vars = compact('associations', 'validate', 'primaryKey', 'useTable', 'displayField');
 278:             $vars['useDbConfig'] = $this->connection;
 279:             if ($this->bake($currentModelName, $vars)) {
 280:                 if ($this->_checkUnitTest()) {
 281:                     $this->bakeFixture($currentModelName, $useTable);
 282:                     $this->bakeTest($currentModelName, $useTable, $associations);
 283:                 }
 284:             }
 285:         } else {
 286:             return false;
 287:         }
 288:     }
 289: 
 290:  291:  292:  293:  294:  295:  296:  297: 
 298:     protected function _printAssociation($modelName, $type, $associations) {
 299:         if (!empty($associations[$type])) {
 300:             for ($i = 0, $len = count($associations[$type]); $i < $len; $i++) {
 301:                 $out = "\t" . $modelName . ' ' . $type . ' ' . $associations[$type][$i]['alias'];
 302:                 $this->out($out);
 303:             }
 304:         }
 305:     }
 306: 
 307:  308:  309:  310:  311:  312: 
 313:     public function findPrimaryKey($fields) {
 314:         $name = 'id';
 315:         foreach ($fields as $name => $field) {
 316:             if (isset($field['key']) && $field['key'] === 'primary') {
 317:                 break;
 318:             }
 319:         }
 320:         return $this->in(__d('cake_console', 'What is the primaryKey?'), null, $name);
 321:     }
 322: 
 323:  324:  325:  326:  327:  328: 
 329:     public function findDisplayField($fields) {
 330:         $fieldNames = array_keys($fields);
 331:         $prompt = __d('cake_console', "A displayField could not be automatically detected\nwould you like to choose one?");
 332:         $continue = $this->in($prompt, array('y', 'n'));
 333:         if (strtolower($continue) === 'n') {
 334:             return false;
 335:         }
 336:         $prompt = __d('cake_console', 'Choose a field from the options above:');
 337:         $choice = $this->inOptions($fieldNames, $prompt);
 338:         return $fieldNames[$choice];
 339:     }
 340: 
 341:  342:  343:  344:  345:  346: 
 347:     public function doValidation($model) {
 348:         if (!$model instanceof Model) {
 349:             return false;
 350:         }
 351:         $fields = $model->schema();
 352: 
 353:         if (empty($fields)) {
 354:             return false;
 355:         }
 356:         $validate = array();
 357:         $this->initValidations();
 358:         foreach ($fields as $fieldName => $field) {
 359:             $validation = $this->fieldValidation($fieldName, $field, $model->primaryKey);
 360:             if (!empty($validation)) {
 361:                 $validate[$fieldName] = $validation;
 362:             }
 363:         }
 364:         return $validate;
 365:     }
 366: 
 367:  368:  369:  370:  371: 
 372:     public function initValidations() {
 373:         $options = $choices = array();
 374:         if (class_exists('Validation')) {
 375:             $options = get_class_methods('Validation');
 376:         }
 377:         sort($options);
 378:         $default = 1;
 379:         foreach ($options as $option) {
 380:             if ($option{0} !== '_') {
 381:                 $choices[$default] = $option;
 382:                 $default++;
 383:             }
 384:         }
 385:         $choices[$default] = 'none'; 
 386:         $this->_validations = $choices;
 387:         return $choices;
 388:     }
 389: 
 390:  391:  392:  393:  394:  395:  396:  397: 
 398:     public function fieldValidation($fieldName, $metaData, $primaryKey = 'id') {
 399:         $defaultChoice = count($this->_validations);
 400:         $validate = $alreadyChosen = array();
 401: 
 402:         $anotherValidator = 'y';
 403:         while ($anotherValidator === 'y') {
 404:             if ($this->interactive) {
 405:                 $this->out();
 406:                 $this->out(__d('cake_console', 'Field: <info>%s</info>', $fieldName));
 407:                 $this->out(__d('cake_console', 'Type: <info>%s</info>', $metaData['type']));
 408:                 $this->hr();
 409:                 $this->out(__d('cake_console', 'Please select one of the following validation options:'));
 410:                 $this->hr();
 411: 
 412:                 $optionText = '';
 413:                 for ($i = 1, $m = $defaultChoice / 2; $i <= $m; $i++) {
 414:                     $line = sprintf("%2d. %s", $i, $this->_validations[$i]);
 415:                     $optionText .= $line . str_repeat(" ", 31 - strlen($line));
 416:                     if ($m + $i !== $defaultChoice) {
 417:                         $optionText .= sprintf("%2d. %s\n", $m + $i, $this->_validations[$m + $i]);
 418:                     }
 419:                 }
 420:                 $this->out($optionText);
 421:                 $this->out(__d('cake_console', "%s - Do not do any validation on this field.", $defaultChoice));
 422:                 $this->hr();
 423:             }
 424: 
 425:             $prompt = __d('cake_console', "... or enter in a valid regex validation string.\n");
 426:             $methods = array_flip($this->_validations);
 427:             $guess = $defaultChoice;
 428:             if ($metaData['null'] != 1 && !in_array($fieldName, array($primaryKey, 'created', 'modified', 'updated'))) {
 429:                 if ($fieldName === 'email') {
 430:                     $guess = $methods['email'];
 431:                 } elseif ($metaData['type'] === 'string' && $metaData['length'] == 36) {
 432:                     $guess = $methods['uuid'];
 433:                 } elseif ($metaData['type'] === 'string') {
 434:                     $guess = $methods['notEmpty'];
 435:                 } elseif ($metaData['type'] === 'text') {
 436:                     $guess = $methods['notEmpty'];
 437:                 } elseif ($metaData['type'] === 'integer') {
 438:                     $guess = $methods['numeric'];
 439:                 } elseif ($metaData['type'] === 'float') {
 440:                     $guess = $methods['numeric'];
 441:                 } elseif ($metaData['type'] === 'boolean') {
 442:                     $guess = $methods['boolean'];
 443:                 } elseif ($metaData['type'] === 'date') {
 444:                     $guess = $methods['date'];
 445:                 } elseif ($metaData['type'] === 'time') {
 446:                     $guess = $methods['time'];
 447:                 } elseif ($metaData['type'] === 'datetime') {
 448:                     $guess = $methods['datetime'];
 449:                 } elseif ($metaData['type'] === 'inet') {
 450:                     $guess = $methods['ip'];
 451:                 }
 452:             }
 453: 
 454:             if ($this->interactive === true) {
 455:                 $choice = $this->in($prompt, null, $guess);
 456:                 if (in_array($choice, $alreadyChosen)) {
 457:                     $this->out(__d('cake_console', "You have already chosen that validation rule,\nplease choose again"));
 458:                     continue;
 459:                 }
 460:                 if (!isset($this->_validations[$choice]) && is_numeric($choice)) {
 461:                     $this->out(__d('cake_console', 'Please make a valid selection.'));
 462:                     continue;
 463:                 }
 464:                 $alreadyChosen[] = $choice;
 465:             } else {
 466:                 $choice = $guess;
 467:             }
 468: 
 469:             if (isset($this->_validations[$choice])) {
 470:                 $validatorName = $this->_validations[$choice];
 471:             } else {
 472:                 $validatorName = Inflector::slug($choice);
 473:             }
 474: 
 475:             if ($choice != $defaultChoice) {
 476:                 $validate[$validatorName] = $choice;
 477:                 if (is_numeric($choice) && isset($this->_validations[$choice])) {
 478:                     $validate[$validatorName] = $this->_validations[$choice];
 479:                 }
 480:             }
 481:             $anotherValidator = 'n';
 482:             if ($this->interactive && $choice != $defaultChoice) {
 483:                 $anotherValidator = $this->in(__d('cake_console', 'Would you like to add another validation rule?'), array('y', 'n'), 'n');
 484:             }
 485:         }
 486:         return $validate;
 487:     }
 488: 
 489:  490:  491:  492:  493:  494: 
 495:     public function doAssociations($model) {
 496:         if (!$model instanceof Model) {
 497:             return false;
 498:         }
 499:         if ($this->interactive === true) {
 500:             $this->out(__d('cake_console', 'One moment while the associations are detected.'));
 501:         }
 502: 
 503:         $fields = $model->schema(true);
 504:         if (empty($fields)) {
 505:             return array();
 506:         }
 507: 
 508:         if (empty($this->_tables)) {
 509:             $this->_tables = (array)$this->getAllTables();
 510:         }
 511: 
 512:         $associations = array(
 513:             'belongsTo' => array(),
 514:             'hasMany' => array(),
 515:             'hasOne' => array(),
 516:             'hasAndBelongsToMany' => array()
 517:         );
 518: 
 519:         $associations = $this->findBelongsTo($model, $associations);
 520:         $associations = $this->findHasOneAndMany($model, $associations);
 521:         $associations = $this->findHasAndBelongsToMany($model, $associations);
 522: 
 523:         if ($this->interactive !== true) {
 524:             unset($associations['hasOne']);
 525:         }
 526: 
 527:         if ($this->interactive === true) {
 528:             $this->hr();
 529:             if (empty($associations)) {
 530:                 $this->out(__d('cake_console', 'None found.'));
 531:             } else {
 532:                 $this->out(__d('cake_console', 'Please confirm the following associations:'));
 533:                 $this->hr();
 534:                 $associations = $this->confirmAssociations($model, $associations);
 535:             }
 536:             $associations = $this->doMoreAssociations($model, $associations);
 537:         }
 538:         return $associations;
 539:     }
 540: 
 541:  542:  543:  544:  545:  546: 
 547:     public function doActsAs($model) {
 548:         if (!$model instanceof Model) {
 549:             return false;
 550:         }
 551:         $behaviors = array();
 552:         $fields = $model->schema(true);
 553:         if (empty($fields)) {
 554:             return array();
 555:         }
 556: 
 557:         if (isset($fields['lft']) && $fields['lft']['type'] === 'integer' &&
 558:             isset($fields['rght']) && $fields['rght']['type'] === 'integer' &&
 559:             isset($fields['parent_id'])) {
 560:             $behaviors[] = 'Tree';
 561:         }
 562:         return $behaviors;
 563:     }
 564: 
 565:  566:  567:  568:  569:  570:  571: 
 572:     public function findBelongsTo(Model $model, $associations) {
 573:         $fieldNames = array_keys($model->schema(true));
 574:         foreach ($fieldNames as $fieldName) {
 575:             $offset = substr($fieldName, -3) === '_id';
 576:             if ($fieldName != $model->primaryKey && $fieldName !== 'parent_id' && $offset !== false) {
 577:                 $tmpModelName = $this->_modelNameFromKey($fieldName);
 578:                 $associations['belongsTo'][] = array(
 579:                     'alias' => $tmpModelName,
 580:                     'className' => $tmpModelName,
 581:                     'foreignKey' => $fieldName,
 582:                 );
 583:             } elseif ($fieldName === 'parent_id') {
 584:                 $associations['belongsTo'][] = array(
 585:                     'alias' => 'Parent' . $model->name,
 586:                     'className' => $model->name,
 587:                     'foreignKey' => $fieldName,
 588:                 );
 589:             }
 590:         }
 591:         return $associations;
 592:     }
 593: 
 594:  595:  596:  597:  598:  599:  600: 
 601:     public function findHasOneAndMany(Model $model, $associations) {
 602:         $foreignKey = $this->_modelKey($model->name);
 603:         foreach ($this->_tables as $otherTable) {
 604:             $tempOtherModel = $this->_getModelObject($this->_modelName($otherTable), $otherTable);
 605:             $tempFieldNames = array_keys($tempOtherModel->schema(true));
 606: 
 607:             $pattern = '/_' . preg_quote($model->table, '/') . '|' . preg_quote($model->table, '/') . '_/';
 608:             $possibleJoinTable = preg_match($pattern, $otherTable);
 609:             if ($possibleJoinTable) {
 610:                 continue;
 611:             }
 612:             foreach ($tempFieldNames as $fieldName) {
 613:                 $assoc = false;
 614:                 if ($fieldName !== $model->primaryKey && $fieldName === $foreignKey) {
 615:                     $assoc = array(
 616:                         'alias' => $tempOtherModel->name,
 617:                         'className' => $tempOtherModel->name,
 618:                         'foreignKey' => $fieldName
 619:                     );
 620:                 } elseif ($otherTable === $model->table && $fieldName === 'parent_id') {
 621:                     $assoc = array(
 622:                         'alias' => 'Child' . $model->name,
 623:                         'className' => $model->name,
 624:                         'foreignKey' => $fieldName
 625:                     );
 626:                 }
 627:                 if ($assoc) {
 628:                     $associations['hasOne'][] = $assoc;
 629:                     $associations['hasMany'][] = $assoc;
 630:                 }
 631: 
 632:             }
 633:         }
 634:         return $associations;
 635:     }
 636: 
 637:  638:  639:  640:  641:  642:  643: 
 644:     public function findHasAndBelongsToMany(Model $model, $associations) {
 645:         $foreignKey = $this->_modelKey($model->name);
 646:         foreach ($this->_tables as $otherTable) {
 647:             $tableName = null;
 648:             $offset = strpos($otherTable, $model->table . '_');
 649:             $otherOffset = strpos($otherTable, '_' . $model->table);
 650: 
 651:             if ($offset !== false) {
 652:                 $tableName = substr($otherTable, strlen($model->table . '_'));
 653:             } elseif ($otherOffset !== false) {
 654:                 $tableName = substr($otherTable, 0, $otherOffset);
 655:             }
 656:             if ($tableName && in_array($tableName, $this->_tables)) {
 657:                 $habtmName = $this->_modelName($tableName);
 658:                 $associations['hasAndBelongsToMany'][] = array(
 659:                     'alias' => $habtmName,
 660:                     'className' => $habtmName,
 661:                     'foreignKey' => $foreignKey,
 662:                     'associationForeignKey' => $this->_modelKey($habtmName),
 663:                     'joinTable' => $otherTable
 664:                 );
 665:             }
 666:         }
 667:         return $associations;
 668:     }
 669: 
 670:  671:  672:  673:  674:  675:  676: 
 677:     public function confirmAssociations(Model $model, $associations) {
 678:         foreach ($associations as $type => $settings) {
 679:             if (!empty($associations[$type])) {
 680:                 foreach ($associations[$type] as $i => $assoc) {
 681:                     $prompt = "{$model->name} {$type} {$assoc['alias']}?";
 682:                     $response = $this->in($prompt, array('y', 'n'), 'y');
 683: 
 684:                     if (strtolower($response) === 'n') {
 685:                         unset($associations[$type][$i]);
 686:                     } elseif ($type === 'hasMany') {
 687:                         unset($associations['hasOne'][$i]);
 688:                     }
 689:                 }
 690:                 $associations[$type] = array_merge($associations[$type]);
 691:             }
 692:         }
 693:         return $associations;
 694:     }
 695: 
 696:  697:  698:  699:  700:  701:  702: 
 703:     public function doMoreAssociations(Model $model, $associations) {
 704:         $prompt = __d('cake_console', 'Would you like to define some additional model associations?');
 705:         $wannaDoMoreAssoc = $this->in($prompt, array('y', 'n'), 'n');
 706:         $possibleKeys = $this->_generatePossibleKeys();
 707:         while (strtolower($wannaDoMoreAssoc) === 'y') {
 708:             $assocs = array('belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany');
 709:             $this->out(__d('cake_console', 'What is the association type?'));
 710:             $assocType = intval($this->inOptions($assocs, __d('cake_console', 'Enter a number')));
 711: 
 712:             $this->out(__d('cake_console', "For the following options be very careful to match your setup exactly.\n" .
 713:                 "Any spelling mistakes will cause errors."));
 714:             $this->hr();
 715: 
 716:             $alias = $this->in(__d('cake_console', 'What is the alias for this association?'));
 717:             $className = $this->in(__d('cake_console', 'What className will %s use?', $alias), null, $alias);
 718: 
 719:             if ($assocType === 0) {
 720:                 if (!empty($possibleKeys[$model->table])) {
 721:                     $showKeys = $possibleKeys[$model->table];
 722:                 } else {
 723:                     $showKeys = null;
 724:                 }
 725:                 $suggestedForeignKey = $this->_modelKey($alias);
 726:             } else {
 727:                 $otherTable = Inflector::tableize($className);
 728:                 if (in_array($otherTable, $this->_tables)) {
 729:                     if ($assocType < 3) {
 730:                         if (!empty($possibleKeys[$otherTable])) {
 731:                             $showKeys = $possibleKeys[$otherTable];
 732:                         } else {
 733:                             $showKeys = null;
 734:                         }
 735:                     } else {
 736:                         $showKeys = null;
 737:                     }
 738:                 } else {
 739:                     $otherTable = $this->in(__d('cake_console', 'What is the table for this model?'));
 740:                     $showKeys = $possibleKeys[$otherTable];
 741:                 }
 742:                 $suggestedForeignKey = $this->_modelKey($model->name);
 743:             }
 744:             if (!empty($showKeys)) {
 745:                 $this->out(__d('cake_console', 'A helpful List of possible keys'));
 746:                 $foreignKey = $this->inOptions($showKeys, __d('cake_console', 'What is the foreignKey?'));
 747:                 $foreignKey = $showKeys[intval($foreignKey)];
 748:             }
 749:             if (!isset($foreignKey)) {
 750:                 $foreignKey = $this->in(__d('cake_console', 'What is the foreignKey? Specify your own.'), null, $suggestedForeignKey);
 751:             }
 752:             if ($assocType === 3) {
 753:                 $associationForeignKey = $this->in(__d('cake_console', 'What is the associationForeignKey?'), null, $this->_modelKey($model->name));
 754:                 $joinTable = $this->in(__d('cake_console', 'What is the joinTable?'));
 755:             }
 756:             $associations[$assocs[$assocType]] = array_values((array)$associations[$assocs[$assocType]]);
 757:             $count = count($associations[$assocs[$assocType]]);
 758:             $i = ($count > 0) ? $count : 0;
 759:             $associations[$assocs[$assocType]][$i]['alias'] = $alias;
 760:             $associations[$assocs[$assocType]][$i]['className'] = $className;
 761:             $associations[$assocs[$assocType]][$i]['foreignKey'] = $foreignKey;
 762:             if ($assocType === 3) {
 763:                 $associations[$assocs[$assocType]][$i]['associationForeignKey'] = $associationForeignKey;
 764:                 $associations[$assocs[$assocType]][$i]['joinTable'] = $joinTable;
 765:             }
 766:             $wannaDoMoreAssoc = $this->in(__d('cake_console', 'Define another association?'), array('y', 'n'), 'y');
 767:         }
 768:         return $associations;
 769:     }
 770: 
 771:  772:  773:  774:  775: 
 776:     protected function _generatePossibleKeys() {
 777:         $possible = array();
 778:         foreach ($this->_tables as $otherTable) {
 779:             $tempOtherModel = new Model(array('table' => $otherTable, 'ds' => $this->connection));
 780:             $modelFieldsTemp = $tempOtherModel->schema(true);
 781:             foreach ($modelFieldsTemp as $fieldName => $field) {
 782:                 if ($field['type'] === 'integer' || $field['type'] === 'string') {
 783:                     $possible[$otherTable][] = $fieldName;
 784:                 }
 785:             }
 786:         }
 787:         return $possible;
 788:     }
 789: 
 790:  791:  792:  793:  794:  795:  796: 
 797:     public function bake($name, $data = array()) {
 798:         if ($name instanceof Model) {
 799:             if (!$data) {
 800:                 $data = array();
 801:                 $data['associations'] = $this->doAssociations($name);
 802:                 $data['validate'] = $this->doValidation($name);
 803:                 $data['actsAs'] = $this->doActsAs($name);
 804:             }
 805:             $data['primaryKey'] = $name->primaryKey;
 806:             $data['useTable'] = $name->table;
 807:             $data['useDbConfig'] = $name->useDbConfig;
 808:             $data['name'] = $name = $name->name;
 809:         } else {
 810:             $data['name'] = $name;
 811:         }
 812: 
 813:         $defaults = array(
 814:             'associations' => array(),
 815:             'actsAs' => array(),
 816:             'validate' => array(),
 817:             'primaryKey' => 'id',
 818:             'useTable' => null,
 819:             'useDbConfig' => 'default',
 820:             'displayField' => null
 821:         );
 822:         $data = array_merge($defaults, $data);
 823: 
 824:         $pluginPath = '';
 825:         if ($this->plugin) {
 826:             $pluginPath = $this->plugin . '.';
 827:         }
 828: 
 829:         $this->Template->set($data);
 830:         $this->Template->set(array(
 831:             'plugin' => $this->plugin,
 832:             'pluginPath' => $pluginPath
 833:         ));
 834:         $out = $this->Template->generate('classes', 'model');
 835: 
 836:         $path = $this->getPath();
 837:         $filename = $path . $name . '.php';
 838:         $this->out("\n" . __d('cake_console', 'Baking model class for %s...', $name), 1, Shell::QUIET);
 839:         $this->createFile($filename, $out);
 840:         ClassRegistry::flush();
 841:         return $out;
 842:     }
 843: 
 844:  845:  846:  847:  848:  849: 
 850:     public function bakeTest($className) {
 851:         $this->Test->interactive = $this->interactive;
 852:         $this->Test->plugin = $this->plugin;
 853:         $this->Test->connection = $this->connection;
 854:         return $this->Test->bake('Model', $className);
 855:     }
 856: 
 857:  858:  859:  860:  861:  862: 
 863:     public function listAll($useDbConfig = null) {
 864:         $this->_tables = $this->getAllTables($useDbConfig);
 865: 
 866:         $this->_modelNames = array();
 867:         $count = count($this->_tables);
 868:         for ($i = 0; $i < $count; $i++) {
 869:             $this->_modelNames[] = $this->_modelName($this->_tables[$i]);
 870:         }
 871:         if ($this->interactive === true) {
 872:             $this->out(__d('cake_console', 'Possible Models based on your current database:'));
 873:             $len = strlen($count + 1);
 874:             for ($i = 0; $i < $count; $i++) {
 875:                 $this->out(sprintf("%${len}d. %s", $i + 1, $this->_modelNames[$i]));
 876:             }
 877:         }
 878:         return $this->_tables;
 879:     }
 880: 
 881:  882:  883:  884:  885:  886:  887: 
 888:     public function getTable($modelName, $useDbConfig = null) {
 889:         $useTable = Inflector::tableize($modelName);
 890:         if (in_array($modelName, $this->_modelNames)) {
 891:             $modelNames = array_flip($this->_modelNames);
 892:             $useTable = $this->_tables[$modelNames[$modelName]];
 893:         }
 894: 
 895:         if ($this->interactive === true) {
 896:             if (!isset($useDbConfig)) {
 897:                 $useDbConfig = $this->connection;
 898:             }
 899:             $db = ConnectionManager::getDataSource($useDbConfig);
 900:             $fullTableName = $db->fullTableName($useTable, false);
 901:             $tableIsGood = false;
 902:             if (array_search($useTable, $this->_tables) === false) {
 903:                 $this->out();
 904:                 $this->out(__d('cake_console', "Given your model named '%s',\nCake would expect a database table named '%s'", $modelName, $fullTableName));
 905:                 $tableIsGood = $this->in(__d('cake_console', 'Do you want to use this table?'), array('y', 'n'), 'y');
 906:             }
 907:             if (strtolower($tableIsGood) === 'n') {
 908:                 $useTable = $this->in(__d('cake_console', 'What is the name of the table?'));
 909:             }
 910:         }
 911:         return $useTable;
 912:     }
 913: 
 914:  915:  916:  917:  918:  919:  920: 
 921:     public function getAllTables($useDbConfig = null) {
 922:         if (!isset($useDbConfig)) {
 923:             $useDbConfig = $this->connection;
 924:         }
 925: 
 926:         $tables = array();
 927:         $db = ConnectionManager::getDataSource($useDbConfig);
 928:         $db->cacheSources = false;
 929:         $usePrefix = empty($db->config['prefix']) ? '' : $db->config['prefix'];
 930:         if ($usePrefix) {
 931:             foreach ($db->listSources() as $table) {
 932:                 if (!strncmp($table, $usePrefix, strlen($usePrefix))) {
 933:                     $tables[] = substr($table, strlen($usePrefix));
 934:                 }
 935:             }
 936:         } else {
 937:             $tables = $db->listSources();
 938:         }
 939:         if (empty($tables)) {
 940:             $this->err(__d('cake_console', 'Your database does not have any tables.'));
 941:             return $this->_stop();
 942:         }
 943:         sort($tables);
 944:         return $tables;
 945:     }
 946: 
 947:  948:  949:  950:  951:  952: 
 953:     public function getName($useDbConfig = null) {
 954:         $this->listAll($useDbConfig);
 955: 
 956:         $enteredModel = '';
 957: 
 958:         while (!$enteredModel) {
 959:             $enteredModel = $this->in(__d('cake_console', "Enter a number from the list above,\n" .
 960:                 "type in the name of another model, or 'q' to exit"), null, 'q');
 961: 
 962:             if ($enteredModel === 'q') {
 963:                 $this->out(__d('cake_console', 'Exit'));
 964:                 return $this->_stop();
 965:             }
 966: 
 967:             if (!$enteredModel || intval($enteredModel) > count($this->_modelNames)) {
 968:                 $this->err(__d('cake_console', "The model name you supplied was empty,\n" .
 969:                     "or the number you selected was not an option. Please try again."));
 970:                 $enteredModel = '';
 971:             }
 972:         }
 973:         if (intval($enteredModel) > 0 && intval($enteredModel) <= count($this->_modelNames)) {
 974:             return $this->_modelNames[intval($enteredModel) - 1];
 975:         }
 976: 
 977:         return $enteredModel;
 978:     }
 979: 
 980:  981:  982:  983:  984: 
 985:     public function getOptionParser() {
 986:         $parser = parent::getOptionParser();
 987:         return $parser->description(
 988:                 __d('cake_console', 'Bake models.')
 989:             )->addArgument('name', array(
 990:                 'help' => __d('cake_console', 'Name of the model to bake. Can use Plugin.name to bake plugin models.')
 991:             ))->addSubcommand('all', array(
 992:                 'help' => __d('cake_console', 'Bake all model files with associations and validation.')
 993:             ))->addOption('plugin', array(
 994:                 'short' => 'p',
 995:                 'help' => __d('cake_console', 'Plugin to bake the model into.')
 996:             ))->addOption('theme', array(
 997:                 'short' => 't',
 998:                 'help' => __d('cake_console', 'Theme to use when baking code.')
 999:             ))->addOption('connection', array(
1000:                 'short' => 'c',
1001:                 'help' => __d('cake_console', 'The connection the model table is on.')
1002:             ))->addOption('force', array(
1003:                 'short' => 'f',
1004:                 'help' => __d('cake_console', 'Force overwriting existing files without prompting.')
1005:             ))->epilog(__d('cake_console', 'Omitting all arguments and options will enter into an interactive mode.'));
1006:     }
1007: 
1008: 1009: 1010: 1011: 1012: 1013: 1014: 1015: 
1016:     public function bakeFixture($className, $useTable = null) {
1017:         $this->Fixture->interactive = $this->interactive;
1018:         $this->Fixture->connection = $this->connection;
1019:         $this->Fixture->plugin = $this->plugin;
1020:         $this->Fixture->bake($className, $useTable);
1021:     }
1022: 
1023: }
1024: