Plugin Directory

Changeset 3262394


Ignore:
Timestamp:
03/26/2025 07:07:00 PM (11 months ago)
Author:
ivankomlev
Message:

Version 1.5.8 update.

Location:
customtables/trunk
Files:
21 edited

Legend:

Unmodified
Added
Removed
  • customtables/trunk/CustomTables.php

    r3259510 r3262394  
    44Plugin URI: https://ct4.us
    55Description: Custom Tables solution for WordPress
    6 Version: 1.5.7
     6Version: 1.5.8
    77Requires at least: 6.0
    88Requires PHP: 7.4.0
     
    3232
    3333define(CTWP . 'PLUGIN_NAME', 'customtables');
    34 define(CTWP . 'PLUGIN_VERSION', '1.5.7');
     34define(CTWP . 'PLUGIN_VERSION', '1.5.8');
    3535define(CTWP . 'PLUGIN_NAME_DIR', plugin_dir_path(__FILE__));
    3636define(CTWP . 'PLUGIN_NAME_URL', plugin_dir_url(__FILE__));
     
    116116function enqueue_codemirror()
    117117{
    118     $version = '1.5.7';
     118    $version = '1.5.8';
    119119    wp_enqueue_style('customtables-js-modal', plugin_dir_url(__FILE__) . 'libraries/customtables/media/css/modal.css', false, $version);
    120120    wp_enqueue_style('customtables-js-layouteditor', plugin_dir_url(__FILE__) . 'libraries/customtables/media/css/layouteditor.css', false, $version);
  • customtables/trunk/libraries/customtables/ct/Environment.php

    r3244178 r3262394  
    3232    var bool $clean;
    3333    var string $frmt;
    34     var string $WebsiteRoot;
     34    var string $WebsiteRoot;//With trailing front slash /
    3535    var bool $advancedTagProcessor;
    3636    var bool $isMobile;
  • customtables/trunk/libraries/customtables/filter/filtering.php

    r3259497 r3262394  
    919919
    920920        if ($valueStart and $valueEnd) {
    921             //Breadcrumbs
    922             $this->PathValue[] = $title1 . ' '
    923                 . esc_html__("from", "customtables") . ' ' . $titleStart . ' '
    924                 . esc_html__("to", "customtables") . ' ' . $titleEnd;
    925 
    926             $whereClause->addCondition($fieldRow1['realfieldname'], $valueStart, '>=');
    927             $whereClause->addCondition($fieldRow1['realfieldname'], $valueEnd, '<=');
     921
     922            if ($valueStart == $valueEnd) {
     923                //Breadcrumbs
     924                $this->PathValue[] = $title1 . ' '
     925                    . esc_html__("Date", "customtables") . ' ' . $titleStart;
     926
     927                $whereClause->addCondition($fieldRow1['realfieldname'], $valueStart . ' 00:00:00', '>=');
     928                $whereClause->addCondition($fieldRow1['realfieldname'], $valueEnd . ' 23:59:59', '<=');
     929
     930            } else {
     931                //Breadcrumbs
     932                $this->PathValue[] = $title1 . ' '
     933                    . esc_html__("from", "customtables") . ' ' . $titleStart . ' '
     934                    . esc_html__("to", "customtables") . ' ' . $titleEnd;
     935
     936                $whereClause->addCondition($fieldRow1['realfieldname'], $valueStart . ' 00:00:00', '>=');
     937                $whereClause->addCondition($fieldRow1['realfieldname'], $valueEnd . ' 23:59:59', '<=');
     938            }
     939
    928940        } elseif ($valueStart and $valueEnd === null) {
    929941            $this->PathValue[] = $title1 . ' '
    930942                . esc_html__("From", "customtables") . ' ' . $titleStart;
    931943
    932             $whereClause->addCondition($fieldRow1['realfieldname'], $valueStart, '>=');
     944            $whereClause->addCondition($fieldRow1['realfieldname'] . ' 00:00:00', $valueStart, '>=');
    933945        } elseif ($valueStart === null and $valueEnd) {
    934946            $this->PathValue[] = $title1 . ' '
    935947                . esc_html__("To", "customtables") . ' ' . $valueEnd;
    936948
    937             $whereClause->addCondition($fieldRow1['realfieldname'], $valueEnd, '<=');
    938         }
     949            $whereClause->addCondition($fieldRow1['realfieldname'], $valueEnd . ' 23:59:59', '<=');
     950        }
     951
    939952        return $whereClause;
    940953    }
     
    974987            $value2 = $value;
    975988            $title2 = $fieldRow2['fieldtitle' . $this->ct->Languages->Postfix];
     989            $value2isFieldName = true;
    976990        } else {
    977991            $value2 = $value;
    978992            $title2 = $value;
     993            $value2isFieldName = false;
    979994        }
    980995
     
    9871002        elseif ($value2 == 'NULL' and $comparison_operator == '!=')
    9881003            $whereClause->addCondition($value1, null, 'NOT NULL');
    989         else
    990             $whereClause->addCondition($value1, $value2, $comparison_operator);
     1004        else {
     1005            if ($value2isFieldName or ($comparison_operator != '=' and $comparison_operator != '==')) {
     1006                $whereClause->addCondition($value1, $value2, $comparison_operator);
     1007            } else {
     1008                $whereClause->addCondition($value1, $value2 . ' 00:00:00', '>=');
     1009                $whereClause->addCondition($value1, $value2 . ' 23:59:59', '<=');
     1010            }
     1011        }
     1012
    9911013        return $whereClause;
    9921014    }
     
    11391161        $rows = database::loadAssocList($ct->Table->realtablename, $selects, $whereClause, $fieldRow['realfieldname']);
    11401162
    1141         $result .= '<select id="' . $control_name . 'SQLJoinLink" class="' . common::convertClassString('form-select') . '" onchange="ctInputbox_UpdateSQLJoinLink(\'' . $control_name . '\',\'' . $control_name_postfix . '\')">';
     1163        $result .= '<select id="' . $control_name . 'SQLJoinLink" class="' . common::convertClassString('form-select') . '" onchange="CTEditHelper.ctInputbox_UpdateSQLJoinLink(\'' . $control_name . '\',\'' . $control_name_postfix . '\')">';
    11421164        $result .= '<option value="">- ' . esc_html__("Select", "customtables") . '</option>';
    11431165
  • customtables/trunk/libraries/customtables/html/inputbox.php

    r3256163 r3262394  
    561561            $onchange .= ';';
    562562        }
    563         if (substr($attributes['onchange'], -1) !== ';') {
     563
     564        if (!empty($attributes['onchange']) and substr($attributes['onchange'], -1) !== ';') {
    564565            $attributes['onchange'] .= ';';
    565566        }
  • customtables/trunk/libraries/customtables/html/inputbox/multilingualstring.php

    r3228624 r3262394  
    8989        if (str_contains(($this->attributes['onchange'] ?? ''), 'ct_UpdateSingleValue(')) {
    9090
    91             $attributes['onchange'] = "ct_UpdateSingleValue('" . $this->ct->Env->WebsiteRoot . "',"
     91            $attributes['onchange'] = 'ct_UpdateSingleValue('
    9292                . $this->ct->Params->ItemId . ",'" . $this->field->fieldname . $postfix . "',"
    9393                . "'" . $this->row[$this->ct->Table->realidfieldname] . "',"
  • customtables/trunk/libraries/customtables/html/inputbox/signature.php

    r3228624 r3262394  
    5858        $result .= '
    5959<script>
    60     ctInputbox_signature("' . $ctInputbox_signature . '")
     60    CTEditHelper.ctInputbox_signature("' . $ctInputbox_signature . '")
    6161</script>';
    6262        return $result;
  • customtables/trunk/libraries/customtables/html/searchbox/date.php

    r3228624 r3262394  
    2929        common::loadJQueryUI();
    3030
    31         $js = '
     31        if (empty($value)) {
     32            $result = $this->ct->Filter->whereClause->getWhereParamValue($this->ct->Table->fieldPrefix . 'CreatedAt');
     33            if ($result['value'] !== null)
     34                $value = explode(' ', $result['value'])[0]; // Extracts only the date part
     35
     36        }
     37
     38        $matchType = $this->attributes['data-match'];
     39
     40        $valueParts = explode('-to-', $value);
     41
     42        $valueStart = isset($valueParts[0]) ? trim($valueParts[0]) : '';
     43
     44        // Sanitize and validate date format
     45        $dateFormat = 'Y-m-d'; // Adjust the format according to your needs
     46
     47        if ($valueStart) {
     48            $startDateTime = DateTime::createFromFormat($dateFormat, $valueStart);
     49
     50            if ($startDateTime !== false) {
     51                $valueStart = $startDateTime->format($dateFormat);
     52            } else {
     53                // Invalid date format, handle the error or set a default value
     54                $valueStart = ''; // Set to default or perform error handling
     55            }
     56        }
     57
     58        if ($matchType == 'exact') {
     59
     60            $js = '
     61
     62jQuery(document).ready(function($) {
     63    $("#' . $this->objectName . '_exact").datepicker({
     64        dateFormat: "yy-mm-dd",
     65        onSelect: function(selectedDate) {
     66            //$("#' . $this->objectName . '").datepicker("option", "minDate", selectedDate);
     67        }
     68    });
     69});
     70
     71';
     72
     73            if (isset($this->ct->LayoutVariables['script']))
     74                $this->ct->LayoutVariables['script'] .= $js;
     75            else
     76                $this->ct->LayoutVariables['script'] = $js;
     77
     78            $hidden = '<input type="hidden" name="' . $this->objectName . '" id="' . $this->objectName . '" value="' . $valueStart . '">';
     79            $jsOnChange = 'ctSearchBarDateUpdate(\'' . $this->field->fieldname . '\',function () {' . $this->attributes['onchange'] . '});';
     80
     81            $start = '<input onblur="' . $jsOnChange . '" onchange="' . $jsOnChange . '" value="' . $valueStart . '" type="text"'
     82                . ' class="' . ($this->attributes['class'] ?? '') . '" id="' . $this->objectName . '_exact"'
     83                . ' placeholder="' . $this->field->title . ' - ' . esc_html__("Date", "customtables") . '"'
     84                . ' style="display:inline-block;width:49%;margin-left:0;margin-right:0;float:left;">';
     85
     86            return $hidden . '<div style="position: relative;">' . $start . '</div>';
     87        } else {
     88
     89            $js = '
    3290
    3391jQuery(document).ready(function($) {
     
    49107';
    50108
    51         $this->ct->LayoutVariables['script'] .= $js;
     109            $this->ct->LayoutVariables['script'] .= $js;
    52110
    53         $valueParts = explode('-to-', $value);
     111            $valueEnd = isset($valueParts[1]) ? trim($valueParts[1]) : '';
     112            if ($valueEnd) {
     113                $endDateTime = DateTime::createFromFormat($dateFormat, $valueEnd);
    54114
    55         $valueStart = isset($valueParts[0]) ? trim($valueParts[0]) : '';
    56         $valueEnd = isset($valueParts[1]) ? trim($valueParts[1]) : '';
     115                if ($endDateTime !== false) {
     116                    $valueEnd = $endDateTime->format($dateFormat);
     117                } else {
     118                    // Invalid date format, handle the error or set a default value
     119                    $valueEnd = ''; // Set to default or perform error handling
     120                }
     121            }
    57122
    58         // Sanitize and validate date format
    59         $dateFormat = 'Y-m-d'; // Adjust the format according to your needs
     123            $hidden = '<input type="hidden" name="' . $this->objectName . '" id="' . $this->objectName . '" value="' . $valueStart . '-to-' . $valueEnd . '">';
     124            $jsOnChange = 'ctSearchBarDateRangeUpdate(\'' . $this->field->fieldname . '\')';
    60125
    61         if ($valueStart) {
    62             $startDateTime = DateTime::createFromFormat($dateFormat, $valueStart);
     126            $start = '<input onblur="' . $jsOnChange . '" onchange="' . $jsOnChange . '" value="' . $valueStart . '" type="text"'
     127                . ' class="' . ($this->attributes['class'] ?? '') . '" id="' . $this->objectName . '_start"'
     128                . ' placeholder="' . $this->field->title . ' - ' . esc_html__("Start", "customtables") . '"'
     129                . ' style="display:inline-block;width:49%;margin-left:0;margin-right:0;float:left;">';
    63130
    64             if ($startDateTime !== false) {
    65                 $valueStart = $startDateTime->format($dateFormat);
    66             } else {
    67                 // Invalid date format, handle the error or set a default value
    68                 $valueStart = ''; // Set to default or perform error handling
    69             }
     131            $end = '<input onblur="' . $jsOnChange . '" onchange="' . $jsOnChange . '" value="' . $valueEnd . '" type="text"'
     132                . ' class="' . ($this->attributes['class'] ?? '') . '" id="' . $this->objectName . '_end"'
     133                . ' placeholder="' . $this->field->title . ' - ' . esc_html__("End", "customtables") . '"'
     134                . ' style="display:inline-block;width:49%;margin-left:0;margin-right:0;float:right;">';
     135
     136            return $hidden . '<div style="position: relative;">' . $start . $end . '</div>';
    70137        }
    71 
    72         if ($valueEnd) {
    73             $endDateTime = DateTime::createFromFormat($dateFormat, $valueEnd);
    74 
    75             if ($endDateTime !== false) {
    76                 $valueEnd = $endDateTime->format($dateFormat);
    77             } else {
    78                 // Invalid date format, handle the error or set a default value
    79                 $valueEnd = ''; // Set to default or perform error handling
    80             }
    81         }
    82 
    83         $jsOnChange = 'ctSearchBarDateRangeUpdate(\'' . $this->field->fieldname . '\')';
    84 
    85         $hidden = '<input type="hidden" name="' . $this->objectName . '" id="' . $this->objectName . '" value="' . $valueStart . '-to-' . $valueEnd . '">';
    86 
    87         $start = '<input onblur="' . $jsOnChange . '" onchange="' . $jsOnChange . '" value="' . $valueStart . '" type="text"'
    88             . ' class="' . ($this->attributes['class'] ?? '') . '" id="' . $this->objectName . '_start"'
    89             . ' placeholder="' . $this->field->title . ' - ' . esc_html__("Start", "customtables") . '"'
    90             . ' style="display:inline-block;width:49%;margin-left:0;margin-right:0;float:left;">';
    91 
    92         $end = '<input onblur="' . $jsOnChange . '" onchange="' . $jsOnChange . '" value="' . $valueEnd . '" type="text"'
    93             . ' class="' . ($this->attributes['class'] ?? '') . '" id="' . $this->objectName . '_end"'
    94             . ' placeholder="' . $this->field->title . ' - ' . esc_html__("End", "customtables") . '"'
    95             . ' style="display:inline-block;width:49%;margin-left:0;margin-right:0;float:right;">';
    96 
    97         return $hidden . '<div style="position: relative;">' . $start . $end . '</div>';
    98138    }
    99139}
  • customtables/trunk/libraries/customtables/html/searchbox/tablejoin.php

    r3247356 r3262394  
    349349                        if (typeof ctInputbox_removeEmptyParents === "function") {
    350350                            ctInputbox_removeEmptyParents("' . $control_name . '","");
    351                             ctInputbox_UpdateSQLJoinLink("' . $control_name . '","");           
     351                            CTEditHelper.ctInputbox_UpdateSQLJoinLink("' . $control_name . '","");           
    352352';
    353353            if (str_contains(($this->attributes['class'] ?? ''), ' ct_virtualselect_selectbox'))
  • customtables/trunk/libraries/customtables/html/searchinputbox.php

    r3248691 r3262394  
    105105            'url' => 'string',
    106106            'virtual' => 'string',
    107             'email' => 'string'
     107            'email' => 'string',
     108            'creationtime' => 'date',
     109            'changetime' => 'date',
     110            'lastviewtime' => 'date'
    108111        ];
    109112
  • customtables/trunk/libraries/customtables/html/toolbar.php

    r3255615 r3262394  
    5656
    5757                case 'refresh':
    58                     $rid = 'esRefreshIcon' . $this->rid;
     58                    $rid = 'ctRefreshIcon' . $this->rid;
    5959                    $icon = Icons::iconRefresh($this->ct->Env->toolbarIcons);
    6060                    $moduleIDString = $this->ct->Params->ModuleId === null ? 'null' : $this->ct->Params->ModuleId;
     
    134134        $a = '<a href="' . $link . '">' . $icon . '</a>';
    135135
    136         return '<div id="esEditIcon' . $this->rid . '" class="toolbarIcons">' . $a . '</div>';
     136        return '<div id="ctEditIcon' . $this->rid . '" class="toolbarIcons">' . $a . '</div>';
    137137    }
    138138
     
    250250        }
    251251        if ($min_ordering_field !== null) {
    252             $fieldTitleValue = $this->getFieldCleanValue4RDI($min_ordering_field);
    253             return substr($fieldTitleValue, -100);
     252
     253            //$fieldRow = $this->ct->Table->getFieldByName()
     254
     255            $valueProcessor = new Value($this->ct);
     256            $fieldTitleValue = $valueProcessor->renderValue($min_ordering_field, $this->ct->Table->record, [], true);
     257
     258            //$fieldTitleValue = $this->getFieldCleanValue4RDI($min_ordering_field);
     259            return $fieldTitleValue;//substr($fieldTitleValue, -100);
    254260        }
    255261        return null;
    256     }
    257 
    258     protected function getFieldCleanValue4RDI($mFld): string
    259     {
    260         $titleField = $mFld['realfieldname'];
    261         if (str_contains($mFld['type'], 'multi'))
    262             $titleField .= $this->ct->Languages->Postfix;
    263 
    264         $fieldTitleValue = $this->row[$titleField];
    265         $deleteLabel = common::ctStripTags($fieldTitleValue ?? '');
    266 
    267         $deleteLabel = trim(preg_replace("/[^a-zA-Z\d ,.]/", "", $deleteLabel));
    268         return preg_replace('/\s{3,}/', ' ', $deleteLabel);
    269262    }
    270263
     
    286279    {
    287280        $deleteLabel = $this->firstFieldValueLabel();
    288         $icon = Icons::iconDelete($this->ct->Env->toolbarIcons);
    289         $message = 'Do you want to delete (' . $deleteLabel . ')?';
    290281        $moduleIDString = $this->ct->Params->ModuleId === null ? 'null' : $this->ct->Params->ModuleId;
    291         $href = 'javascript:ctDeleteRecord(\'' . $message . '\', ' . $this->Table->tableid . ', \'' . $this->listing_id . '\', \'esDeleteIcon' . $this->rid . '\', ' . $moduleIDString . ');';
    292         return '<div id="esDeleteIcon' . $this->rid . '" class="toolbarIcons"><a href="' . $href . '">' . $icon . '</a></div>';
     282
     283        $href = 'javascript:ctDeleteRecord(' . $this->Table->tableid . ', \'' . $this->listing_id . '\', ' . $moduleIDString . ');';
     284
     285        $messageDiv = '<div id="ctDeleteMessage' . $this->rid . '" style="display:none;">Do you want to delete ' . $deleteLabel . '?</div>';
     286        $a = '<a href="' . $href . '">' . Icons::iconDelete($this->ct->Env->toolbarIcons) . '</a>';
     287        $result = '<div id="ctDeleteIcon' . $this->rid . '" class="toolbarIcons">' . $messageDiv . $a . '</div>';;
     288
     289        return $result;
    293290    }
    294291
     
    296293    {
    297294        if ($this->isPublishable) {
    298             $rid = 'esPublishIcon' . $this->rid;
     295            $rid = 'ctPublishIcon' . $this->rid;
    299296
    300297            $moduleIDString = $this->ct->Params->ModuleId === null ? 'null' : $this->ct->Params->ModuleId;
     
    314311        return '';
    315312    }
     313
     314    protected function getFieldCleanValue4RDI($mFld): string
     315    {
     316        $titleField = $mFld['realfieldname'];
     317        if (str_contains($mFld['type'], 'multi'))
     318            $titleField .= $this->ct->Languages->Postfix;
     319
     320        $fieldTitleValue = $this->row[$titleField];
     321        $deleteLabel = common::ctStripTags($fieldTitleValue ?? '');
     322
     323        $deleteLabel = trim(preg_replace("/[^a-zA-Z\d ,.]/", "", $deleteLabel));
     324        return preg_replace('/\s{3,}/', ' ', $deleteLabel);
     325    }
    316326}
  • customtables/trunk/libraries/customtables/layouts/Twig_HTML_Tags.php

    r3255615 r3262394  
    10001000            $onclick = 'setTask(event, "' . $task . '","' . $redirect . '",true,"' . $formName . '",' . $isModal . ',null,' . ($this->ct->Params->ModuleId === null ? 'null' : $this->ct->Params->ModuleId) . ');';
    10011001        else
    1002             $onclick = 'setTask(event, "' . $task . '","' . $redirect . '",true,"' . $formName . '",' . $isModal . ',"' . $parentField . '"' . ($this->ct->Params->ModuleId === null ? 'null' : $this->ct->Params->ModuleId) . ');';
     1002            $onclick = 'setTask(event, "' . $task . '","' . $redirect . '",true,"' . $formName . '",' . $isModal . ',"' . $parentField . '",' . ($this->ct->Params->ModuleId === null ? 'null' : $this->ct->Params->ModuleId) . ');';
    10031003
    10041004        return '<input id="' . $buttonId . '" type="submit" class="' . common::convertClassString($the_class) . '"' . $attribute . ' onClick=\'' . $onclick . '\' value="' . $title . '">';
  • customtables/trunk/libraries/customtables/layouts/twig.php

    r3248691 r3262394  
    631631
    632632                $listOfRecords = implode(',', $this->ct->Table->recordlist);
    633                 $onchange = 'ct_UpdateAllRecordsValues(\'' . $this->ct->Env->WebsiteRoot . '\',' . $this->ct->Params->ItemId . ',\''
     633                $onchange = 'ct_UpdateAllRecordsValues(' . $this->ct->Params->ItemId . ',\''
    634634                    . $this->field->fieldname . '\',\'' . $listOfRecords . '\',\''
    635635                    . $postfix . '\',' . ($this->ct->Params->ModuleId ?? 0) . ');';
    636636            } else {
    637                 $onchange = 'ct_UpdateSingleValue(\'' . $this->ct->Env->WebsiteRoot . '\',' . $this->ct->Params->ItemId . ',\''
     637                $onchange = 'ct_UpdateSingleValue(' . $this->ct->Params->ItemId . ',\''
    638638                    . $this->field->fieldname . '\',\'' . $this->ct->Table->record[$this->ct->Table->realidfieldname] . '\',\''
    639639                    . $postfix . '\',' . ($this->ct->Params->ModuleId ?? 0) . ');';
  • customtables/trunk/libraries/customtables/media/js/base64.js

    r3202701 r3262394  
    55 *
    66 **/
    7 const Base64 = {
    87
    9     // private property
    10     //_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
    11     _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
     8if (typeof globalThis.CustomTablesEdit === 'undefined') {
    129
    13     // public method for encoding
    14     encode: function (input) {
    15         let output = "";
    16         let chr1, chr2, chr3, enc1, enc2, enc3, enc4;
    17         let i = 0;
     10    const Base64 = {
    1811
    19         input = Base64._utf8_encode(input);
     12        // private property
     13        //_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
     14        _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
    2015
    21         while (i < input.length) {
     16        // public method for encoding
     17        encode: function (input) {
     18            let output = "";
     19            let chr1, chr2, chr3, enc1, enc2, enc3, enc4;
     20            let i = 0;
    2221
    23             chr1 = input.charCodeAt(i++);
    24             chr2 = input.charCodeAt(i++);
    25             chr3 = input.charCodeAt(i++);
     22            input = Base64._utf8_encode(input);
    2623
    27             enc1 = chr1 >> 2;
    28             enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
    29             enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
    30             enc4 = chr3 & 63;
     24            while (i < input.length) {
    3125
    32             if (isNaN(chr2)) {
    33                 enc3 = enc4 = 64;
    34             } else if (isNaN(chr3)) {
    35                 enc4 = 64;
     26                chr1 = input.charCodeAt(i++);
     27                chr2 = input.charCodeAt(i++);
     28                chr3 = input.charCodeAt(i++);
     29
     30                enc1 = chr1 >> 2;
     31                enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
     32                enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
     33                enc4 = chr3 & 63;
     34
     35                if (isNaN(chr2)) {
     36                    enc3 = enc4 = 64;
     37                } else if (isNaN(chr3)) {
     38                    enc4 = 64;
     39                }
     40
     41                output = output + this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
     42
    3643            }
    3744
    38             output = output + this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
     45            let r = 4 - (output.length % 4);
    3946
     47            const a = [];
     48            while (a.length < r) {
     49                a.push('=');
     50            }
     51            let s = a.join('');
     52
     53            return output + s;
     54        },
     55
     56
     57        // public method for decoding
     58        decode: function (input) {
     59            let output = "";
     60            let chr1, chr2, chr3;
     61            let enc1, enc2, enc3, enc4;
     62            let i = 0;
     63
     64            input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
     65
     66            while (i < input.length) {
     67
     68                enc1 = this._keyStr.indexOf(input.charAt(i++));
     69                enc2 = this._keyStr.indexOf(input.charAt(i++));
     70                enc3 = this._keyStr.indexOf(input.charAt(i++));
     71                enc4 = this._keyStr.indexOf(input.charAt(i++));
     72
     73                chr1 = (enc1 << 2) | (enc2 >> 4);
     74                chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
     75                chr3 = ((enc3 & 3) << 6) | enc4;
     76
     77                output = output + String.fromCharCode(chr1);
     78
     79                if (enc3 !== 64) {
     80                    output = output + String.fromCharCode(chr2);
     81                }
     82                if (enc4 !== 64) {
     83                    output = output + String.fromCharCode(chr3);
     84                }
     85
     86            }
     87
     88            output = Base64._utf8_decode(output);
     89
     90            return output;
     91
     92        },
     93
     94        // private method for UTF-8 encoding
     95        _utf8_encode: function (string) {
     96            string = string.replace(/\r\n/g, "\n");
     97            let utftext = "";
     98
     99            for (let n = 0; n < string.length; n++) {
     100
     101                const c = string.charCodeAt(n);
     102
     103                if (c < 128) {
     104                    utftext += String.fromCharCode(c);
     105                } else if ((c > 127) && (c < 2048)) {
     106                    utftext += String.fromCharCode((c >> 6) | 192);
     107                    utftext += String.fromCharCode((c & 63) | 128);
     108                } else {
     109                    utftext += String.fromCharCode((c >> 12) | 224);
     110                    utftext += String.fromCharCode(((c >> 6) & 63) | 128);
     111                    utftext += String.fromCharCode((c & 63) | 128);
     112                }
     113
     114            }
     115
     116            return utftext;
     117        },
     118
     119        // private method for UTF-8 decoding
     120        _utf8_decode: function (utftext) {
     121            let string = "";
     122            let i = 0;
     123            let c1;
     124            let c2;
     125            let c3;
     126            let c = c1 = c2 = 0;
     127
     128            while (i < utftext.length) {
     129
     130                c = utftext.charCodeAt(i);
     131
     132                if (c < 128) {
     133                    string += String.fromCharCode(c);
     134                    i++;
     135                } else if ((c > 191) && (c < 224)) {
     136                    c2 = utftext.charCodeAt(i + 1);
     137                    string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
     138                    i += 2;
     139                } else {
     140                    c2 = utftext.charCodeAt(i + 1);
     141                    c3 = utftext.charCodeAt(i + 2);
     142                    string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
     143                    i += 3;
     144                }
     145
     146            }
     147
     148            return string;
    40149        }
    41150
    42         let r = 4 - (output.length % 4);
     151    };
    43152
    44         const a = [];
    45         while (a.length < r) {
    46             a.push('=');
    47         }
    48         let s = a.join('');
    49 
    50         return output + s;
    51     },
    52 
    53 
    54     // public method for decoding
    55     decode: function (input) {
    56         let output = "";
    57         let chr1, chr2, chr3;
    58         let enc1, enc2, enc3, enc4;
    59         let i = 0;
    60 
    61         input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
    62 
    63         while (i < input.length) {
    64 
    65             enc1 = this._keyStr.indexOf(input.charAt(i++));
    66             enc2 = this._keyStr.indexOf(input.charAt(i++));
    67             enc3 = this._keyStr.indexOf(input.charAt(i++));
    68             enc4 = this._keyStr.indexOf(input.charAt(i++));
    69 
    70             chr1 = (enc1 << 2) | (enc2 >> 4);
    71             chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
    72             chr3 = ((enc3 & 3) << 6) | enc4;
    73 
    74             output = output + String.fromCharCode(chr1);
    75 
    76             if (enc3 !== 64) {
    77                 output = output + String.fromCharCode(chr2);
    78             }
    79             if (enc4 !== 64) {
    80                 output = output + String.fromCharCode(chr3);
    81             }
    82 
    83         }
    84 
    85         output = Base64._utf8_decode(output);
    86 
    87         return output;
    88 
    89     },
    90 
    91     // private method for UTF-8 encoding
    92     _utf8_encode: function (string) {
    93         string = string.replace(/\r\n/g, "\n");
    94         let utftext = "";
    95 
    96         for (let n = 0; n < string.length; n++) {
    97 
    98             const c = string.charCodeAt(n);
    99 
    100             if (c < 128) {
    101                 utftext += String.fromCharCode(c);
    102             } else if ((c > 127) && (c < 2048)) {
    103                 utftext += String.fromCharCode((c >> 6) | 192);
    104                 utftext += String.fromCharCode((c & 63) | 128);
    105             } else {
    106                 utftext += String.fromCharCode((c >> 12) | 224);
    107                 utftext += String.fromCharCode(((c >> 6) & 63) | 128);
    108                 utftext += String.fromCharCode((c & 63) | 128);
    109             }
    110 
    111         }
    112 
    113         return utftext;
    114     },
    115 
    116     // private method for UTF-8 decoding
    117     _utf8_decode: function (utftext) {
    118         let string = "";
    119         let i = 0;
    120         let c1;
    121         let c2;
    122         let c3;
    123         let c = c1 = c2 = 0;
    124 
    125         while (i < utftext.length) {
    126 
    127             c = utftext.charCodeAt(i);
    128 
    129             if (c < 128) {
    130                 string += String.fromCharCode(c);
    131                 i++;
    132             } else if ((c > 191) && (c < 224)) {
    133                 c2 = utftext.charCodeAt(i + 1);
    134                 string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
    135                 i += 2;
    136             } else {
    137                 c2 = utftext.charCodeAt(i + 1);
    138                 c3 = utftext.charCodeAt(i + 2);
    139                 string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
    140                 i += 3;
    141             }
    142 
    143         }
    144 
    145         return string;
    146     }
    147 
    148 };
     153    globalThis.Base64 = Base64; // Store globally
     154}
  • customtables/trunk/libraries/customtables/media/js/catalog.js

    r3256163 r3262394  
    88 **/
    99
    10 let ctLinkLoading = false;
    11 
    1210function ctCreateUser(msg, listing_id, toolbarBoxId, ModuleId) {
    1311    if (confirm(msg)) {
     
    7270
    7371function esEditObject(objId, toolbarBoxId, Itemid, tmpl, returnto) {
    74     if (ctLinkLoading) return;
    75 
    76     ctLinkLoading = true;
     72    if (CTEditHelper.ctLinkLoading) return;
     73
     74    CTEditHelper.ctLinkLoading = true;
    7775    document.getElementById(toolbarBoxId).innerHTML = '';
    7876
    7977    let return_to = btoa(window.location.href);
    80     let link = ctWebsiteRoot + 'index.php?option=com_customtables&view=edititem&listing_id=' + objId + '&Itemid=' + Itemid + '&returnto=' + return_to;
     78    let link = CTEditHelper.websiteRoot + 'index.php?option=com_customtables&view=edititem&listing_id=' + objId + '&Itemid=' + Itemid + '&returnto=' + return_to;
    8179
    8280    if (tmpl !== '') link += '&tmpl=' + tmpl;
     
    9997
    10098    let http = CreateHTTPRequestObject();   // defined in ajax.js
    101 
    10299    let addParams = ['clean=1'];
    103100    let url = esPrepareLink(['task', "listing_id", 'returnto', 'ids'], addParams);
    104 
    105     console.warn(url);
    106101
    107102    if (http) {
     
    111106
    112107            if (http.readyState === 4) {
     108
    113109                let res = http.response.replace(/(\r\n|\n|\r)/gm, "");
    114110
    115111                if (responses.indexOf(res) !== -1) {
    116112
    117                     let element_tableid_tr = "ctTable_" + tableid + '_' + recordId;
    118 
    119                     let table_object = document.getElementById("ctTable_" + tableid);
     113                    let table_object = findTableByRowId(tableid + '_' + recordId);
    120114                    if (!reload && table_object && CTEditHelper.cmsName === 'Joomla') {
    121                         let index = findRowIndexById("ctTable_" + tableid, element_tableid_tr);
    122                         if (task === 'delete')
     115
     116                        if (task === 'delete') {
     117                            let index = findRowIndexById(table_object, tableid, recordId, "ctDeleteIcon");
    123118                            table_object.deleteRow(index);
    124                         else
     119                        } else {
     120
     121                            let icon = 'ctEditIcon';
     122
     123                            if (task === 'copy') {
     124                                window.location.reload();
     125                                return;
     126                            } else if (task === 'refresh')
     127                                icon = 'ctRefreshIcon';
     128                            else if (task === 'publish' || task === 'unpublish')
     129                                icon = 'ctPublishIcon';
     130
     131                            let index = findRowIndexById(table_object, tableid, recordId, icon);
    125132                            ctCatalogUpdate(tableid, recordId, index, ModuleId);
     133                        }
    126134                    } else {
    127135                        window.location.reload();
    128136                    }
    129137
    130                     ctLinkLoading = false;
     138                    CTEditHelper.ctLinkLoading = false;
    131139
    132140                    if (last) {
     
    144152
    145153function ctRefreshRecord(tableid, recordId, toolbarBoxId, ModuleId) {
    146     if (ctLinkLoading) return;
    147     ctLinkLoading = true;
     154    if (CTEditHelper.ctLinkLoading) return;
     155    CTEditHelper.ctLinkLoading = true;
    148156    runTheTask('refresh', tableid, recordId, ['refreshed'], false, false, ModuleId);
    149157}
    150158
    151159function ctCopyRecord(tableid, listing_id, toolbarBoxId, ModuleId) {
    152     if (ctLinkLoading) return;
    153 
    154     ctLinkLoading = true;
     160    if (CTEditHelper.ctLinkLoading) return;
     161
     162    CTEditHelper.ctLinkLoading = true;
    155163
    156164    if (document.getElementById(toolbarBoxId))
     
    207215
    208216function ctPublishRecord(tableid, recordId, toolbarBoxId, publish, ModuleId) {
    209     if (ctLinkLoading) return;
    210 
    211     ctLinkLoading = true;
     217    if (CTEditHelper.ctLinkLoading) return;
     218
     219    CTEditHelper.ctLinkLoading = true;
    212220    document.getElementById(toolbarBoxId).innerHTML = '';
    213221    runTheTask((publish === 0 ? 'unpublish' : 'publish'), tableid, recordId, ['published', 'unpublished'], false, false, ModuleId);
    214222}
    215223
    216 function findRowIndexById(tableid, rowId) {
    217 
    218     let table_object = document.getElementById(tableid);
    219 
    220     if (table_object) {
    221         let rows = table_object.rows;
    222         for (let i = 0; i < rows.length; i++) {
    223             if (rows.item(i).id === rowId) return i;
    224         }
    225     }
     224function findTableByRowId(rowId) {
     225    let row = document.getElementById(`ctTable_${rowId}`);
     226    return row ? row.closest("table") : null;
     227}
     228
     229function findRowIndexById(table, tableid, id, icon) {
     230
     231    //icon = "ctDeleteIcon"
     232    if (!table) return -2;
     233    let lookingFor = '#' + icon + tableid + "x" + id;
     234    console.warn("lookingFor", lookingFor)
     235    let rows = table.rows;
     236    console.log("count:", rows.length)
     237    for (let i = 0; i < rows.length; i++) {
     238
     239        let deleteIcon = rows[i].querySelector(lookingFor);
     240        if (deleteIcon) {
     241            return i;
     242        }
     243    }
     244
    226245    return -1;
    227246}
    228247
    229 function ctDeleteRecord(msg, tableid, recordId, toolbarBoxId, ModuleId) {
    230     if (ctLinkLoading) return;
    231 
    232     ctLinkLoading = true;
    233 
    234     if (confirm(msg)) {
    235         runTheTask('delete', tableid, recordId, ['deleted'], false, false, ModuleId);
    236     } else {
    237         ctLinkLoading = false;
     248function ctDeleteRecord(tableid, recordId, ModuleId) {
     249    if (CTEditHelper.ctLinkLoading) return;
     250
     251    CTEditHelper.ctLinkLoading = true;
     252
     253    let msgObj = document.getElementById('ctDeleteMessage' + tableid + 'x' + recordId);
     254    if (msgObj) {
     255        // Strip HTML tags and sanitize the message
     256        let msg = msgObj.textContent || msgObj.innerText || "";
     257
     258        if (confirm(msg)) {
     259            runTheTask('delete', tableid, recordId, ['deleted'], false, false, ModuleId);
     260        } else {
     261            CTEditHelper.ctLinkLoading = false;
     262        }
    238263    }
    239264}
     
    245270
    246271function ctSearchBoxDo() {
    247     if (ctLinkLoading) return;
    248 
    249     ctLinkLoading = true;
     272    if (CTEditHelper.ctLinkLoading) return;
     273
     274    CTEditHelper.ctLinkLoading = true;
    250275    let w = [];
    251276    let allSearchElements = document.querySelectorAll('[ctSearchBoxField]');
     
    265290                    if (objValue.length < l) {
    266291                        alert(obj.dataset.label + ": " + TranslateText('COM_CUSTOMTABLES_SEARCH_ALERT_MINLENGTH', l));
    267                         ctLinkLoading = false;
     292                        CTEditHelper.ctLinkLoading = false;
    268293                        return;
    269294                    }
     
    304329
    305330function ctSearchReset() {
    306     if (ctLinkLoading) return;
    307 
    308     ctLinkLoading = true;
     331    if (CTEditHelper.ctLinkLoading) return;
     332
     333    CTEditHelper.ctLinkLoading = true;
    309334
    310335    window.location.href = esPrepareLink(['where', 'task', "listing_id", 'returnto'], []);
     
    347372function ctToolBarDO(task, tableid, ModuleId) {
    348373
    349     if (ctLinkLoading) return;
    350 
    351     ctLinkLoading = true;
     374    if (CTEditHelper.ctLinkLoading) return;
     375
     376    CTEditHelper.ctLinkLoading = true;
    352377    const elements = getListOfSelectedRecords(tableid);
    353378
    354379    if (elements.length === 0) {
    355380        alert(TranslateText('COM_CUSTOMTABLES_JS_SELECT_RECORDS'));
    356         ctLinkLoading = false;
     381        CTEditHelper.ctLinkLoading = false;
    357382        return;
    358383    }
     
    364389
    365390        if (!confirm(msg)) {
    366             ctLinkLoading = false;
     391            CTEditHelper.ctLinkLoading = false;
    367392            return;
    368393        }
     
    406431}
    407432
    408 function ct_UpdateAllRecordsValues(WebsiteRoot, Itemid, fieldname_, record_ids, postfix, ModuleId) {
     433function ct_UpdateAllRecordsValues(Itemid, fieldname_, record_ids, postfix, ModuleId) {
    409434    let ids = record_ids.split(',');
    410435    const obj_checkbox_off = document.getElementById(ctFieldInputPrefix + "_" + fieldname_ + "_off");
     
    418443            document.getElementById(objectName).checked = parseInt(obj_checkbox_off.value) === 1;
    419444
    420             ct_UpdateSingleValue(WebsiteRoot, Itemid, fieldname_, ids[i], postfix, ModuleId);
     445            ct_UpdateSingleValue(Itemid, fieldname_, ids[i], postfix, ModuleId);
    421446        }
    422447
     
    429454            let obj = document.getElementById(objectName);
    430455            obj.value = value;
    431             ct_UpdateSingleValue(WebsiteRoot, Itemid, fieldname_, ids[i], postfix, ModuleId);
     456            ct_UpdateSingleValue(Itemid, fieldname_, ids[i], postfix, ModuleId);
    432457            if (obj.dataset.type === "sqljoin") {
    433458
    434459                let tableid = obj.dataset.tableid;
    435                 let table_object = document.getElementById("ctTable_" + tableid);
     460                //let table_object = document.getElementById("ctTable_" + tableid);
     461                let table_object = findTableByRowId(tableid + '_' + ids[i]);
    436462
    437463                if (table_object) {
    438                     let element_tableid_tr = "ctTable_" + tableid + '_' + ids[i];
    439                     let index = findRowIndexById("ctTable_" + tableid, element_tableid_tr);
     464                    let index = findRowIndexById(table_object, tableid, ids[i], 'ctEditIcon');
    440465                    ctCatalogUpdate(tableid, ids[i], index, ModuleId);
    441466                }
     
    445470}
    446471
    447 function ct_UpdateSingleValue(WebsiteRoot, Itemid, fieldname_, record_id, postfix, ModuleId) {
     472function ct_UpdateSingleValue(Itemid, fieldname_, record_id, postfix, ModuleId) {
    448473
    449474    let params = "";
     
    464489        params += "&" + ctFieldInputPrefix + fieldname_ + "=" + document.getElementById(objectName).value;
    465490    }
    466     ct_UpdateSingleValueSet(WebsiteRoot, Itemid, fieldname_, record_id, postfix, ModuleId, params);
    467 }
    468 
    469 function ct_UpdateSingleValueSet(WebsiteRoot, Itemid, fieldname_, record_id, postfix, ModuleId, valueParam) {
     491    ct_UpdateSingleValueSet(Itemid, fieldname_, record_id, postfix, ModuleId, params);
     492}
     493
     494function ct_UpdateSingleValueSet(Itemid, fieldname_, record_id, postfix, ModuleId, valueParam) {
    470495
    471496    const fieldname = fieldname_.split('_')[0];
     
    537562}
    538563
    539 function ctCatalogUpdate(tableid, recordsId, row_index, ModuleId) {
    540 
    541     let element_tableid = "ctTable_" + tableid;
     564function ctCatalogUpdate(tableid, listing_id, row_index, ModuleId) {
     565
     566    //let element_tableid = "ctTable_" + tableid;
    542567
    543568    let deleteParams = ['task', "listing_id", 'returnto', 'ids', 'option', 'view', 'clean', 'component', 'frmt'];
    544     let addParams = ['listing_id=' + recordsId, 'number=' + row_index, 'clean=1'];
     569    let addParams = ['listing_id=' + listing_id, 'number=' + row_index, 'clean=1'];
    545570
    546571    if (CTEditHelper.cmsName === 'Joomla') {
     
    566591            if (http.readyState === 4) {
    567592                let res = http.response;
    568                 let tableObj = document.getElementById(element_tableid);
     593
     594                //let tableObj = document.getElementById(element_tableid);
     595                let tableObj = findTableByRowId(tableid + '_' + listing_id);
     596
    569597                if (tableObj) {
    570598                    let rows = tableObj.rows;
     
    614642        let element_tableid_tr = "ctTable_" + to_parts[1] + '_' + to_parts[2];
    615643
    616         let table_object = document.getElementById("ctTable_" + to_parts[1]);
     644        //let table_object = document.getElementById("ctTable_" + to_parts[1]);
     645        let table_object = findTableByRowId(to_parts[1] + '_' + to_parts[2]);
    617646
    618647        let index;
    619         if (table_object) index = findRowIndexById("ctTable_" + to_parts[1], element_tableid_tr);
     648        if (table_object) index = findRowIndexById(table_object, to_parts[1], to_parts[2], 'ctEditIcon');
    620649
    621650        let deleteParams = ['task', "listing_id", 'returnto', 'ids', 'option', 'view', 'clean', 'component', 'frmt'];
     
    678707                let res = http.response;
    679708
    680                 ctShowPopUp(res, true);
     709                if (res.indexOf('view-login') !== -1) {
     710                    alert('Session expired. Please login again.');
     711                    location.reload();
     712                    return;
     713                } else {
     714                    ctShowPopUp(res, true);
     715                }
    681716
    682717                //Activate Calendars if found
     
    735770    }, 300)
    736771}
     772
     773function ctSearchBarDateUpdate(fieldName, callback) {
     774    setTimeout(function () {
     775        let obj = document.getElementById(ctFieldInputPrefix + "search_box_" + fieldName);
     776
     777        // Store the previous value in dataset
     778        let v = document.getElementById(ctFieldInputPrefix + "search_box_" + fieldName + "_exact").value;
     779
     780        if (obj.value !== v) {
     781
     782            obj.value = v;
     783
     784            // Execute callback if provided
     785            if (typeof callback === "function") {
     786                callback();
     787            }
     788        }
     789    }, 300);
     790}
  • customtables/trunk/libraries/customtables/media/js/edit.js

    r3256443 r3262394  
    77 * @license GNU/GPL Version 2 or later - http://www.gnu.org/licenses/gpl-2.0.html
    88 **/
    9 class CustomTablesEdit {
    10 
    11     constructor(cmsName = 'Joomla', cmsVersion = 5, itemId = 0) {
    12         this.GoogleDriveTokenClient = [];
    13         this.GoogleDriveAccessToken = null;
    14         this.cmsName = cmsName;
    15         this.cmsVersion = cmsVersion;
    16         this.itemId = itemId;
    17     }
    18 
    19     GoogleDriveInitClient(fieldName, GoogleDriveAPIKey, GoogleDriveClientId) {
    20         this.GoogleDriveTokenClient[fieldName] = google.accounts.oauth2.initTokenClient({
    21             client_id: GoogleDriveClientId,
    22             scope: "https://www.googleapis.com/auth/drive.readonly",
    23             callback: (tokenResponse) => {
    24                 if (tokenResponse && tokenResponse.access_token) {
    25                     this.GoogleDriveAccessToken = tokenResponse.access_token;
    26                     CTEditHelper.GoogleDriveLoadPicker(fieldName, GoogleDriveAPIKey, tokenResponse.access_token);
    27                 }
    28             },
    29         });
    30     }
    31 
    32     GoogleDriveLoadPicker(fieldName, GoogleDriveAPIKey, access_token) {
    33         gapi.load("picker", {
    34             callback: function () {
    35                 CTEditHelper.GoogleDriveCreatePicker(fieldName, GoogleDriveAPIKey, access_token);
    36             }
    37         });
    38     }
    39 
    40     GoogleDriveCreatePicker(fieldName, GoogleDriveAPIKey, access_token) {
    41         if (access_token) {
    42             const pickerBuilder = new google.picker.PickerBuilder()
    43                 .addView(google.picker.ViewId.DOCS)
    44                 .addView(google.picker.ViewId.FOLDERS)
    45                 .addView(new google.picker.DocsView(google.picker.ViewId.DOCS)
    46                     .setIncludeFolders(true)
    47                     .setOwnedByMe(false)
    48                     .setLabel("Shared with me"))
    49                 .setOAuthToken(access_token)
    50                 .setDeveloperKey(GoogleDriveAPIKey)
    51                 .setCallback(function (data) {
    52                     CTEditHelper.GoogleDrivePickerCallback(fieldName, data, access_token)
     9
     10if (typeof globalThis.CustomTablesEdit === 'undefined') {
     11    class CustomTablesEdit {
     12
     13        //Always used as "CTEditHelper"
     14        constructor(cmsName = 'Joomla', cmsVersion = 5, itemId = null, websiteRoot = null) {
     15            this.GoogleDriveTokenClient = [];
     16            this.GoogleDriveAccessToken = null;
     17            this.cmsName = cmsName;
     18            this.cmsVersion = cmsVersion;
     19            this.itemId = itemId;
     20
     21            this.ct_signaturePad_fields = [];
     22            this.ct_signaturePad = [];
     23            this.ct_signaturePad_formats = [];
     24
     25            this.ctInputBoxRecords_dynamic_filter = [];
     26
     27            this.ctLinkLoading = false;
     28
     29            this.websiteRoot = websiteRoot;//With trailing front slash /
     30        }
     31
     32        GoogleDriveInitClient(fieldName, GoogleDriveAPIKey, GoogleDriveClientId) {
     33            this.GoogleDriveTokenClient[fieldName] = google.accounts.oauth2.initTokenClient({
     34                client_id: GoogleDriveClientId,
     35                scope: "https://www.googleapis.com/auth/drive.readonly",
     36                callback: (tokenResponse) => {
     37                    if (tokenResponse && tokenResponse.access_token) {
     38                        this.GoogleDriveAccessToken = tokenResponse.access_token;
     39                        CTEditHelper.GoogleDriveLoadPicker(fieldName, GoogleDriveAPIKey, tokenResponse.access_token);
     40                    }
     41                },
     42            });
     43        }
     44
     45        GoogleDriveLoadPicker(fieldName, GoogleDriveAPIKey, access_token) {
     46            gapi.load("picker", {
     47                callback: function () {
     48                    CTEditHelper.GoogleDriveCreatePicker(fieldName, GoogleDriveAPIKey, access_token);
     49                }
     50            });
     51        }
     52
     53        GoogleDriveCreatePicker(fieldName, GoogleDriveAPIKey, access_token) {
     54            if (access_token) {
     55                const pickerBuilder = new google.picker.PickerBuilder()
     56                    .addView(google.picker.ViewId.DOCS)
     57                    .addView(google.picker.ViewId.FOLDERS)
     58                    .addView(new google.picker.DocsView(google.picker.ViewId.DOCS)
     59                        .setIncludeFolders(true)
     60                        .setOwnedByMe(false)
     61                        .setLabel("Shared with me"))
     62                    .setOAuthToken(access_token)
     63                    .setDeveloperKey(GoogleDriveAPIKey)
     64                    .setCallback(function (data) {
     65                        CTEditHelper.GoogleDrivePickerCallback(fieldName, data, access_token)
     66                    });
     67
     68                if (google.picker.ViewId.SHARED_DRIVES) {
     69                    pickerBuilder.addView(google.picker.ViewId.SHARED_DRIVES);
     70                }
     71
     72                const picker = pickerBuilder.build();
     73                picker.setVisible(true);
     74            }
     75        }
     76
     77        GoogleDrivePickerCallback(fieldName, data, access_token) {
     78
     79            if (data[google.picker.Response.ACTION] === google.picker.Action.PICKED) {
     80                if (data[google.picker.Response.DOCUMENTS] && data[google.picker.Response.DOCUMENTS].length > 0) {
     81                    const file = data[google.picker.Response.DOCUMENTS][0];
     82                    CTEditHelper.GoogleDriveGetFileMetadata(fieldName, file.id, access_token);
     83                } else {
     84                    console.log("No file was selected or the response format has changed.");
     85                    document.getElementById("ct_eventsmessage_" + fieldName).innerHTML = "No file was selected.";
     86                }
     87            } else if (data[google.picker.Response.ACTION] === google.picker.Action.CANCEL) {
     88                console.log("User closed the Picker or canceled selection.");
     89                document.getElementById("ct_eventsmessage_" + fieldName).innerHTML = "File selection was canceled.";
     90            }
     91        }
     92
     93        formatFileSize(bytes) {
     94            if (bytes === 0) return '0 Bytes';
     95            const k = 1024;
     96            const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
     97            const i = Math.floor(Math.log(bytes) / Math.log(k));
     98            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
     99        }
     100
     101        emptyContainers(boxId, className) {
     102            const parentElement = document.getElementById(boxId);
     103
     104            if (parentElement) {
     105                const containers = parentElement.getElementsByClassName(className);
     106
     107                Array.from(containers).forEach(container => {
     108                    container.innerHTML = '';
    53109                });
    54 
    55             if (google.picker.ViewId.SHARED_DRIVES) {
    56                 pickerBuilder.addView(google.picker.ViewId.SHARED_DRIVES);
    57             }
    58 
    59             const picker = pickerBuilder.build();
    60             picker.setVisible(true);
    61         }
    62     }
    63 
    64     GoogleDrivePickerCallback(fieldName, data, access_token) {
    65 
    66         if (data[google.picker.Response.ACTION] === google.picker.Action.PICKED) {
    67             if (data[google.picker.Response.DOCUMENTS] && data[google.picker.Response.DOCUMENTS].length > 0) {
    68                 const file = data[google.picker.Response.DOCUMENTS][0];
    69                 CTEditHelper.GoogleDriveGetFileMetadata(fieldName, file.id, access_token);
    70             } else {
    71                 console.log("No file was selected or the response format has changed.");
    72                 document.getElementById("ct_eventsmessage_" + fieldName).innerHTML = "No file was selected.";
    73             }
    74         } else if (data[google.picker.Response.ACTION] === google.picker.Action.CANCEL) {
    75             console.log("User closed the Picker or canceled selection.");
    76             document.getElementById("ct_eventsmessage_" + fieldName).innerHTML = "File selection was canceled.";
    77         }
    78     }
    79 
    80 
    81     formatFileSize(bytes) {
    82         if (bytes === 0) return '0 Bytes';
    83         const k = 1024;
    84         const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
    85         const i = Math.floor(Math.log(bytes) / Math.log(k));
    86         return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    87     }
    88 
    89     emptyContainers(boxId, className) {
    90         const parentElement = document.getElementById(boxId);
    91 
    92         if (parentElement) {
    93             const containers = parentElement.getElementsByClassName(className);
    94 
    95             Array.from(containers).forEach(container => {
    96                 container.innerHTML = '';
    97             });
    98         }
    99     }
    100 
    101     GoogleDriveGetFileMetadata(fieldName, fileId, access_token) {
    102 
    103         gapi.client.load("drive", "v3", () => {
    104             gapi.client.drive.files.get({
    105                 fileId: fileId,
    106                 fields: "id, name, mimeType, webContentLink, size"
    107             }).then(function (response) {
    108 
    109                 CTEditHelper.emptyContainers("ct_uploadfile_box_" + fieldName, "ajax-file-upload-error");
    110                 CTEditHelper.emptyContainers("ct_uploadfile_box_" + fieldName, "ajax-file-upload-container");
    111 
    112                 let buttonId = "CustomTablesGoogleDrivePick_" + fieldName;
    113                 const file = response.result;
    114                 let prefix;
    115                 const button = document.getElementById(buttonId);
    116                 if (button) {
    117                     const acceptValue = button.dataset.accept;
    118                     if (acceptValue) {
    119                         let parts = file.name.toLowerCase().split(".");
    120                         let fileExtension = parts[parts.length - 1];
    121                         let acceptTypes = acceptValue.split(' ');
    122                         if (acceptTypes.indexOf(fileExtension) === -1) {
    123                             let content = '<div class="ajax-file-upload-error"><b>' + file.name + '</b> is not allowed. Allowed extensions: ' + acceptValue + '</div>';
    124                             document.getElementById("ct_eventsmessage_" + fieldName).innerHTML = content;
     110            }
     111        }
     112
     113        GoogleDriveGetFileMetadata(fieldName, fileId, access_token) {
     114
     115            gapi.client.load("drive", "v3", () => {
     116                gapi.client.drive.files.get({
     117                    fileId: fileId,
     118                    fields: "id, name, mimeType, webContentLink, size"
     119                }).then(function (response) {
     120
     121                    CTEditHelper.emptyContainers("ct_uploadfile_box_" + fieldName, "ajax-file-upload-error");
     122                    CTEditHelper.emptyContainers("ct_uploadfile_box_" + fieldName, "ajax-file-upload-container");
     123
     124                    let buttonId = "CustomTablesGoogleDrivePick_" + fieldName;
     125                    const file = response.result;
     126                    let prefix;
     127                    const button = document.getElementById(buttonId);
     128                    if (button) {
     129                        const acceptValue = button.dataset.accept;
     130                        if (acceptValue) {
     131                            let parts = file.name.toLowerCase().split(".");
     132                            let fileExtension = parts[parts.length - 1];
     133                            let acceptTypes = acceptValue.split(' ');
     134                            if (acceptTypes.indexOf(fileExtension) === -1) {
     135                                let content = '<div class="ajax-file-upload-error"><b>' + file.name + '</b> is not allowed. Allowed extensions: ' + acceptValue + '</div>';
     136                                document.getElementById("ct_eventsmessage_" + fieldName).innerHTML = content;
     137                                return;
     138                            }
     139                        } else {
     140                            console.error('Accept file extensions not found.', error);
     141                            return;
     142                        }
     143
     144                        prefix = button.dataset.prefix;
     145                        if (!prefix) {
     146                            console.error('Prefix not found.', error);
    125147                            return;
    126148                        }
    127149                    } else {
    128                         console.error('Accept file extensions not found.', error);
     150                        console.error('Button "' + buttonId + '" not found.', error);
    129151                        return;
    130152                    }
    131153
    132                     prefix = button.dataset.prefix;
    133                     if (!prefix) {
    134                         console.error('Prefix not found.', error);
     154                    let fileSize = CTEditHelper.formatFileSize(file.size);
     155                    let content = '<div class="ajax-file-upload-statusbar"><div class="ajax-file-upload-filename">1). ' + file.name + ' (' + fileSize + ')</div></div>';
     156                    document.getElementById("ct_eventsmessage_" + fieldName).innerHTML = content;
     157                    document.getElementById(prefix + fieldName + '_filename').value = file.name;
     158
     159                    let data = JSON.stringify({
     160                        fileId: file.id,
     161                        fileName: file.name,
     162                        //mimeType: file.mimeType,
     163                        //size: file.size,
     164                        //downloadUrl: file.webContentLink,
     165                        accessToken: access_token
     166                    })
     167                    document.getElementById(prefix + fieldName + '_data').value = data;
     168                }, function (error) {
     169                    console.error("Error getting file metadata:", error);
     170                    document.getElementById("ct_eventsmessage_" + fieldName).innerHTML = "Error getting file metadata.";
     171                });
     172            });
     173        }
     174
     175        //A method to create or update table records using JavaScript. CustomTables handles data sanitization and validation.
     176        saveRecord(url, fieldsAndValues, listing_id, successCallback, errorCallback) {
     177
     178            let completeURL = url + '?view=edititem&task=save&tmpl=component&clean=1';
     179            if (listing_id !== undefined && listing_id !== null)
     180                completeURL += '&listing_id=' + listing_id;
     181
     182            let postData = new URLSearchParams();
     183
     184            // Iterate over keysObject and append each key-value pair
     185            for (const key in fieldsAndValues) {
     186                if (fieldsAndValues.hasOwnProperty(key)) {
     187                    postData.append(ctFieldInputPrefix + key, fieldsAndValues[key]);
     188                }
     189            }
     190
     191            fetch(completeURL, {
     192                method: 'POST',
     193                headers: {
     194                    'Content-Type': 'application/x-www-form-urlencoded',
     195                },
     196                body: postData,
     197            })
     198                .then(response => {
     199                    if (response.redirected) {
     200                        if (errorCallback && typeof errorCallback === 'function') {
     201                            errorCallback('Login required or not authorized.');
     202                        } else {
     203                            console.error('Login required or not authorized. Error status code 200: Redirect.');
     204                        }
     205                        return null;
     206                    }
     207
     208                    if (!response.ok) {
     209                        // If the HTTP status code is not successful, throw an error object that includes the response
     210                        throw {status: 'error', message: 'HTTP status code: ' + response.status, response: response};
     211                    }
     212                    return response.json();
     213                })
     214                .then(data => {
     215                    if (data === null)
    135216                        return;
    136                     }
    137                 } else {
    138                     console.error('Button "' + buttonId + '" not found.', error);
    139                     return;
    140                 }
    141 
    142                 let fileSize = CTEditHelper.formatFileSize(file.size);
    143                 let content = '<div class="ajax-file-upload-statusbar"><div class="ajax-file-upload-filename">1). ' + file.name + ' (' + fileSize + ')</div></div>';
    144                 document.getElementById("ct_eventsmessage_" + fieldName).innerHTML = content;
    145                 document.getElementById(prefix + fieldName + '_filename').value = file.name;
    146 
    147                 let data = JSON.stringify({
    148                     fileId: file.id,
    149                     fileName: file.name,
    150                     //mimeType: file.mimeType,
    151                     //size: file.size,
    152                     //downloadUrl: file.webContentLink,
    153                     accessToken: access_token
     217
     218                    if (data.status === 'saved') {
     219                        if (successCallback && typeof successCallback === 'function') {
     220                            successCallback(data);
     221                        } else {
     222
     223                        }
     224                    } else if (data.status === 'error') {
     225                        if (errorCallback && typeof errorCallback === 'function') {
     226                            errorCallback(data);
     227                        } else {
     228                            console.error(data.message);
     229                        }
     230                    }
    154231                })
    155                 document.getElementById(prefix + fieldName + '_data').value = data;
    156             }, function (error) {
    157                 console.error("Error getting file metadata:", error);
    158                 document.getElementById("ct_eventsmessage_" + fieldName).innerHTML = "Error getting file metadata.";
     232                .catch(error => {
     233                    if (errorCallback && typeof errorCallback === 'function') {
     234                        errorCallback({
     235                            status: 'error',
     236                            message: 'An error occurred during the request.',
     237                        });
     238                    } else {
     239                        console.error('Error', error);
     240                        console.log(completeURL);
     241                    }
     242                });
     243        }
     244
     245        //TODO: no usages found
     246        async refreshRecord(url, listing_id, successCallback, errorCallback, ModuleId) {
     247            let completeURL = url + '?tmpl=component&clean=1&task=refresh';
     248            if (listing_id !== undefined && listing_id !== null)
     249                completeURL += '&ids=' + listing_id;
     250
     251            try {
     252                const response = await fetch(completeURL);
     253                if (!response.ok) {
     254                    throw new Error('Network response was not ok');
     255                }
     256                const data = await response.json();
     257                console.log(data);
     258            } catch (error) {
     259                console.error('There was a problem with the fetch operation:', error);
     260            }
     261
     262            //let postData = new URLSearchParams();
     263            //postData.append('task', 'refresh');
     264
     265            fetch(completeURL, {
     266                method: 'GET'
     267            })
     268                .then(response => {
     269
     270                    if (response.redirected) {
     271                        if (errorCallback && typeof errorCallback === 'function') {
     272                            errorCallback('Login required or not authorized.');
     273                        } else {
     274                            console.error('Login required or not authorized. Error status code 200: Redirect.');
     275                        }
     276                        return null;
     277                    }
     278
     279                    if (!response.ok) {
     280                        // If the HTTP status code is not successful, throw an error object that includes the response
     281                        throw {status: 'error', message: 'HTTP status code: ' + response.status, response: response};
     282                    }
     283                    return response.json();
     284                })
     285                .then(data => {
     286                    if (data === null)
     287                        return;
     288
     289                    if (data.status === 'saved') {
     290                        if (successCallback && typeof successCallback === 'function') {
     291                            successCallback(data);
     292                        } else {
     293
     294                        }
     295                    } else if (data.status === 'error') {
     296                        if (errorCallback && typeof errorCallback === 'function') {
     297                            errorCallback(data);
     298                        } else {
     299                            console.error(data.message);
     300                        }
     301                    }
     302                })
     303                .catch(error => {
     304                    if (errorCallback && typeof errorCallback === 'function') {
     305                        errorCallback({
     306                            status: 'error',
     307                            message: 'An error occurred during the request.',
     308                        });
     309                    } else {
     310                        console.error('Error 145:', error);
     311                        console.log(completeURL);
     312                    }
     313                });
     314        }
     315
     316        //Reloads a particular table row (record) after changes have been made. It identifies the table and the specific row based on the provided listing_id and then triggers a refresh to update the displayed data.
     317        reloadRecord(listing_id) {
     318
     319            // Select all table elements whose id attribute starts with 'ctTable_'
     320            const tables = document.querySelectorAll('table[id^="ctTable_"]');
     321            tables.forEach(table => {
     322                let parts = table.id.split("_");
     323                if (parts.length === 2) {
     324                    let tableId = parts[1];
     325                    let trId = 'ctTable_' + tableId + '_' + listing_id;
     326                    const records = table.querySelectorAll('tr[id^="' + trId + '"]');
     327                    if (records.length == 1) {
     328                        let table_object = findTableByRowId(tableid + '_' + listing_id);
     329                        let index = findRowIndexById(table_object, tableId, listing_id, 'ctEditIcon');
     330                        ctCatalogUpdate(tableId, listing_id, index, ModuleId);
     331                    }
     332                }
    159333            });
    160         });
    161     }
    162 
    163     //A method to create or update table records using JavaScript. CustomTables handles data sanitization and validation.
    164     saveRecord(url, fieldsAndValues, listing_id, successCallback, errorCallback) {
    165 
    166         let completeURL = url + '?view=edititem&task=save&tmpl=component&clean=1';
    167         if (listing_id !== undefined && listing_id !== null)
    168             completeURL += '&listing_id=' + listing_id;
    169 
    170         let postData = new URLSearchParams();
    171 
    172         // Iterate over keysObject and append each key-value pair
    173         for (const key in fieldsAndValues) {
    174             if (fieldsAndValues.hasOwnProperty(key)) {
    175                 postData.append(ctFieldInputPrefix + key, fieldsAndValues[key]);
    176             }
    177         }
    178 
    179         fetch(completeURL, {
    180             method: 'POST',
    181             headers: {
    182                 'Content-Type': 'application/x-www-form-urlencoded',
    183             },
    184             body: postData,
    185         })
    186             .then(response => {
    187                 if (response.redirected) {
    188                     if (errorCallback && typeof errorCallback === 'function') {
    189                         errorCallback('Login required or not authorized.');
    190                     } else {
    191                         console.error('Login required or not authorized. Error status code 200: Redirect.');
    192                     }
    193                     return null;
    194                 }
    195 
    196                 if (!response.ok) {
    197                     // If the HTTP status code is not successful, throw an error object that includes the response
    198                     throw {status: 'error', message: 'HTTP status code: ' + response.status, response: response};
    199                 }
    200                 return response.json();
    201             })
    202             .then(data => {
    203                 if (data === null)
    204                     return;
    205 
    206                 if (data.status === 'saved') {
    207                     if (successCallback && typeof successCallback === 'function') {
    208                         successCallback(data);
    209                     } else {
    210 
    211                     }
    212                 } else if (data.status === 'error') {
    213                     if (errorCallback && typeof errorCallback === 'function') {
    214                         errorCallback(data);
    215                     } else {
    216                         console.error(data.message);
    217                     }
    218                 }
    219             })
    220             .catch(error => {
    221                 if (errorCallback && typeof errorCallback === 'function') {
    222                     errorCallback({
    223                         status: 'error',
    224                         message: 'An error occurred during the request.',
    225                     });
    226                 } else {
    227                     console.error('Error', error);
    228                     console.log(completeURL);
    229                 }
    230             });
    231     }
    232 
    233     //TODO: no usages found
    234     async refreshRecord(url, listing_id, successCallback, errorCallback, ModuleId) {
    235         let completeURL = url + '?tmpl=component&clean=1&task=refresh';
    236         if (listing_id !== undefined && listing_id !== null)
    237             completeURL += '&ids=' + listing_id;
    238 
    239         try {
    240             const response = await fetch(completeURL);
    241             if (!response.ok) {
    242                 throw new Error('Network response was not ok');
    243             }
    244             const data = await response.json();
    245             console.log(data);
    246         } catch (error) {
    247             console.error('There was a problem with the fetch operation:', error);
    248         }
    249 
    250         //let postData = new URLSearchParams();
    251         //postData.append('task', 'refresh');
    252 
    253         fetch(completeURL, {
    254             method: 'GET'
    255         })
    256             .then(response => {
    257 
    258                 if (response.redirected) {
    259                     if (errorCallback && typeof errorCallback === 'function') {
    260                         errorCallback('Login required or not authorized.');
    261                     } else {
    262                         console.error('Login required or not authorized. Error status code 200: Redirect.');
    263                     }
    264                     return null;
    265                 }
    266 
    267                 if (!response.ok) {
    268                     // If the HTTP status code is not successful, throw an error object that includes the response
    269                     throw {status: 'error', message: 'HTTP status code: ' + response.status, response: response};
    270                 }
    271                 return response.json();
    272             })
    273             .then(data => {
    274                 if (data === null)
    275                     return;
    276 
    277                 if (data.status === 'saved') {
    278                     if (successCallback && typeof successCallback === 'function') {
    279                         successCallback(data);
    280                     } else {
    281 
    282                     }
    283                 } else if (data.status === 'error') {
    284                     if (errorCallback && typeof errorCallback === 'function') {
    285                         errorCallback(data);
    286                     } else {
    287                         console.error(data.message);
    288                     }
    289                 }
    290             })
    291             .catch(error => {
    292                 if (errorCallback && typeof errorCallback === 'function') {
    293                     errorCallback({
    294                         status: 'error',
    295                         message: 'An error occurred during the request.',
    296                     });
    297                 } else {
    298                     console.error('Error 145:', error);
    299                     console.log(completeURL);
    300                 }
    301             });
    302     }
    303 
    304     //Reloads a particular table row (record) after changes have been made. It identifies the table and the specific row based on the provided listing_id and then triggers a refresh to update the displayed data.
    305     reloadRecord(listing_id) {
    306 
    307         // Select all table elements whose id attribute starts with 'ctTable_'
    308         const tables = document.querySelectorAll('table[id^="ctTable_"]');
    309         tables.forEach(table => {
    310             let parts = table.id.split("_");
    311             if (parts.length === 2) {
    312                 let tableId = parts[1];
    313                 let trId = 'ctTable_' + tableId + '_' + listing_id;
    314                 const records = table.querySelectorAll('tr[id^="' + trId + '"]');
    315                 if (records.length == 1) {
    316                     let index = findRowIndexById(table.id, trId);
    317                     ctCatalogUpdate(tableId, listing_id, index, ModuleId);
    318                 }
    319             }
    320         });
    321     }
    322 
    323     ImageGalleryInitImagePreviews(inputId) {
    324         const input = document.getElementById(inputId);
    325 
    326         input.onchange = function (event) {
    327             const previewContainer = document.getElementById(inputId + '_previewNew');
    328             previewContainer.innerHTML = '';
    329 
    330             Array.from(event.target.files).forEach((file, index) => {
    331                 if (file.type.startsWith('image/')) {
    332                     const reader = new FileReader();
    333                     const div = document.createElement('div');
    334                     div.className = 'preview-item';
    335                     div.dataset.fileIndex = index;
    336 
    337                     reader.onload = function (e) {
    338                         div.innerHTML = `
     334        }
     335
     336        ImageGalleryInitImagePreviews(inputId) {
     337            const input = document.getElementById(inputId);
     338
     339            input.onchange = function (event) {
     340                const previewContainer = document.getElementById(inputId + '_previewNew');
     341                previewContainer.innerHTML = '';
     342
     343                Array.from(event.target.files).forEach((file, index) => {
     344                    if (file.type.startsWith('image/')) {
     345                        const reader = new FileReader();
     346                        const div = document.createElement('div');
     347                        div.className = 'preview-item';
     348                        div.dataset.fileIndex = index;
     349
     350                        reader.onload = function (e) {
     351                            div.innerHTML = `
    339352                        <img src="${e.target.result}" class="preview-image" />
    340353                        <button type="button" class="remove-btn"
    341354                                onclick="CTEditHelper.ImageGalleryRemoveFile(this, '${inputId}', ${index})">×</button>
    342355                    `;
    343                     };
    344 
    345                     previewContainer.appendChild(div);
    346                     reader.readAsDataURL(file);
    347                 }
    348             });
    349         };
    350     }
    351 
    352     ImageGalleryRemoveFile(button, inputId, fileIndex) {
    353 
    354         if (fileIndex < 0) {
    355             //mark to delete existing file
    356             const input = document.getElementById(inputId + '_uploaded');
    357             if (input) {
    358                 let files = input.value.split(',');
    359                 let newFiles = [];
     356                        };
     357
     358                        previewContainer.appendChild(div);
     359                        reader.readAsDataURL(file);
     360                    }
     361                });
     362            };
     363        }
     364
     365        ImageGalleryRemoveFile(button, inputId, fileIndex) {
     366
     367            if (fileIndex < 0) {
     368                //mark to delete existing file
     369                const input = document.getElementById(inputId + '_uploaded');
     370                if (input) {
     371                    let files = input.value.split(',');
     372                    let newFiles = [];
     373
     374                    for (let i = 0; i < files.length; i++) {
     375                        if (parseInt(files[i]) != -fileIndex)
     376                            newFiles.push(files[i]);
     377                        else
     378                            newFiles.push(fileIndex);
     379                    }
     380
     381                    input.value = newFiles.join(',');
     382                    const container = button.closest('.preview-item');
     383                    container.remove();
     384                }
     385            } else {
     386                const input = document.getElementById(inputId);
     387                const container = button.closest('.preview-item');
     388
     389                const dt = new DataTransfer();
     390                const files = input.files;
    360391
    361392                for (let i = 0; i < files.length; i++) {
    362                     if (parseInt(files[i]) != -fileIndex)
    363                         newFiles.push(files[i]);
    364                     else
    365                         newFiles.push(fileIndex);
    366                 }
    367 
    368                 input.value = newFiles.join(',');
    369                 const container = button.closest('.preview-item');
     393                    if (i !== fileIndex) {
     394                        dt.items.add(files[i]);
     395                    }
     396                }
     397
     398                input.files = dt.files;
    370399                container.remove();
    371             }
    372         } else {
    373             const input = document.getElementById(inputId);
    374             const container = button.closest('.preview-item');
    375 
    376             const dt = new DataTransfer();
    377             const files = input.files;
    378 
    379             for (let i = 0; i < files.length; i++) {
    380                 if (i !== fileIndex) {
    381                     dt.items.add(files[i]);
    382                 }
    383             }
    384 
    385             input.files = dt.files;
    386             container.remove();
    387 
    388             // Reindex remaining previews
    389             const previews = document.querySelectorAll('.preview-item');
    390             previews.forEach((preview, index) => {
    391                 preview.dataset.fileIndex = index;
    392                 const removeBtn = preview.querySelector('.remove-btn');
    393                 removeBtn.setAttribute('onclick', `CTEditHelper.ImageGalleryRemoveFile(this, '${inputId}', ${index})`);
    394             });
    395         }
    396     }
    397 
    398     checkRequiredFields(formObject) {
    399         if (!checkFilters())
    400             return false;
    401 
    402         if (ct_signaturePad_fields.length > 0) {
    403             if (!ctInputbox_signature_apply()) {
    404                 event.preventDefault();
     400
     401                // Reindex remaining previews
     402                const previews = document.querySelectorAll('.preview-item');
     403                previews.forEach((preview, index) => {
     404                    preview.dataset.fileIndex = index;
     405                    const removeBtn = preview.querySelector('.remove-btn');
     406                    removeBtn.setAttribute('onclick', `CTEditHelper.ImageGalleryRemoveFile(this, '${inputId}', ${index})`);
     407                });
     408            }
     409        }
     410
     411        checkRequiredFields(formObject) {
     412            if (!checkFilters())
    405413                return false;
    406             }
    407         }
    408 
    409         let requiredFields = formObject.getElementsByClassName("required");
    410         let label = "One field";
    411 
    412         for (let i = 0; i < requiredFields.length; i++) {
    413             if (typeof requiredFields[i].id != "undefined") {
    414                 if (requiredFields[i].id.indexOf("sqljoin_table_" + ctFieldInputPrefix) !== -1) {
    415                     if (!CheckSQLJoinRadioSelections(requiredFields[i].id))
    416                         return false;
    417                 }
    418                 if (requiredFields[i].id.indexOf("ct_uploadfile_box_") !== -1) {
    419                     if (!CheckImageUploader(requiredFields[i].id)) {
     414
     415            if (this.ct_signaturePad_fields.length > 0) {
     416                if (!CTEditHelper.ctInputbox_signature_apply()) {
     417                    event.preventDefault();
     418                    return false;
     419                }
     420            }
     421
     422            let requiredFields = formObject.getElementsByClassName("required");
     423            let label = "One field";
     424
     425            for (let i = 0; i < requiredFields.length; i++) {
     426                if (typeof requiredFields[i].id != "undefined") {
     427                    if (requiredFields[i].id.indexOf("sqljoin_table_" + ctFieldInputPrefix) !== -1) {
     428                        if (!CheckSQLJoinRadioSelections(requiredFields[i].id))
     429                            return false;
     430                    }
     431                    if (requiredFields[i].id.indexOf("ct_uploadfile_box_") !== -1) {
     432                        if (!CheckImageUploader(requiredFields[i].id)) {
     433                            let d = requiredFields[i].dataset;
     434                            if (d.label)
     435                                label = d.label;
     436                            else
     437                                label = "Unlabeled field";
     438
     439                            let imageObjectName = requiredFields[i].id + '_image';
     440                            let imageObject = document.getElementById(imageObjectName);
     441
     442                            if (imageObject)
     443                                return true;
     444
     445                            alert(TranslateText('COM_CUSTOMTABLES_REQUIRED', label));
     446                            return false;
     447                        }
     448                    }
     449                }
     450
     451                if (typeof requiredFields[i].name != "undefined") {
     452                    let n = requiredFields[i].name.toString();
     453
     454                    if (n.indexOf(ctFieldInputPrefix) !== -1) {
     455
     456                        let objName = n.replace('_selector', '');
     457
    420458                        let d = requiredFields[i].dataset;
    421459                        if (d.label)
    422                             label = d.label;
     460                            label = d.label
    423461                        else
    424462                            label = "Unlabeled field";
    425463
    426                         let imageObjectName = requiredFields[i].id + '_image';
    427                         let imageObject = document.getElementById(imageObjectName);
    428 
    429                         if (imageObject)
    430                             return true;
    431 
    432                         alert(TranslateText('COM_CUSTOMTABLES_REQUIRED', label));
    433                         return false;
    434                     }
    435                 }
    436             }
    437 
    438             if (typeof requiredFields[i].name != "undefined") {
    439                 let n = requiredFields[i].name.toString();
    440 
    441                 if (n.indexOf(ctFieldInputPrefix) !== -1) {
    442 
    443                     let objName = n.replace('_selector', '');
    444 
    445                     let d = requiredFields[i].dataset;
    446                     if (d.label)
    447                         label = d.label
    448                     else
    449                         label = "Unlabeled field";
    450 
    451                     if (d.type === 'sqljoin') {
    452                         if (requiredFields[i].type === "hidden") {
     464                        if (d.type === 'sqljoin') {
     465                            if (requiredFields[i].type === "hidden") {
     466                                let obj = document.getElementById(objName);
     467
     468                                if (obj.value === '') {
     469                                    alert(TranslateText('COM_CUSTOMTABLES_REQUIRED', label));
     470                                    return false;
     471                                }
     472                            }
     473
     474                        } else if (requiredFields[i].type === "text") {
    453475                            let obj = document.getElementById(objName);
    454 
    455476                            if (obj.value === '') {
    456477                                alert(TranslateText('COM_CUSTOMTABLES_REQUIRED', label));
    457478                                return false;
    458479                            }
     480                        } else if (requiredFields[i].type === "select-one") {
     481                            let obj = document.getElementById(objName);
     482
     483                            if (obj.value === null || obj.value === '') {
     484                                alert(TranslateText('COM_CUSTOMTABLES_NOT_SELECTED', label));
     485                                return false;
     486                            }
     487                        } else if (requiredFields[i].type === "select-multiple") {
     488                            let count_multiple_obj = document.getElementById(lbln);
     489                            let options = count_multiple_obj.options;
     490                            let count_multiple = 0;
     491
     492                            for (let i2 = 0; i2 < options.length; i2++) {
     493                                if (options[i2].selected)
     494                                    count_multiple++;
     495                            }
     496
     497                            if (count_multiple === 0) {
     498                                alert(TranslateText('COM_CUSTOMTABLES_NOT_SELECTED', label));
     499                                return false;
     500                            }
     501                        } else if (d.selector == 'switcher') {
     502                            //Checkbox element with Yes/No visual effect
     503                            if (d.label)
     504                                label = d.label;
     505                            else
     506                                label = "Unlabeled field";
     507
     508                            if (requiredFields[i].value === "1") {
     509
     510                                if (d.valuerulecaption && d.valuerulecaption !== "")
     511                                    alert(d.valuerulecaption);
     512                                else
     513                                    alert(TranslateText('COM_CUSTOMTABLES_REQUIRED', label));
     514                                return false;
     515                            }
     516                        } else if (d.type == 'checkbox') {
     517                            //Simple HTML Checkbox element
     518                            if (d.label)
     519                                label = d.label;
     520                            else
     521                                label = "Unlabeled field";
     522
     523                            if (!requiredFields[i].checked) {
     524                                if (d.valuerulecaption && d.valuerulecaption !== "")
     525                                    alert(d.valuerulecaption);
     526                                else
     527                                    alert(TranslateText('COM_CUSTOMTABLES_REQUIRED', label));
     528                                return false;
     529                            }
    459530                        }
    460 
    461                     } else if (requiredFields[i].type === "text") {
    462                         let obj = document.getElementById(objName);
    463                         if (obj.value === '') {
    464                             alert(TranslateText('COM_CUSTOMTABLES_REQUIRED', label));
    465                             return false;
     531                    }
     532                }
     533            }
     534            return true;
     535        }
     536
     537        convertDateTypeValues(elements) {
     538            for (let i = 0; i < elements.length; i++) {
     539                if (elements[i].name && elements[i].name !== '' && elements[i].name !== 'returnto') {
     540                    if (elements[i].dataset.type === "date") {
     541                        if (elements[i].dataset.format !== "%Y-%m-%d") {
     542                            //convert date to %Y-%m-%d
     543                            let dateValue = elements[i].value;
     544                            if (dateValue) {
     545                                // Parse the format string
     546                                let format = elements[i].dataset.format;
     547                                let day, month, year;
     548
     549                                // Convert Joomla's format to parts
     550                                let parts = dateValue.split(/[-/.]/);
     551                                let formatParts = format.split(/[-/.]/);
     552
     553                                // Map the parts to corresponding values
     554                                formatParts.forEach((part, index) => {
     555                                    if (part === '%d') day = parts[index];
     556                                    else if (part === '%m') month = parts[index];
     557                                    else if (part === '%Y') year = parts[index];
     558                                });
     559
     560                                // Create standardized date string
     561                                elements[i].value = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
     562                            }
    466563                        }
    467                     } else if (requiredFields[i].type === "select-one") {
    468                         let obj = document.getElementById(objName);
    469 
    470                         if (obj.value === null || obj.value === '') {
    471                             alert(TranslateText('COM_CUSTOMTABLES_NOT_SELECTED', label));
    472                             return false;
     564                    }
     565                }
     566            }
     567        }
     568
     569        ctInputbox_signature(inputbox_id, width, height, format) {
     570
     571            let canvas = document.getElementById(inputbox_id + '_canvas');
     572
     573            this.ct_signaturePad_fields.push(inputbox_id);
     574            this.ct_signaturePad[inputbox_id] = new SignaturePad(canvas, {
     575                backgroundColor: "rgb(255, 255, 255)"
     576            });
     577
     578            this.ct_signaturePad_formats[inputbox_id] = format;
     579
     580            canvas.width = width;
     581            canvas.height = height;
     582            canvas.getContext("2d").scale(1, 1);
     583
     584            document.getElementById(inputbox_id + '_clear').addEventListener('click', function () {
     585                this.ct_signaturePad[inputbox_id].clear();
     586            });
     587        }
     588
     589        ctInputbox_signature_apply() {
     590
     591            if (this.ct_signaturePad_fields.length === 0)
     592                return true;
     593
     594            let inputbox_id = this.ct_signaturePad_fields[0];
     595
     596            if (this.ct_signaturePad[inputbox_id].isEmpty()) {
     597                alert(TranslateText('COM_CUSTOMTABLES_JS_SIGNATURE_REQUIRED'));
     598                return false;
     599            } else {
     600
     601                let format = this.ct_signaturePad_formats[inputbox_id];
     602
     603                let dataURL = this.ct_signaturePad[inputbox_id].toDataURL('image/' + format);
     604                document.getElementById(inputbox_id).setAttribute("value", dataURL);
     605                return true;
     606            }
     607        }
     608
     609        ctInputbox_UpdateSQLJoinLink(control_name, control_name_postfix) {
     610            //Old calls replaced
     611            setTimeout(this.ctInputbox_UpdateSQLJoinLink_do(control_name, control_name_postfix), 100);
     612        }
     613
     614        ctInputbox_UpdateSQLJoinLink_do(control_name, control_name_postfix) {
     615            //Old calls replaced
     616            let controlElement = document.getElementById(control_name);
     617            let selectedControlElements = Array.from(controlElement.options)
     618                .filter(option => option.selected)
     619                .map(option => option.value);
     620
     621            let l = document.getElementById(control_name + control_name_postfix);
     622            let o = document.getElementById(control_name + 'SQLJoinLink');
     623            let v = '';
     624
     625            if (o) {
     626                if (o.selectedIndex === -1)
     627                    return false;
     628
     629                v = o.options[o.selectedIndex].value;
     630            }
     631
     632            let selectedValue = null
     633            let ctInputBoxRecords_current_value = document.getElementById(control_name + '_ctInputBoxRecords_current_value');
     634
     635            if (ctInputBoxRecords_current_value)
     636                selectedValue = String(ctInputBoxRecords_current_value.innerHTML);
     637
     638            ctInputBoxRecords_removeOptions(l);
     639
     640            if (control_name_postfix !== '_selector') {
     641                let opt = document.createElement("option");
     642                opt.value = '0';
     643                opt.innerHTML = TranslateText('COM_CUSTOMTABLES_SELECT');
     644                l.appendChild(opt);
     645            }
     646
     647            let elements = JSON.parse(document.getElementById(control_name + control_name_postfix + '_elements').textContent);
     648            let elementsID = document.getElementById(control_name + control_name_postfix + '_elementsID').innerHTML.split(",");
     649            let elementsPublished = document.getElementById(control_name + control_name_postfix + '_elementsPublished').innerHTML.split(",");
     650
     651            let filterElement = document.getElementById(control_name + control_name_postfix + '_elementsFilter');
     652            let elementsFilter = []
     653            if (filterElement)
     654                elementsFilter = filterElement.innerHTML.split(";");
     655
     656            for (let i = 0; i < elements.length; i++) {
     657                let f = elementsFilter[i];
     658
     659                if (elements[i] !== "") {
     660
     661                    let eid = String(elementsID[i]);
     662                    if (selectedControlElements.indexOf(eid) === -1) {
     663
     664                        let published = parseInt(elementsPublished[i]);
     665
     666                        if (typeof f != "undefined") {
     667                            let f_list = f.split(",");
     668
     669                            if (f_list.indexOf(v) !== -1) {
     670                                let opt = document.createElement("option");
     671                                opt.value = eid;
     672                                if (eid === selectedValue)
     673                                    opt.selected = true;
     674
     675                                if (published === 0)
     676                                    opt.style.cssText = "color:red;";
     677
     678                                opt.innerHTML = elements[i];
     679                                l.appendChild(opt);
     680                            }
     681                        } else {
     682
     683                            let opt = document.createElement("option");
     684                            opt.value = eid;
     685                            if (eid === selectedValue)
     686                                opt.selected = true;
     687
     688                            if (published === 0)
     689                                opt.style.cssText = "color:red;";
     690
     691                            opt.innerHTML = elements[i];
     692                            l.appendChild(opt);
    473693                        }
    474                     } else if (requiredFields[i].type === "select-multiple") {
    475                         let count_multiple_obj = document.getElementById(lbln);
    476                         let options = count_multiple_obj.options;
    477                         let count_multiple = 0;
    478 
    479                         for (let i2 = 0; i2 < options.length; i2++) {
    480                             if (options[i2].selected)
    481                                 count_multiple++;
    482                         }
    483 
    484                         if (count_multiple === 0) {
    485                             alert(TranslateText('COM_CUSTOMTABLES_NOT_SELECTED', label));
    486                             return false;
    487                         }
    488                     } else if (d.selector == 'switcher') {
    489                         //Checkbox element with Yes/No visual effect
    490                         if (d.label)
    491                             label = d.label;
    492                         else
    493                             label = "Unlabeled field";
    494 
    495                         if (requiredFields[i].value === "1") {
    496 
    497                             if (d.valuerulecaption && d.valuerulecaption !== "")
    498                                 alert(d.valuerulecaption);
    499                             else
    500                                 alert(TranslateText('COM_CUSTOMTABLES_REQUIRED', label));
    501                             return false;
    502                         }
    503                     } else if (d.type == 'checkbox') {
    504                         //Simple HTML Checkbox element
    505                         if (d.label)
    506                             label = d.label;
    507                         else
    508                             label = "Unlabeled field";
    509 
    510                         if (!requiredFields[i].checked) {
    511                             if (d.valuerulecaption && d.valuerulecaption !== "")
    512                                 alert(d.valuerulecaption);
    513                             else
    514                                 alert(TranslateText('COM_CUSTOMTABLES_REQUIRED', label));
    515                             return false;
    516                         }
    517                     }
    518                 }
    519             }
    520         }
    521         return true;
    522     }
    523 
    524     convertDateTypeValues(elements) {
    525         for (let i = 0; i < elements.length; i++) {
    526             if (elements[i].name && elements[i].name !== '' && elements[i].name !== 'returnto') {
    527                 if (elements[i].dataset.type === "date") {
    528                     if (elements[i].dataset.format !== "%Y-%m-%d") {
    529                         //convert date to %Y-%m-%d
    530                         let dateValue = elements[i].value;
    531                         if (dateValue) {
    532                             // Parse the format string
    533                             let format = elements[i].dataset.format;
    534                             let day, month, year;
    535 
    536                             // Convert Joomla's format to parts
    537                             let parts = dateValue.split(/[-/.]/);
    538                             let formatParts = format.split(/[-/.]/);
    539 
    540                             // Map the parts to corresponding values
    541                             formatParts.forEach((part, index) => {
    542                                 if (part === '%d') day = parts[index];
    543                                 else if (part === '%m') month = parts[index];
    544                                 else if (part === '%Y') year = parts[index];
    545                             });
    546 
    547                             // Create standardized date string
    548                             elements[i].value = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
    549                         }
    550                     }
    551                 }
    552             }
    553         }
    554     }
     694                    }
     695                }
     696            }
     697
     698            return true;
     699        }
     700
     701        ctInputBoxRecords_addItem(control_name, control_name_postfix) {
     702
     703            let o = document.getElementById(control_name + control_name_postfix);
     704            o.selectedIndex = 0;
     705
     706            if (this.ctInputBoxRecords_dynamic_filter[control_name] !== '') {
     707
     708                let ctInputBoxRecords_current_value = document.getElementById(control_name + '_ctInputBoxRecords_current_value');
     709                if (ctInputBoxRecords_current_value)
     710                    ctInputBoxRecords_current_value.innerHTML = '';
     711
     712                let SQLJoinLink = document.getElementById(control_name + control_name_postfix + 'SQLJoinLink');
     713                if (SQLJoinLink)// {
     714                    SQLJoinLink.selectedIndex = 0;
     715
     716                this.ctInputbox_UpdateSQLJoinLink(control_name, control_name_postfix);
     717            }
     718
     719            document.getElementById(control_name + '_addButton').style.visibility = "hidden";
     720            document.getElementById(control_name + '_addBox').style.visibility = "visible";
     721        }
     722    }
     723
     724    globalThis.CustomTablesEdit = CustomTablesEdit; // Store globally
    555725}
    556726
     
    616786
    617787    let fieldsProcessed = [];
    618     let params = "";
     788    let params = new URLSearchParams();
     789
    619790    let opt;
    620791    for (let i = 0; i < elements.length; i++) {
     
    628799                    opt = options[x];
    629800                    if (opt.selected)
    630                         params += "&" + elements[i].name + "=" + opt.value;
     801                        params.append(elements[i].name, opt.value);
    631802                }
    632803
    633804            } else if (elements[i].type === "checkbox") {
    634805                // Handle checkboxes: add "true" if checked, "false" if unchecked
    635                 params += "&" + elements[i].name + "=" + (elements[i].checked ? "true" : "false");
     806                params.append(elements[i].name, (elements[i].checked ? "true" : "false"));
    636807            } else if (elements[i].type === "radio") {
    637808                // Handle radio buttons: Check if any radio button with the same name is selected
     
    641812                for (let r = 0; r < radios.length; r++) {
    642813                    if (radios[r].checked) {
    643                         params += "&" + radios[r].name + "=" + radios[r].value;
     814                        params.append(radios[r].name, radios[r].value);
    644815                        radioChecked = true;
    645816                        break;  // No need to check further once one is selected
     
    649820                // If no radio button is selected, set a default value (if desired)
    650821                if (!radioChecked) {
    651                     params += "&" + elements[i].name + "=none";  // You can set "none" or another default value
     822                    params.append(elements[i].name, "none");
    652823                }
    653824            } else {
    654                 params += "&" + elements[i].name + "=" + elements[i].value;
     825                params.append(elements[i].name, elements[i].value);
    655826            }
    656827            fieldsProcessed.push(elements[i].name);
     
    661832
    662833    if (http) {
    663 
    664         http.open("POST", url + "&clean=1&ctmodalform=1&load=1", true);
     834        params.append('task', "save");
     835        params.append('clean', "1");
     836        params.append('frmt', "json");
     837        params.append('ctmodalform', "1");
     838        params.append('load', "1");
     839
     840        let clean_url = url.replace('%addRecord%', '');
     841        console.log("clean_url:", clean_url)
     842        console.log("params:", params.toString())
     843
     844        http.open("POST", clean_url, true);
    665845        http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
     846        http.setRequestHeader("X-Requested-With", "XMLHttpRequest"); // Prevent full-page redirects
     847
    666848        http.onreadystatechange = function () {
    667849            if (http.readyState === 4) {
     
    669851
    670852                try {
     853                    let responseString = http.response.toString();
     854                    console.log('responseString:', responseString);
    671855                    response = JSON.parse(http.response.toString());
    672856                } catch (e) {
    673                     console.log(url + "&clean=1&ctmodalform=1&load=1");
    674                     console.log(http.response.toString());
    675                     return console.error(e);
    676                 }
    677 
    678                 if (response.status === "saved") {
    679                     let element_tableid_tr = "ctTable_" + tableid + '_' + recordId;
     857
     858                    let r = http.response.toString();
     859                    if (r.indexOf('view-login') !== -1) {
     860                        alert('Session expired. Please login again.');
     861                        location.reload();
     862                        return;
     863                    } else {
     864                        console.log(clean_url);
     865                        console.log(http.response.toString());
     866                        return console.error(e);
     867                    }
     868                }
     869
     870                if (response.success) {
     871                    //let element_tableid_tr = "ctTable_" + tableid + '_' + recordId;
    680872                    let table_object = document.getElementById("ctTable_" + tableid);
    681873
    682874                    if (table_object) {
    683                         let index = findRowIndexById("ctTable_" + tableid, element_tableid_tr);
     875                        let index = findRowIndexById(table_object, tableid, recordId, 'ctEditIcon');
    684876                        ctCatalogUpdate(tableid, recordId, index, ModuleId);
    685877                    }
     
    691883                    }
    692884
    693                     if (hideModelOnSave)
     885                    if (hideModelOnSave) {
    694886                        ctHidePopUp();
     887                        return;
     888                    }
    695889
    696890                    if (returnLinkEncoded !== "")
     
    698892
    699893                } else {
     894                    /*
    700895                    if (http.response.indexOf('<div class="alert-message">Nothing to save</div>') !== -1)
    701896                        alert('Nothing to save. Check Edit From layout.');
     
    703898                        alert(TranslateText('COM_CUSTOMTABLES_JS_SESSION_EXPIRED'));
    704899                    else
    705                         alert(http.response);
     900                        */
     901                    alert(response.message);
    706902                }
    707903            }
    708904        };
    709         http.send(params);
     905        http.send(params.toString());
    710906    }
    711907}
     
    10711267                let js = 'ctTableJoinAddRecordModalForm(\'' + control_name + '\',' + sub_index + ');';
    10721268                let addText = TranslateText('COM_CUSTOMTABLES_ADD');
    1073                 NoItemsText = addText + '<a href="javascript:' + js + '" className="toolbarIcons"><img src="' + ctWebsiteRoot + 'components/com_customtables/libraries/customtables/media/images/icons/new.png" alt="' + addText + '" title="' + addText + '"></a>';
     1269                NoItemsText = addText + '<a href="javascript:' + js + '" className="toolbarIcons"><img src="' + CTEditHelper.websiteRoot + 'components/com_customtables/libraries/customtables/media/images/icons/new.png" alt="' + addText + '" title="' + addText + '"></a>';
    10741270            } else
    10751271                NoItemsText = TranslateText('COM_CUSTOMTABLES_SELECT_NOTHING')
     
    11331329    let wrapper = document.getElementById(control_name + "Wrapper");
    11341330
    1135     let query = ctWebsiteRoot + 'index.php/' + wrapper.dataset.addrecordmenualias;
     1331    let query = CTEditHelper.websiteRoot + 'index.php' + (wrapper.dataset.addrecordmenualias.indexOf('/') === -1 ? '/' : '') + wrapper.dataset.addrecordmenualias;
    11361332    if (wrapper.dataset.addrecordmenualias.indexOf('?') === -1)
    11371333        query += '?';
     
    11591355
    11601356        if (link.length === 2)//to make sure that it will work in the back-end
    1161             url = ctWebsiteRoot + 'administrator/index.php?option=com_customtables&view=records&frmt=json&key=' + wrapper.dataset.key + '&index=' + index;
     1357            url = CTEditHelper.websiteRoot + 'administrator/index.php?option=com_customtables&view=records&frmt=json&key=' + wrapper.dataset.key + '&index=' + index;
    11621358        else
    1163             url = ctWebsiteRoot + 'index.php?option=com_customtables&view=catalog&tmpl=component&frmt=json&key=' + wrapper.dataset.key + '&index=' + index;
     1359            url = CTEditHelper.websiteRoot + 'index.php?option=com_customtables&view=catalog&tmpl=component&frmt=json&key=' + wrapper.dataset.key + '&index=' + index;
    11641360
    11651361    } else if (CTEditHelper.cmsName === "WordPress") {
    1166         url = ctWebsiteRoot + 'index.php?page=customtables-api-tablejoin&key=' + wrapper.dataset.key + '&index=' + index;
     1362        url = CTEditHelper.websiteRoot + 'index.php?page=customtables-api-tablejoin&key=' + wrapper.dataset.key + '&index=' + index;
    11671363    }
    11681364
     
    12491445                    response = JSON.parse(http.response.toString());
    12501446                } catch (e) {
    1251                     console.log(http.response.toString());
    1252                     console.log(url);
    12531447                    return console.error(e);
    12541448                }
     
    12611455
    12621456// --------------------- Inputbox: Records
    1263 let ctInputBoxRecords_dynamic_filter = [];
     1457
    12641458
    12651459function ctInputBoxRecords_removeOptions(selectobj) {
     
    12701464}
    12711465
    1272 function ctInputBoxRecords_addItem(control_name, control_name_postfix) {
    1273 
    1274     let o = document.getElementById(control_name + control_name_postfix);
    1275     o.selectedIndex = 0;
    1276 
    1277     if (ctInputBoxRecords_dynamic_filter[control_name] !== '') {
    1278 
    1279         let ctInputBoxRecords_current_value = document.getElementById(control_name + '_ctInputBoxRecords_current_value');
    1280         if (ctInputBoxRecords_current_value)
    1281             ctInputBoxRecords_current_value.innerHTML = '';
    1282 
    1283         let SQLJoinLink = document.getElementById(control_name + control_name_postfix + 'SQLJoinLink');
    1284         if (SQLJoinLink)// {
    1285             SQLJoinLink.selectedIndex = 0;
    1286         ctInputbox_UpdateSQLJoinLink(control_name, control_name_postfix);
    1287     }
    1288 
    1289     document.getElementById(control_name + '_addButton').style.visibility = "hidden";
    1290     document.getElementById(control_name + '_addBox').style.visibility = "visible";
    1291 }
    12921466
    12931467function ctInputBoxRecords_DoAddItem(control_name, control_name_postfix) {
     
    13451519    if (isHidden) {
    13461520        ctInputBoxRecords_cancel(control_name, '_selector');
    1347         ctInputBoxRecords_addItem(control_name, '_selector')
     1521        CTEditHelper.ctInputBoxRecords_addItem(control_name, '_selector')
    13481522    }
    13491523}
     
    13741548
    13751549        if (CTEditHelper.cmsName === "Joomla")
    1376             deleteImage = ctWebsiteRoot + 'components/com_customtables/libraries/customtables/media/images/icons/cancel.png';
     1550            deleteImage = CTEditHelper.websiteRoot + 'components/com_customtables/libraries/customtables/media/images/icons/cancel.png';
    13771551        else if (CTEditHelper.cmsName === "WordPress")
    1378             deleteImage = ctWebsiteRoot + 'wp-content/plugins/customtables/libraries/customtables/media/images/icons/cancel.png';
     1552            deleteImage = CTEditHelper.websiteRoot + 'wp-content/plugins/customtables/libraries/customtables/media/images/icons/cancel.png';
    13791553
    13801554        v += '<td style="border-bottom:1px dotted grey;min-width:16px;">';
     
    14211595}
    14221596
    1423 function ctInputbox_UpdateSQLJoinLink(control_name, control_name_postfix) {
    1424     //Old calls replaced
    1425     setTimeout(ctInputbox_UpdateSQLJoinLink_do(control_name, control_name_postfix), 100);
    1426 }
    1427 
    1428 function ctInputbox_UpdateSQLJoinLink_do(control_name, control_name_postfix) {
    1429     //Old calls replaced
    1430     let controlElement = document.getElementById(control_name);
    1431     let selectedControlElements = Array.from(controlElement.options)
    1432         .filter(option => option.selected)
    1433         .map(option => option.value);
    1434 
    1435     let l = document.getElementById(control_name + control_name_postfix);
    1436     let o = document.getElementById(control_name + 'SQLJoinLink');
    1437     let v = '';
    1438 
    1439     if (o) {
    1440         if (o.selectedIndex === -1)
    1441             return false;
    1442 
    1443         v = o.options[o.selectedIndex].value;
    1444     }
    1445 
    1446     let selectedValue = null
    1447     let ctInputBoxRecords_current_value = document.getElementById(control_name + '_ctInputBoxRecords_current_value');
    1448 
    1449     if (ctInputBoxRecords_current_value)
    1450         selectedValue = String(ctInputBoxRecords_current_value.innerHTML);
    1451 
    1452     ctInputBoxRecords_removeOptions(l);
    1453 
    1454     if (control_name_postfix !== '_selector') {
    1455         let opt = document.createElement("option");
    1456         opt.value = '0';
    1457         opt.innerHTML = TranslateText('COM_CUSTOMTABLES_SELECT');
    1458         l.appendChild(opt);
    1459     }
    1460 
    1461     let elements = JSON.parse(document.getElementById(control_name + control_name_postfix + '_elements').textContent);
    1462     let elementsID = document.getElementById(control_name + control_name_postfix + '_elementsID').innerHTML.split(",");
    1463     let elementsPublished = document.getElementById(control_name + control_name_postfix + '_elementsPublished').innerHTML.split(",");
    1464 
    1465     let filterElement = document.getElementById(control_name + control_name_postfix + '_elementsFilter');
    1466     let elementsFilter = []
    1467     if (filterElement)
    1468         elementsFilter = filterElement.innerHTML.split(";");
    1469 
    1470     for (let i = 0; i < elements.length; i++) {
    1471         let f = elementsFilter[i];
    1472 
    1473         if (elements[i] !== "") {
    1474 
    1475             let eid = String(elementsID[i]);
    1476             if (selectedControlElements.indexOf(eid) === -1) {
    1477 
    1478                 let published = parseInt(elementsPublished[i]);
    1479 
    1480                 if (typeof f != "undefined") {
    1481                     let f_list = f.split(",");
    1482 
    1483                     if (f_list.indexOf(v) !== -1) {
    1484                         let opt = document.createElement("option");
    1485                         opt.value = eid;
    1486                         if (eid === selectedValue)
    1487                             opt.selected = true;
    1488 
    1489                         if (published === 0)
    1490                             opt.style.cssText = "color:red;";
    1491 
    1492                         opt.innerHTML = elements[i];
    1493                         l.appendChild(opt);
    1494                     }
    1495                 } else {
    1496 
    1497                     let opt = document.createElement("option");
    1498                     opt.value = eid;
    1499                     if (eid === selectedValue)
    1500                         opt.selected = true;
    1501 
    1502                     if (published === 0)
    1503                         opt.style.cssText = "color:red;";
    1504 
    1505                     opt.innerHTML = elements[i];
    1506                     l.appendChild(opt);
    1507                 }
    1508             }
    1509         }
    1510     }
    1511 
    1512     return true;
    1513 }
    15141597
    15151598// ------------------------ Google Map coordinates
     
    15711654}
    15721655
    1573 let ct_signaturePad_fields = [];
    1574 let ct_signaturePad = [];
    1575 let ct_signaturePad_formats = [];
    1576 
    1577 function ctInputbox_signature(inputbox_id, width, height, format) {
    1578 
    1579     let canvas = document.getElementById(inputbox_id + '_canvas');
    1580 
    1581     ct_signaturePad_fields.push(inputbox_id);
    1582     ct_signaturePad[inputbox_id] = new SignaturePad(canvas, {
    1583         backgroundColor: "rgb(255, 255, 255)"
    1584     });
    1585 
    1586     ct_signaturePad_formats[inputbox_id] = format;
    1587 
    1588     canvas.width = width;
    1589     canvas.height = height;
    1590     canvas.getContext("2d").scale(1, 1);
    1591 
    1592     document.getElementById(inputbox_id + '_clear').addEventListener('click', function () {
    1593         ct_signaturePad[inputbox_id].clear();
    1594     });
    1595 }
    1596 
    1597 function ctInputbox_signature_apply() {
    1598 
    1599     if (ct_signaturePad_fields.length === 0)
    1600         return true;
    1601 
    1602     let inputbox_id = ct_signaturePad_fields[0];
    1603 
    1604     if (ct_signaturePad[inputbox_id].isEmpty()) {
    1605         alert(TranslateText('COM_CUSTOMTABLES_JS_SIGNATURE_REQUIRED'));
    1606         return false;
    1607     } else {
    1608 
    1609         let format = ct_signaturePad_formats[inputbox_id];
    1610 
    1611         //let dataURL = ct_signaturePad[inputbox_id].toDataURL('image/'+format+'+xml');
    1612         let dataURL = ct_signaturePad[inputbox_id].toDataURL('image/' + format);
    1613         document.getElementById(inputbox_id).setAttribute("value", dataURL);
    1614         return true;
    1615     }
    1616 }
    16171656
    16181657/*
     
    18051844
    18061845        if (link.length === 2)//to make sure that it will work in the back-end
    1807             url = ctWebsiteRoot + 'administrator/index.php?option=com_customtables&view=catalog&tmpl=component&from=json&key=' + key + '&index=0&where=' + encodeURIComponent(where);
     1846            url = CTEditHelper.websiteRoot + 'administrator/index.php?option=com_customtables&view=catalog&tmpl=component&from=json&key=' + key + '&index=0&where=' + encodeURIComponent(where);
    18081847        else
    1809             url = ctWebsiteRoot + 'index.php?option=com_customtables&view=catalog&tmpl=component&from=json&key=' + key + '&index=0&where=' + encodeURIComponent(where);
     1848            url = CTEditHelper.websiteRoot + 'index.php?option=com_customtables&view=catalog&tmpl=component&from=json&key=' + key + '&index=0&where=' + encodeURIComponent(where);
    18101849
    18111850    } else if (CTEditHelper.cmsName === "WordPress") {
    1812         url = ctWebsiteRoot + 'index.php?page=customtables-api-tablejoin&key=' + key + '&index=0&where=' + encodeURIComponent(where);
     1851        url = CTEditHelper.websiteRoot + 'index.php?page=customtables-api-tablejoin&key=' + key + '&index=0&where=' + encodeURIComponent(where);
    18131852        console.error(url);
    18141853        console.error("updateChildTableJoinField is going to be supported by WP yet.");
     
    18441883    for (let i = 0; i < valueFiltersNames.length; i++) {
    18451884        if (valueFiltersNames[i] !== null) {
    1846             let value = response['record']['es_' + valueFiltersNames[i]];
    1847             NewValueFilters.push(value);
     1885            console.warn("response", response['record']);
     1886            console.warn("valueFiltersNames[i]", valueFiltersNames[i]);
     1887
     1888            let value = "";
     1889            if (response['record']) {
     1890                value = response['record']['es_' + valueFiltersNames[i]];
     1891                NewValueFilters.push(value);
     1892            }
    18481893
    18491894            let index = i - 1;
     
    18741919
    18751920        if (link.length === 2)//to make sure that it will work in the back-end
    1876             url = ctWebsiteRoot + 'administrator/index.php?option=com_customtables&view=catalog&tmpl=component&from=json&key=' + key + '&index=0&limit=20&';
     1921            url = CTEditHelper.websiteRoot + 'administrator/index.php?option=com_customtables&view=catalog&tmpl=component&from=json&key=' + key + '&index=0&limit=20&';
    18771922        else
    1878             url = ctWebsiteRoot + 'index.php?option=com_customtables&view=catalog&tmpl=component&from=json&key=' + key + '&index=0&limit=20&';
     1923            url = CTEditHelper.websiteRoot + 'index.php?option=com_customtables&view=catalog&tmpl=component&from=json&key=' + key + '&index=0&limit=20&';
    18791924
    18801925    } else if (CTEditHelper.cmsName === "WordPress") {
  • customtables/trunk/libraries/customtables/media/js/extratasks.js

    r3219911 r3262394  
    5454function ctQueryAPI(task, old_params, new_params, tableid, fieldid) {
    5555    let parts = location.href.split("/administrator/");
    56     let websiteroot = parts[0] + "/administrator/";
    57     let url = websiteroot + "index.php?option=com_customtables&view=api&frmt=json&task=" + task + "&old_typeparams=" + old_params;
     56    let url = parts[0] + "/administrator/index.php?option=com_customtables&view=api&frmt=json&task=" + task + "&old_typeparams=" + old_params;
    5857    url += "&tableid=" + tableid;
    5958    url += "&new_typeparams=" + new_params + "&fieldid=" + fieldid + "&startindex=" + extraTasksUpdate_startindex + "&stepsize=" + extraTasksUpdate_stepsize;
  • customtables/trunk/libraries/customtables/media/js/layoutwizard.js

    r3252609 r3262394  
    6363    if (window.Joomla instanceof Object) {
    6464        const parts = location.href.split("/administrator/");
    65         const websiteRoot = parts[0] + "/administrator/";
    66         url = websiteRoot + "index.php?option=com_customtables&view=api&frmt=json&task=getfields&tableid=" + tableid;
     65        url = parts[0] + "/administrator/index.php?option=com_customtables&view=api&frmt=json&task=getfields&tableid=" + tableid;
    6766    } else if (document.body.classList.contains('wp-admin') || document.querySelector('#wpadminbar')) {
    6867        let parts = location.href.split("wp-admin/admin.php?");
  • customtables/trunk/libraries/customtables/media/js/modal.js

    r3202599 r3262394  
    148148    for (let i = 0; i < scripts.length; i++) {
    149149        if (scripts[i].src === "") {
    150             eval(scripts[i].innerHTML);
     150
     151            try {
     152                eval(scripts[i].innerHTML);
     153            } catch (error) {
     154                console.warn("Executing script:", scripts[i].innerHTML);
     155                console.warn("Error executing script:", error);
     156            }
    151157        }
    152158    }
    153159}
    154 
    155160
    156161function ctHidePopUp() {
  • customtables/trunk/libraries/customtables/media/js/uploader.js

    r3219911 r3262394  
    88 **/
    99
    10 const uploaderParams = [];
     10if (!window.uploaderParams) { // This works only if uploaderParams is defined globally.
     11    window.uploaderParams = [];
     12}
    1113
    1214function updateUploadedFileBox(index) {
  • customtables/trunk/libraries/customtables/media/xml/tags.xml

    r3250784 r3262394  
    959959                <param name="parameter" label="Parameter" type="string" description="Url query parameter"
    960960                       example="firstname"/>
     961            </params>
     962        </tag>
     963
     964        <tag name="getwhere" label="Query Where" startchar="{" endchar="}"
     965             description="Generates and returns a URL query string segment for the 'where' parameter. This tag is useful when constructing URLs dynamically based on specific conditions or filtering criteria. For example, to retrieve entries created on a particular date, you can use: ?view=catalog&#38;where=CreatedAt%3D2025-03-20."
     966             twigclass="url"
     967             wordpress="true">
     968            <params>
     969                <param name="parameter" label="Parameter" type="string"
     970                       description="The name of the field to filter by within the URL query. This parameter should match the field name used in your database or catalog."
     971                       example="CreatedAt"/>
    961972            </params>
    962973        </tag>
  • customtables/trunk/readme.txt

    r3259510 r3262394  
    66Requires at least: 6.0
    77Tested up to: 6.7.2
    8 Stable tag: 1.5.7
     8Stable tag: 1.5.8
    99Requires PHP: 7.4
    1010License: GPLv2
     
    5050
    5151== Changelog ==
     52
     53= 1.5.8 =
     54
     55- Fixed import issue when tables have different field prefixes.
     56- Fixed addForeignKey for user fields.
     57- Fixed removeForeignKey.
     58- {{ html.pagination }} added to Auto Layout Creator (WP) & default simple catalog layout.
     59- Search by exact date in datetime fields added.
     60- Date field search now applies global search filter.
     61- Fixed ordering by User Field in WP.
     62- Fixed current language detection in WP.
     63- Error message text improved for better clarity.
     64- Fixed domain validation bug in URL Field type.
     65- Fixed Lookup Table self-parent value selection issue.
     66- Fixed delete confirmation message (now correctly shows item name instead of ID).
     67- Prevented duplicate JS function declarations.
     68- Added new virtual field option: Stored Decimal.
    5269
    5370= 1.5.7 =
Note: See TracChangeset for help on using the changeset viewer.