· 6 months ago · Apr 10, 2025, 03:20 PM
1<?php
2
3/**
4 *
5 * CV Manager - File to create a new project, like laravel artisan but for simple structure project
6 *
7 * @author Vinícius Cordeiro <vinicordeirogo@gmail.com>
8 * @version 1.0.0
9 * @copyright 2025 Vinícius Cordeiro
10 */
11
12ini_set('display_errors', 1);
13ini_set('display_startup_errors', 1);
14
15error_reporting(E_ERROR | E_WARNING | E_PARSE);
16
17// Increase the memory limit
18ini_set('memory_limit', '-1');
19// Set the maximum execution time
20set_time_limit(0);
21
22$files = [
23 'app' => [
24 'Core' => [
25 'Controller.php',
26 'Middleware.php',
27 'Request.php',
28 'Api.php',
29 'Auth.php',
30 'Cookie.php',
31 'Session.php',
32 'Tests.php',
33 ],
34 'Controllers' => [
35 'HomeController.php',
36 'LoginController.php',
37 'RegisterController.php',
38 'UserController.php',
39 'ErrorController.php',
40 'ExampleController.php'
41 ],
42 'Models' => [
43 'Model.php',
44 ],
45 'Views' => [
46 'View.php',
47 ],
48 'Helpers' => [
49 'Helper.php',
50 ],
51 'bootstrap.php',
52 'functions.php',
53 ],
54 'config' => [
55 'app.config.php',
56 'database.config.php',
57 'errors.config.php',
58 'types.config.php',
59 'version.config.php',
60 'session.config.php',
61 ],
62 'libs' => [
63 'autoload.php',
64 ],
65 'storage' => [
66 'logs' => [
67 'debug.log',
68 ],
69 'sessions' => [],
70 'uploads' => [
71 'images' => [],
72 'files' => [],
73 'avatars' => [],
74 ]
75 ],
76 'routes' => [
77 'base.php',
78 'web.php'
79 ],
80 'tests' => [
81 'debug' => [
82 'showVersion.php',
83 ]
84 ],
85 'resources' => [
86 'css' => [
87 'app.css',
88 ],
89 'js' => [
90 'app.js',
91 'home/' => [
92 'app.js'
93 ],
94 'example/' => [
95 'app.js'
96 ]
97 ],
98 'images' => [],
99 'fonts' => [],
100 'views' => [
101 'layouts' => [
102 'guest.tpl',
103 'admin.tpl',
104 'error.tpl',
105 ],
106 'components' => [
107 'menu.tpl',
108 'header.tpl',
109 'footer.tpl'
110 ],
111 'errors' => [
112 '404.tpl',
113 '500.tpl',
114 '403.tpl'
115 ],
116 ]
117 ],
118 '/' => [
119 'index.php',
120 'README.md',
121 'composer.json',
122 '.gitignore',
123 'favicon.ico',
124 'LICENSE',
125 'tailwind.config.js',
126 ],
127];
128
129// Ask if the user want to give a name to the project
130echo 'Please enter the name of the project: ';
131$project_name = trim(fgets(STDIN));
132
133// Do not allow the user to give an empty name
134if ($project_name == '') {
135 echo 'Please enter a name for the project, it cannot be empty.' . PHP_EOL . 'Exiting...' . PHP_EOL;
136 exit;
137}
138
139// Ask if the user want to set the author of the project
140echo 'Please enter the author of the project: ';
141if (strtolower($author = trim(fgets(STDIN))) == 'y') {
142 // Ask for the name of the project
143 echo 'Enter the author of the project: ';
144 $author = trim(fgets(STDIN));
145}
146
147// Do not allow the user to give an empty name
148if ($author == '') {
149 echo 'Please enter an author for the project, it cannot be empty.' . PHP_EOL . 'Exiting...' . PHP_EOL;
150 exit;
151}
152
153// Create the root folder
154mkdir($project_name, 0777, true);
155// Create icon with the initials of the project name
156$icon = strtoupper(substr($project_name, 0, 4));
157// using Imagick
158$imagick = new Imagick();
159$imagick->newImage(32, 32, new ImagickPixel('transparent'));
160$draw = new ImagickDraw();
161$draw->setFillColor(new ImagickPixel('black'));
162$draw->setFontSize(18);
163$draw->setTextAlignment(Imagick::ALIGN_CENTER);
164$draw->setTextKerning(0);
165$draw->setStrokeColor(new ImagickPixel('white'));
166$draw->setStrokeWidth(2);
167$draw->setGravity(Imagick::GRAVITY_CENTER);
168$imagick->annotateImage($draw, 16, 16, 0, $icon);
169$imagick->setImageFormat('ico');
170$imagick->writeImage($project_name . '/favicon.ico');
171$imagick->clear();
172
173
174mkdir($project_name . '/resources/images/', 0777, true);
175// Create icon with the initials of the project name
176$icon = strtoupper(substr($project_name, 0, 4));
177// using Imagick
178$imagick = new Imagick();
179$imagick->newImage(64, 64, new ImagickPixel('purple'));
180$draw = new ImagickDraw();
181$draw->setFillColor(new ImagickPixel('black'));
182$draw->setFontSize(16);
183$draw->setTextAlignment(Imagick::ALIGN_CENTER);
184$draw->setTextKerning(0);
185$draw->setStrokeColor(new ImagickPixel('white'));
186$draw->setStrokeWidth(2);
187$draw->setGravity(Imagick::GRAVITY_CENTER);
188$imagick->annotateImage($draw, 24, 24, 0, $icon);
189$imagick->setImageFormat('png');
190$imagick->writeImage($project_name . '/resources/images/' . '/logo.png');
191$imagick->clear();
192
193
194// Ask if the user want to give a description to the project
195echo 'Enter the description to the project: [default is empty] ';
196$project_description = trim(fgets(STDIN));
197
198// Ask if the user want to set the Keywords of the project
199echo 'Enter the keywords of the project: [separated by commas, default is empty] ';
200$keywords = trim(fgets(STDIN));
201
202$project_version = "1.0.0";
203
204// Ask if the user want to give a description to the project
205echo 'Enter the version to the project? [default is 1.0.0] ';
206$project_version = trim(fgets(STDIN));
207
208// Get the email from the author, between the < and >
209$authorNameWithoutEmail = preg_replace("/<(.*)>/", "", $author);
210
211$startFile = "<?php
212/**
213 * " . $project_name . "
214 * " . $project_description . "
215 * @file [file]
216 * @package cvmanager
217 * @author $author
218 * @version " . $project_version . "
219 * @copyright " . date('Y') . " " . $authorNameWithoutEmail . "
220 */";
221
222// Get the Root folder parameter passed on the command line if not specified use the project name on the current directory of the script
223$root = $project_name && $project_name != '' ? $project_name : '/var/www/html/';
224
225$errors = [];
226
227function createFolderStructure($array, $rootFolder, $startFile = "") {
228 foreach ($array as $key => $value) {
229 $path = $rootFolder . DIRECTORY_SEPARATOR . $key;
230 if (is_array($value)) {
231 if (!is_dir($path)) {
232 mkdir($path, 0777, true);
233 }
234 createFolderStructure($value, $path, $startFile); // recursive call
235 } else {
236 $filePath = $rootFolder . DIRECTORY_SEPARATOR . $value;
237 if (!file_exists($filePath)) {
238 // Check if the file it's a php file by chekcing
239 if (!file_put_contents($filePath, '')) { // create empty file
240 $errors[] = [
241 'error' => 'Failed to create file ' . $filePath,
242 'folder' => $rootFolder,
243 'file' => $value
244 ];
245 }
246 echo $filePath . PHP_EOL;
247
248 if (pathinfo($filePath, PATHINFO_EXTENSION) == 'php') {
249 file_put_contents($filePath, str_replace('[file]', $value, $startFile));
250 }
251 // chmod the file
252 if (!chmod($filePath, 0777)) {
253 $errors[] = [
254 'error' => 'Failed to change permission of file ' . $filePath,
255 'folder' => $rootFolder,
256 'file' => $value,
257 ];
258 }
259 }
260 }
261 }
262}
263
264$rootFolder = $root;
265createFolderStructure($files, $rootFolder, $startFile);
266
267// Populate the app.config.php with the template app.config.php
268if (file_exists($root . '/config/' . 'app.config.php')) {
269 $appConfig = "
270<?php
271/**
272 * " . $project_name . "
273 * " . $project_description . "
274 * @file app.config.php
275 * @package cvmanager
276 * @author $author
277 * @version " . $project_version . "
278 * @copyright " . date('Y') . " $author
279 */
280return [
281 'name' => 'CV Manager',
282 'debug' => 1,
283 'url' => 'http://localhost/',
284 'path' => '/var/www/cvmanager.com',
285 'timezone' => 'America/Sao_Paulo',
286 'locale' => 'pt-BR',
287 'default_locale' => 'pt-BR',
288 'default_timezone' => 'America/Sao_Paulo',
289 'app_namespace' => 'App',
290 'resources' => '/resources',
291 'css' => '/resources/css',
292 'js' => '/resources/js',
293 'views' => '/resources/views',
294 'libs' => '/libs',
295 'images' => '/resources/images',
296 'fonts' => '/resources/fonts',
297 'js' => '/resources/js',
298 'config' => '/config',
299 'routes' => '/routes',
300];";
301
302
303 file_put_contents($root . '/config/' . 'app.config.php', $appConfig);
304
305 chmod($root . '/' . 'app.config.php', 0777);
306}
307
308// Populate the version.config.php
309if (file_exists($root . '/config/' . 'version.config.php')) {
310 $versionConfig = "
311<?php
312/**
313 * " . $project_name . "
314 * " . $project_description . "
315 * @file version.config.php
316 * @package cvmanager
317 * @author $author
318 * @version " . $project_version . "
319 * @copyright " . date('Y') . " $author
320 */
321return [
322 'current' => '1.0.0',
323 'next' => '1.0.1',
324 'last' => '1.0.0',
325 'js' => '1.0.0',
326 'css' => '1.0.0',
327 'app' => '1.0.0',
328];";
329
330 file_put_contents($root . '/config/' . 'version.config.php', $versionConfig);
331
332 chmod($root . '/' . 'version.config.php', 0777);
333}
334
335// Populate the smtp.config.php with the template smtp.config.php
336if (file_exists($root . '/config/' . 'smtp.config.php')) {
337 $smtpConfig = "
338<?php
339/**
340 * " . $project_name . "
341 * " . $project_description . "
342 * @file smtp.config.php
343 * @package cvmanager
344 * @author $author
345 * @version " . $project_version . "
346 * @copyright " . date('Y') . " $author
347 */
348return [
349 'host' => 'smtp.gmail.com',
350 'port' => 587,
351 'encryption' => 'tls',
352 'username' => 'vinicordeirogo',
353 'password' => 'password',
354 'from' => 'vinicordeirogo',
355 'from_name' => 'Vinícius Cordeiro',
356];";
357
358 file_put_contents($root . '/config/' . 'smtp.config.php', $smtpConfig);
359
360 chmod($root . '/' . 'smtp.config.php', 0777);
361}
362
363
364// Populate the types.config.php with the template types.config.php
365if (file_exists($root . '/config/' . 'types.config.php')) {
366 $typesConfig = " <?php
367/**
368 * " . $project_name . "
369 * " . $project_description . "
370 * @file types.config.php
371 * @package cvmanager
372 * @author $author
373 * @version " . $project_version . "
374 * @copyright " . date('Y') . " $author
375 */
376
377namespace Config;
378
379 enum Method : string {
380 case GET = 'GET';
381 case POST = 'POST';
382 case PUT = 'PUT';
383 case PATCH = 'PATCH';
384 case DELETE = 'DELETE';
385 }
386 enum Role : string {
387 case ADMIN = 'ADMIN';
388 case USER = 'USER';
389 }
390 enum Status : string {
391 case ACTIVE = 'ACTIVE';
392 case INACTIVE = 'INACTIVE';
393 }
394
395 ";
396
397 file_put_contents($root . '/config/' . 'types.config.php', $typesConfig);
398
399 chmod($root . '/' . 'types.config.php', 0777);
400}
401
402// Populate the errors.config.php with the template errors.config.php
403if (file_exists($root . '/config/' . 'errors.config.php')) {
404 $errorsConfig = "<?php
405/**
406 * " . $project_name . "
407 * " . $project_description . "
408 * @file errors.config.php
409 * @package cvmanager
410 * @author $author
411 * @version " . $project_version . "
412 * @copyright " . date('Y') . " $author
413 */
414
415return [
416 '404' => [
417 'title' => 'Page not found',
418 'message' => 'The page you are looking for was not found.',
419 'code' => 404,
420 ],
421 '403' => [
422 'title' => 'Forbidden',
423 'message' => 'You do not have permission to access this page.',
424 'code' => 403,
425 ],
426 '500' => [
427 'title' => 'Internal Server Error',
428 'message' => 'An error occurred on the server.',
429 'code' => 500,
430 ],
431 '401' => [
432 'title' => 'Unauthorized',
433 'message' => 'You are not authorized to access this page.',
434 'code' => 401,
435 ],
436 '400' => [
437 'title' => 'Bad Request',
438 'message' => 'The request was invalid.',
439 'code' => 400,
440 ],
441 '409' => [
442 'title' => 'Conflict',
443 'message' => 'The request could not be completed due to a conflict.',
444 'code' => 409,
445 ],
446 '422' => [
447 'title' => 'Unprocessable Entity',
448 'message' => 'The request was valid, but the server was unable to process it.',
449 'code' => 422,
450 ],
451 '429' => [
452 'title' => 'Too Many Requests',
453 'message' => 'The user has made too many requests in a given amount of time.',
454 'code' => 429,
455 ],
456 '503' => [
457 'title' => 'Service Unavailable',
458 'message' => 'The server is currently unable to handle the request.',
459 'code' => 503,
460 ],
461];";
462
463 file_put_contents($root . '/config/' . 'errors.config.php', $errorsConfig);
464
465 chmod($root . '/' . 'errors.config.php', 0777);
466}
467
468if (file_exists($root . '/app/' . 'functions.php')) {
469 // Populate the functions.php with the template functions.php
470 $functions = "" . str_replace('[file]', 'functions.php', $startFile) . "
471 function config(\$key) {
472
473 // Get the configuration file, if app.\$key then get from the app.config.php file
474 \$prefix = explode('.', \$key);
475 if(!file_exists(__DIR__ . '/../config/' . \$prefix[0] . '.config.php')) {
476 throw new Exception('Config file not found: ' . \$prefix[0] . '.config.php');
477 }
478 // Get the config file
479 \$config = require __DIR__ . '/../config/' . \$prefix[0] . '.config.php';
480
481 \$file = __DIR__ . '/../config/' . \$prefix[0] . '.config.php';
482 // Check if the key exists in the config file if not throw an exception
483 if(array_key_exists(\$prefix[1] , \$config)) {
484 return \$config[\$prefix[1]];
485 } else {
486 throw new Exception('Config key not found: ' . \$key . ' in file: ' . \$file);
487 }
488 }";
489
490 file_put_contents($root . '/app/' . 'functions.php', $functions);
491}
492
493
494if (file_exists($root . '/app/' . 'bootstrap.php')) {
495 // Populate the bootstrap.php with the template bootstrap.php
496 $bootstrap = "" . str_replace('[file]', 'bootstrap.php', $startFile) . "
497
498// Load the configuration file of the application
499include __DIR__ . '/../config/app.config.php';
500
501// Load the types file of the application
502include __DIR__ . '/../config/types.config.php';
503
504// Load the errors file of the application
505include __DIR__ . '/../config/errors.config.php';
506
507// Load the database file of the application
508include __DIR__ . '/../config/database.config.php';
509
510// Load the helpers file of the application
511include __DIR__ . '/../app/functions.php';
512";
513
514 file_put_contents($root . '/app/' . 'bootstrap.php', $bootstrap);
515
516 // change the permission of the file and chown
517 chmod($root . '/app/' . 'bootstrap.php', 0777);
518}
519
520// Populate the index.php with the template index.php
521if (file_exists($root . '/' . 'index.php')) {
522 $index = "" . str_replace('[file]', 'index.php', $startFile) . "
523
524// Load the bootstrap file of the application
525require_once 'app/bootstrap.php';
526
527// Enable error reporting based on the debug mode specified on the app.config file
528if (config('app.debug')) {
529 error_reporting(E_WARNING | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE);
530 ini_set('display_errors', 1);
531} else {
532 error_reporting(0);
533 ini_set('display_errors', 0);
534}
535
536try {
537 phpinfo();
538} catch (\Exception \$e) {
539
540}
541
542exit;
543";
544 file_put_contents($root . '/' . 'index.php', $index);
545
546 // change the permission of the file and chown
547 chmod($root . '/' . 'index.php', 0777);
548}
549
550
551
552
553// Create the composer.json file
554$composer = "{
555 \"name\": \"vinicordeirogo/" . strtolower(str_replace(' ', '-', $project_name)) . "\",
556 \"description\": \"" . $project_description . "\",
557 \"type\": \"project\",
558 \"readme\": \"README.md\",
559 \"authors\": [
560 {
561 \"name\": \"Vinícius Gonçalves Cordeiro\",
562 \"email\": \"vinicordeirogo@gmail.com\"
563 }
564 ],
565 \"require\": {
566 \"guzzlehttp/guzzle\": \"^7.9\",
567 \"adodb/adodb-php\": \"^5.22\",
568 \"smarty/smarty\": \"^5.4\",
569 \"phpmailer/phpmailer\": \"^6.9\",
570 \"phpoffice/common\": \"^1.0\",
571 \"phpoffice/phpspreadsheet\": \"^4.1\",
572 \"phpoffice/phpword\": \"^1.3\",
573 \"phpoffice/phppresentation\": \"^0.2.0\"
574 },
575 \"autoload\": {
576 \"psr-4\": {
577 \"App\\\\\": \"app/\",
578 \"Config\\\\\": \"config/\",
579 \"Routes\\\\\": \"routes/\"
580 },
581 \"classmap\": [
582 \"app/Core/\",
583 \"app/Controllers/\",
584 \"app/Helpers/\",
585 \"app/Models/\",
586 \"app/Views/\",
587 \"config/\",
588 \"routes/\"
589 ]
590 },
591 \"config\": {
592 \"vendor-dir\": \"libs/\"
593 }
594}
595";
596
597// Create the composer.json file
598file_put_contents($root . '/' . 'composer.json', $composer);
599
600
601// Create the tailwind.config.js
602if (file_exists($root . '/tailwind.config.js')) {
603 $tailwindConfig = "
604
605module.exports = {
606
607 theme: {
608 fontFamily: {
609 sans: ['Nunito', 'sans-serif'],
610 mono: ['Fira Code', 'monospace'],
611 },
612 colors: {
613 transparent: 'transparent',
614 current: 'currentColor',
615 black: '#000',
616 white: '#fff',
617 primary: {
618 50: '#eff6ff',
619 100: '#dbeafe',
620 200: '#bfdbfe',
621 300: '#93c5fd',
622 400: '#60a5fa',
623 500: '#3b82f6',
624 600: '#2563eb',
625 700: '#1d4ed8',
626 800: '#1e40af',
627 900: '#1e3a8a',
628 },
629 secondary: {
630 50: '#f9fafb',
631 100: '#f3f4f6',
632 200: '#e5e7eb',
633 300: '#d1d5db',
634 400: '#9ca3af',
635 500: '#6b7280',
636 600: '#4b5563',
637 700: '#374151',
638 800: '#1f2937',
639 900: '#111827',
640 }
641 }
642 extend: {
643
644 },
645 },
646 darkMode: 'class',
647 plugins: [
648 require('@tailwindcss/typography'),
649 require('@tailwindcss/forms'),
650 require('@tailwindcss/aspect-ratio'),
651 ],
652};
653";
654 file_put_contents($root . '/' . 'tailwind.config.js', $tailwindConfig);
655}
656
657// Ask if the user wants to create a git repository
658echo 'Do you want to create a git repository? (Y/n) ';
659$git = trim(fgets(STDIN));
660
661if (strtolower($git) == 'y') {
662 // Create the git repository on main branch and Initial commit message
663
664 exec(' cd /var/www/' . $project_name . ' && git init && git add . && git commit -m "Initial commit"');
665}
666
667
668// Ask if the user wants to create a database
669echo 'Do you want to create a database? (Y/n) ';
670$database = trim(fgets(STDIN));
671
672if (strtolower($database) == 'y') {
673 // Create the database
674 echo 'Enter the name of the database: ';
675 $database_name = trim(fgets(STDIN));
676 echo 'Enter the username of the database: ';
677 $database_username = trim(fgets(STDIN));
678 echo 'Enter the password of the database: ';
679 $database_password = trim(fgets(STDIN));
680
681 // Create the database
682 exec('mysql -u ' . $database_username . ' -p' . $database_password . ' -e "CREATE DATABASE ' . $database_name . ';"');
683
684 $adodbSessionSQL = "
685 CREATE TABLE IF NOT EXISTS sessions2 (
686 sesskey VARCHAR( 64 ) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
687 expiry DATETIME NOT NULL ,
688 expireref VARCHAR( 250 ) DEFAULT '',
689 created DATETIME NOT NULL ,
690 modified DATETIME NOT NULL ,
691 sessdata LONGTEXT,
692 PRIMARY KEY ( sesskey ) ,
693 INDEX sess2_expiry( expiry ),
694 INDEX sess2_expireref( expireref )
695 ) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
696 ";
697
698 // Create the database
699 exec('mysql -u ' . $database_username . ' -p' . $database_password . ' ' . $database_name . ' -e "' . $adodbSessionSQL . '"');
700}
701
702
703// Run the composer update and dump-autoload command
704exec('cd /var/www/' . $project_name . ' && composer update ');
705
706// Run the composer dump-autoload command
707exec('composer dump-autoload -o');
708
709try {
710 exec('php -S 0.0.0.0:8000 -t /var/www/' . $project_name . ' &');
711 exec('firefox http://127.0.0.1:8000/');
712} catch (Exception $e) {
713 $errors[] = [
714 'error' => 'Error on other runtime',
715 'message' => $e->getMessage(),
716 'file' => $e->getFile(),
717 'line' => $e->getLine(),
718 ];
719}
720
721if (count($errors) > 0) {
722 echo 'Errors: ' . PHP_EOL;
723 foreach ($errors as $error) {
724 echo $error['folder'] . ' - ' . $error['file'] . ' - ' . $error['message'] . PHP_EOL;
725 }
726
727 // Dump the errors on a file on the root folder
728 file_put_contents($root . '/' . 'errors.log', json_encode($errors));
729}
730
731
732return true;
733