ViewTask.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 1.2.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Console\Command\Task;
  16. use Cake\Console\Shell;
  17. use Cake\Core\App;
  18. use Cake\Core\Configure;
  19. use Cake\ORM\Table;
  20. use Cake\ORM\TableRegistry;
  21. use Cake\Utility\Inflector;
  22. /**
  23. * Task class for creating and updating view files.
  24. *
  25. */
  26. class ViewTask extends BakeTask {
  27. /**
  28. * Tasks to be loaded by this Task
  29. *
  30. * @var array
  31. */
  32. public $tasks = ['Model', 'Template'];
  33. /**
  34. * path to View directory
  35. *
  36. * @var array
  37. */
  38. public $pathFragment = 'Template/';
  39. /**
  40. * Name of the controller being used
  41. *
  42. * @var string
  43. */
  44. public $controllerName = null;
  45. /**
  46. * Classname of the controller being used
  47. *
  48. * @var string
  49. */
  50. public $controllerClass = null;
  51. /**
  52. * Name of the table views are being baked against.
  53. *
  54. * @var string
  55. */
  56. public $tableName = null;
  57. /**
  58. * The template file to use
  59. *
  60. * @var string
  61. */
  62. public $template = null;
  63. /**
  64. * Actions to use for scaffolding
  65. *
  66. * @var array
  67. */
  68. public $scaffoldActions = ['index', 'view', 'add', 'edit'];
  69. /**
  70. * An array of action names that don't require templates. These
  71. * actions will not emit errors when doing bakeActions()
  72. *
  73. * @var array
  74. */
  75. public $noTemplateActions = ['delete'];
  76. /**
  77. * Override initialize
  78. *
  79. * @return void
  80. */
  81. public function initialize() {
  82. $this->path = current(App::path('Template'));
  83. }
  84. /**
  85. * Execution method always used for tasks
  86. *
  87. * @return mixed
  88. */
  89. public function execute($name = null, $template = null, $action = null) {
  90. parent::execute();
  91. if (!isset($this->connection)) {
  92. $this->connection = 'default';
  93. }
  94. if (empty($name)) {
  95. $this->out(__d('cake_console', 'Possible tables to bake views for based on your current database:'));
  96. $this->Model->connection = $this->connection;
  97. foreach ($this->Model->listAll() as $table) {
  98. $this->out('- ' . $this->_controllerName($table));
  99. }
  100. return true;
  101. }
  102. if (strtolower($name) === 'all') {
  103. return $this->all();
  104. }
  105. $controller = null;
  106. if (!empty($this->params['controller'])) {
  107. $controller = $this->params['controller'];
  108. }
  109. $this->controller($name, $controller);
  110. if (isset($template)) {
  111. $this->template = $template;
  112. }
  113. if (!$action) {
  114. $action = $this->template;
  115. }
  116. if ($action) {
  117. return $this->bake($action, true);
  118. }
  119. $vars = $this->_loadController();
  120. $methods = $this->_methodsToBake();
  121. foreach ($methods as $method) {
  122. $content = $this->getContent($method, $vars);
  123. if ($content) {
  124. $this->bake($method, $content);
  125. }
  126. }
  127. }
  128. /**
  129. * Set the controller related properties.
  130. *
  131. * @param string $table The table/model that is being baked.
  132. * @param string $controller The controller name if specified.
  133. * @return void
  134. */
  135. public function controller($table, $controller = null) {
  136. $this->tableName = $this->_controllerName($table);
  137. if (empty($controller)) {
  138. $controller = $this->tableName;
  139. }
  140. $this->controllerName = $controller;
  141. $plugin = $prefix = null;
  142. if (!empty($this->params['plugin'])) {
  143. $plugin = $this->params['plugin'] . '.';
  144. }
  145. if (!empty($this->params['prefix'])) {
  146. $prefix = $this->params['prefix'] . '/';
  147. }
  148. $this->controllerClass = App::className($plugin . $prefix . $controller, 'Controller', 'Controller');
  149. }
  150. /**
  151. * Get the path base for views.
  152. *
  153. * @return string
  154. */
  155. public function getPath() {
  156. $path = parent::getPath();
  157. if (!empty($this->params['prefix'])) {
  158. $path .= $this->_camelize($this->params['prefix']) . DS;
  159. }
  160. $path .= $this->controllerName . DS;
  161. return $path;
  162. }
  163. /**
  164. * Get a list of actions that can / should have views baked for them.
  165. *
  166. * @return array Array of action names that should be baked
  167. */
  168. protected function _methodsToBake() {
  169. $base = Configure::read('App.namespace');
  170. $methods = [];
  171. if (class_exists($this->controllerClass)) {
  172. $methods = array_diff(
  173. array_map('strtolower', get_class_methods($this->controllerClass)),
  174. array_map('strtolower', get_class_methods($base . '\Controller\AppController'))
  175. );
  176. }
  177. if (empty($methods)) {
  178. $methods = $this->scaffoldActions;
  179. }
  180. foreach ($methods as $i => $method) {
  181. if ($method[0] === '_') {
  182. unset($methods[$i]);
  183. }
  184. }
  185. return $methods;
  186. }
  187. /**
  188. * Bake All views for All controllers.
  189. *
  190. * @return void
  191. */
  192. public function all() {
  193. $this->Model->connection = $this->connection;
  194. $tables = $this->Model->listAll();
  195. foreach ($tables as $table) {
  196. $this->execute($table);
  197. }
  198. }
  199. /**
  200. * Loads Controller and sets variables for the template
  201. * Available template variables:
  202. *
  203. * - 'modelClass'
  204. * - 'primaryKey'
  205. * - 'displayField'
  206. * - 'singularVar'
  207. * - 'pluralVar'
  208. * - 'singularHumanName'
  209. * - 'pluralHumanName'
  210. * - 'fields'
  211. * - 'keyFields'
  212. * - 'schema'
  213. *
  214. * @return array Returns an variables to be made available to a view template
  215. */
  216. protected function _loadController() {
  217. $modelObj = TableRegistry::get($this->tableName);
  218. $primaryKey = (array)$modelObj->primaryKey();
  219. $displayField = $modelObj->displayField();
  220. $singularVar = $this->_singularName($this->controllerName);
  221. $singularHumanName = $this->_singularHumanName($this->controllerName);
  222. $schema = $modelObj->schema();
  223. $fields = $schema->columns();
  224. $associations = $this->_associations($modelObj);
  225. $keyFields = [];
  226. if (!empty($associations['BelongsTo'])) {
  227. foreach ($associations['BelongsTo'] as $assoc) {
  228. $keyFields[$assoc['foreignKey']] = $assoc['variable'];
  229. }
  230. }
  231. $pluralVar = Inflector::variable($this->controllerName);
  232. $pluralHumanName = $this->_pluralHumanName($this->controllerName);
  233. return compact(
  234. 'modelClass', 'schema',
  235. 'primaryKey', 'displayField',
  236. 'singularVar', 'pluralVar',
  237. 'singularHumanName', 'pluralHumanName',
  238. 'fields', 'associations', 'keyFields'
  239. );
  240. }
  241. /**
  242. * Bake a view file for each of the supplied actions
  243. *
  244. * @param array $actions Array of actions to make files for.
  245. * @param array $vars
  246. * @return void
  247. */
  248. public function bakeActions(array $actions, $vars) {
  249. foreach ($actions as $action) {
  250. $content = $this->getContent($action, $vars);
  251. $this->bake($action, $content);
  252. }
  253. }
  254. /**
  255. * handle creation of baking a custom action view file
  256. *
  257. * @return void
  258. */
  259. public function customAction() {
  260. $action = '';
  261. while (!$action) {
  262. $action = $this->in(__d('cake_console', 'Action Name? (use lowercase_underscored function name)'));
  263. if (!$action) {
  264. $this->out(__d('cake_console', 'The action name you supplied was empty. Please try again.'));
  265. }
  266. }
  267. $this->out();
  268. $this->hr();
  269. $this->out(__d('cake_console', 'The following view will be created:'));
  270. $this->hr();
  271. $this->out(__d('cake_console', 'Controller Name: %s', $this->controllerName));
  272. $this->out(__d('cake_console', 'Action Name: %s', $action));
  273. $this->out(__d('cake_console', 'Path: %s', $this->getPath() . $this->controllerName . DS . Inflector::underscore($action) . ".ctp"));
  274. $this->hr();
  275. $looksGood = $this->in(__d('cake_console', 'Look okay?'), ['y', 'n'], 'y');
  276. if (strtolower($looksGood) === 'y') {
  277. $this->bake($action, ' ');
  278. return $this->_stop();
  279. }
  280. $this->out(__d('cake_console', 'Bake Aborted.'));
  281. }
  282. /**
  283. * Assembles and writes bakes the view file.
  284. *
  285. * @param string $action Action to bake.
  286. * @param string $content Content to write.
  287. * @return string Generated file content.
  288. */
  289. public function bake($action, $content = '') {
  290. if ($content === true) {
  291. $content = $this->getContent($action);
  292. }
  293. if (empty($content)) {
  294. return false;
  295. }
  296. $this->out("\n" . __d('cake_console', 'Baking `%s` view file...', $action), 1, Shell::QUIET);
  297. $path = $this->getPath();
  298. $filename = $path . Inflector::underscore($action) . '.ctp';
  299. $this->createFile($filename, $content);
  300. return $content;
  301. }
  302. /**
  303. * Builds content from template and variables
  304. *
  305. * @param string $action name to generate content to
  306. * @param array $vars passed for use in templates
  307. * @return string content from template
  308. */
  309. public function getContent($action, $vars = null) {
  310. if (!$vars) {
  311. $vars = $this->_loadController();
  312. }
  313. $this->Template->set('action', $action);
  314. $this->Template->set('plugin', $this->plugin);
  315. $this->Template->set($vars);
  316. $template = $this->getTemplate($action);
  317. if ($template) {
  318. return $this->Template->generate('views', $template);
  319. }
  320. return false;
  321. }
  322. /**
  323. * Gets the template name based on the action name
  324. *
  325. * @param string $action name
  326. * @return string template name
  327. */
  328. public function getTemplate($action) {
  329. if ($action != $this->template && in_array($action, $this->noTemplateActions)) {
  330. return false;
  331. }
  332. if (!empty($this->template) && $action != $this->template) {
  333. return $this->template;
  334. }
  335. $themePath = $this->Template->getThemePath();
  336. if (!empty($this->params['prefix'])) {
  337. $prefixed = Inflector::underscore($this->params['prefix']) . '_' . $action;
  338. if (file_exists($themePath . 'views/' . $prefixed . '.ctp')) {
  339. return $prefixed;
  340. }
  341. $generic = preg_replace('/(.*)(_add|_edit)$/', '\1_form', $prefixed);
  342. if (file_exists($themePath . 'views/' . $generic . '.ctp')) {
  343. return $generic;
  344. }
  345. }
  346. if (file_exists($themePath . 'views/' . $action . '.ctp')) {
  347. return $action;
  348. }
  349. if (in_array($action, ['add', 'edit'])) {
  350. return 'form';
  351. }
  352. return $action;
  353. }
  354. /**
  355. * Gets the option parser instance and configures it.
  356. *
  357. * @return \Cake\Console\ConsoleOptionParser
  358. */
  359. public function getOptionParser() {
  360. $parser = parent::getOptionParser();
  361. $parser->description(
  362. __d('cake_console', 'Bake views for a controller, using built-in or custom templates. ')
  363. )->addArgument('controller', [
  364. 'help' => __d('cake_console', 'Name of the controller views to bake. Can be Plugin.name as a shortcut for plugin baking.')
  365. ])->addArgument('action', [
  366. 'help' => __d('cake_console', "Will bake a single action's file. core templates are (index, add, edit, view)")
  367. ])->addArgument('alias', [
  368. 'help' => __d('cake_console', 'Will bake the template in <action> but create the filename after <alias>.')
  369. ])->addOption('controller', [
  370. 'help' => __d('cake_console', 'The controller name if you have a controller that does not follow conventions.')
  371. ])->addOption('prefix', [
  372. 'help' => __d('cake_console', 'The routing prefix to generate views for.'),
  373. ])->addSubcommand('all', [
  374. 'help' => __d('cake_console', 'Bake all CRUD action views for all controllers. Requires models and controllers to exist.')
  375. ]);
  376. return $parser;
  377. }
  378. /**
  379. * Returns associations for controllers models.
  380. *
  381. * @param Table $model
  382. * @return array associations
  383. */
  384. protected function _associations(Table $model) {
  385. $keys = ['BelongsTo', 'HasOne', 'HasMany', 'BelongsToMany'];
  386. $associations = [];
  387. foreach ($keys as $type) {
  388. foreach ($model->associations()->type($type) as $assoc) {
  389. $target = $assoc->target();
  390. $assocName = $assoc->name();
  391. $alias = $target->alias();
  392. $associations[$type][$assocName] = [
  393. 'property' => $assoc->property(),
  394. 'variable' => Inflector::variable($assocName),
  395. 'primaryKey' => (array)$target->primaryKey(),
  396. 'displayField' => $target->displayField(),
  397. 'foreignKey' => $assoc->foreignKey(),
  398. 'controller' => Inflector::underscore($alias),
  399. 'fields' => $target->schema()->columns(),
  400. ];
  401. }
  402. }
  403. return $associations;
  404. }
  405. }