1<?php
2/**
3 * Template Task can generate templated output Used in other Tasks
4 *
5 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
6 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
7 *
8 * Licensed under The MIT License
9 * For full copyright and license information, please see the LICENSE.txt
10 * Redistributions of files must retain the above copyright notice.
11 *
12 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
13 * @link          https://cakephp.org CakePHP(tm) Project
14 * @since         CakePHP(tm) v 1.3
15 * @license       https://opensource.org/licenses/mit-license.php MIT License
16 */
17
18App::uses('AppShell', 'Console/Command');
19App::uses('Folder', 'Utility');
20
21/**
22 * Template Task can generate templated output Used in other Tasks.
23 * Acts like a simplified View class.
24 *
25 * @package       Cake.Console.Command.Task
26 */
27class TemplateTask extends AppShell {
28
29/**
30 * variables to add to template scope
31 *
32 * @var array
33 */
34	public $templateVars = array();
35
36/**
37 * Paths to look for templates on.
38 * Contains a list of $theme => $path
39 *
40 * @var array
41 */
42	public $templatePaths = array();
43
44/**
45 * Initialize callback. Setup paths for the template task.
46 *
47 * @return void
48 */
49	public function initialize() {
50		$this->templatePaths = $this->_findThemes();
51	}
52
53/**
54 * Find the paths to all the installed shell themes in the app.
55 *
56 * Bake themes are directories not named `skel` inside a `Console/Templates` path.
57 * They are listed in this order: app -> plugin -> default
58 *
59 * @return array Array of bake themes that are installed.
60 */
61	protected function _findThemes() {
62		$paths = App::path('Console');
63
64		$plugins = App::objects('plugin');
65		foreach ($plugins as $plugin) {
66			$paths[] = $this->_pluginPath($plugin) . 'Console' . DS;
67		}
68
69		$core = current(App::core('Console'));
70		$separator = DS === '/' ? '/' : '\\\\';
71		$core = preg_replace('#shells' . $separator . '$#', '', $core);
72
73		$Folder = new Folder($core . 'Templates' . DS . 'default');
74
75		$contents = $Folder->read();
76		$themeFolders = $contents[0];
77
78		$paths[] = $core;
79
80		foreach ($paths as $i => $path) {
81			$paths[$i] = rtrim($path, DS) . DS;
82		}
83
84		$themes = array();
85		foreach ($paths as $path) {
86			$Folder = new Folder($path . 'Templates', false);
87			$contents = $Folder->read();
88			$subDirs = $contents[0];
89			foreach ($subDirs as $dir) {
90				if (empty($dir) || preg_match('@^skel$|_skel$@', $dir)) {
91					continue;
92				}
93				$Folder = new Folder($path . 'Templates' . DS . $dir);
94				$contents = $Folder->read();
95				$subDirs = $contents[0];
96				if (array_intersect($contents[0], $themeFolders)) {
97					$templateDir = $path . 'Templates' . DS . $dir . DS;
98					$themes[$dir] = $templateDir;
99				}
100			}
101		}
102		return $themes;
103	}
104
105/**
106 * Set variable values to the template scope
107 *
108 * @param string|array $one A string or an array of data.
109 * @param string|array $two Value in case $one is a string (which then works as the key).
110 *   Unused if $one is an associative array, otherwise serves as the values to $one's keys.
111 * @return void
112 */
113	public function set($one, $two = null) {
114		if (is_array($one)) {
115			if (is_array($two)) {
116				$data = array_combine($one, $two);
117			} else {
118				$data = $one;
119			}
120		} else {
121			$data = array($one => $two);
122		}
123
124		if (!$data) {
125			return false;
126		}
127		$this->templateVars = $data + $this->templateVars;
128	}
129
130/**
131 * Runs the template
132 *
133 * @param string $directory directory / type of thing you want
134 * @param string $filename template name
135 * @param array $vars Additional vars to set to template scope.
136 * @return string contents of generated code template
137 */
138	public function generate($directory, $filename, $vars = null) {
139		if ($vars !== null) {
140			$this->set($vars);
141		}
142		if (empty($this->templatePaths)) {
143			$this->initialize();
144		}
145		$themePath = $this->getThemePath();
146		$templateFile = $this->_findTemplate($themePath, $directory, $filename);
147		if ($templateFile) {
148			extract($this->templateVars);
149			ob_start();
150			ob_implicit_flush(0);
151			include $templateFile;
152			$content = ob_get_clean();
153			return $content;
154		}
155		return '';
156	}
157
158/**
159 * Find the theme name for the current operation.
160 * If there is only one theme in $templatePaths it will be used.
161 * If there is a -theme param in the cli args, it will be used.
162 * If there is more than one installed theme user interaction will happen
163 *
164 * @return string returns the path to the selected theme.
165 */
166	public function getThemePath() {
167		if (count($this->templatePaths) === 1) {
168			$paths = array_values($this->templatePaths);
169			return $paths[0];
170		}
171		if (!empty($this->params['theme']) && isset($this->templatePaths[$this->params['theme']])) {
172			return $this->templatePaths[$this->params['theme']];
173		}
174
175		$this->hr();
176		$this->out(__d('cake_console', 'You have more than one set of templates installed.'));
177		$this->out(__d('cake_console', 'Please choose the template set you wish to use:'));
178		$this->hr();
179
180		$i = 1;
181		$indexedPaths = array();
182		foreach ($this->templatePaths as $key => $path) {
183			$this->out($i . '. ' . $key);
184			$indexedPaths[$i] = $path;
185			$i++;
186		}
187		$index = $this->in(__d('cake_console', 'Which bake theme would you like to use?'), range(1, $i - 1), 1);
188		$themeNames = array_keys($this->templatePaths);
189		$this->params['theme'] = $themeNames[$index - 1];
190		return $indexedPaths[$index];
191	}
192
193/**
194 * Find a template inside a directory inside a path.
195 * Will scan all other theme dirs if the template is not found in the first directory.
196 *
197 * @param string $path The initial path to look for the file on. If it is not found fallbacks will be used.
198 * @param string $directory Subdirectory to look for ie. 'views', 'objects'
199 * @param string $filename lower_case_underscored filename you want.
200 * @return string filename will exit program if template is not found.
201 */
202	protected function _findTemplate($path, $directory, $filename) {
203		$themeFile = $path . $directory . DS . $filename . '.ctp';
204		if (file_exists($themeFile)) {
205			return $themeFile;
206		}
207		foreach ($this->templatePaths as $path) {
208			$templatePath = $path . $directory . DS . $filename . '.ctp';
209			if (file_exists($templatePath)) {
210				return $templatePath;
211			}
212		}
213		$this->err(__d('cake_console', 'Could not find template for %s', $filename));
214		return false;
215	}
216
217}
218