| 1 |
<?php |
| 2 |
|
| 3 |
/** |
| 4 |
* CF7 Extension |
| 5 |
* |
| 6 |
* @package NotificationX\Extensions |
| 7 |
*/ |
| 8 |
|
| 9 |
namespace NotificationX\Extensions\NJF; |
| 10 |
|
| 11 |
use NotificationX\Core\Helper; |
| 12 |
use NotificationX\Core\Rules; |
| 13 |
use NotificationX\GetInstance; |
| 14 |
use NotificationX\Extensions\Extension; |
| 15 |
use NotificationX\Extensions\GlobalFields; |
| 16 |
|
| 17 |
/** |
| 18 |
* NinjaForms Extension |
| 19 |
* @method static NinjaForms get_instance($args = null) |
| 20 |
*/ |
| 21 |
class NinjaForms extends Extension { |
| 22 |
/** |
| 23 |
* Instance of NinjaForms |
| 24 |
* |
| 25 |
* @var NinjaForms |
| 26 |
*/ |
| 27 |
use GetInstance; |
| 28 |
|
| 29 |
public $priority = 15; |
| 30 |
public $id = 'njf'; |
| 31 |
public $img = ''; |
| 32 |
public $doc_link = 'https://notificationx.com/docs/contact-form-submission-alert/'; |
| 33 |
public $types = 'form'; |
| 34 |
public $module = 'modules_njf'; |
| 35 |
public $module_priority = 10; |
| 36 |
public $class = 'Ninja_Forms'; |
| 37 |
|
| 38 |
/** |
| 39 |
* Initially Invoked when initialized. |
| 40 |
*/ |
| 41 |
public function __construct() { |
| 42 |
parent::__construct(); |
| 43 |
} |
| 44 |
|
| 45 |
public function init_extension() |
| 46 |
{ |
| 47 |
$this->title = __('Ninja Forms', 'notificationx'); |
| 48 |
$this->module_title = __('Ninja Forms', 'notificationx'); |
| 49 |
} |
| 50 |
|
| 51 |
public function init() { |
| 52 |
parent::init(); |
| 53 |
|
| 54 |
add_action('ninja_forms_after_submission', array($this, 'save_new_records')); |
| 55 |
} |
| 56 |
|
| 57 |
public function init_fields(){ |
| 58 |
parent::init_fields(); |
| 59 |
add_filter('nx_form_list', [$this, 'nx_form_list'], 9); |
| 60 |
|
| 61 |
} |
| 62 |
|
| 63 |
/** |
| 64 |
* This functions is hooked |
| 65 |
* |
| 66 |
* @return void |
| 67 |
*/ |
| 68 |
public function admin_actions() { |
| 69 |
parent::admin_actions(); |
| 70 |
|
| 71 |
add_filter("nx_can_entry_{$this->id}", array($this, 'can_entry'), 10, 3); |
| 72 |
} |
| 73 |
|
| 74 |
/** |
| 75 |
* This functions is hooked |
| 76 |
* |
| 77 |
* @hooked nx_public_action |
| 78 |
* @return void |
| 79 |
*/ |
| 80 |
public function public_actions() { |
| 81 |
parent::public_actions(); |
| 82 |
|
| 83 |
} |
| 84 |
|
| 85 |
public function source_error_message($messages) { |
| 86 |
if (!$this->class_exists()) { |
| 87 |
$url = admin_url('plugin-install.php?s=ninja+forms&tab=search&type=term'); |
| 88 |
$messages[$this->id] = [ |
| 89 |
'message' => sprintf( '%s <a href="%s" target="_blank">%s</a> %s', |
| 90 |
__( 'You have to install', 'notificationx' ), |
| 91 |
$url, |
| 92 |
__( 'Ninja Forms', 'notificationx' ), |
| 93 |
__( 'plugin first.', 'notificationx' ) |
| 94 |
), |
| 95 |
'html' => true, |
| 96 |
'type' => 'error', |
| 97 |
'rules' => Rules::is('source', $this->id), |
| 98 |
]; |
| 99 |
} |
| 100 |
return $messages; |
| 101 |
} |
| 102 |
|
| 103 |
public function nx_form_list($forms) { |
| 104 |
$_forms = GlobalFields::get_instance()->normalize_fields($this->get_forms(), 'source', $this->id); |
| 105 |
return array_merge($forms, $_forms); |
| 106 |
} |
| 107 |
|
| 108 |
public function get_forms() { |
| 109 |
$forms = []; |
| 110 |
if (!class_exists('Ninja_Forms')) { |
| 111 |
return []; |
| 112 |
} |
| 113 |
global $wpdb; |
| 114 |
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- False positive: the query is prepared via $this->wpdb->prepare(), which this sniff does not recognise, and only $wpdb->prefix table names are interpolated. Audited 2026-07-16. |
| 115 |
$form_result = $wpdb->get_results('SELECT id, title FROM `' . $wpdb->prefix . 'nf3_forms` ORDER BY title'); |
| 116 |
if (!empty($form_result)) { |
| 117 |
foreach ($form_result as $form) { |
| 118 |
$key = $this->key($form->id); |
| 119 |
$forms[$key] = $form->title; |
| 120 |
} |
| 121 |
} |
| 122 |
|
| 123 |
return $forms; |
| 124 |
} |
| 125 |
|
| 126 |
public function saved_post($post, $data, $nx_id) { |
| 127 |
$this->delete_notification(null, $nx_id); |
| 128 |
$this->get_notification_ready($data); |
| 129 |
} |
| 130 |
|
| 131 |
/** |
| 132 |
* This function responsible for making ready the notifications for the first time |
| 133 |
* we have made a notification. |
| 134 |
* |
| 135 |
* @param string $type |
| 136 |
* @param array $data |
| 137 |
* @return void |
| 138 |
*/ |
| 139 |
public function get_notification_ready($data = array()) { |
| 140 |
$form_list = !empty($data['__form_list']['value']) ? $data['__form_list']['value'] : (!empty($data['form_list']['value']) ? $data['form_list']['value'] : null); |
| 141 |
if( !empty($form_list) ) { |
| 142 |
$form_list = explode('_',$form_list); |
| 143 |
$submissions = $this->get_submissions($form_list[1], $data); |
| 144 |
if( count( $submissions ) > 0 ) { |
| 145 |
$entries = []; |
| 146 |
foreach ( $submissions as $submission ) { |
| 147 |
if( !empty( $submission ) ) { |
| 148 |
if (!empty($submission)) { |
| 149 |
$key = $this->key($form_list[1]); |
| 150 |
$entries[] = [ |
| 151 |
'nx_id' => $data['nx_id'], |
| 152 |
'source' => $this->id, |
| 153 |
'entry_key' => $key, |
| 154 |
'data' => $submission, |
| 155 |
]; |
| 156 |
} |
| 157 |
} |
| 158 |
} |
| 159 |
$this->update_notifications($entries); |
| 160 |
} |
| 161 |
} |
| 162 |
} |
| 163 |
|
| 164 |
public function get_submissions( $form_id, $data ) { |
| 165 |
$subs = Ninja_Forms()->form( $form_id )->get_subs( array(), FALSE ); |
| 166 |
$fields = Ninja_Forms()->form( $form_id )->get_fields(); |
| 167 |
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context. |
| 168 |
$hidden_field_types = apply_filters( 'nf_sub_hidden_field_types', array() ); |
| 169 |
$display_from = !empty( $data['display_from'] ) ? intval( $data['display_from'] ) : 30; |
| 170 |
$cutoff_timestamp = strtotime("-{$display_from} days"); |
| 171 |
|
| 172 |
foreach( $subs as $sub ){ |
| 173 |
$timestamp = strtotime( $sub->get_sub_date('Y-m-d H:i') ); |
| 174 |
// Skip submissions older than $display_from days |
| 175 |
if ($timestamp < $cutoff_timestamp) { |
| 176 |
continue; |
| 177 |
} |
| 178 |
$value[ 'title' ] = $sub->get_form_title(); |
| 179 |
$value[ 'timestamp' ] = $timestamp; |
| 180 |
|
| 181 |
// boolean - does this submission use a repeater |
| 182 |
$hasRepeater = false; |
| 183 |
// How many repeater submissions does this submission have |
| 184 |
$submissionCount = 0; |
| 185 |
// Ids of fields in the repeater |
| 186 |
$fieldsetFieldIds=[]; |
| 187 |
|
| 188 |
foreach ($fields as $field_id => $field) { |
| 189 |
// Bypass existing method if fieldset repeater |
| 190 |
if('repeater'===$field->get_setting('type')){ |
| 191 |
$hasRepeater = true; |
| 192 |
|
| 193 |
$fieldsetSubmission= $sub->get_field_value( $field_id ); |
| 194 |
$fieldsetSettings = $field->get_settings(); |
| 195 |
$fieldsetLabels = Ninja_Forms()->fieldsetRepeater |
| 196 |
->getFieldsetLabels($field_id, $fieldsetSettings, true); |
| 197 |
|
| 198 |
foreach($fieldsetLabels as $fieldsetFieldId =>$fieldsetFieldLabel){ |
| 199 |
|
| 200 |
$fieldsetFieldIds[]=$fieldsetFieldId; |
| 201 |
|
| 202 |
$field_labels[$fieldsetFieldId]= \WPN_Helper::maybe_escape_csv_column( $fieldsetFieldLabel ); |
| 203 |
|
| 204 |
$fieldType = Ninja_Forms()->fieldsetRepeater->getFieldtype($fieldsetFieldId, $fieldsetSettings); |
| 205 |
|
| 206 |
$fieldsetFieldSubmissionCollection=Ninja_Forms()->fieldsetRepeater |
| 207 |
->extractSubmissionsByFieldsetField($fieldsetFieldId, $fieldsetSubmission); |
| 208 |
|
| 209 |
$submissionCount = count($fieldsetFieldSubmissionCollection); |
| 210 |
|
| 211 |
foreach ($fieldsetFieldSubmissionCollection as &$fieldsetFieldSubmission) { |
| 212 |
|
| 213 |
if(is_array($fieldsetFieldSubmission['value'])){ |
| 214 |
|
| 215 |
$fieldsetFieldSubmission['value']= implode(', ',$fieldsetFieldSubmission['value']); |
| 216 |
} |
| 217 |
} |
| 218 |
|
| 219 |
|
| 220 |
$value[$fieldsetFieldId]= array_column($fieldsetFieldSubmissionCollection,'value'); |
| 221 |
} |
| 222 |
|
| 223 |
}else{ |
| 224 |
if (!is_int($field_id)) continue; |
| 225 |
if( in_array( $field->get_setting( 'type' ), $hidden_field_types ) ) continue; |
| 226 |
|
| 227 |
if ( $field->get_setting( 'admin_label' ) ) { |
| 228 |
$field_labels[ $field->get_id() ] = \WPN_Helper::maybe_escape_csv_column( $field->get_setting( 'admin_label' ) ); |
| 229 |
} else { |
| 230 |
$field_labels[ $field->get_id() ] = \WPN_Helper::maybe_escape_csv_column( $field->get_setting( 'label' ) ); |
| 231 |
} |
| 232 |
|
| 233 |
$field_value = maybe_unserialize( $sub->get_field_value( $field_id ) ); |
| 234 |
|
| 235 |
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context. |
| 236 |
$field_value = apply_filters('nf_subs_export_pre_value', $field_value, $field_id); |
| 237 |
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context. |
| 238 |
$field_value = apply_filters('ninja_forms_subs_export_pre_value', $field_value, $field_id, $form_id); |
| 239 |
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context. |
| 240 |
$field_value = apply_filters( 'ninja_forms_subs_export_field_value_' . $field->get_setting( 'type' ), $field_value, $field ); |
| 241 |
|
| 242 |
if ( is_array($field_value ) ) { |
| 243 |
$field_value = implode( ',', $field_value ); |
| 244 |
} |
| 245 |
|
| 246 |
$value[ $field_labels[ $field->get_id() ] ] = $field_value; |
| 247 |
|
| 248 |
} |
| 249 |
} |
| 250 |
|
| 251 |
if(!$hasRepeater){ |
| 252 |
$value_array[] = $value; |
| 253 |
}else{ |
| 254 |
// The the submission has repeater fields, create an indexed array first |
| 255 |
$repeatingValueArray=[]; |
| 256 |
$index = 0; |
| 257 |
|
| 258 |
do { |
| 259 |
// iterate each column in the row 'value' |
| 260 |
foreach($value as $fieldId=>$columnValue){ |
| 261 |
|
| 262 |
// If the column in the row value is not a repeater |
| 263 |
// fieldset field, simply copy it into a new row of the |
| 264 |
// repeating value array |
| 265 |
if(!in_array($fieldId,$fieldsetFieldIds)){ |
| 266 |
$repeatingValueArray[$index][]=$columnValue; |
| 267 |
}else{ |
| 268 |
|
| 269 |
// If the column in the row value is a repeater |
| 270 |
// fieldset field, copy the next submission index value |
| 271 |
|
| 272 |
|
| 273 |
$repeatingValueArray[$index][]=$columnValue[$index]; |
| 274 |
} |
| 275 |
} |
| 276 |
// at the end of the row value columns, increment the index |
| 277 |
// until all the submission index values are added |
| 278 |
$index++; |
| 279 |
} while ($index < $submissionCount); |
| 280 |
|
| 281 |
// After iterating the row value once for each submission index, |
| 282 |
// add the repeatingValueArray to the value array |
| 283 |
|
| 284 |
$value_array[]=$repeatingValueArray; |
| 285 |
} |
| 286 |
|
| 287 |
} |
| 288 |
return $value_array; |
| 289 |
} |
| 290 |
|
| 291 |
public function restResponse($args) { |
| 292 |
if (!class_exists('Ninja_Forms')) { |
| 293 |
return []; |
| 294 |
} |
| 295 |
|
| 296 |
global $wpdb; |
| 297 |
$table_name = $wpdb->prefix . 'nf3_forms'; |
| 298 |
if (!empty($args['inputValue'])) { |
| 299 |
$limit = 10; |
| 300 |
// Prepare the query with a LIKE condition |
| 301 |
$query = $wpdb->prepare( |
| 302 |
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- False positive: the query is prepared via $this->wpdb->prepare(), which this sniff does not recognise, and only $wpdb->prefix table names are interpolated. Audited 2026-07-16. |
| 303 |
"SELECT id, title FROM {$table_name} WHERE title LIKE %s LIMIT %d", |
| 304 |
'%' . $wpdb->esc_like($args['inputValue']) . '%',$limit |
| 305 |
); |
| 306 |
// Execute the query and retrieve the results |
| 307 |
// phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- False positive: the query is prepared via $this->wpdb->prepare(), which this sniff does not recognise, and only $wpdb->prefix table names are interpolated. Audited 2026-07-16. |
| 308 |
$form_result = $wpdb->get_results($query); |
| 309 |
if (!empty($form_result)) { |
| 310 |
foreach ($form_result as $form) { |
| 311 |
$key = $this->key($form->id); |
| 312 |
$forms[$key] = $form->title; |
| 313 |
} |
| 314 |
} |
| 315 |
$result = array_values(GlobalFields::get_instance()->normalize_fields($forms, 'source', $this->id)); |
| 316 |
return $result; |
| 317 |
} |
| 318 |
|
| 319 |
if (isset($args['form_id'])) { |
| 320 |
if( is_array( $args['form_id'] ) ) { |
| 321 |
$form_id = intval($args['form_id']['value']); |
| 322 |
}else{ |
| 323 |
$form_id = intval($args['form_id']); |
| 324 |
} |
| 325 |
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- False positive: the query is prepared via $this->wpdb->prepare(), which this sniff does not recognise, and only $wpdb->prefix table names are interpolated. Audited 2026-07-16. |
| 326 |
$queryresult = $wpdb->get_results('SELECT meta_value FROM `' . $wpdb->prefix . 'nf3_form_meta` WHERE parent_id = ' . $form_id . ' AND meta_key = "formContentData"'); |
| 327 |
|
| 328 |
if(isset($queryresult[0]) && isset($queryresult[0]->meta_value)){ |
| 329 |
$formdata = $queryresult[0]->meta_value; |
| 330 |
|
| 331 |
$keys = $this->keys_generator($formdata); |
| 332 |
|
| 333 |
$returned_keys = array(); |
| 334 |
|
| 335 |
if (is_array($keys) && !empty($keys)) { |
| 336 |
foreach ($keys as $key) { |
| 337 |
$returned_keys[] = array( |
| 338 |
'label' => ucwords(str_replace('_', ' ', str_replace('-', ' ', $key))), |
| 339 |
'value' => "tag_$key", |
| 340 |
); |
| 341 |
} |
| 342 |
|
| 343 |
return $returned_keys; |
| 344 |
} |
| 345 |
} |
| 346 |
} |
| 347 |
wp_send_json_error([]); |
| 348 |
} |
| 349 |
|
| 350 |
public function keys_generator($fieldsString) { |
| 351 |
$fields = array(); |
| 352 |
$fieldsdata = unserialize($fieldsString); |
| 353 |
if (!empty($fieldsdata)) { |
| 354 |
foreach ($fieldsdata as $field) { |
| 355 |
if(!is_string($field)){ |
| 356 |
$field = !empty($field['cells'][0]['fields'][0]) ? $field['cells'][0]['fields'][0] : null; |
| 357 |
} |
| 358 |
if ($field && Helper::filter_contactform_key_names($field)) { |
| 359 |
$fields[] = Helper::rename_contactform_key_names($field); |
| 360 |
} |
| 361 |
} |
| 362 |
} |
| 363 |
return $fields; |
| 364 |
} |
| 365 |
|
| 366 |
public function save_new_records($form_data) { |
| 367 |
foreach ($form_data['fields'] as $field) { |
| 368 |
$arr = Helper::rename_contactform_key_names($field['key']); |
| 369 |
$data[$arr] = $field['value']; |
| 370 |
} |
| 371 |
$data['title'] = $form_data['settings']['title']; |
| 372 |
$data['timestamp'] = time(); |
| 373 |
|
| 374 |
if (!empty($data)) { |
| 375 |
$key = $this->key($form_data['form_id']); |
| 376 |
$this->save([ |
| 377 |
'source' => $this->id, |
| 378 |
'entry_key' => $key, |
| 379 |
'data' => $data, |
| 380 |
]); |
| 381 |
return true; |
| 382 |
} |
| 383 |
return false; |
| 384 |
} |
| 385 |
|
| 386 |
public function key($key) { |
| 387 |
$key = $this->id . '_' . $key; |
| 388 |
return $key; |
| 389 |
} |
| 390 |
|
| 391 |
/** |
| 392 |
* Limit entry by selected form in 'Select a Form'; |
| 393 |
* |
| 394 |
* @param [type] $return |
| 395 |
* @param [type] $entry |
| 396 |
* @param [type] $settings |
| 397 |
* @return boolean |
| 398 |
*/ |
| 399 |
public function can_entry($return, $entry, $settings){ |
| 400 |
if(!empty($settings['form_list']) && !empty($entry['entry_key'])){ |
| 401 |
$selected_form = $settings['form_list']; |
| 402 |
$form_id = $entry['entry_key']; |
| 403 |
if($selected_form != $form_id){ |
| 404 |
return false; |
| 405 |
} |
| 406 |
|
| 407 |
} |
| 408 |
return $return; |
| 409 |
} |
| 410 |
|
| 411 |
public function doc() { |
| 412 |
/* translators: %1$s: Ninja Forms installed & configured link URL, %2$s: documentation link URL, %3$s: Watch video tutorial link URL, %4$s: Integration with Ninja Forms link URL, %5$s: WordPress Contact Forms Submission Rate link URL */ |
| 413 |
return sprintf(__('<p>Make sure that you have <a target="_blank" href="%1$s">Ninja Forms installed & configured</a> to use its campaign & form subscriptions data. For further assistance, check out our step by step <a target="_blank" href="%2$s">documentation</a>.</p> |
| 414 |
<p>🎦 <a target="_blank" href="%3$s">Watch video tutorial</a> to learn quickly</p> |
| 415 |
<p>👉 NotificationX <a target="_blank" href="%4$s">Integration with Ninja Forms</a></p> |
| 416 |
<p><strong>Recommended Blog:</strong></p> |
| 417 |
<p>🔥 Hacks to Increase Your <a target="_blank" href="%5$s">WordPress Contact Forms Submission Rate</a> Using NotificationX</p>', 'notificationx'), |
| 418 |
'https://wordpress.org/plugins/ninja-forms/', |
| 419 |
'https://notificationx.com/docs/ninja-forms/', |
| 420 |
'https://www.youtube.com/watch?v=Ibv84iGcBHE', |
| 421 |
'https://notificationx.com/integrations/ninja-forms/', |
| 422 |
'https://notificationx.com/blog/wordpress-contact-forms/' |
| 423 |
); |
| 424 |
} |
| 425 |
} |
| 426 |
|