Task.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. <?php
  2. /**
  3. * Interface that all minion tasks must implement
  4. *
  5. * @package KO7/Minion
  6. *
  7. * @copyright (c) 2007-2016 Kohana Team
  8. * @copyright (c) since 2016 Koseven Team
  9. * @license https://koseven.dev/LICENSE
  10. */
  11. abstract class KO7_Minion_Task {
  12. /**
  13. * The separator used to separate different levels of tasks
  14. * @var string
  15. */
  16. public static $task_separator = ':';
  17. /**
  18. * Converts a task (e.g. db:migrate to a class name)
  19. *
  20. * @param string Task name
  21. * @return string Class name
  22. */
  23. public static function convert_task_to_class_name($task)
  24. {
  25. $task = trim($task);
  26. if (empty($task))
  27. return '';
  28. return 'Task_'.implode('_', array_map('ucfirst', explode(Minion_Task::$task_separator, $task)));
  29. }
  30. /**
  31. * Gets the task name of a task class / task object
  32. *
  33. * @param string|Minion_Task The task class / object
  34. * @return string The task name
  35. */
  36. public static function convert_class_to_task($class)
  37. {
  38. if (is_object($class))
  39. {
  40. $class = get_class($class);
  41. }
  42. return strtolower(str_replace('_', Minion_Task::$task_separator, substr($class, 5)));
  43. }
  44. /**
  45. * Factory for loading minion tasks
  46. *
  47. * @param array An array of command line options. It should contain the 'task' key
  48. * @throws Minion_Exception_InvalidTask
  49. * @return Minion_Task The Minion task
  50. */
  51. public static function factory($options)
  52. {
  53. if (($task = Arr::get($options, 'task')) !== NULL)
  54. {
  55. unset($options['task']);
  56. }
  57. else if (($task = Arr::get($options, 0)) !== NULL)
  58. {
  59. // The first positional argument (aka 0) may be the task name
  60. unset($options[0]);
  61. }
  62. else
  63. {
  64. // If we didn't get a valid task, generate the help
  65. $task = 'help';
  66. }
  67. $class = Minion_Task::convert_task_to_class_name($task);
  68. if ( ! class_exists($class))
  69. {
  70. throw new Minion_Exception_InvalidTask(
  71. "Task ':task' is not a valid minion task",
  72. [':task' => $class]
  73. );
  74. }
  75. $class = new $class;
  76. if ( ! $class instanceof Minion_Task)
  77. {
  78. throw new Minion_Exception_InvalidTask(
  79. "Task ':task' is not a valid minion task",
  80. [':task' => $class]
  81. );
  82. }
  83. $class->set_options($options);
  84. // Show the help page for this task if requested
  85. if (array_key_exists('help', $options))
  86. {
  87. $class->_method = '_help';
  88. }
  89. return $class;
  90. }
  91. /**
  92. * The list of options this task accepts and their default values.
  93. *
  94. * protected $_options = array(
  95. * 'limit' => 4,
  96. * 'table' => NULL,
  97. * );
  98. *
  99. * @var array
  100. */
  101. protected $_options = [];
  102. /**
  103. * Populated with the accepted options for this task.
  104. * This array is automatically populated based on $_options.
  105. *
  106. * @var array
  107. */
  108. protected $_accepted_options = [];
  109. protected $_method = '_execute';
  110. protected function __construct()
  111. {
  112. // Populate $_accepted_options based on keys from $_options
  113. $this->_accepted_options = array_keys($this->_options);
  114. }
  115. /**
  116. * The file that get's passes to Validation::errors() when validation fails
  117. * @var string|NULL
  118. */
  119. protected $_errors_file = 'validation';
  120. /**
  121. * Gets the task name for the task
  122. *
  123. * @return string
  124. */
  125. public function __toString()
  126. {
  127. static $task_name = NULL;
  128. if ($task_name === NULL)
  129. {
  130. $task_name = Minion_Task::convert_class_to_task($this);
  131. }
  132. return $task_name;
  133. }
  134. /**
  135. * Sets options for this task
  136. *
  137. * $param array the array of options to set
  138. * @return this
  139. */
  140. public function set_options(array $options)
  141. {
  142. foreach ($options as $key => $value)
  143. {
  144. $this->_options[$key] = $value;
  145. }
  146. return $this;
  147. }
  148. /**
  149. * Get the options that were passed into this task with their defaults
  150. *
  151. * @return array
  152. */
  153. public function get_options()
  154. {
  155. return (array) $this->_options;
  156. }
  157. /**
  158. * Get a set of options that this task can accept
  159. *
  160. * @return array
  161. */
  162. public function get_accepted_options()
  163. {
  164. return (array) $this->_accepted_options;
  165. }
  166. /**
  167. * Adds any validation rules/labels for validating _options
  168. *
  169. * public function build_validation(Validation $validation)
  170. * {
  171. * return parent::build_validation($validation)
  172. * ->rule('paramname', 'not_empty'); // Require this param
  173. * }
  174. *
  175. * @param Validation the validation object to add rules to
  176. *
  177. * @return Validation
  178. */
  179. public function build_validation(Validation $validation)
  180. {
  181. // Add a rule to each key making sure it's in the task
  182. foreach ($validation->data() as $key => $value)
  183. {
  184. $validation->rule($key, [$this, 'valid_option'], [':validation', ':field']);
  185. }
  186. return $validation;
  187. }
  188. /**
  189. * Returns $_errors_file
  190. *
  191. * @return string
  192. */
  193. public function get_errors_file()
  194. {
  195. return $this->_errors_file;
  196. }
  197. /**
  198. * Execute the task with the specified set of options
  199. *
  200. * @return null
  201. */
  202. public function execute()
  203. {
  204. $options = $this->get_options();
  205. // Validate $options
  206. $validation = Validation::factory($options);
  207. $validation = $this->build_validation($validation);
  208. if ( $this->_method != '_help' AND ! $validation->check())
  209. {
  210. echo View::factory('minion/error/validation')
  211. ->set('task', Minion_Task::convert_class_to_task($this))
  212. ->set('errors', $validation->errors($this->get_errors_file()));
  213. }
  214. else
  215. {
  216. // Finally, run the task
  217. $method = $this->_method;
  218. echo $this->{$method}($options);
  219. }
  220. }
  221. abstract protected function _execute(array $params);
  222. /**
  223. * Outputs help for this task
  224. *
  225. * @return null
  226. */
  227. protected function _help(array $params)
  228. {
  229. $tasks = $this->_compile_task_list(KO7::list_files('classes/task'));
  230. $inspector = new ReflectionClass($this);
  231. list($description, $tags) = $this->_parse_doccomment($inspector->getDocComment());
  232. $view = View::factory('minion/help/task')
  233. ->set('description', $description)
  234. ->set('tags', (array) $tags)
  235. ->set('task', Minion_Task::convert_class_to_task($this));
  236. echo $view;
  237. }
  238. public function valid_option(Validation $validation, $option)
  239. {
  240. if ( ! in_array($option, $this->_accepted_options))
  241. {
  242. $validation->error($option, 'minion_option');
  243. }
  244. }
  245. /**
  246. * Parses a doccomment, extracting both the comment and any tags associated
  247. *
  248. * Based on the code in Kodoc::parse()
  249. *
  250. * @param string The comment to parse
  251. * @return array First element is the comment, second is an array of tags
  252. */
  253. protected function _parse_doccomment($comment)
  254. {
  255. // Normalize all new lines to \n
  256. $comment = str_replace(["\r\n", "\n"], "\n", $comment);
  257. // Remove the phpdoc open/close tags and split
  258. $comment = array_slice(explode("\n", $comment), 1, -1);
  259. // Tag content
  260. $tags = [];
  261. foreach ($comment as $i => $line)
  262. {
  263. // Remove all leading whitespace
  264. $line = preg_replace('/^\s*\* ?/m', '', $line);
  265. // Search this line for a tag
  266. if (preg_match('/^@(\S+)(?:\s*(.+))?$/', $line, $matches))
  267. {
  268. // This is a tag line
  269. unset($comment[$i]);
  270. $name = $matches[1];
  271. $text = isset($matches[2]) ? $matches[2] : '';
  272. $tags[$name] = $text;
  273. }
  274. else
  275. {
  276. $comment[$i] = (string) $line;
  277. }
  278. }
  279. $comment = trim(implode("\n", $comment));
  280. return [$comment, $tags];
  281. }
  282. /**
  283. * Compiles a list of available tasks from a directory structure
  284. *
  285. * @param array Directory structure of tasks
  286. * @param string prefix
  287. * @return array Compiled tasks
  288. */
  289. protected function _compile_task_list(array $files, $prefix = '')
  290. {
  291. $output = [];
  292. foreach ($files as $file => $path)
  293. {
  294. $file = substr($file, strrpos($file, DIRECTORY_SEPARATOR) + 1);
  295. if (is_array($path) AND count($path))
  296. {
  297. $task = $this->_compile_task_list($path, $prefix.$file.Minion_Task::$task_separator);
  298. if ($task)
  299. {
  300. $output = array_merge($output, $task);
  301. }
  302. }
  303. else
  304. {
  305. $output[] = strtolower($prefix.substr($file, 0, -strlen(EXT)));
  306. }
  307. }
  308. return $output;
  309. }
  310. /**
  311. * Sets the domain name for minion tasks
  312. * Minion tasks have no $_SERVER variables; to use the base url functions
  313. * the domain name can be set in the site config file, or as argument.
  314. *
  315. * @param string $domain_name the url of the server: example https://www.example.com
  316. */
  317. public static function set_domain_name($domain_name = '')
  318. {
  319. if (Request::$initial === NULL)
  320. {
  321. $domain_name = empty($domain_name) ? Arr::get(KO7::$config->load('site'), 'minion_domain_name', '') : $domain_name;
  322. // Add trailing slash
  323. KO7::$base_url = preg_replace('~^https?://[^/]+$~', '$0/', $domain_name);
  324. // Initialize Request Class
  325. $request = Request::factory();
  326. // Set HTTPS for https based urls
  327. $request->secure(preg_match_all('#(https)://#i', KO7::$base_url, $result) === 1);
  328. Request::$initial = $request;
  329. }
  330. }
  331. }