-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathModuleMaker.php
More file actions
5738 lines (4808 loc) · 296 KB
/
ModuleMaker.php
File metadata and controls
5738 lines (4808 loc) · 296 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?php
# ModuleMaker.php
#
# Created 2025/03/11 by Dave Henderson ([email protected])
# Updated 2025/09/29 by Dave Henderson ([email protected])
#
# Unless a valid Cliquesoft Private License (CPLv1) has been purchased for your
# device, this software is licensed under the Cliquesoft Public License (CPLv2)
# as found on the Cliquesoft website at www.cliquesoft.org.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the appropriate Cliquesoft License for details.
# Constant Definitions
define("MODULE",'Module Maker'); # the name of this module
define("SCRIPT",basename($_SERVER['SCRIPT_NAME'])); # the name of this script (for tracing bugs and automated messages)
# Module Requirements NOTE: MUST come below Module Constant Definitions
require_once('../../sqlaccess');
require_once('../data/_modules/ApplicationSettings/config.php');
require_once('_Project.php');
require_once('_Contact.php');
require_once('_Database.php');
require_once('_Security.php');
# Start or resume the PHP session NOTE: gains access to $_SESSION variables in this script
session_start();
# format the dates in UTC
$_ = gmdate("Y-m-d H:i:s",time()); # used this mannor so all the times will be the exact same (also see http://php.net/manual/en/function.gmdate.php)
# define general info for any error generated below
$__sInfo['name'] = 'Unknown';
$__sInfo['contact'] = 'Unknown';
$__sInfo['other'] = 'n/a';
# define the maintenance function
function ModuleMaker_Maintenance() {
return true;
}
# define the commerce function
function ModuleMaker_Commerce() {
# there is currently no processing for commerce
return true;
}
# now exit this script if it is being called from the maintenance.php file (since we just needed access to the above functions)
if ((php_sapi_name() == 'cli' && count($argv) == 1)) { return true; } # NOTE: this should execute when sourcing this file to call maintenance and commerce only!
# create the header for any processing below...
if (($_POST['A'] == 'Load' && $_POST['T'] == 'Module') || ($_POST['A'] == 'render' && $_POST['T'] == 'layout')) { # if we're loading the HTML/css, then...
header('Content-Type: text/html; charset=utf-8');
} else { # otherwise, we're interacting with the database and need to use XML
header('Content-Type: text/xml; charset=utf-8');
echo "<?xml version='1.0' encoding='UTF-8'?>\n\n";
}
# -- Common Functions -- LEFT OFF - get these below 2 functions to work with consolidated code
function ProcessHTML($sModule,$sTab,$idTabs,$nTabIndex,$sType='tab') {
# Generates the HTML code of the passed tab id, for the header buttons or tab.
# sModule The module name to process NOTE: this value needs to be in CamelCase!
# sTab The tab name to process NOTE: this can be blank if sType=='buttons'
# idTabs The DB row id of the tab to process
# nTabIndex The index of the tab that this HTML belongs to (e.g. Help=0, List=1, General=2, ...)
# sType Defines which type of HTML to generate: (header) buttons, tab
global $__sInfo,$_LinkDB;
# https://stackoverflow.com/questions/74193905/how-to-bring-different-values-from-results-column-into-single-row-by-value-from
$__sInfo['error'] = "Failed to obtain objects to render for the requested tab in the database.";
# LEFT OFF - update the below sLanguage value to be dynamic based on the languages selected on the dashboard
$__sInfo['command'] = "
SELECT
tblObjects.*,tblTabs.sName AS sTab,tblGroups.sName AS sGroup,CONCAT(IFNULL(GROUP_CONCAT(tblStyles.sName SEPARATOR ' '),''), IF(tblObjects.bEncrypted=1,' encrypted', '')) AS sClasses, tblLanguages.sPlaceholder, tblLanguages.sLabel, tblLanguages.sTooltip, tblLanguages.sDescription
FROM
".DB_PRFX."ModuleMaker_Layout tblLayout
LEFT JOIN
".DB_PRFX."ModuleMaker_Groups tblGroups ON tblLayout.fkGroups=tblGroups.id
LEFT JOIN
".DB_PRFX."ModuleMaker_Tabs tblTabs ON tblGroups.fkTabs=tblTabs.id
LEFT JOIN
".DB_PRFX."ModuleMaker_Objects tblObjects ON tblGroups.id=tblObjects.fkGroups
LEFT JOIN
".DB_PRFX."ModuleMaker_Styling tblStyling ON tblObjects.id=tblStyling.fkObjects
LEFT JOIN
".DB_PRFX."ModuleMaker_Styles tblStyles ON tblStyling.fkStyles=tblStyles.id
LEFT JOIN
".DB_PRFX."ModuleMaker_Languages tblLanguages ON tblObjects.id=tblLanguages.fkObjects AND tblLanguages.sLanguage='en'
WHERE
tblLayout.fkTabs=? AND tblLayout.bDisabled=0 AND tblGroups.bDisabled=0 AND tblObjects.bDisabled=0 AND tblLanguages.bDisabled=0 AND tblObjects.nParent=0 AND (tblObjects.sVisibility='a' OR tblObjects.sVisibility='b') AND (IFNULL(tblStyling.bDisabled,0)=0)
GROUP BY
tblObjects.sName
ORDER BY
tblLayout.nIndex ASC,tblObjects.nLine ASC,tblObjects.nIndex ASC";
# VER2 - perhaps continue playing with this to get the parents and children to be listed behind one another
# WHERE
# tblLayout.fkTabs=3 AND tblLayout.bDisabled=0 AND tblGroups.bDisabled=0 AND tblObjects.bDisabled=0 AND tblLanguages.bDisabled=0 AND (tblObjects.sVisibility='a' OR tblObjects.sVisibility='b') AND (IFNULL(tblStyling.bDisabled,0)=0)
# GROUP BY
# tblObjects.sName
# ORDER BY
# tblLayout.nIndex ASC,tblObjects.nLine ASC,tblObjects.nParent ASC,tblObjects.nIndex ASC
# UPDATED 2025/09/27
# tblObjects.*,tblTabs.sName AS sTab,tblGroups.sName AS sGroup,IFNULL(GROUP_CONCAT(tblStyles.sName SEPARATOR ' '),'') AS sClasses
$__sInfo['values'] = '[i] '.$idTabs;
$Objects = $_LinkDB->prepare($__sInfo['command']);
$Objects->bind_param('i', $idTabs);
$Objects->execute();
$Objects = $Objects->get_result();
#file_put_contents('debug.txt', "03\n", FILE_APPEND);
if ($sType == 'tab') # if we're writing the HTML for the iterated tab, then...
{ $HTML = "\t<div id='oTabs".$nTabIndex."_".$sModule."' class='Tab'>\n"; }
else # otherwise we're writing the header buttons, so create the default 'Help' now...
{ $HTML = ''; }
# initialize the following variables
$sLabels = '';
$sObjects = '';
$sHidden = '';
$nLine = -1;
# MOVED 2025/09/29 - moved into the ProcessObject() function
# $bSeparate = false;
$sGroup = '';
# MOVED 2025/09/29 - moved into the ProcessObject() function
# $s_Load = []; # creates an array to store the onLoad code for searchable textboxes
$s_Listing = []; # creates an array to store each groups Listing value
$bTable = false; # indicates if we're processing a table or not
$sCode = '';
# Determine any search result listing text
while ($object = $Objects->fetch_assoc()) {
if ($sGroup != $object['sGroup']) { # if we've switched to a new group, then...
$sGroup = $object['sGroup']; # store the iterated group so we know when we iterate to a new one
$s_Listing[$object['sGroup']] = ''; # create an initial blank value
}
if ($object['sListing'] == 'n') { continue; } # skip all objects that are not part of the listing value
$sValue = ($object['bEncrypted'] ? '!'.$object['sName'] : $object['sName']);
if ($object['sListing'] == 'y') { $s_Listing[$object['sGroup']] .= ($s_Listing[$object['sGroup']] == '' ? $sValue : '|'.$sValue); }
else if ($object['sListing'] == ',') { $s_Listing[$object['sGroup']] .= ($s_Listing[$object['sGroup']] == '' ? $sValue : '|,'.$sValue.','); }
else if ($object['sListing'] == '[') { $s_Listing[$object['sGroup']] .= ($s_Listing[$object['sGroup']] == '' ? $sValue : '|['.$sValue.']'); }
}
# Write each object associated with the tab
$sGroup = ''; # reset the variable value
$Objects->data_seek(0); # reset the record pointer back to the first record in the results list to re-process below
while ($object = $Objects->fetch_assoc()) {
#file_put_contents('debug.txt', "Object |".$object['sName']."|\n", FILE_APPEND);
# Perform some variable adjustments
$object['sClasses'] = str_replace('.', '', $object['sClasses']); # remove periods from ALL custom class names
$sName = $object['sName']."_".$sModule;
# 1. Obtain any javascript related to the object
$__sInfo['error'] = "Failed to obtain javascript for the iterated object in the database.";
$__sInfo['command'] = "
SELECT
tblActions.id,tblActions.sName,tblCode.sCode
FROM
".DB_PRFX."ModuleMaker_Actions tblActions
LEFT JOIN
".DB_PRFX."ModuleMaker_Code tblCode ON tblActions.fkCode_EventJS=tblCode.id
WHERE
tblActions.bDisabled=0 AND tblActions.fkObjects=".$object['id']." AND tblActions.fkCode_EventJS<>0";
$__sInfo['values'] = 'None';
$CodeJS = $_LinkDB->query($__sInfo['command']);
# Add the code for the buttons
# https://stackoverflow.com/questions/19282760/what-is-a-fallback-for-the-hamburger-icon-or-html-entity-9776
if ($sType == 'buttons') { # if we're writing the HTML for the tab header buttons, then...
if (strpos($object['sClasses'], 'HeaderButton') === false) { continue; }
if ($CodeJS->num_rows > 0) { # if there is saved javascript for the object, then...
while ($codejs = $CodeJS->fetch_assoc())
{ $sCode .= ' '.$codejs['sName'].'="'.$codejs['sCode'].'"'; }
# REMOVED 2025/09/13 - removed so that the header buttons CAN have different styling
# $object['sClasses'] = str_replace('HeaderButton', '', $object['sClasses']); # remove the class so as to not impose that classes' css on the object
$HTML .= " <li><input type='button' id='".$sName."' value='".$language['sLabel']."' class='".$object['sClasses']."' title=\"".$language['sTooltip']."\"".$sCode." />\n";
}
continue; # the below code is for the module, so go ahead and iterate through the stored objects
} else if ($sType == 'tab') {
# Start/End a group of objects
// if ($sGroup != $object['sGroup']) {
if ($sGroup == '') { # NOTE: this should ony run once in the beginning to create the HTML of the first group
#file_put_contents('debug.txt', " Group change (top) |".$sGroup."|".$object['sGroup']."|\n", FILE_APPEND);
// if ($sGroup != '') { $HTML .= "\n\t\t</ul>\n\t\t</form>\n"; } # if we ARE adding a second+ group, then we need to terminate the previous one
$HTML .= "\t\t<form id='".$object['sGroup']."_".$sModule."' class='Group' data-listing='".$s_Listing[$object['sGroup']]."'>\n"; # now start the first/new group
$HTML .= "\t\t <ul>";
$sGroup = $object['sGroup']; # store the iterated group so we know when we iterate to a new one
}
# Skip adding the object depending on its visibility
if ($object['sVisibility'] == 'f') { continue; } # if the object is only visible in the front-end (e.g. website), then skip processing it here
if (strpos($object['sClasses'], 'HeaderButton') !== false) { continue; } # we do NOT process tab header buttons in this call to ProcessHTML, but in a different call and where the <ul class='Buttons'> section is ProcessObject()...
}
# -- Lets process the object! --
# 2. Obtain any children to the iterated object
# NOTE: we had to separate this because we can't get the database result set to list parents followed by their children; perhaps revision will enable that
# https://stackoverflow.com/questions/27984786/how-can-i-use-ifnull-in-where-clause
$__sInfo['error'] = "Failed to obtain child objects for the iterated parent in the database.";
# LEFT OFF - update the below sLanguage value to be dynamic based on the languages selected on the dashboard
$__sInfo['command'] = "
SELECT
tblObjects.*,IFNULL(GROUP_CONCAT(tblStyles.sName SEPARATOR ' '),'') AS sClasses, tblLanguages.sPlaceholder, tblLanguages.sLabel, tblLanguages.sTooltip, tblLanguages.sDescription
FROM
".DB_PRFX."ModuleMaker_Objects tblObjects
LEFT JOIN
".DB_PRFX."ModuleMaker_Styling tblStyling ON tblObjects.id=tblStyling.fkObjects
LEFT JOIN
".DB_PRFX."ModuleMaker_Styles tblStyles ON tblStyling.fkStyles=tblStyles.id
LEFT JOIN
".DB_PRFX."ModuleMaker_Languages tblLanguages ON tblObjects.id=tblLanguages.fkObjects AND tblLanguages.sLanguage='en'
WHERE
tblObjects.nParent=".$object['id']." AND tblObjects.bDisabled=0 AND tblLanguages.bDisabled=0 AND (IFNULL(tblStyling.bDisabled,0)=0)
GROUP BY
tblObjects.sName
ORDER BY
tblObjects.nIndex ASC";
$__sInfo['values'] = 'None';
$Children = $_LinkDB->query($__sInfo['command']);
# MERGED 2025/09/29 - this is merged into the $Object and $Children sql calls above
# # 3. Obtain the desired language
# $__sInfo['error'] = "Failed to obtain languages for the iterated (child) object in the database.";
# $__sInfo['command'] = "SELECT * FROM ".DB_PRFX."ModuleMaker_Languages WHERE bDisabled=0 AND fkObjects=".($Children->num_rows === false || $Children->num_rows === 0 ? $object['id'] : $child['id'])." AND sLanguage='en'";
# $__sInfo['values'] = 'None';
# $Languages = $_LinkDB->query($__sInfo['command']);
# if ($Languages->num_rows > 0) { # if there are saved languages, then store them
# $language = $Languages->fetch_assoc();
# } else { # otherwise, we need to store blank values
# $language['sPlaceholder'] = '';
# $language['sLabel'] = '';
# $language['sTooltip'] = '';
# $language['sDescription'] = '';
# }
#file_put_contents('debug.txt', "Object |".$object['sName']."|\n", FILE_APPEND);
# Process any children to the parent object
if ($Children->num_rows > 0) {
$nChildIndex = 0; # NOTE: the parent would have an index of 0, so this gets incremented immediately below to 1
while ($child = $Children->fetch_assoc()) {
# LEFT OFF - call the ProcessObject here if there are children; cleanup the children code block in ProcessObject; also figure out what we're going to do with $nChildIndex
}
}
# LEFT OFF - the below call will be for non-children objects
$HTML .= ProcessObject($sModule,$sTab,$sGroup,$object,$language,$CodeJS,$s_Listing,$sLabels,$sObjects,$sHidden,$nLine,$bTable);
}
if ($sType == 'buttons') { return $HTML; } # if we're writing the HTML for the tab header buttons, then return the HTML and exit this function
# make some adjustments to the variable values before adding to the HTML
if ($sLabels != '') { $sLabels = '<labels>'.$sLabels.'</labels>'; } # if we have labels for the objects on a line, then we need to surround with HTML tags
if ($sObjects == '' && $sLabels != '') { $sObjects = ' '; } # if the entry is in the <labels> and not in the body, then we need to add a space
# LEFT OFF - the below may need to be uncommented
# if (strpos($object['sClasses'], 'hiddenLine') !== false) { $sHidden = " class='hiddenLine ".$sName."'"; }
if ($nLine == -1) { $HTML .= "\n\t\t\t<li".$sHidden.">".$sLabels.$sObjects;} # if the last object to be processed was the only one on the line, then...
else if ($nLine > -1) { $HTML .= $sLabels.$sObjects;} # otherwise it was the last one of several, so we need a different output...
$HTML .= "\n\t\t </ul>\n\t\t</form>";
$HTML .= "\n\t</div>\n\n\n";
# REMOVED 2025/09/05 - this was not in the JS side
# $nIndex++;
return $HTML;
}
function ProcessObject($sModule,$sTab,$sGroup,$object,$language,$CodeJS,$s_Listing,&$sLabels,&$sObjects,&$sHidden,&$nLine,&$bTable) {
# This is a supplemental function to ProcessHTML that parses each passed object
# sModule The module name to process NOTE: this value needs to be in CamelCase!
# sTab The tab name to process NOTE: this can be blank if sType=='buttons'
# idTabs The DB row id of the tab to process
# nTabIndex The index of the tab that this HTML belongs to (e.g. Help=0, List=1, General=2, ...)
# sType Defines which type of HTML to generate: (header) buttons, tab
global $__sInfo,$_LinkDB;
$HTML = '';
$sName = $object['sName']."_".$sModule;
$sCode = '';
$s_Load = []; # creates an array to store the onLoad code for searchable textboxes
$bSeparate = false;
# -- EVERYTHING FROM HERE DOWN: x pass into function: $sModule, $sTab, $sGroup, $object (array), $language (array), $CodeJS, $s_Listing
# x pass by reference: $sLabels, $sObjects, $sHidden, $nLine, $bTable
# x define internally: $s_Load, $bSeparate
# x define $sCode in function (it's initialized and used above here); define $sName; define $HTML and return it's value
# Store any javascript event code for the object
$bBlur = false; # if the onBlur event was processed
$bDate = false; # if the calendar toggle code has been added for applicable textboxes
$bClick = false;
$bSearch = false;
$sBlur = "document.getElementById('o".$object['sName']."Results_".$sModule."').style.display='none';";
$sDate = "Project('Calendar',event);";
$sClick = $sBlur;
# UPDATED 2025/09/27
## DEV NOTE: the field tag is split as follows: include_new_option{true|false}|pipe_separated_display_values
# $sSearch = "if(!Search('able','".$sName."','o".$object['sName']."Results_".$sModule."','o".$object['sName']."HID_".$sModule."')){return true;} Search('Submit','".$sName."','o".$object['sName']."Results_".$sModule."','".$sModule."_".$sTab."','".$object['sName']."','".($object['sTag'] != '' && strpos($object['sTag'],'|') !== false ? substr($object['sTag'],strpos($object['sTag'],'|')+1) : '')."'".($object['sTag'] != '' ? ",'".substr($object['sTag'],0,strpos($object['sTag'],'|'))."'" : '').");";
# DEV NOTE: the field tag indicates the Search('Submit',...,bNew) value; this is only applicable for textboxes
$sSearch = "if(!Search('able','".$sName."','o".$object['sName']."Results_".$sModule."','o".$object['sName']."HID_".$sModule."')){return true;} Search('Submit','".$sName."','o".$object['sName']."Results_".$sModule."','".$sModule."_".$sTab."','".$object['sName']."','".$object['sTag']."'".($s_Listing[$object['sGroup']] != '' ? ",'".$s_Listing[$object['sGroup']]."'" : '').");";
# REMOVED 2025/09/18
# $bHideBlur = false; # if the UI cleanup code has been added for applicable textboxes
# $bHideClick = false;
$s_Load[$object['sName']] = "function(){alert('Assign the loading function using the fields \"-Load from Search Results-\" action.');}";
if ($CodeJS->num_rows > 0) { # if there is saved javascript for the object, then...
while ($codejs = $CodeJS->fetch_assoc()) {
# VER2 - is there a better way to identify javascript events; the below may cause issues!
if (substr($codejs['sName'],0,2) != 'on') { continue; } # skip any code that isn't a javascript event (e.g. onClick, onMouseOver, etc)
# as long as we're NOT dealing with an onLoad event for a searchable field, then...
# if ($codejs['sName'] != 'onLoad' && strpos($object['sClasses'], 'searchable') === false)
if ($codejs['sName'] != 'onLoad')
{ $sCode .= ' '.$codejs['sName'].'="'; }
#file_put_contents('debug.txt', "code |".$object['sName']."|".$codejs['sName']."|\n", FILE_APPEND);
# if we are dealing with a textbox -AND- not a span (e.g. not 'textbox12'), then...
# UPDATED 2025/09/18 - there can only be one primary styling per object
# if (strpos($object['sClasses'], 'textbox') !== false && strpos($object['sClasses'], 'infobox') === false) {
if (strpos($object['sClasses'], 'textbox') !== false) {
# if we've cycled to an 'onBlur' event, then...
if ($codejs['sName'] == 'onBlur') {
if (strpos($object['sClasses'], 'searchable') !== false) { # if it is a searchable field...
# UPDATED 2025/09/18 - the user-defined code never got added
# $sCode .= (($sCode == '') ? '' : ' ') . "document.getElementById('oMatches".$object['sName']."_".$sModule."').style.display='none';";
$sCode .= $sBlur.' ';
# UPDATED 2025/09/18
# $bHideBlur = true;
$bBlur = true;
}
# if we've cycled to an 'onClick' event, then...
} else if ($codejs['sName'] == 'onClick') {
if ($object['sType'] == 'e') { # if it is a date entry...
# UPDATED 2025/09/18 - some of this is now handled below
# $sCode .= "Project('Calendar',event); ".$codejs['sCode'].'"';
$sCode .= $sDate.' ';
$bDate = true;
} else if (strpos($object['sClasses'], 'searchable') !== false) { # if it is a searchable field...
# UPDATED 2025/09/18 - the user-defined code never got added
# $sCode .= (($sCode == '') ? '' : ' ') . "document.getElementById('oMatches".$object['sName']."_".$sModule."').style.display='none';";
$sCode .= $sClick.' ';
# UPDATED 2025/09/18
# $bHideClick = true;
$bClick = true;
}
# if we've cycled to an 'onKeyUp' event -AND- it is a searchable field, then...
} else if ($codejs['sName'] == 'onKeyUp' && strpos($object['sClasses'], 'searchable') !== false) {
$sCode .= $sSearch.' ';
$bSearch = true;
# if we've cycled to an 'onLoad' event -AND- it is a searchable field, then...
} else if ($codejs['sName'] == 'onLoad' && strpos($object['sClasses'], 'searchable') !== false) {
# DEV NOTE: the onLoad action for a searchable textbox is executed when an item in the matching combobox is selected
# if the user specified the value as a function, then preserve what they entered
if (substr($codejs['sCode'],0,10) == 'function(') { $s_Load[$object['sName']] = $codejs['sCode']; }
# if the user specified the value as a string, then replace the quotes with special characters (since it's embedded in the HTML and already surrounded by quotes)
else if (substr($codejs['sCode'],0,1) == '"') { $s_Load[$object['sName']] = '"'.substr($codejs['sCode'],1,-1).'"'; }
# otherwise they did NOT specify either, so encapsulate with special characters
else { $s_Load[$object['sName']] = '"'.$codejs['sCode'].'"'; }
continue; # we can continue to the next iteration since we've stored this code for use later down in the script
}
# UPDATED - this step is now taken regardless
# # otherwise, we need to add the saved Javascript code
# } else { $sCode .= $codejs['sCode'].'"'; }
}
$sCode .= $codejs['sCode'].'"';
}
}
# UPDATED 2025/09/18 - there can only be one primary styling per object
# if (strpos($object['sClasses'], 'textbox') !== false && strpos($object['sClasses'], 'infobox') === false) {
if (strpos($object['sClasses'], 'textbox') !== false) {
# if the 'onBlur' event was not added above, check if we need to add it now
# UPDATED 2025/09/18 - if it wasn't added above, then we need to write the entire HTML blurb now
# if (! $bHideBlur && strpos($object['sClasses'], 'searchable') !== false)
# { $bHideBlur = true; $sCode .= ($bBlur ? ' ' : ' onBlur="') . "document.getElementById('oMatches".$object['sName']."_".$sModule."').style.display='none';"; }
# if ($bBlur || $bHideBlur) { $sCode .= '"'; }
if (! $bBlur && strpos($object['sClasses'], 'searchable') !== false) { $sCode .= ' onBlur="'.$sBlur.'"'; }
# if the 'onClick' event was not added above, check if we need to add it now
# UPDATED 2025/09/18 - if it wasn't added above, then we need to write the entire HTML blurb now
# if (! $bDate && $object['sType'] == 'e')
# { $bDate = true; $sCode .= " onClick=\"Project('Calendar',event);"; }
if (! $bDate && $object['sType'] == 'e') { $sCode .= ' onClick="'.$sDate.'"'; }
# if the 'onClick' event was not added above, check if we need to add it now
# UPDATED 2025/09/18 - if it wasn't added above, then we need to write the entire HTML blurb now
# if (! $bHideClick && strpos($object['sClasses'], 'textbox') !== false && strpos($object['sClasses'], 'searchable') !== false)
# { $bHideClick = true; $sCode .= ($bDate ? ' ' : ' onClick="') . "document.getElementById('oMatches".$object['sName']."_".$sModule."').style.display='none';"; }
# if ($bDate || $bHideClick) { $sCode .= '"'; }
if (! $bClick && strpos($object['sClasses'], 'textbox') !== false && strpos($object['sClasses'], 'searchable') !== false) { $sCode .= ' onClick="'.$sClick.'"'; }
# if the 'onKeyUp' event was not added above, check that we need to add it now
if (! $bSearch && strpos($object['sClasses'], 'textbox') !== false && strpos($object['sClasses'], 'searchable') !== false) { $sCode .= ' onKeyUp="'.$sSearch.'"'; }
}
# add any 'tag' value (unless it's a searchable textbox)
if ($object['sTag'] != '' && strpos($object['sClasses'], 'textbox') === false && strpos($object['sClasses'], 'searchable') === false) { $sCode .= ' '.$object['sTag']; }
# if we need to add a new line, then do so!
if ($nLine != $object['nLine']) {
#file_put_contents('debug.txt', "New Line |".($bSeparate ? 'true' : 'false')."|".$object['sClasses']."|".$sLabels."|".$sObjects."|\n", FILE_APPEND);
# if we have labels for the objects on a line (that's not a button since that goes for its value), then we need to surround with HTML tags
if ($sLabels != '') { $sLabels = '<labels>'.$sLabels.'</labels>'; } # if we have labels for the objects on a line, then we need to surround with HTML tags
if ($sObjects == '' && $sLabels != '') { $sObjects = ' '; } # if the entry is in the <labels> and not in the body, then we need to add a space
if (strpos($object['sClasses'], 'hiddenLine') !== false) { $sHidden = " class='hiddenLine ".$sName."'"; }
if (! $bSeparate) { # if we're not separating the <labels> from the objects on different lines, then...
# UPDATED 2025/09/08
# $HTML .= $sLabels.$sObjects."\n\t\t\t<li".$sHidden.">";
if ($sGroup != $object['sGroup'] && $nLine == -1) { # if the iterated object is the first object in the new group, then...
#file_put_contents('debug.txt', " NL top\n", FILE_APPEND);
$HTML .= "\n\t\t\t<li".$sHidden.">".$sLabels.$sObjects;
} else if ($sGroup != $object['sGroup'] && $nLine > -1) { # if we've stored several objects for the new group, then...
#file_put_contents('debug.txt', " NL mid\n", FILE_APPEND);
$HTML .= $sLabels.$sObjects;
} else { # otherwise we're adding a new object for an existing group, so...
#file_put_contents('debug.txt', " NL btm\n", FILE_APPEND);
$HTML .= $sLabels.$sObjects."\n\t\t\t<li".$sHidden.">";
}
} else {
#file_put_contents('debug.txt', " NL separate\n", FILE_APPEND);
$HTML .= $sLabels." \n\t\t\t<li>".$sObjects."\n\t\t\t<li".$sHidden.">"; } # otherwise we are, so make that change in the HTML
$nLine = $object['nLine'];
# reset variables
$sObjects = '';
$sLabels = '';
$sHidden = '';
$bSeparate = false;
}
# Start/End a group of objects
if ($sGroup != $object['sGroup']) {
#file_put_contents('debug.txt', " Group change (btm) |".$sGroup."|".$object['sGroup']."|\n", FILE_APPEND);
# UPDATED 2025/09/08
# if ($sGroup != '') { $HTML .= "\n\t\t</ul>\n\t\t</form>\n"; } # if we ARE adding a second+ group, then we need to terminate the previous one
$HTML .= "\n\t\t </ul>\n\t\t</form>\n"; # if we ARE adding a second+ group, then we need to terminate the previous one
$HTML .= "\t\t<form id='".$sModule."_".$object['sTab'].($object['sTab'] != $object['sGroup'] ? '_'.$object['sGroup'] : '')."' class='Group' data-listing='".$s_Listing[$object['sGroup']]."'>\n"; # now start the first/new group
$HTML .= "\t\t <ul>";
$sGroup = $object['sGroup']; # store the iterated group so we know when we iterate to a new one
$nLine = -1; # reset the line so that the block of code above this one works correctly
}
# construct the <labels> for the line (excluding buttons since the sLabel is its text, images/videos since the sLabel is placed elsewhere)
# NOTES:
# - this MUST come below the appendage of a new <li> (in the code block above)
# - checkboxes and radio buttons use their 'placeholder' value as their <label> values
# - buttons and checkboxes can be included in the <labels> by giving it that css class
if ($language['sLabel'] != '' && strpos($object['sClasses'], 'button') === false && strpos($object['sClasses'], 'image') === false && strpos($object['sClasses'], 'video') === false) {
if (strpos($object['sClasses'], 'labels') === false)
{ $sLabels .= ($sLabels == '') ? $language['sLabel'] : ', '.$language['sLabel']; }
if (strpos($object['sClasses'], 'separate') !== false)
{ $bSeparate = true; }
}
#file_put_contents('debug.txt', "Processing object\n", FILE_APPEND);
# Process the (parental) object
if (strpos($object['sClasses'], 'checkbox') !== false) {
# if we are processing a boolean checkbox group, then...
if ($object['sType'] == 'b') { # NOTE: boolean for this object just means it's true (checked) or false (unchecked)
if (strpos($object['sClasses'], 'labels') === false) {
$sObjects .= "<input type='checkbox' id='".$sName."' class='".$object['sClasses']."' title=\"".$language['sTooltip']."\"".$sCode." /><label for='".$sName."' class='checkboxLabel' title=\"".$language['sTooltip']."\">".$language['sPlaceholder']."</label>";
} else {
$object['sClasses'] = str_replace('labels', '', $object['sClasses']); # remove the class so as to not impose that classes' css on the object
$sLabels .= "<input type='checkbox' id='".$sName."' class='".$object['sClasses']."' title=\"".$language['sTooltip']."\"".$sCode." /><label for='".$sName."' class='checkboxLabel' title=\"".$language['sTooltip']."\">".$language['sPlaceholder']."</label>";
}
# otherwise, we have a list so process the "parent"
} else {
if (strpos($object['sClasses'], 'labels') === false) {
$sObjects .= "<input type='checkbox' id='".$sName."' value='".$object['sValue']."' class='".$object['sClasses']."' title=\"".$language['sTooltip']."\"".$sCode." /><label for='".$sName."' class='checkboxLabel' title=\"".$language['sTooltip']."\">".$language['sPlaceholder']."</label>";
} else {
$object['sClasses'] = str_replace('labels', '', $object['sClasses']); # remove the class so as to not impose that classes' css on the object
$sLabels .= "<input type='checkbox' id='".$sName."' value='".$object['sValue']."' class='".$object['sClasses']."' title=\"".$language['sTooltip']."\"".$sCode." /><label for='".$sName."' class='checkboxLabel' title=\"".$language['sTooltip']."\">".$language['sPlaceholder']."</label>";
}
}
} else if (strpos($object['sClasses'], 'radio') !== false) {
// if we are processing a boolean radio button group, then...
if ($object['sType'] == 'b') {
# DEV NOTE: the $language['sPlaceholder'] defines the value here separated via pipe (e.g. true|false, yes|no, ok|cancel, etc)
$sObjects .= "<input type='radio' id='".$object['sName']."0_".$sModule."' name='".$sName."' value='true' class='".$object['sClasses']."' title=\"".$language['sTooltip']."\"".$sCode." /><label for='true' class='radioLabel' title=\"".$language['sTooltip']."\">".substr($language['sPlaceholder'],0,strpos($language['sPlaceholder'],'|'))."</label>" .
"<input type='radio' id='".$object['sName']."1_".$sModule."' name='".$sName."' value='false' class='".$object['sClasses']."' title=\"".$language['sTooltip']."\"".$sCode." /><label for='false' class='radioLabel' title=\"".$language['sTooltip']."\">".substr($language['sPlaceholder'],strpos($language['sPlaceholder'],'|')+1)."</label>";
// otherwise, we have a list so process the "parent"
} else { $sObjects .= "<input type='radio' id='".$object['sName']."0_".$sModule."' name='".$sName."' value='".$object['sValue']."' class='".$object['sClasses']."' title=\"".$language['sTooltip']."\"".$sCode." /><label for='".$object['sValue']."' class='radiolabel' title=\"".$language['sTooltip']."\">".$language['sPlaceholder']."</label>"; }
} else if (strpos($object['sClasses'], 'image') !== false) {
if (strpos($object['sClasses'], 'labels') === false) {
$sObjects .= "<img id='".$sName."' src='".$object['sValue']."' class='".$object['sClasses']."' alt=\"".$language['sPlaceholder']."\" title=\"".$language['sTooltip']."\"".$sCode." />";
} else {
$object['sClasses'] = str_replace('labels', '', $object['sClasses']); # remove the class so as to not impose that classes' css on the object
$sLabels .= "<img id='".$sName."' src='".$object['sValue']."' class='".$object['sClasses']."' alt=\"".$language['sPlaceholder']."\" title=\"".$language['sTooltip']."\"".$sCode." />";
}
if ($language['sLabel'] != '')
{ $sLabels .= "<label class='imageLabel' title=\"".$language['sTooltip']."\"".$sCode.">".$language['sLabel']."</label>"; }
} else if (strpos($object['sClasses'], 'video') !== false) {
# VER2 - implement video
if ($language['sLabel'] != '')
{ $sLabels .= "<label class='videoLabel' title=\"".$language['sTooltip']."\"".$sCode.">".$language['sLabel']."</label>"; }
} else if (strpos($object['sClasses'], 'button') !== false) {
# DEV NOTE: buttons can be included in the <labels> by giving it that css class
if (strpos($object['sClasses'], 'labels') === false) {
$sObjects .= "<input type='button' id='".$sName."' value='".$language['sLabel']."' class='".$object['sClasses']."' title=\"".$language['sTooltip']."\"".$sCode." />";
} else {
$object['sClasses'] = str_replace('labels', '', $object['sClasses']); # remove the class so as to not impose that classes' css on the object
$sLabels .= "<input type='button' id='".$sName."' value='".$language['sLabel']."' class='".$object['sClasses']."' title=\"".$language['sTooltip']."\"".$sCode." />";
}
} else if (strpos($object['sClasses'], 'combobox') !== false) {
$sObjects .= "<select id='".$sName."' size='2' class='".$object['sClasses']."' data-description=\"".($language['sDescription'] != '' ? str_replace('"', '\"', $language['sDescription']) : '')."\" title=\"".$language['sTooltip']."\"".$sCode." />\n";
# if we are processing a boolean object, then...
if ($object['sType'] == 'b') {
# DEV NOTE: the $language['sPlaceholder'] defines the <option> text here separated via pipe (e.g. true|false, yes|no, ok|cancel, etc); $object['sValue'] determines pre-selected option - either by <option value> or <option>text</option>
$sObjects .= "\t\t\t\t<option value='true'".($object['sValue'] == 'true' || $object['sValue'] == substr($language['sPlaceholder'],0,strpos($language['sPlaceholder'],'|')) ? ' selected' : '').">".($language['sPlaceholder'] != '' ? substr($language['sPlaceholder'],0,strpos($language['sPlaceholder'],'|')) : 'True')."</option>\n" .
"\t\t\t\t<option value='false'".($object['sValue'] == 'false' || $object['sValue'] == substr($language['sPlaceholder'],strpos($language['sPlaceholder'],'|')+1) ? ' selected' : '').">".($language['sPlaceholder'] != '' ? substr($language['sPlaceholder'],strpos($language['sPlaceholder'],'|')+1) : 'False')."</option>\n";
}
} else if (strpos($object['sClasses'], 'listbox') !== false) {
if (strpos($object['sClasses'], 'labels') === false) {
$sObjects .= "<select id='".$sName."' size='1' class='".$object['sClasses']."' data-description=\"".($language['sDescription'] != '' ? str_replace('"', '\"', $language['sDescription']) : '')."\" title=\"".$language['sTooltip']."\"".$sCode." />\n";
# if we are processing a boolean object, then...
if ($object['sType'] == 'b') {
# DEV NOTE: the $language['sPlaceholder'] defines the <option> text here separated via pipe (e.g. true|false, yes|no, ok|cancel, etc); $object['sValue'] determines pre-selected option - either by <option value> or <option>text</option>
$sObjects .= "\t\t\t\t<option value='true'".($object['sValue'] == 'true' || $object['sValue'] == substr($language['sPlaceholder'],0,strpos($language['sPlaceholder'],'|')) ? ' selected' : '').">".($language['sPlaceholder'] != '' ? substr($language['sPlaceholder'],0,strpos($language['sPlaceholder'],'|')) : 'True')."</option>\n" .
"\t\t\t\t<option value='false'".($object['sValue'] == 'false' || $object['sValue'] == substr($language['sPlaceholder'],strpos($language['sPlaceholder'],'|')+1) ? ' selected' : '').">".($language['sPlaceholder'] != '' ? substr($language['sPlaceholder'],strpos($language['sPlaceholder'],'|')+1) : 'False')."</option>\n";
}
} else {
$object['sClasses'] = str_replace('labels', '', $object['sClasses']); # remove the class so as to not impose that classes' css on the object
$sLabels .= "<select id='".$sName."' size='1' class='".$object['sClasses']."' data-description=\"".($language['sDescription'] != '' ? str_replace('"', '\"', $language['sDescription']) : '')."\" title=\"".$language['sTooltip']."\"".$sCode." />\n";
if ($object['sType'] == 'b') {
$sLabels .= "\t\t\t\t<option value='true'".($object['sValue'] == 'true' || $object['sValue'] == substr($language['sPlaceholder'],0,strpos($language['sPlaceholder'],'|')) ? ' selected' : '').">".($language['sPlaceholder'] != '' ? substr($language['sPlaceholder'],0,strpos($language['sPlaceholder'],'|')) : 'True')."</option>\n" .
"\t\t\t\t<option value='false'".($object['sValue'] == 'false' || $object['sValue'] == substr($language['sPlaceholder'],strpos($language['sPlaceholder'],'|')+1) ? ' selected' : '').">".($language['sPlaceholder'] != '' ? substr($language['sPlaceholder'],strpos($language['sPlaceholder'],'|')+1) : 'False')."</option>\n";
}
}
} else if (strpos($object['sClasses'], 'passwordtoggle') !== false) {
$sObjects .= "<img src='".$object['sValue']."' class='".$object['sClasses']."' title='".$language['sTooltip']."' onClick=\"Security('TogglePassword','Password');\" />";
} else if (strpos($object['sClasses'], 'password') !== false) {
$sObjects .= "<input type='password' id='".$sName."' class='".$object['sClasses']."' maxlength='".$object['nMaxLength']."' placeholder=\"".$language['sPlaceholder']."\" ".($object['bMandatory'] ? "data-mandatory='true' " : '')."data-validate=\"".($object['sValidate'] != '' ? str_replace('"', '\"', $object['sValidate']) : '')."\" data-description=\"".($language['sDescription'] != '' ? str_replace('"', '\"', $language['sDescription']) : '')."\" title=\"".$language['sTooltip']."\"".$sCode." /><input type='password' id='confirm_".$sModule."' class='".$object['sClasses']." space' maxlength='".$object['nMaxLength']."' placeholder=\"Confirm\" data-validate=\"".($object['sValidate'] != '' ? str_replace('"', '\"', $object['sValidate']) : '')."\" data-description=\"Confirm Password\" title=\"Re-type the same password to confirm it was entered correctly\" />";
} else if (strpos($object['sClasses'], 'infobox') !== false) { # WARNING: this MUST come BEFORE the textbox indexOf (since span's can have related textbox css assignments [e.g. textbox12])
if (strpos($object['sClasses'], 'labels') === false) {
$sObjects .= "<span id='".$sName."' class='".$object['sClasses']."' title=\"".$language['sTooltip']."\"".$sCode."> </span>";
} else {
$object['sClasses'] = str_replace('labels', '', $object['sClasses']); # remove the class so as to not impose that classes' css on the object
$sLabels .= "<span id='".$sName."' class='".$object['sClasses']."' title=\"".$language['sTooltip']."\"".$sCode."> </span>";
}
} else if (strpos($object['sClasses'], 'textbox') !== false) {
if (strpos($object['sClasses'], 'labels') === false) {
if (strpos($object['sClasses'], 'searchable') !== false)
{ $sObjects .= "<select id='o".$object['sName']."Results_".$sModule."' size='10' class='matches' onChange=\"Search('Select','o".$object['sName']."Results_".$sModule."','o".$object['sName']."HID_".$sModule."','".$sName."',".$s_Load[$object['sName']].");\"></select><input type='hidden' id='o".$object['sName']."HID_".$sModule."' value='0' />"; }
# DEV NOTE: each REQUIRED element needs a 'data-mandatory=true' (and data-description=[e.g.]'Physical address line 1') attribute to indicate if a value MUST be present before saving a record; this is for blank textboxes|textareas|passwords, 'Select...' listboxes|comboboxes
$sObjects .= "<input type='textbox' id='".$sName."' class='".$object['sClasses']."' value=\"".$object['sValue']."\" maxlength='".$object['nMaxLength']."' placeholder=\"".$language['sPlaceholder']."\" ".($object['bMandatory'] ? "data-mandatory='true' " : '')."data-validate=\"".($object['sValidate'] != '' ? str_replace('"', '\"', $object['sValidate']) : '')."\" data-description=\"".($language['sDescription'] != '' ? str_replace('"', '\"', $language['sDescription']) : '')."\" title=\"".$language['sTooltip']."\"".$sCode." />";
} else {
$object['sClasses'] = str_replace('labels', '', $object['sClasses']); # remove the class so as to not impose that classes' css on the object
if (strpos($object['sClasses'], 'searchable') !== false)
{ $sLabels .= "<select id='o".$object['sName']."Results_".$sModule."' size='10' class='matches' onChange=\"Search('Select','o".$object['sName']."Results_".$sModule."','o".$object['sName']."HID_".$sModule."','".$sName."',".$s_Load[$object['sName']].");\"></select><input type='hidden' id='o".$object['sName']."HID_".$sModule."' value='0' />"; }
$sLabels .= "<input type='textbox' id='".$sName."' class='".$object['sClasses']."' value=\"".$object['sValue']."\" maxlength='".$object['nMaxLength']."' placeholder=\"".$language['sPlaceholder']."\" ".($object['bMandatory'] ? "data-mandatory='true' " : '')."data-validate=\"".($object['sValidate'] != '' ? str_replace('"', '\"', $object['sValidate']) : '')."\" data-description=\"".($language['sDescription'] != '' ? str_replace('"', '\"', $language['sDescription']) : '')."\" title=\"".$language['sTooltip']."\"".$sCode." />";
}
} else if (strpos($object['sClasses'], 'textarea') !== false) {
$sObjects .= "<textarea id='".$sName."' class='".$object['sClasses']."' maxlength='".$object['nMaxLength']."' placeholder=\"".$language['sPlaceholder']."\" ".($object['bMandatory'] ? "data-mandatory='true' " : '')."data-validate=\"".($object['sValidate'] != '' ? str_replace('"', '\"', $object['sValidate']) : '')."\" data-description=\"".($language['sDescription'] != '' ? str_replace('"', '\"', $language['sDescription']) : '')."\" title=\"".$language['sTooltip']."\"".$sCode.">".$object['sValue']."</textarea>";
} else if (strpos($object['sClasses'], 'table') !== false) {
# DEV NOTE: tables should be basically constructed with all the first object in the tab>group being the table and all subsequent objects being children of that field
# the Interface>Placement tab will mean each line in that combobox is a <tr> and each field on the same line is a <td> in that <tr>
# UPDATED 2025/09/27
# $sObjects .= "<table id='".$sName."' class='".$object['sClasses']."' title=\"".$language['sTooltip']."\"".$sCode."></table>";
$sObjects .= "<table id='".$sName."' class='".$object['sClasses']."' title=\"".$language['sTooltip']."\"".$sCode.">";
$bTable = true;
# REMOVED 2025/09/29 - this code isn't in a loop any longer since it's now in its own function
# continue; # skip processing any children for now...
# LEFT OFF - we processing the table, all the code above need to work to put its children in a <tr>/<td> and then the below code for any <select>/<radio> children they may have
# the problem I face is that I can't get the SQL results to sort the records so that children are directly under their parents so we can just loop through ALL objects when $bTable==true
}
// VER2 - can the below code be merged above since there's a lot of duplication of code between the two
# Process any children to the parent object
if ($Children->num_rows > 0) {
$nChildIndex = 0; # NOTE: the parent would have an index of 0, so this gets incremented immediately below to 1
while ($child = $Children->fetch_assoc()) {
#file_put_contents('debug.txt', "Processing child |".$child['sName']."|\n", FILE_APPEND);
# VER2 - merge the below SQL calls into the Objects code above
# LEFT OFF - update the below sLanguage value to be dynamic based on the languages selected on the dashboard
# 8. Obtain child languages
$__sInfo['error'] = "Failed to obtain languages for the iterated child object in the database.";
$__sInfo['command'] = "SELECT * FROM ".DB_PRFX."ModuleMaker_Languages WHERE bDisabled=0 AND fkObjects=".$child['id']." AND sLanguage='en'";
$__sInfo['values'] = 'None';
$ChildLangs = $_LinkDB->query($__sInfo['command']);
if ($ChildLangs->num_rows > 0) { # if there are saved languages, then...
$language = $ChildLangs->fetch_assoc();
} else { # otherwise retain the values from the parent object set above
$language['sPlaceholder'] = '';
$language['sLabel'] = '';
$language['sTooltip'] = '';
$language['sDescription'] = '';
}
# 9. Obtain child javascript
$__sInfo['error'] = "Failed to obtain javascript for the iterated child object in the database.";
$__sInfo['command'] = "
SELECT
tblActions.id,tblActions.sName,tblCode.sCode
FROM
".DB_PRFX."ModuleMaker_Actions tblActions
LEFT JOIN
".DB_PRFX."ModuleMaker_Code tblCode ON tblActions.fkCode_EventJS=tblCode.id
WHERE
tblActions.bDisabled=0 AND tblActions.fkObjects=".$child['id']." AND tblActions.fkCode_EventJS<>0";
$__sInfo['values'] = 'None';
$CodeJS = $_LinkDB->query($__sInfo['command']);
# -- Lets do some work! --
# Store any javascript event code for the object
# NOTE: we only need minimal script storage here since there are limited objects that can be children to a parent object
$sCode = '';
if ($CodeJS->num_rows > 0) { # if there is saved javascript for the object, then...
while ($codejs = $CodeJS->fetch_assoc()) {
# VER2 - is there a better way to identify javascript events; the below may cause issues!
if (substr($codejs['sName'],0,2) != 'on') { continue; }
$sCode .= ' '.$codejs['sName'].'="'.$codejs['sCode'].'"';
}
}
# Add any 'tag' value
if ($child['sTag'] != '') { $sCode .= ' '.$child['sTag']; }
# Perform some code adjustments
$nChildIndex++;
$child['sClasses'] = str_replace('.', '', $child['sClasses']); # remove periods from ALL custom class names
# $language['sPlaceholder'] = ''; # these are established above
# $language['sTooltip'] = '';
# $language['sLabel'] = '';
# $language['sDescription'] = '';
# Process the (child) object
# NOTE: checkboxes don't need to have children like radio buttons
if (strpos($object['sClasses'], 'radio') !== false) {
# if we are processing a boolean radio button group, then...
if ($child['sType'] == 'b') {
# DEV NOTE: the $language['sPlaceholder'] defines the value here separated via pipe (e.g. true|false, yes|no, ok|cancel, etc)
$sObjects .= "<input type='radio' id='".$object['sName']."0_".$sModule."' name='".$sName."' value='true' class='".$child['sClasses']."' title=\"".$language['sTooltip']."\"".$sCode." /><label for='true' class='radioLabel' title=\"".$language['sTooltip']."\">".substr($language['sPlaceholder'],0,strpos($language['sPlaceholder'],'|'))."</label>" .
"<input type='radio' id='".$object['sName']."1_".$sModule."' name='".$sName."' value='false' class='".$child['sClasses']."' title=\"".$language['sTooltip']."\"".$sCode." /><label for='false' class='radioLabel' title=\"".$language['sTooltip']."\">".substr($language['sPlaceholder'],strpos($language['sPlaceholder'],'|')+1)."</label>";
# otherwise, we have a list so process the "child"
} else { $sObjects .= "<input type='radio' id='".$object['sName'].$nChildIndex."_".$sModule."' name='".$sName."' value='".$child['sValue']."' class='".$child['sClasses']."' title=\"".$language['sTooltip']."\"".$sCode." /><label for='".$child['sValue']."' class='radioLabel' title=\"".$language['sTooltip']."\">".$language['sPlaceholder']."</label>"; }
} else if (strpos($object['sClasses'], 'combobox') !== false || strpos($object['sClasses'], 'listbox') !== false)
{ $sObjects .= "\t\t\t\t<option value='".$child['sValue']."' ".$child['sTag']." title=\"".$language['sTooltip']."\">".$language['sLabel']."</option>\n"; }
}
}
#file_put_contents('debug.txt', "done with object/child\n", FILE_APPEND);
# add a trailing tag is the object is a combo or listbox
if (strpos($object['sClasses'], 'combobox') !== false || strpos($object['sClasses'], 'listbox') !== false) { $sObjects .= "\t\t\t </select>"; }
# -- END OF FUNCTION
}
function ProcessCSS($sModule,$idThemes,$nIndent=0,$sType='project') {
# Generates the CSS code of the passed theme id, for the project or module.
# sModule The module name to process NOTE: this value needs to be in CamelCase!
# idThemes The DB row id of the theme to process
# nIndent The number of tabs to indent the code
# sType Defines which type of CSS to generate: project, module
global $__sInfo,$_LinkDB;
if ($sType == 'project') {
$__sInfo['error'] = "Failed to obtain objects to render for the requested tab in the database.";
$__sInfo['command'] = "
SELECT
tblTheming.id,tblStyles.sName,tblCode.sCode AS sCSS
FROM
".DB_PRFX."ModuleMaker_Theming tblTheming
LEFT JOIN
".DB_PRFX."ModuleMaker_Styles tblStyles ON tblTheming.fkStyles=tblStyles.id
LEFT JOIN
".DB_PRFX."ModuleMaker_Code tblCode ON tblTheming.fkCode=tblCode.id
WHERE
tblTheming.fkThemes=? AND tblTheming.fkStyles<>0 AND tblTheming.fkObjects=0 AND tblStyles.bDisabled=0";
} else {
# LEFT OFF - we need to grab only the styles for the groups associated with the module here
# https://stackoverflow.com/questions/8532283/mysql-numbers-in-varchar-columns-query-issue
$__sInfo['error'] = "Failed to obtain the object specific css for the requested module in the database.";
$__sInfo['command'] = "
SELECT
tblTheming.id,tblObjects.sName,tblCode.sCode AS sCSS
FROM
".DB_PRFX."ModuleMaker_Theming tblTheming
LEFT JOIN
".DB_PRFX."ModuleMaker_Objects tblObjects ON tblTheming.fkObjects=tblObjects.id
LEFT JOIN
".DB_PRFX."ModuleMaker_Code tblCode ON tblTheming.fkCode=tblCode.id
WHERE
tblTheming.fkThemes=? AND tblTheming.fkStyles=0 AND tblTheming.fkObjects<>0 AND tblObjects.bDisabled=0";
}
$__sInfo['values'] = '[i] '.$_POST['idThemes'];
$Styles = $_LinkDB->prepare($__sInfo['command']);
$Styles->bind_param('i', $_POST['idThemes']);
$Styles->execute();
$Styles = $Styles->get_result();
$CSS = '';
$sPrefix = '';
$sTabs = '';
for ($i=0; $i<$nIndent; $i++) { $sTabs .= "\t"; }
if ($Styles->num_rows > 0) { # if there are saved styles, then...
while ($style = $Styles->fetch_assoc()) {
if ($sType == 'project') {
# remove the preceeding dot if we're dealing with any object styles (or if it's a custom style that would already have the dot)
switch($style['sName']) {
case '*':
case 'html':
case 'body':
case 'a':
case 'h1':
case 'h2':
case 'h3':
case 'ul':
case 'li':
case 'optgroup':
case 'option':
case 'labels':
$sPrefix = '';
break;
default:
$sPrefix = '.';
}
# also check these two values and adjust sPrefix accordingly
if (substr($style['sName'],0,1) == '.' || substr($style['sName'],0,1) == '#') { $sPrefix = ''; }
} else {
$style['sName'] = '#'.$style['sName'].'_'.$sModule;
}
$CSS .= $sTabs . $sPrefix . $style['sName'] . " {" .
str_replace("\n", "\n\t".$sTabs, "\n".$style['sCSS']) . "\n" .
$sTabs . "}\n\n";
}
}
return $CSS;
}
function ProcessJS($sModule,$idModules,$sType='module') {
# Generates the Javascript code for the library or module.
# sModule The library/module name to process NOTE: this value needs to be in CamelCase!
# idModules The DB row id of the library/module to process
# sType Defines which type of Javascript to generate: library, module
global $__sInfo,$_LinkDB;
# 1. Obtain all the module javascript
# https://stackoverflow.com/questions/31743348/mysql-select-with-if-statement-and-like-operator
$__sInfo['error'] = "Failed to obtain the Javascript for the requested module/library in the database.";
$__sInfo['command'] = "
SELECT
tblActions.id,
tblActions.sName AS sAction,
COUNT(tblParameters.id) AS nParameters,
COUNT(IF(tblParameters.sDefault<>'',null,tblParameters.sDefault)) AS nMandatory,
IF(GROUP_CONCAT(tblParameters.sName SEPARATOR ',') LIKE '%callback%','1','0') AS bCallback,
tblModuleJS.sCode AS sModuleHeader,
tblActionJS.sCode AS sActionHeader
FROM
".DB_PRFX."ModuleMaker_Actions tblActions
LEFT JOIN
".DB_PRFX."ModuleMaker_Parameters tblParameters ON tblActions.id=tblParameters.fkActions AND tblParameters.bDisabled=0
LEFT JOIN
".DB_PRFX."ModuleMaker_Code tblActionJS ON tblActions.fkCode_HeaderJS=tblActionJS.id
LEFT JOIN
".DB_PRFX."ModuleMaker_Modules tblModules ON tblActions.fkModules=tblModules.id
LEFT JOIN
".DB_PRFX."ModuleMaker_Code tblModuleJS ON tblModules.fkCode_HeaderJS=tblModuleJS.id
WHERE
tblActions.fkModules=".$idModules." AND tblActions.bDisabled=0
GROUP BY
tblActions.sName";
$__sInfo['values'] = 'None';
$Actions = $_LinkDB->query($__sInfo['command']);
# 2. Check if there are any actions to process
if ($Actions->num_rows === false || $Actions->num_rows == 0) { return ''; } # if there are NO saved Actions, then return a blank value
$action = $Actions->fetch_assoc(); # otherwise grab the first record so we can print the file header
# 3. Create the Javascript header
$JS = $action['sModuleHeader'] .
"\n\n" .
"// -- Global Variables --\n\n";
$bAJAX = false; # to determine if we're dealing with a module/library utilizing ajax
$Actions->data_seek(0); # reset the record pointer back to the first record in the results list to re-process below
while ($action = $Actions->fetch_assoc()) { # cycle all the matching code
if (! is_null($action['sPHP'])) { # if we have accompanying PHP, then...
$bAJAX = true; # indicate we'll be using ajax in this module/library
break; # break from the loop
}
}
#file_put_contents('debug.txt', "10\n", FILE_APPEND);
if ($bAJAX) {
$JS .= "var _o".$sModule."; // used for this modules' AJAX communication\n" .
"var _n".$sModule."Timeout = 0; // used to acknowledge the module's html/css has loaded so any preliminary initialization javascript calls can start\n" .
"var _s".$sModule."Prior = ''; // the prior action that was attempted but failed (e.g. the server was busy, so re-attempt previous action)\n";
}
// LEFT OFF - fill in this section with custom variables from the UI
$JS .= "\n\n\n\n" .
"// -- ".$sModule." API --\n\n" .
"function ".$sModule."(sAction) {\n\n";
if ($sType == 'library') {
$JS .= " // local variable assignments\n" .
" var mRequirements = true; // used to indicate each function meets their requirements to run\n" .
" var mCallback = null; // the callback to perform\n" .
" var bCallback = false; // whether a callback needs to be performed; NOTE: this is set WITHIN each function\n" .
"\n" .
" switch(sAction) {\n";
# cycle through the actions here
$Actions->data_seek(0); # reset the record pointer back to the first record in the results list to re-process below
while ($action = $Actions->fetch_assoc()) {
$JS .= "\t\tcase \"".$action['sName']."\":\n";
if ($action['nMandatory'] > 0)
{ $JS .= "\t\t\tif (arguments.length < ".$action['nMandatory'].") { mRequirements = false; } else { mRequirements = ".$action['nMandatory']."; }\n"; }
if ($action['bCallback'])
{ $JS .= "\t\t\tif (arguments.length > ".$action['nParameters'].") { mCallback = arguments[".$action['nParameters']."]; }\n"; }
$JS .= "\t\t\tbreak;\n\n\n";
}
$JS .= "\t\tdefault:\n" .
"\t\t\tProject('Popup','fail',\"ERROR: ".$sModule."('\"+sAction+\"') is not a valid action for the function API.\");\n" .
"\t\t\treturn false;\n" .
" }\n" .
"\n" .
" // sanity checks\n" .
" if (typeof mRequirements == 'boolean' && ! mRequirements) { // first check if the mandatory parameter count is met\n" .
" alert(\"ERROR: ".$sModule."('\"+sAction+\"') was called without the sufficent number of parameters.\");\n" .
" return false;\n" .
" } else if (typeof mRequirements == 'number') { // second check if those mandatory parameters have an actual value\n" .
" for (let i=0; i<mRequirements; i++) {\n" .
" if (arguments[i] == '') {\n" .
" alert(\"ERROR: ".$sModule."('\"+sAction+\"') was called with mandatory parameter #\"+i+\" being blank.\");\n" .
" return false;\n" .
" }\n" .
" }\n" .
" }\n" .
"\n" .
" // lets perform some work!\n" .
" switch(sAction) {\n" .
"\n\n" .
" // --- LIBRARY ACTIONS ---\n" .
"\n\n";
} else if ($sType == 'module') {
$JS .= "\n\n" .
" // --- STARTUP ACTIONS ---\n" .
"\n\n" .
" // Initializes the UI\n" .
" case 'Initialize': // WARNING: the 'A' and 'T' values are capitalized!\n" .
" if (! document.getElementById('oTitle_".$sModule."')){ // if the HTML/css side hasn't loaded yet, then...\n" .
" if (_n".$sModule."Timeout == 30) { // check if we've met the 30 second timeout threshold\n" .
" _n".$sModule."Timeout == 0; // reset the value\n" .
" Project('Popup','fail',\"The HTML file for the module could not be loaded. Please contact support for assistance.\"); // relay the error to the end user\n" .
" return false; // exit since we can't use this module under these conditions\n" .
" }\n" .
"\n" .
" // if we've made it here, the modules' html hasn't yet loaded, so let wait a second and try again\n" .
" _n".$sModule."Timeout++; // increase the timeout counter by one for each failed attempt\n" .
" setTimeout(".$sModule."(sAction,Callback),1000);\n" .
" return false; // in any case, exit this function to prevent JS problems!\n" .
" }\n" .
"\n";
# REMOVED 2025/09/15 - none of these are used any longer
# " MESSAGE = 1; // so we can render any returned messages to the UI instead of an alert() call (see _ajax.js)\n";
# " _sModuleMakerPrior = sAction; // set the action that we are attempting\n";
# "\n";
$JS .= " Ajax('Call',_o".$sModule.",_sUriProject+'code/".$sModule.".php','!'+sAction+'!,>Module<,(sUsername),(sSessionID)','',function(){".$sModule."('s_'+sAction,Callback);});\n" .
" break;\n" .
" case 's_Initialize': // success!\n";
// LEFT OFF - fill in this section with custom variables from the UI
// - is it possible to cycle all the <select>'s and check if their "element.options[element.selectedIndex].selected == true"? if this is false for 'default selections', then we can set selectedIndex=-1 so that unspecified selected options of <select>'s defaults to no selection at all. alternatively we can establish another data-deselect attribute...
// - cycle all the tabs to check if a 'History' is present and call it to be loaded here
# REMOVED 2025/09/15 - none of these are used any longer
# " // reset the (form) objects\n";
# " _sModuleMakerPrior = ''; // remove the action we were attempting now that we've achieved success\n";
# " MESSAGE = 0; // turn this back off now that we're done\n";
$JS .= " break;\n" .
"\n\n\n\n\n\n\n\n\n\n";
}
# 4. Create each Javascript action using order of operations code
# REMOVED 2025/09/20 - we put a check at the top for this
# if ($Actions->num_rows > 0) { # if there are saved Actions, then...
#file_put_contents('debug.txt', "inside code!\n", FILE_APPEND);
# cycle through the actions here
$Actions->data_seek(0); # reset the record pointer back to the first record in the results list to re-process below
while ($action = $Actions->fetch_assoc()) {
# 'GROUP_CONCAT() AS sValues' Explained:
# if there is a hard coded "sValue", then we need to surround it with (double) quotes (epochs [e], object names [O], strings [s]) -OR- pass directly (booleans [b], events [E], functions [f], numbers [n], objects [o])
# if there is an object reference, then directly pass the object [o] or its id [O] -OR- the objects value (booleans [b], epochs [e], numbers [n], strings [s]) NOTE: can NOT pass: events [E], functions [f]
# otherwise use the sDefault value if there is NOT a fkObjects value -OR- null if none of that matches (since the parameter should be optional)
# NOTE: the nInline column determines if this is Custom Code or an Action (with or without parameters)
# https://stackoverflow.com/questions/5064977/detect-if-value-is-number-in-mysql
$__sInfo['error'] = "Failed to obtain the Javascript for the requested module in the database.";
$__sInfo['command'] = "
SELECT
tblOperations.id,
IF(tblCode.sCode<>'',tblCode.sCode,tblActions.sName) AS sCode,
GROUP_CONCAT(
IF(tblValues.sValue<>'',
CASE
WHEN tblParameters.sDataType='e' THEN CONCAT(\"'\",tblValues.sValue,\"'\")
WHEN tblParameters.sDataType='O' THEN CONCAT(\"'\",tblValues.sValue,\"'\")
WHEN tblParameters.sDataType='s' THEN CONCAT('\"',tblValues.sValue,'\"')
ELSE tblValues.sValue
END,
IFNULL(
IF(tblValues.fkObjects<>0,
CASE
WHEN tblParameters.sDataType='o' THEN CONCAT(\"document.getElementById('\",tblObjects.sName,\"')\")
WHEN tblParameters.sDataType='O' THEN CONCAT(\"'\",tblObjects.sName,\"'\")
ELSE CONCAT(\"document.getElementById('\",tblObjects.sName,\"').value\")
END,
tblParameters.sDefault
),
null
)
)
ORDER BY tblParameters.nIndex SEPARATOR ','
) AS sValues,
IF(tblCode.sCode<>'',null,tblOperations.bInline) AS bInline
FROM
".DB_PRFX."ModuleMaker_Operations tblOperations
LEFT JOIN
".DB_PRFX."ModuleMaker_Code tblCode ON tblOperations.fkCode=tblCode.id
LEFT JOIN
".DB_PRFX."ModuleMaker_Actions tblActions ON tblOperations.fkActions=tblActions.id AND tblActions.bDisabled=0
LEFT JOIN
".DB_PRFX."ModuleMaker_Parameters tblParameters ON tblActions.id=tblParameters.fkActions AND tblParameters.bDisabled=0