create-prisma-php-app 1.13.1 → 1.13.3

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.
@@ -181,9 +181,10 @@ function dynamicRoute($uri)
181
181
  $segmentMatch = singleDynamicRoute($uriSegments, $routeSegments);
182
182
  if (!empty($segmentMatch)) {
183
183
  $trimSegmentMatch = trim($segmentMatch, '[]');
184
- $dynamicRouteParams = [$trimSegmentMatch => $uriSegments[array_search($segmentMatch, $routeSegments)]];
184
+ $dynamicRouteParams = new \ArrayObject([$trimSegmentMatch => $uriSegments[array_search($segmentMatch, $routeSegments)]], \ArrayObject::ARRAY_AS_PROPS);
185
185
 
186
- $dynamicRouteUri = str_replace($segmentMatch, $uriSegments[array_search($segmentMatch, $routeSegments)], $normalizedRoute); $dynamicRouteUriDirname = dirname($dynamicRouteUri);
186
+ $dynamicRouteUri = str_replace($segmentMatch, $uriSegments[array_search($segmentMatch, $routeSegments)], $normalizedRoute);
187
+ $dynamicRouteUriDirname = dirname($dynamicRouteUri);
187
188
  $dynamicRouteUriDirname = rtrim($dynamicRouteUriDirname, '/');
188
189
 
189
190
  $expectedUri = '/src/app/' . $normalizedUri;
@@ -206,7 +207,7 @@ function dynamicRoute($uri)
206
207
  $pattern = '/\[\.\.\.(.*?)\]/';
207
208
  if (preg_match($pattern, $normalizedRoute, $matches)) {
208
209
  $contentWithinBrackets = $matches[1];
209
- $dynamicRouteParams = [$contentWithinBrackets => $explodedNormalizedUri];
210
+ $dynamicRouteParams = new \ArrayObject([$contentWithinBrackets => $explodedNormalizedUri], \ArrayObject::ARRAY_AS_PROPS);
210
211
  }
211
212
  if (strpos($normalizedRoute, 'route.php') !== false) {
212
213
  $uriMatch = $normalizedRoute;
@@ -5,3 +5,42 @@ function redirect(string $url): void
5
5
  header("Location: $url");
6
6
  exit;
7
7
  }
8
+
9
+ function isAjaxRequest()
10
+ {
11
+ $isAjax = false;
12
+
13
+ // Check for standard AJAX header
14
+ if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
15
+ $isAjax = true;
16
+ }
17
+
18
+ // Check for HTMX request header
19
+ if (!empty($_SERVER['HTTP_HX_REQUEST'])) {
20
+ $isAjax = true;
21
+ }
22
+
23
+ // Check for common AJAX content types
24
+ if (!empty($_SERVER['CONTENT_TYPE'])) {
25
+ $ajaxContentTypes = [
26
+ 'application/json',
27
+ 'application/x-www-form-urlencoded',
28
+ 'multipart/form-data',
29
+ ];
30
+
31
+ foreach ($ajaxContentTypes as $contentType) {
32
+ if (strpos($_SERVER['CONTENT_TYPE'], $contentType) !== false) {
33
+ $isAjax = true;
34
+ break;
35
+ }
36
+ }
37
+ }
38
+
39
+ // Check for common AJAX request methods
40
+ $ajaxMethods = ['POST', 'PUT', 'PATCH', 'DELETE'];
41
+ if (in_array(strtoupper($_SERVER['REQUEST_METHOD']), $ajaxMethods)) {
42
+ $isAjax = true;
43
+ }
44
+
45
+ return $isAjax;
46
+ }
@@ -22,6 +22,8 @@ $isDelete = $requestMethod === 'DELETE';
22
22
  $isPatch = $requestMethod === 'PATCH';
23
23
  $isHead = $requestMethod === 'HEAD';
24
24
  $isOptions = $requestMethod === 'OPTIONS';
25
+ $isAjax = isAjaxRequest();
26
+ $isHtmx = !empty($_SERVER['HTTP_HX_REQUEST']) && strtolower($_SERVER['HTTP_HX_REQUEST']) === 'true';
25
27
  $contentType = $_SERVER['CONTENT_TYPE'] ?? '';
26
28
  $requestedWith = $_SERVER['HTTP_X_REQUESTED_WITH'] ?? '';
27
29
  $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
@@ -189,6 +189,11 @@ class FormHandler
189
189
  public function validateField($field, $rules)
190
190
  {
191
191
  $value = Validator::validateString($this->data[$field] ?? null);
192
+
193
+ if (!isset($rules['required']) && empty($value)) {
194
+ return;
195
+ }
196
+
192
197
  foreach ($rules as $rule => $options) {
193
198
  $ruleValue = $options;
194
199
  $customMessage = null;
@@ -415,177 +420,190 @@ class FormHandler
415
420
  ?>
416
421
 
417
422
  <script>
418
- class FormHandler {
419
- constructor() {
420
- this.errors = [];
421
- this.dataRulesElements = document.querySelectorAll('[data-rules]');
422
- this.init();
423
- }
424
-
425
- init() {
426
- this.dataRulesElements.forEach(fieldElement => {
427
- this.initializeFieldFromDOM(fieldElement);
428
- });
429
- }
423
+ if (typeof FormHandler === 'undefined') {
424
+ class FormHandler {
425
+ constructor() {
426
+ this.errors = [];
427
+ this.dataRulesElements = document.querySelectorAll('[data-rules]');
428
+ this.init();
429
+ }
430
430
 
431
- initializeFieldFromDOM(fieldElement) {
432
- if (!fieldElement) {
433
- // console.error('Element not found for field:', fieldElement);
434
- return;
431
+ init() {
432
+ this.dataRulesElements.forEach(fieldElement => {
433
+ this.initializeFieldFromDOM(fieldElement);
434
+ });
435
435
  }
436
436
 
437
- const fieldName = fieldElement.name;
438
- const rules = JSON.parse(fieldElement.getAttribute('data-rules') || '{}');
437
+ initializeFieldFromDOM(fieldElement) {
438
+ if (!fieldElement) return;
439
439
 
440
- const immediateObserver = (e) => {
441
- const target = e.target;
442
- this.watch(target);
440
+ const fieldName = fieldElement.name;
441
+ const rules = JSON.parse(fieldElement.getAttribute('data-rules') || '{}');
443
442
 
444
- const errors = this.validateField(target, target.value, rules);
445
- const errorContainer = document.getElementById(`fh-error-${target.name}`);
443
+ const errors = this.validateField(fieldElement, fieldElement.value, rules);
444
+ const errorContainer = document.getElementById(`fh-error-${fieldElement.name}`);
446
445
  if (errorContainer) {
447
- errorContainer.textContent = errors.join(', ');
446
+ if (errorContainer.dataset.errorMessage) {
447
+ errorContainer.textContent = errors.join(', ');
448
+ }
448
449
  }
449
- };
450
450
 
451
- fieldElement.addEventListener('input', immediateObserver);
452
- }
451
+ const immediateObserver = (e) => {
452
+ const target = e.target;
453
+ this.watch(target);
454
+
455
+ const errors = this.validateField(target, target.value, rules);
456
+ const errorContainer = document.getElementById(`fh-error-${target.name}`);
457
+ if (errorContainer) {
458
+ errorContainer.textContent = errors.join(', ');
459
+ }
460
+ };
461
+
462
+ fieldElement.addEventListener('input', immediateObserver);
463
+ }
453
464
 
454
- updateElementDisplay(displayElement, field) {
455
- const tagName = field.tagName.toUpperCase();
456
- if (tagName === 'INPUT' || tagName === 'TEXTAREA') {
457
- if (displayElement.tagName === 'INPUT' || displayElement.tagName === 'TEXTAREA') {
458
- displayElement.value = field.value;
465
+ updateElementDisplay(displayElement, field) {
466
+ const tagName = field.tagName.toUpperCase();
467
+ if (tagName === 'INPUT' || tagName === 'TEXTAREA') {
468
+ if (displayElement.tagName === 'INPUT' || displayElement.tagName === 'TEXTAREA') {
469
+ displayElement.value = field.value;
470
+ } else {
471
+ displayElement.dataset.watchValue = field.value;
472
+ displayElement.textContent = field.value;
473
+ }
459
474
  } else {
460
- displayElement.dataset.watchValue = field.value;
461
- displayElement.textContent = field.value;
475
+ displayElement.textContent = field.textContent;
462
476
  }
463
- } else {
464
- displayElement.textContent = field.textContent;
465
477
  }
466
- }
467
478
 
468
- watch(field) {
469
- if (!field) return;
479
+ watch(field) {
480
+ if (!field) return;
470
481
 
471
- const watchElement = document.getElementById(`fh-watch-${field.name}`);
472
- if (watchElement) {
473
- this.updateElementDisplay(watchElement, field);
482
+ const watchElement = document.getElementById(`fh-watch-${field.name}`);
483
+ if (watchElement) {
484
+ this.updateElementDisplay(watchElement, field);
485
+ }
474
486
  }
475
- }
476
487
 
477
- clearErrors() {
478
- const errorElements = document.querySelectorAll('[id^="fh-error-"]');
479
- errorElements.forEach(element => {
480
- element.textContent = '';
481
- });
488
+ clearErrors() {
489
+ const errorElements = document.querySelectorAll('[id^="fh-error-"]');
490
+ errorElements.forEach(element => {
491
+ element.textContent = '';
492
+ });
482
493
 
483
- this.errors = [];
484
- }
485
-
486
- getErrors(field) {
487
- if (field) {
488
- return document.getElementById(`fh-error-${field}`).textContent;
489
- } else {
490
- return this.errors;
494
+ this.errors = [];
491
495
  }
492
- }
493
496
 
494
- validateField(field, value, rules) {
495
- if (!rules) return [];
496
- this.errors = [];
497
-
498
- for (const [rule, options] of Object.entries(rules)) {
499
- let ruleValue = options;
500
- let customMessage = null;
501
-
502
- if (typeof options === 'object') {
503
- ruleValue = options.value;
504
- customMessage = options.message || null;
497
+ getErrors(field) {
498
+ if (field) {
499
+ return document.getElementById(`fh-error-${field}`).textContent;
500
+ } else {
501
+ return this.errors;
505
502
  }
503
+ }
506
504
 
507
- switch (rule) {
508
- case 'text':
509
- case 'search':
510
- if (typeof value !== 'string') {
511
- this.errors.push(customMessage || 'Must be a string.');
512
- }
513
- break;
514
- case 'email':
515
- if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
516
- this.errors.push(customMessage || 'Invalid email format.');
517
- }
518
- break;
519
- case 'number':
520
- if (isNaN(value)) {
521
- this.errors.push(customMessage || 'Must be a number.');
522
- }
523
- break;
524
- case 'date':
525
- if (isNaN(Date.parse(value))) {
526
- this.errors.push(customMessage || 'Invalid date format.');
527
- }
528
- break;
529
- case 'range':
530
- const [min, max] = ruleValue;
531
- if (isNaN(value) || value < min || value > max) {
532
- this.errors.push(customMessage || `Must be between ${min} and ${max}.`);
533
- }
534
- break;
535
- case 'url':
536
- try {
537
- new URL(value);
538
- } catch (e) {
539
- this.errors.push(customMessage || 'Invalid URL format.');
540
- }
541
- break;
542
- case 'required':
543
- if (!value) {
544
- this.errors.push(customMessage || 'This field is required.');
545
- }
546
- break;
547
- case 'min':
548
- if (Number(value) < ruleValue) {
549
- this.errors.push(customMessage || `Must be at least ${ruleValue}.`);
550
- }
551
- break;
552
- case 'max':
553
- if (Number(value) > ruleValue) {
554
- this.errors.push(customMessage || `Must be at most ${ruleValue}.`);
555
- }
556
- break;
557
- case 'minLength':
558
- if (value.length < ruleValue) {
559
- this.errors.push(customMessage || `Must be at least ${ruleValue} characters.`);
560
- }
561
- break;
562
- case 'maxLength':
563
- if (value.length > ruleValue) {
564
- this.errors.push(customMessage || `Must be at most ${ruleValue} characters.`);
565
- }
566
- break;
567
- case 'pattern':
568
- if (!new RegExp(ruleValue).test(value)) {
569
- this.errors.push(customMessage || 'Invalid format.');
570
- }
571
- break;
572
- case 'accept':
573
- if (!ruleValue.split(',').includes(value)) {
574
- this.errors.push(customMessage || 'Invalid file format.');
575
- }
576
- break;
577
- default:
578
- // Optionally handle unknown rules or log them
579
- break;
505
+ validateField(field, value, rules) {
506
+ if (!rules) return [];
507
+ this.errors = [];
508
+
509
+ for (const [rule, options] of Object.entries(rules)) {
510
+ let ruleValue = options;
511
+ let customMessage = null;
512
+
513
+ if (typeof options === 'object') {
514
+ ruleValue = options.value;
515
+ customMessage = options.message || null;
516
+ }
517
+
518
+ switch (rule) {
519
+ case 'text':
520
+ case 'search':
521
+ if (typeof value !== 'string') {
522
+ this.errors.push(customMessage || 'Must be a string.');
523
+ }
524
+ break;
525
+ case 'email':
526
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
527
+ this.errors.push(customMessage || 'Invalid email format.');
528
+ }
529
+ break;
530
+ case 'number':
531
+ if (isNaN(value)) {
532
+ this.errors.push(customMessage || 'Must be a number.');
533
+ }
534
+ break;
535
+ case 'date':
536
+ if (isNaN(Date.parse(value))) {
537
+ this.errors.push(customMessage || 'Invalid date format.');
538
+ }
539
+ break;
540
+ case 'range':
541
+ const [min, max] = ruleValue;
542
+ if (isNaN(value) || value < min || value > max) {
543
+ this.errors.push(customMessage || `Must be between ${min} and ${max}.`);
544
+ }
545
+ break;
546
+ case 'url':
547
+ try {
548
+ new URL(value);
549
+ } catch (e) {
550
+ this.errors.push(customMessage || 'Invalid URL format.');
551
+ }
552
+ break;
553
+ case 'required':
554
+ if (!value) {
555
+ this.errors.push(customMessage || 'This field is required.');
556
+ }
557
+ break;
558
+ case 'min':
559
+ if (Number(value) < ruleValue) {
560
+ this.errors.push(customMessage || `Must be at least ${ruleValue}.`);
561
+ }
562
+ break;
563
+ case 'max':
564
+ if (Number(value) > ruleValue) {
565
+ this.errors.push(customMessage || `Must be at most ${ruleValue}.`);
566
+ }
567
+ break;
568
+ case 'minLength':
569
+ if (value.length < ruleValue) {
570
+ this.errors.push(customMessage || `Must be at least ${ruleValue} characters.`);
571
+ }
572
+ break;
573
+ case 'maxLength':
574
+ if (value.length > ruleValue) {
575
+ this.errors.push(customMessage || `Must be at most ${ruleValue} characters.`);
576
+ }
577
+ break;
578
+ case 'pattern':
579
+ if (!new RegExp(ruleValue).test(value)) {
580
+ this.errors.push(customMessage || 'Invalid format.');
581
+ }
582
+ break;
583
+ case 'accept':
584
+ if (!ruleValue.split(',').includes(value)) {
585
+ this.errors.push(customMessage || 'Invalid file format.');
586
+ }
587
+ break;
588
+ default:
589
+ // Optionally handle unknown rules or log them
590
+ break;
591
+ }
580
592
  }
581
- }
582
593
 
583
- return this.errors;
594
+ return this.errors;
595
+ }
584
596
  }
585
- }
586
597
 
587
- let formHandler = null;
588
- document.addEventListener('DOMContentLoaded', function() {
589
- formHandler = new FormHandler();
590
- });
598
+ let formHandler = null;
599
+ // Initialize FormHandler on initial page load
600
+ document.addEventListener('DOMContentLoaded', function() {
601
+ formHandler = new FormHandler();
602
+ });
603
+
604
+ // Reinitialize FormHandler when HTMX swaps content
605
+ document.body.addEventListener('htmx:afterOnLoad', function() {
606
+ formHandler = new FormHandler();
607
+ });
608
+ }
591
609
  </script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-prisma-php-app",
3
- "version": "1.13.1",
3
+ "version": "1.13.3",
4
4
  "description": "Prisma-PHP: A Revolutionary Library Bridging PHP with Prisma ORM",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",