create-prisma-php-app 4.0.0-alpha.1 → 4.0.0-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/bootstrap.php +10 -10
  2. package/dist/index.js +351 -424
  3. package/dist/settings/files-list.json +1 -1
  4. package/dist/settings/restart-websocket.bat +1 -1
  5. package/dist/settings/restart-websocket.ts +9 -2
  6. package/dist/src/Lib/AI/ChatGPTClient.php +147 -0
  7. package/dist/src/Lib/Auth/Auth.php +544 -0
  8. package/dist/src/Lib/Auth/AuthConfig.php +89 -0
  9. package/dist/src/Lib/CacheHandler.php +121 -0
  10. package/dist/src/Lib/ErrorHandler.php +322 -0
  11. package/dist/src/Lib/FileManager/UploadFile.php +383 -0
  12. package/dist/src/Lib/Headers/Boom.php +208 -0
  13. package/dist/src/Lib/IncludeTracker.php +59 -0
  14. package/dist/src/Lib/MainLayout.php +215 -0
  15. package/dist/src/Lib/Middleware/AuthMiddleware.php +154 -0
  16. package/dist/src/Lib/PHPMailer/Mailer.php +169 -0
  17. package/dist/src/Lib/PHPX/Exceptions/ComponentValidationException.php +49 -0
  18. package/dist/src/Lib/PHPX/IPHPX.php +22 -0
  19. package/dist/src/Lib/PHPX/PHPX.php +173 -0
  20. package/dist/src/Lib/PHPX/TemplateCompiler.php +584 -0
  21. package/dist/src/Lib/PHPX/TwMerge.php +195 -0
  22. package/dist/src/Lib/PHPX/TypeCoercer.php +490 -0
  23. package/dist/src/Lib/PartialRenderer.php +40 -0
  24. package/dist/src/Lib/PrismaPHPSettings.php +181 -0
  25. package/dist/src/Lib/Request.php +476 -0
  26. package/dist/src/Lib/Set.php +102 -0
  27. package/dist/src/Lib/StateManager.php +127 -0
  28. package/dist/src/Lib/Validator.php +738 -0
  29. package/dist/src/{Websocket → Lib/Websocket}/ConnectionManager.php +1 -1
  30. package/dist/{websocket-server.php → src/Lib/Websocket/websocket-server.php} +7 -2
  31. package/dist/src/app/assets/images/prisma-php-black.svg +5 -5
  32. package/dist/src/app/error.php +1 -1
  33. package/dist/src/app/index.php +1 -1
  34. package/dist/src/app/js/index.js +1 -1
  35. package/dist/src/app/layout.php +2 -2
  36. package/package.json +1 -1
  37. package/vendor/autoload.php +0 -25
  38. package/vendor/composer/ClassLoader.php +0 -579
  39. package/vendor/composer/InstalledVersions.php +0 -359
  40. package/vendor/composer/LICENSE +0 -21
  41. package/vendor/composer/autoload_classmap.php +0 -10
  42. package/vendor/composer/autoload_namespaces.php +0 -9
  43. package/vendor/composer/autoload_psr4.php +0 -10
  44. package/vendor/composer/autoload_real.php +0 -38
  45. package/vendor/composer/autoload_static.php +0 -25
  46. package/vendor/composer/installed.json +0 -825
  47. package/vendor/composer/installed.php +0 -132
  48. package/vendor/composer/platform_check.php +0 -26
@@ -0,0 +1,584 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Lib\PHPX;
6
+
7
+ use Lib\PrismaPHPSettings;
8
+ use Lib\MainLayout;
9
+ use DOMDocument;
10
+ use DOMElement;
11
+ use DOMComment;
12
+ use DOMNode;
13
+ use DOMText;
14
+ use RuntimeException;
15
+ use Bootstrap;
16
+ use LibXMLError;
17
+ use ReflectionClass;
18
+ use ReflectionProperty;
19
+ use ReflectionType;
20
+ use ReflectionNamedType;
21
+ use Lib\PHPX\TypeCoercer;
22
+ use Lib\PHPX\Exceptions\ComponentValidationException;
23
+
24
+ class TemplateCompiler
25
+ {
26
+ protected const BINDING_REGEX = '/\{\{\s*((?:(?!\{\{|\}\})[\s\S])*?)\s*\}\}/uS';
27
+ private const LITERAL_TEXT_TAGS = [
28
+ 'code' => true,
29
+ 'pre' => true,
30
+ 'samp' => true,
31
+ 'kbd' => true,
32
+ 'var' => true,
33
+ ];
34
+ private const SYSTEM_PROPS = [
35
+ 'children' => true,
36
+ 'key' => true,
37
+ 'ref' => true,
38
+ ];
39
+
40
+ protected static array $classMappings = [];
41
+ protected static array $selfClosingTags = [
42
+ 'area',
43
+ 'base',
44
+ 'br',
45
+ 'col',
46
+ 'command',
47
+ 'embed',
48
+ 'hr',
49
+ 'img',
50
+ 'input',
51
+ 'keygen',
52
+ 'link',
53
+ 'meta',
54
+ 'param',
55
+ 'source',
56
+ 'track',
57
+ 'wbr'
58
+ ];
59
+ private static array $sectionStack = [];
60
+ private static int $compileDepth = 0;
61
+ private static array $componentInstanceCounts = [];
62
+ private static array $reflections = [];
63
+ private static array $constructors = [];
64
+ private static array $publicProperties = [];
65
+ private static array $allowedProps = [];
66
+
67
+ public static function compile(string $templateContent): string
68
+ {
69
+ if (self::$compileDepth === 0) {
70
+ self::$componentInstanceCounts = [];
71
+ }
72
+ self::$compileDepth++;
73
+
74
+ if (empty(self::$classMappings)) {
75
+ self::initializeClassMappings();
76
+ }
77
+
78
+ $dom = self::convertToXml($templateContent);
79
+ $root = $dom->documentElement;
80
+
81
+ $output = [];
82
+ foreach ($root->childNodes as $child) {
83
+ $output[] = self::processNode($child);
84
+ }
85
+
86
+ self::$compileDepth--;
87
+ return implode('', $output);
88
+ }
89
+
90
+ public static function injectDynamicContent(string $htmlContent): string
91
+ {
92
+ $headOpenPattern = '/(<head\b[^>]*>)/i';
93
+
94
+ $htmlContent = preg_replace(
95
+ $headOpenPattern,
96
+ '$1' . MainLayout::outputMetadata(),
97
+ $htmlContent,
98
+ 1
99
+ );
100
+
101
+ $headClosePattern = '/(<\/head\s*>)/i';
102
+ $headScripts = MainLayout::outputHeadScripts();
103
+ $htmlContent = preg_replace(
104
+ $headClosePattern,
105
+ $headScripts . '$1',
106
+ $htmlContent,
107
+ 1
108
+ );
109
+
110
+ if (!isset($_SERVER['HTTP_X_PPHP_NAVIGATION'])) {
111
+ $htmlContent = preg_replace(
112
+ '/<body([^>]*)>/i',
113
+ '<body$1 hidden>',
114
+ $htmlContent,
115
+ 1
116
+ );
117
+ }
118
+
119
+ $bodyClosePattern = '/(<\/body\s*>)/i';
120
+
121
+ $htmlContent = preg_replace(
122
+ $bodyClosePattern,
123
+ MainLayout::outputFooterScripts() . '$1',
124
+ $htmlContent,
125
+ 1
126
+ );
127
+
128
+ return $htmlContent;
129
+ }
130
+
131
+ private static function escapeAmpersands(string $content): string
132
+ {
133
+ return preg_replace(
134
+ '/&(?![a-zA-Z][A-Za-z0-9]*;|#[0-9]+;|#x[0-9A-Fa-f]+;)/',
135
+ '&amp;',
136
+ $content
137
+ );
138
+ }
139
+
140
+ private static function escapeAttributeAngles(string $html): string
141
+ {
142
+ return preg_replace_callback(
143
+ '/(\s[\w:-]+=)([\'"])(.*?)\2/s',
144
+ fn($m) => $m[1] . $m[2] . str_replace(['<', '>'], ['&lt;', '&gt;'], $m[3]) . $m[2],
145
+ $html
146
+ );
147
+ }
148
+
149
+ private static function escapeMustacheAngles(string $content): string
150
+ {
151
+ return preg_replace_callback(
152
+ '/\{\{[\s\S]*?\}\}/u',
153
+ fn($m) => str_replace(['<', '>'], ['&lt;', '&gt;'], $m[0]),
154
+ $content
155
+ );
156
+ }
157
+
158
+ public static function convertToXml(string $templateContent): DOMDocument
159
+ {
160
+ $content = self::protectInlineScripts($templateContent);
161
+ $content = self::normalizeNamedEntities($content);
162
+
163
+ $content = self::escapeAmpersands($content);
164
+ $content = self::escapeAttributeAngles($content);
165
+ $content = self::escapeMustacheAngles($content);
166
+
167
+ $xml = "<root>{$content}</root>";
168
+
169
+ $dom = new DOMDocument('1.0', 'UTF-8');
170
+ libxml_use_internal_errors(true);
171
+ if (!$dom->loadXML($xml, LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_NONET)) {
172
+ throw new RuntimeException(
173
+ 'XML Parsing Failed: ' . implode('; ', self::getXmlErrors())
174
+ );
175
+ }
176
+ libxml_clear_errors();
177
+ libxml_use_internal_errors(false);
178
+ return $dom;
179
+ }
180
+
181
+ private static function normalizeNamedEntities(string $html): string
182
+ {
183
+ return preg_replace_callback(
184
+ '/&([a-zA-Z][a-zA-Z0-9]+);/',
185
+ static function (array $m): string {
186
+ $decoded = html_entity_decode($m[0], ENT_HTML5, 'UTF-8');
187
+
188
+ if ($decoded === $m[0]) {
189
+ return $m[0];
190
+ }
191
+
192
+ if (function_exists('mb_ord')) {
193
+ return '&#' . mb_ord($decoded, 'UTF-8') . ';';
194
+ }
195
+
196
+ $code = unpack('N', mb_convert_encoding($decoded, 'UCS-4BE', 'UTF-8'))[1];
197
+ return '&#' . $code . ';';
198
+ },
199
+ $html
200
+ );
201
+ }
202
+
203
+ private static function protectInlineScripts(string $html): string
204
+ {
205
+ return preg_replace_callback(
206
+ '#<script\b([^>]*?)>(.*?)</script>#is',
207
+ static function ($m) {
208
+ if (preg_match('/\bsrc\s*=/i', $m[1])) {
209
+ return $m[0];
210
+ }
211
+
212
+ if (str_contains($m[2], '<![CDATA[')) {
213
+ return $m[0];
214
+ }
215
+
216
+ $type = '';
217
+ if (preg_match('/\btype\s*=\s*([\'"]?)([^\'"\s>]+)/i', $m[1], $t)) {
218
+ $type = strtolower($t[2]);
219
+ }
220
+
221
+ $codeTypes = [
222
+ '',
223
+ 'text/javascript',
224
+ 'application/javascript',
225
+ 'module',
226
+ 'text/php',
227
+ ];
228
+
229
+ if (!in_array($type, $codeTypes, true)) {
230
+ return $m[0];
231
+ }
232
+
233
+ $code = str_replace(']]>', ']]]]><![CDATA[>', $m[2]);
234
+
235
+ return "<script{$m[1]}><![CDATA[\n{$code}\n]]></script>";
236
+ },
237
+ $html
238
+ );
239
+ }
240
+
241
+ public static function innerXml(DOMNode $node): string
242
+ {
243
+ if ($node instanceof DOMDocument) {
244
+ $node = $node->documentElement;
245
+ }
246
+
247
+ /** @var DOMDocument $doc */
248
+ $doc = $node->ownerDocument;
249
+
250
+ $html = '';
251
+ foreach ($node->childNodes as $child) {
252
+ $html .= $doc->saveXML($child);
253
+ }
254
+ return $html;
255
+ }
256
+
257
+ protected static function getXmlErrors(): array
258
+ {
259
+ $errors = libxml_get_errors();
260
+ libxml_clear_errors();
261
+ return array_map(fn($e) => self::formatLibxmlError($e), $errors);
262
+ }
263
+
264
+ protected static function formatLibxmlError(LibXMLError $error): string
265
+ {
266
+ $type = match ($error->level) {
267
+ LIBXML_ERR_WARNING => 'Warning',
268
+ LIBXML_ERR_ERROR => 'Error',
269
+ LIBXML_ERR_FATAL => 'Fatal',
270
+ default => 'Unknown',
271
+ };
272
+ return sprintf(
273
+ "[%s] Line %d, Col %d: %s",
274
+ $type,
275
+ $error->line,
276
+ $error->column,
277
+ trim($error->message)
278
+ );
279
+ }
280
+
281
+ protected static function processNode(DOMNode $node): string
282
+ {
283
+ if ($node instanceof DOMText) {
284
+ return self::processTextNode($node);
285
+ }
286
+
287
+ if ($node instanceof DOMElement) {
288
+ $pushed = false;
289
+ $tag = strtolower($node->nodeName);
290
+
291
+ if (
292
+ $tag === 'script' &&
293
+ !$node->hasAttribute('src') &&
294
+ !$node->hasAttribute('type')
295
+ ) {
296
+ $node->setAttribute('type', 'text/php');
297
+ }
298
+
299
+ if ($node->hasAttribute('pp-component')) {
300
+ self::$sectionStack[] = $node->getAttribute('pp-component');
301
+ $pushed = true;
302
+ }
303
+
304
+ self::processAttributes($node);
305
+
306
+ if (isset(self::$classMappings[$node->nodeName])) {
307
+ $html = self::renderComponent(
308
+ $node,
309
+ $node->nodeName,
310
+ self::getNodeAttributes($node)
311
+ );
312
+ if ($pushed) {
313
+ array_pop(self::$sectionStack);
314
+ }
315
+ return $html;
316
+ }
317
+
318
+ $children = '';
319
+ foreach ($node->childNodes as $c) {
320
+ $children .= self::processNode($c);
321
+ }
322
+ $attrs = self::getNodeAttributes($node) + ['children' => $children];
323
+ $out = self::renderAsHtml($node->nodeName, $attrs);
324
+
325
+ if ($pushed) {
326
+ array_pop(self::$sectionStack);
327
+ }
328
+ return $out;
329
+ }
330
+
331
+ if ($node instanceof DOMComment) {
332
+ return "<!--{$node->textContent}-->";
333
+ }
334
+
335
+ return $node->textContent;
336
+ }
337
+
338
+ private static function processTextNode(DOMText $node): string
339
+ {
340
+ $parent = strtolower($node->parentNode?->nodeName ?? '');
341
+ if (isset(self::LITERAL_TEXT_TAGS[$parent])) {
342
+ return htmlspecialchars(
343
+ $node->textContent,
344
+ ENT_NOQUOTES | ENT_SUBSTITUTE,
345
+ 'UTF-8'
346
+ );
347
+ }
348
+
349
+ return preg_replace_callback(
350
+ self::BINDING_REGEX,
351
+ fn($m) => self::processBindingExpression(trim($m[1])),
352
+ $node->textContent
353
+ );
354
+ }
355
+
356
+ private static function processAttributes(DOMElement $node): void
357
+ {
358
+ foreach ($node->attributes as $a) {
359
+ if (!preg_match(self::BINDING_REGEX, $a->value, $m)) {
360
+ continue;
361
+ }
362
+
363
+ $rawExpr = trim($m[1]);
364
+ $node->setAttribute("pp-bind-{$a->name}", $rawExpr);
365
+ }
366
+ }
367
+
368
+ private static function processBindingExpression(string $expr): string
369
+ {
370
+ $escaped = htmlspecialchars($expr, ENT_QUOTES, 'UTF-8');
371
+
372
+ if (preg_match('/^[\w.]+$/u', $expr)) {
373
+ return "<span pp-bind=\"{$escaped}\"></span>";
374
+ }
375
+
376
+ return "<span pp-bind-expr=\"{$escaped}\"></span>";
377
+ }
378
+
379
+ protected static function renderComponent(
380
+ DOMElement $node,
381
+ string $componentName,
382
+ array $incomingProps
383
+ ): string {
384
+ $mapping = self::selectComponentMapping($componentName);
385
+ $instance = self::initializeComponentInstance($mapping, $incomingProps);
386
+
387
+ $childHtml = '';
388
+ foreach ($node->childNodes as $c) {
389
+ $childHtml .= self::processNode($c);
390
+ }
391
+
392
+ $instance->children = $childHtml;
393
+
394
+ $baseId = 's' . base_convert(sprintf('%u', crc32($mapping['className'])), 10, 36);
395
+ $idx = self::$componentInstanceCounts[$baseId] ?? 0;
396
+ self::$componentInstanceCounts[$baseId] = $idx + 1;
397
+ $sectionId = $idx === 0 ? $baseId : "{$baseId}{$idx}";
398
+
399
+ $html = $instance->render();
400
+ $fragDom = self::convertToXml($html);
401
+ $root = $fragDom->documentElement;
402
+ foreach ($root->childNodes as $c) {
403
+ if ($c instanceof DOMElement) {
404
+ $c->setAttribute('pp-component', $sectionId);
405
+ break;
406
+ }
407
+ }
408
+
409
+ $htmlOut = self::innerXml($fragDom);
410
+ if (
411
+ str_contains($htmlOut, '{{') ||
412
+ self::hasComponentTag($htmlOut) ||
413
+ stripos($htmlOut, '<script') !== false
414
+ ) {
415
+ $htmlOut = self::compile($htmlOut);
416
+ }
417
+
418
+ return $htmlOut;
419
+ }
420
+
421
+ private static function selectComponentMapping(string $componentName): array
422
+ {
423
+ if (!isset(self::$classMappings[$componentName])) {
424
+ throw new RuntimeException("Component {$componentName} not registered");
425
+ }
426
+ $mappings = self::$classMappings[$componentName];
427
+
428
+ $srcNorm = str_replace('\\', '/', SRC_PATH) . '/';
429
+ $relImp = str_replace($srcNorm, '', str_replace('\\', '/', Bootstrap::$contentToInclude));
430
+
431
+ if (isset($mappings[0]) && is_array($mappings[0])) {
432
+ foreach ($mappings as $entry) {
433
+ $imp = isset($entry['importer'])
434
+ ? str_replace('\\', '/', $entry['importer'])
435
+ : '';
436
+ if (str_replace($srcNorm, '', $imp) === $relImp) {
437
+ return $entry;
438
+ }
439
+ }
440
+ return $mappings[0];
441
+ }
442
+ return $mappings;
443
+ }
444
+
445
+ protected static function initializeComponentInstance(array $mapping, array $attributes)
446
+ {
447
+ if (!isset($mapping['className'], $mapping['filePath'])) {
448
+ throw new RuntimeException("Invalid mapping");
449
+ }
450
+
451
+ $className = $mapping['className'];
452
+ $filePath = $mapping['filePath'];
453
+
454
+ require_once str_replace('\\', '/', SRC_PATH . '/' . $filePath);
455
+ if (!class_exists($className)) {
456
+ throw new RuntimeException("Class {$className} not found");
457
+ }
458
+
459
+ self::cacheClassReflection($className);
460
+
461
+ if (!isset(self::$reflections[$className])) {
462
+ $rc = new ReflectionClass($className);
463
+ self::$reflections[$className] = $rc;
464
+ self::$constructors[$className] = $rc->getConstructor();
465
+ self::$publicProperties[$className] = array_filter(
466
+ $rc->getProperties(ReflectionProperty::IS_PUBLIC),
467
+ fn(ReflectionProperty $p) => !$p->isStatic()
468
+ );
469
+ }
470
+
471
+ self::validateComponentProps($className, $attributes);
472
+
473
+ $ref = self::$reflections[$className];
474
+ $ctor = self::$constructors[$className];
475
+ $inst = $ref->newInstanceWithoutConstructor();
476
+
477
+ foreach (self::$publicProperties[$className] as $prop) {
478
+ $name = $prop->getName();
479
+
480
+ if (!array_key_exists($name, $attributes)) {
481
+ continue;
482
+ }
483
+ $value = self::coerce($attributes[$name], $prop->getType());
484
+ $prop->setValue($inst, $value);
485
+ }
486
+
487
+ if ($ctor) {
488
+ $ctor->invoke($inst, $attributes);
489
+ }
490
+
491
+ return $inst;
492
+ }
493
+
494
+ private static function cacheClassReflection(string $className): void
495
+ {
496
+ if (isset(self::$reflections[$className])) {
497
+ return;
498
+ }
499
+
500
+ $rc = new ReflectionClass($className);
501
+ self::$reflections[$className] = $rc;
502
+ self::$constructors[$className] = $rc->getConstructor();
503
+
504
+ $publicProps = array_filter(
505
+ $rc->getProperties(ReflectionProperty::IS_PUBLIC),
506
+ fn(ReflectionProperty $p) => !$p->isStatic()
507
+ );
508
+ self::$publicProperties[$className] = $publicProps;
509
+
510
+ $allowed = self::SYSTEM_PROPS;
511
+ foreach ($publicProps as $prop) {
512
+ $allowed[$prop->getName()] = true;
513
+ }
514
+ self::$allowedProps[$className] = $allowed;
515
+ }
516
+
517
+ private static function validateComponentProps(string $className, array $attributes): void
518
+ {
519
+ foreach (self::$publicProperties[$className] as $prop) {
520
+ $name = $prop->getName();
521
+ $type = $prop->getType();
522
+
523
+ if (
524
+ $type instanceof ReflectionNamedType && $type->isBuiltin()
525
+ && ! $type->allowsNull()
526
+ && ! array_key_exists($name, $attributes)
527
+ ) {
528
+ throw new ComponentValidationException(
529
+ $name,
530
+ $className,
531
+ array_map(fn($p) => $p->getName(), self::$publicProperties[$className])
532
+ );
533
+ }
534
+ }
535
+
536
+ return;
537
+ }
538
+
539
+ private static function coerce(mixed $value, ?ReflectionType $type): mixed
540
+ {
541
+ return TypeCoercer::coerce($value, $type);
542
+ }
543
+
544
+ protected static function initializeClassMappings(): void
545
+ {
546
+ foreach (PrismaPHPSettings::$classLogFiles as $tag => $cls) {
547
+ self::$classMappings[$tag] = $cls;
548
+ }
549
+ }
550
+
551
+ protected static function hasComponentTag(string $html): bool
552
+ {
553
+ return preg_match('/<\/*[A-Z][\w-]*/u', $html) === 1;
554
+ }
555
+
556
+ private static function getNodeAttributes(DOMElement $node): array
557
+ {
558
+ $out = [];
559
+ foreach ($node->attributes as $a) {
560
+ $out[$a->name] = $a->value;
561
+ }
562
+ return $out;
563
+ }
564
+
565
+ private static function renderAsHtml(string $tag, array $attrs): string
566
+ {
567
+ $pairs = [];
568
+ foreach ($attrs as $k => $v) {
569
+ if ($k === 'children') {
570
+ continue;
571
+ }
572
+ $pairs[] = sprintf(
573
+ '%s="%s"',
574
+ $k,
575
+ htmlspecialchars($v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
576
+ );
577
+ }
578
+ $attrStr = $pairs ? ' ' . implode(' ', $pairs) : '';
579
+
580
+ return in_array(strtolower($tag), self::$selfClosingTags, true)
581
+ ? "<{$tag}{$attrStr} />"
582
+ : "<{$tag}{$attrStr}>{$attrs['children']}</{$tag}>";
583
+ }
584
+ }