Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

DevToolsCommand.php 16 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. <?php
  2. namespace Grav\Plugin\Console;
  3. use Grav\Common\Grav;
  4. use Grav\Common\Filesystem\Folder;
  5. use Grav\Common\GPM\GPM;
  6. use Grav\Common\Inflector;
  7. use Grav\Common\Twig\Twig;
  8. use Grav\Common\Utils;
  9. use RocketTheme\Toolbox\File\File;
  10. use Grav\Console\ConsoleCommand;
  11. use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
  12. use Symfony\Component\Console\Input\InputInterface;
  13. use Symfony\Component\Console\Style\SymfonyStyle;
  14. /**
  15. * Class DevToolsCommand
  16. * @package Grav\Plugin\Console
  17. */
  18. class DevToolsCommand extends ConsoleCommand
  19. {
  20. /** @var array */
  21. protected $component = [];
  22. /** @var Inflector */
  23. protected $inflector;
  24. /** @var UniformResourceLocator */
  25. protected $locator;
  26. /** @var Twig */
  27. protected $twig;
  28. /** @var GPM */
  29. protected $gpm;
  30. /** @var array */
  31. protected $options = [];
  32. /** @var array */
  33. protected $reserved_keywords = ['__halt_compiler', 'abstract', 'and', 'array', 'as', 'break', 'callable', 'case', 'catch', 'class', 'clone', 'const', 'continue', 'declare', 'default', 'die', 'do', 'echo', 'else', 'elseif', 'empty', 'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile', 'eval', 'exit', 'extends', 'final', 'for', 'foreach', 'function', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'interface', 'isset', 'list', 'namespace', 'new', 'or', 'print', 'private', 'protected', 'public', 'require', 'require_once', 'return', 'static', 'switch', 'throw', 'trait', 'try', 'unset', 'use', 'var', 'while', 'xor'];
  34. /**
  35. * Initializes the basic requirements for the developer tools
  36. *
  37. * @return void
  38. */
  39. protected function init(): void
  40. {
  41. if (!function_exists('curl_version')) {
  42. exit('FATAL: DEVTOOLS requires PHP Curl module to be installed');
  43. }
  44. $grav = Grav::instance();
  45. $grav['config']->init();
  46. $grav['uri']->init();
  47. $this->inflector = $grav['inflector'];
  48. $this->locator = $grav['locator'];
  49. $this->twig = $grav['twig'];
  50. $this->gpm = new GPM();
  51. //Add `theme://` to prevent fail
  52. $this->locator->addPath('theme', '', []);
  53. $this->locator->addPath('plugin', '', []);
  54. $this->locator->addPath('blueprint', '', []);
  55. // $this->config->set('theme', $config->get('themes.' . $name));
  56. }
  57. /**
  58. * Backwards compatibility to Grav 1.6.
  59. *
  60. * @return InputInterface
  61. */
  62. public function getInput(): InputInterface
  63. {
  64. return $this->input;
  65. }
  66. /**
  67. * Backwards compatibility to Grav 1.6.
  68. *
  69. * @return SymfonyStyle
  70. */
  71. public function getIO(): SymfonyStyle
  72. {
  73. $output = $this->output;
  74. if (!$output instanceof SymfonyStyle) {
  75. $this->output = $output = new SymfonyStyle($this->input, $this->output);
  76. }
  77. return $this->output;
  78. }
  79. /**
  80. * Copies the component type and renames accordingly
  81. *
  82. * @return bool
  83. */
  84. protected function createComponent(): bool
  85. {
  86. $name = $this->component['name'];
  87. $folder_name = strtolower($this->inflector::hyphenize($name));
  88. $new_theme = $folder_name;
  89. $type = $this->component['type'];
  90. $grav = Grav::instance();
  91. $config = $grav['config'];
  92. $current_theme = $config->get('system.pages.theme');
  93. $template = $this->component['template'];
  94. $source_theme = null;
  95. if (isset($this->component['copy'])) {
  96. $current_theme = $this->component['copy'];
  97. $source_theme = $this->locator->findResource('themes://' . $current_theme);
  98. $template_folder = $source_theme;
  99. } else {
  100. $template_folder = __DIR__ . "/../components/{$type}/{$template}";
  101. }
  102. if ($type === 'blueprint') {
  103. $component_folder = $this->locator->findResource('themes://' . $current_theme) . '/blueprints';
  104. } else {
  105. $component_folder = $this->locator->findResource($type . 's://') . DS . $folder_name;
  106. }
  107. if (false === $template_folder) {
  108. $this->output->writeln("<red>Theme {$current_theme} does not exist</red>");
  109. return false;
  110. }
  111. if ($template === 'inheritance') {
  112. $parent_theme = $this->component['extends'];
  113. $yaml_file = $this->locator->findResource('themes://' . $parent_theme) . '/' . $parent_theme . '.yaml';
  114. $this->component['config'] = file_get_contents($yaml_file);
  115. }
  116. if (isset($source_theme)) {
  117. /**
  118. * Copy existing theme and regex-replace old stuff with new
  119. */
  120. // Get source if a symlink
  121. if (is_link($template_folder)) {
  122. $template_folder = readlink($template_folder);
  123. if (false === $template_folder) {
  124. $this->output->writeln("<red>Theme {$current_theme} is a bad symlink</red>");
  125. return false;
  126. }
  127. }
  128. //Copy All files to component folder
  129. try {
  130. Folder::copy($template_folder, $component_folder, '/.git|node_modules/');
  131. } catch (\Exception $e) {
  132. $this->output->writeln("<red>" . $e->getMessage() . "</red>");
  133. return false;
  134. }
  135. // Do some filename renaming
  136. $base_old_filename = $component_folder . '/' . $current_theme;
  137. $base_new_filename = $component_folder . '/' . $new_theme;
  138. @rename($base_old_filename . '.php', $base_new_filename . '.php');
  139. @rename($base_old_filename . '.yaml', $base_new_filename . '.yaml');
  140. $camelized_current = $this->inflector::camelize($current_theme);
  141. $camelized_new = $this->inflector::camelize($name);
  142. $hyphenized_current = $this->inflector::hyphenize($current_theme);
  143. $hyphenized_new = $this->inflector::hyphenize($name);
  144. $titleized_current = $this->inflector::titleize($current_theme);
  145. $titleized_new = $this->inflector::titleize($name);
  146. $underscoreized_current = $this->inflector::underscorize($current_theme);
  147. $underscoreized_new = $this->inflector::underscorize($name);
  148. $variations_regex = [
  149. ["/$camelized_current/", "/$hyphenized_current/"],
  150. [$camelized_new, $hyphenized_new]
  151. ];
  152. if (!in_array("/$titleized_current/", array_values($variations_regex[0]))) {
  153. $current_regex = $variations_regex[0];
  154. $new_regex = $variations_regex[1];
  155. $current_regex[] = "/$titleized_current/";
  156. $new_regex[] = $titleized_new;
  157. $variations_regex = [$current_regex, $new_regex];
  158. }
  159. if (!in_array("/$underscoreized_current/", array_values($variations_regex[0]))) {
  160. $current_regex = $variations_regex[0];
  161. $new_regex = $variations_regex[1];
  162. $current_regex[] = "/$underscoreized_current/";
  163. $new_regex[] = $underscoreized_new;
  164. $variations_regex = [$current_regex, $new_regex];
  165. }
  166. $regex_array = [
  167. $new_theme . '.php' => $variations_regex,
  168. 'blueprints.yaml' => $variations_regex,
  169. 'README.md' => $variations_regex,
  170. ];
  171. foreach ($regex_array as $filename => $data) {
  172. $filename = $component_folder . '/' . $filename;
  173. if (!file_exists($filename)) {
  174. continue;
  175. }
  176. $file = file_get_contents($filename);
  177. if ($file) {
  178. $file = preg_replace($data[0], $data[1], $file);
  179. }
  180. file_put_contents($filename, $file);
  181. }
  182. echo $source_theme;
  183. } else {
  184. /**
  185. * Use components folder and twig processing
  186. */
  187. //Copy All files to component folder
  188. try {
  189. Folder::copy($template_folder, $component_folder);
  190. } catch (\Exception $e) {
  191. $this->output->writeln("<red>" . $e->getMessage() . "</red>");
  192. return false;
  193. }
  194. //Add Twig vars and templates then initialize
  195. $this->twig->twig_vars['component'] = $this->component;
  196. $this->twig->twig_paths[] = $template_folder;
  197. $this->twig->init();
  198. //Get all templates of component then process each with twig and save
  199. $templates = Folder::all($component_folder);
  200. try {
  201. foreach ($templates as $templateFile) {
  202. if (Utils::endsWith($templateFile, '.twig') && !Utils::endsWith($templateFile, '.html.twig')) {
  203. $content = $this->twig->processTemplate($templateFile);
  204. $file = File::instance($component_folder . DS . str_replace('.twig', '', $templateFile));
  205. $file->content($content);
  206. $file->save();
  207. //Delete twig template
  208. $file = File::instance($component_folder . DS . $templateFile);
  209. $file->delete();
  210. }
  211. }
  212. } catch (\Exception $e) {
  213. $this->output->writeln("<red>" . $e->getMessage() . "</red>");
  214. $this->output->writeln("Rolling back...");
  215. Folder::delete($component_folder);
  216. $this->output->writeln($type . "creation failed!");
  217. return false;
  218. }
  219. if ($type !== 'blueprint') {
  220. rename($component_folder . DS . $type . '.php', $component_folder . DS . $folder_name . '.php');
  221. rename($component_folder . DS . $type . '.yaml', $component_folder . DS . $folder_name . '.yaml');
  222. } else {
  223. $bpname = $this->inflector::hyphenize($this->component['bpname']);
  224. rename($component_folder . DS . $type . '.yaml', $component_folder . DS . $bpname . '.yaml');
  225. }
  226. if ($this->component['flex_name']) {
  227. $flex_classes_folder = $component_folder . DS . 'classes' . DS . 'Flex' . DS . 'Types';
  228. $flex_name = strtolower($this->inflector::underscorize($this->component['flex_name']));
  229. $flex_name_camel = $this->inflector::camelize($this->component['flex_name']);
  230. rename($flex_classes_folder . DS . 'flex_name',$flex_classes_folder . DS . $flex_name_camel);
  231. rename($flex_classes_folder . DS . $flex_name_camel . DS . 'Object' . '.php',$flex_classes_folder . DS . $flex_name_camel . DS . $flex_name_camel . 'Object' . '.php');
  232. rename($flex_classes_folder . DS . $flex_name_camel . DS . 'Collection' . '.php',$flex_classes_folder . DS . $flex_name_camel . DS . $flex_name_camel . 'Collection' . '.php');
  233. rename($component_folder . DS . 'blueprints' . DS . 'flex-objects' . DS . $type . '.yaml', $component_folder . DS . 'blueprints' . DS . 'flex-objects' . DS . $flex_name . '.yaml');
  234. }
  235. }
  236. $this->output->writeln('');
  237. $this->output->writeln('<green>SUCCESS</green> ' . $type . ' <magenta>' . $name . '</magenta> -> Created Successfully');
  238. $this->output->writeln('');
  239. $this->output->writeln('Path: <cyan>' . $component_folder . '</cyan>');
  240. $this->output->writeln('');
  241. if ($type === 'plugin') {
  242. $this->output->writeln('<yellow>Please run `cd ' . $component_folder . '` and `composer update` to initialize the autoloader</yellow>');
  243. $this->output->writeln('');
  244. }
  245. return true;
  246. }
  247. /**
  248. * Iterate through all options and validate
  249. *
  250. * @return void
  251. */
  252. protected function validateOptions(): void
  253. {
  254. foreach (array_filter($this->options) as $type => $value) {
  255. $this->validate($type, $value);
  256. }
  257. }
  258. /**
  259. * @param string $type
  260. * @param mixed $value
  261. * @return mixed
  262. */
  263. protected function validate(string $type, $value)
  264. {
  265. switch ($type) {
  266. case 'name':
  267. // Check if name is empty
  268. if ($value === null || trim($value) === '') {
  269. throw new \RuntimeException('Name cannot be empty');
  270. }
  271. // Check if name starts with a numeric character
  272. if (is_numeric($value[0])) {
  273. throw new \RuntimeException('Name must start with an alphabetic character (A-Z, a-z)');
  274. }
  275. if (!$this->options['offline']) {
  276. // Check for name collision with online gpm.
  277. if (false !== $this->gpm->findPackage($value)) {
  278. throw new \RuntimeException('Package name exists in GPM');
  279. }
  280. } else {
  281. $this->output->writeln('');
  282. $this->output->writeln(' <red>Warning</red>: Please note that by skipping the online check, your project\'s plugin or theme name may conflict with an existing plugin or theme.');
  283. }
  284. // Check if it's reserved
  285. if ($this->isReservedWord(strtolower($value))) {
  286. throw new \RuntimeException("\"" . $value . "\" is a reserved word and cannot be used as the name");
  287. }
  288. break;
  289. case 'description':
  290. if ($value === null || trim($value) === '') {
  291. throw new \RuntimeException('Description cannot be empty');
  292. }
  293. break;
  294. case 'themename':
  295. if ($value === null || trim($value) === '') {
  296. throw new \RuntimeException('Theme Name cannot be empty');
  297. }
  298. break;
  299. case 'developer':
  300. if ($value === null || trim($value) === '') {
  301. throw new \RuntimeException('Developer\'s Name cannot be empty');
  302. }
  303. break;
  304. case 'githubid':
  305. // GitHubID can be blank, so nothing here
  306. break;
  307. case 'email':
  308. if (!preg_match('/^(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){255,})(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){65,}@)(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22))(?:\.(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\]))$/iD', $value)) {
  309. throw new \RuntimeException('Not a valid email address');
  310. }
  311. break;
  312. }
  313. return $value;
  314. }
  315. /**
  316. * @param string $word
  317. * @return bool
  318. */
  319. public function isReservedWord(string $word): bool
  320. {
  321. return in_array($word, $this->reserved_keywords, true);
  322. }
  323. }