Brak opisu

admin-utils.js 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  1. /* global wpforms_builder */
  2. ;
  3. var wpf = {
  4. cachedFields: {},
  5. savedState: false,
  6. initialSave: true,
  7. orders: {
  8. fields: [],
  9. choices: {}
  10. },
  11. // This file contains a collection of utility functions.
  12. /**
  13. * Start the engine.
  14. *
  15. * @since 1.0.1
  16. */
  17. init: function() {
  18. wpf.bindUIActions();
  19. // Init Radio Group for Checkboxes.
  20. wpf.initRadioGroupForCheckboxes();
  21. jQuery( wpf.ready );
  22. },
  23. /**
  24. * Document ready.
  25. *
  26. * @since 1.0.1
  27. */
  28. ready: function() {
  29. // Load initial form saved state.
  30. wpf.savedState = wpf.getFormState( '#wpforms-builder-form' );
  31. // Save field and choice order for sorting later.
  32. wpf.setFieldOrders();
  33. wpf.setChoicesOrders();
  34. },
  35. /**
  36. * Element bindings.
  37. *
  38. * @since 1.0.1
  39. */
  40. bindUIActions: function() {
  41. // The following items should all trigger the fieldUpdate trigger.
  42. jQuery( document ).on( 'wpformsFieldAdd', wpf.setFieldOrders );
  43. jQuery( document ).on( 'wpformsFieldDelete', wpf.setFieldOrders );
  44. jQuery( document ).on( 'wpformsFieldMove', wpf.setFieldOrders );
  45. jQuery( document ).on( 'wpformsFieldAdd', wpf.setChoicesOrders );
  46. jQuery( document ).on( 'wpformsFieldChoiceAdd', wpf.setChoicesOrders );
  47. jQuery( document ).on( 'wpformsFieldChoiceDelete', wpf.setChoicesOrders );
  48. jQuery( document ).on( 'wpformsFieldChoiceMove', wpf.setChoicesOrders );
  49. jQuery( document ).on( 'wpformsFieldAdd', wpf.fieldUpdate );
  50. jQuery( document ).on( 'wpformsFieldDelete', wpf.fieldUpdate );
  51. jQuery( document ).on( 'wpformsFieldMove', wpf.fieldUpdate );
  52. jQuery( document ).on( 'focusout', '.wpforms-field-option-row-label input', wpf.fieldUpdate );
  53. jQuery( document ).on( 'wpformsFieldChoiceAdd', wpf.fieldUpdate );
  54. jQuery( document ).on( 'wpformsFieldChoiceDelete', wpf.fieldUpdate );
  55. jQuery( document ).on( 'wpformsFieldChoiceMove', wpf.fieldUpdate );
  56. jQuery( document ).on( 'wpformsFieldDynamicChoiceToggle', wpf.fieldUpdate );
  57. jQuery( document ).on( 'focusout', '.wpforms-field-option-row-choices input.label', wpf.fieldUpdate );
  58. },
  59. /**
  60. * Store the order of the fields.
  61. *
  62. * @since 1.4.5
  63. */
  64. setFieldOrders: function() {
  65. wpf.orders.fields = [];
  66. jQuery( '.wpforms-field-option' ).each(function() {
  67. wpf.orders.fields.push( jQuery( this ).data( 'field-id' ) );
  68. });
  69. },
  70. /**
  71. * Store the order of the choices for each field.
  72. *
  73. * @since 1.4.5
  74. */
  75. setChoicesOrders: function() {
  76. wpf.orders.choices = {};
  77. jQuery( '.choices-list' ).each(function() {
  78. var fieldID = jQuery( this ).data( 'field-id' );
  79. wpf.orders.choices[ 'field_'+ fieldID ] = [];
  80. jQuery( this ).find( 'li' ).each( function() {
  81. wpf.orders.choices[ 'field_' + fieldID ].push( jQuery( this ).data( 'key' ) );
  82. });
  83. });
  84. },
  85. /**
  86. * Return the order of choices for a specific field.
  87. *
  88. * @since 1.4.5
  89. *
  90. * @param int id Field ID.
  91. *
  92. * @return array
  93. */
  94. getChoicesOrder: function( id ) {
  95. var choices = [];
  96. jQuery( '#wpforms-field-option-'+id ).find( '.choices-list li' ).each( function() {
  97. choices.push( jQuery( this ).data( 'key' ) );
  98. });
  99. return choices;
  100. },
  101. /**
  102. * Trigger fired for all field update related actions.
  103. *
  104. * @since 1.0.1
  105. */
  106. fieldUpdate: function() {
  107. var fields = wpf.getFields();
  108. jQuery( document ).trigger( 'wpformsFieldUpdate', [ fields ] );
  109. wpf.debug( 'fieldUpdate triggered' );
  110. },
  111. /**
  112. * Dynamically get the fields from the current form state.
  113. *
  114. * @since 1.0.1
  115. * @param array allowedFields
  116. * @param bool useCache
  117. * @return object
  118. */
  119. getFields: function( allowedFields, useCache ) {
  120. useCache = useCache || false;
  121. if ( useCache && ! jQuery.isEmptyObject(wpf.cachedFields) ) {
  122. // Use cache if told and cache is primed.
  123. var fields = jQuery.extend({}, wpf.cachedFields);
  124. wpf.debug('getFields triggered (cached)');
  125. } else {
  126. // Normal processing, get fields from builder and prime cache.
  127. var formData = wpf.formObject( '#wpforms-field-options' ),
  128. fields = formData.fields,
  129. fieldBlacklist = [ 'entry-preview', 'html', 'pagebreak' ];
  130. if (!fields) {
  131. return false;
  132. }
  133. for( var key in fields) {
  134. if ( ! fields[key].type || jQuery.inArray(fields[key].type, fieldBlacklist) > -1 ){
  135. delete fields[key];
  136. }
  137. }
  138. // Cache the all the fields now that they have been ordered and initially
  139. // processed.
  140. wpf.cachedFields = jQuery.extend({}, fields);
  141. wpf.debug('getFields triggered');
  142. }
  143. // If we should only return specfic field types, remove the others.
  144. if ( allowedFields && allowedFields.constructor === Array ) {
  145. for( var key in fields) {
  146. if ( jQuery.inArray( fields[key].type, allowedFields ) === -1 ){
  147. delete fields[key];
  148. }
  149. }
  150. }
  151. return fields;
  152. },
  153. /**
  154. * Get field settings object.
  155. *
  156. * @since 1.4.5
  157. *
  158. * @param int id Field ID.
  159. *
  160. * @return object
  161. */
  162. getField: function( id ) {
  163. var field = wpf.formObject( '#wpforms-field-option-'+id );
  164. return field.fields[ Object.keys( field.fields )[0] ];
  165. },
  166. /**
  167. * Toggle the loading state/indicator of a field option.
  168. *
  169. * @since 1.2.8
  170. *
  171. * @param {mixed} option jQuery object, or DOM element selector.
  172. * @param {boolean} unload True if you need to unload spinner, and vice versa.
  173. */
  174. fieldOptionLoading: function( option, unload ) {
  175. var $option = jQuery( option ),
  176. $label = $option.find( 'label' ),
  177. spinner = '<i class="wpforms-loading-spinner wpforms-loading-inline"></i>';
  178. unload = typeof unload !== 'undefined';
  179. if ( unload ) {
  180. $label.find( '.wpforms-loading-spinner' ).remove();
  181. $label.find( '.wpforms-help-tooltip' ).show();
  182. $option.find( 'input,select,textarea' ).prop( 'disabled', false );
  183. } else {
  184. $label.append( spinner );
  185. $label.find( '.wpforms-help-tooltip' ).hide();
  186. $option.find( 'input,select,textarea' ).prop( 'disabled', true );
  187. }
  188. },
  189. /**
  190. * Get form state.
  191. *
  192. * @since 1.3.8
  193. * @param object el
  194. */
  195. getFormState: function( el ) {
  196. // Serialize tested the most performant string we can use for
  197. // comparisons.
  198. return jQuery( el ).serialize();
  199. },
  200. /**
  201. * Remove items from an array.
  202. *
  203. * @since 1.0.1
  204. * @param array array
  205. * @param mixed item index/key
  206. * @return array
  207. */
  208. removeArrayItem: function(array, item) {
  209. var removeCounter = 0;
  210. for (var index = 0; index < array.length; index++) {
  211. if (array[index] === item) {
  212. array.splice(index, 1);
  213. removeCounter++;
  214. index--;
  215. }
  216. }
  217. return removeCounter;
  218. },
  219. /**
  220. * Sanitize string.
  221. *
  222. * @since 1.0.1
  223. * @deprecated 1.2.8
  224. *
  225. * @param {string} str String to sanitize.
  226. *
  227. * @returns {string} String after sanitization.
  228. */
  229. sanitizeString: function( str ) {
  230. if ( typeof str === 'string' || str instanceof String ) {
  231. return str.trim();
  232. }
  233. return str;
  234. },
  235. /**
  236. * Update query string in URL.
  237. *
  238. * @since 1.0.0
  239. */
  240. updateQueryString: function(key, value, url) {
  241. if (!url) url = window.location.href;
  242. var re = new RegExp("([?&])" + key + "=.*?(&|#|$)(.*)", "gi"),
  243. hash;
  244. if (re.test(url)) {
  245. if (typeof value !== 'undefined' && value !== null)
  246. return url.replace(re, '$1' + key + "=" + value + '$2$3');
  247. else {
  248. hash = url.split('#');
  249. url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, '');
  250. if (typeof hash[1] !== 'undefined' && hash[1] !== null)
  251. url += '#' + hash[1];
  252. return url;
  253. }
  254. } else {
  255. if (typeof value !== 'undefined' && value !== null) {
  256. var separator = url.indexOf('?') !== -1 ? '&' : '?';
  257. hash = url.split('#');
  258. url = hash[0] + separator + key + '=' + value;
  259. if (typeof hash[1] !== 'undefined' && hash[1] !== null)
  260. url += '#' + hash[1];
  261. return url;
  262. }
  263. else
  264. return url;
  265. }
  266. },
  267. /**
  268. * Get query string in a URL.
  269. *
  270. * @since 1.0.0
  271. */
  272. getQueryString: function(name) {
  273. var match = new RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
  274. return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
  275. },
  276. /**
  277. * Remove defined query parameter in the current URL.
  278. *
  279. * @see https://gist.github.com/simonw/9445b8c24ddfcbb856ec#gistcomment-3117674
  280. *
  281. * @since 1.5.8
  282. *
  283. * @param {string} name The name of the parameter to be removed.
  284. */
  285. removeQueryParam: function( name ) {
  286. if ( wpf.getQueryString( name ) ) {
  287. var replace = '[\\?&]' + name + '=[^&]+',
  288. re = new RegExp( replace );
  289. history.replaceState && history.replaceState(
  290. null, '', location.pathname + location.search.replace( re, '' ).replace( /^&/, '?' ) + location.hash
  291. );
  292. }
  293. },
  294. /**
  295. * Is number?
  296. *
  297. * @since 1.2.3
  298. *
  299. * @param {number|string} n Number to check.
  300. *
  301. * @returns {boolean} Whether this is a number.
  302. */
  303. isNumber: function( n ) {
  304. return ! isNaN( parseFloat( n ) ) && isFinite( n );
  305. },
  306. /**
  307. * Sanitize amount and convert to standard format for calculations.
  308. *
  309. * @since 1.2.6
  310. *
  311. * @param {string} amount Price amount to sanitize.
  312. *
  313. * @returns {string} Sanitized amount.
  314. */
  315. amountSanitize: function( amount ) {
  316. // Convert to string and allow only numbers, dots and commas.
  317. amount = String( amount ).replace( /[^0-9.,]/g, '' );
  318. if ( wpforms_builder.currency_decimal === ',' ) {
  319. if ( wpforms_builder.currency_thousands === '.' && amount.indexOf( wpforms_builder.currency_thousands ) !== -1 ) {
  320. amount = amount.replace( new RegExp( '\\' + wpforms_builder.currency_thousands, 'g' ), '' );
  321. } else if ( wpforms_builder.currency_thousands === '' && amount.indexOf( '.' ) !== -1 ) {
  322. amount = amount.replace( /\./g, '' );
  323. }
  324. amount = amount.replace( wpforms_builder.currency_decimal, '.' );
  325. } else if ( wpforms_builder.currency_thousands === ',' && ( amount.indexOf( wpforms_builder.currency_thousands ) !== -1 ) ) {
  326. amount = amount.replace( new RegExp( '\\' + wpforms_builder.currency_thousands, 'g' ), '' );
  327. }
  328. return wpf.numberFormat( amount, wpforms_builder.currency_decimals, '.', '' );
  329. },
  330. /**
  331. * Format amount.
  332. *
  333. * @since 1.2.6
  334. *
  335. * @param {string} amount Price amount to format.
  336. *
  337. * @returns {string} Formatted amount.
  338. */
  339. amountFormat: function( amount ) {
  340. amount = String( amount );
  341. // Format the amount
  342. if ( wpforms_builder.currency_decimal === ',' && ( amount.indexOf( wpforms_builder.currency_decimal ) !== -1 ) ) {
  343. var sepFound = amount.indexOf( wpforms_builder.currency_decimal );
  344. amount = amount.substr( 0, sepFound ) + '.' + amount.substr( sepFound + 1, amount.length - 1 );
  345. }
  346. // Strip , from the amount (if set as the thousands separator)
  347. if ( wpforms_builder.currency_thousands === ',' && ( amount.indexOf( wpforms_builder.currency_thousands ) !== -1 ) ) {
  348. amount = amount.replace( /,/g, '' );
  349. }
  350. if ( wpf.empty( amount ) ) {
  351. amount = 0;
  352. }
  353. return wpf.numberFormat( amount, wpforms_builder.currency_decimals, wpforms_builder.currency_decimal, wpforms_builder.currency_thousands );
  354. },
  355. /**
  356. * Format amount with currency symbol.
  357. *
  358. * @since 1.6.2
  359. *
  360. * @param {string} amount Amount to format.
  361. *
  362. * @returns {string} Formatted amount (for instance $ 128.00).
  363. */
  364. amountFormatCurrency: function( amount ) {
  365. var sanitized = wpf.amountSanitize( amount ),
  366. formatted = wpf.amountFormat( sanitized ),
  367. result;
  368. if ( wpforms_builder.currency_symbol_pos === 'right' ) {
  369. result = formatted + ' ' + wpforms_builder.currency_symbol;
  370. } else {
  371. result = wpforms_builder.currency_symbol + ' ' + formatted;
  372. }
  373. return result;
  374. },
  375. /**
  376. * Format number.
  377. *
  378. * @see http://locutus.io/php/number_format/
  379. *
  380. * @since 1.2.6
  381. *
  382. * @param {string} number Number to format.
  383. * @param {number} decimals How many decimals should be there.
  384. * @param {string} decimalSep What is the decimal separator.
  385. * @param {string} thousandsSep What is the thousands separator.
  386. *
  387. * @returns {string} Formatted number.
  388. */
  389. numberFormat: function( number, decimals, decimalSep, thousandsSep ) {
  390. number = ( number + '' ).replace( /[^0-9+\-Ee.]/g, '' );
  391. var n = ! isFinite( +number ) ? 0 : +number;
  392. var prec = ! isFinite( +decimals ) ? 0 : Math.abs( decimals );
  393. var sep = ( typeof thousandsSep === 'undefined' ) ? ',' : thousandsSep;
  394. var dec = ( typeof decimalSep === 'undefined' ) ? '.' : decimalSep;
  395. var s = '';
  396. var toFixedFix = function( n, prec ) {
  397. var k = Math.pow( 10, prec );
  398. return '' + ( Math.round( n * k ) / k ).toFixed( prec );
  399. };
  400. // @todo: for IE parseFloat(0.55).toFixed(0) = 0;
  401. s = ( prec ? toFixedFix( n, prec ) : '' + Math.round( n ) ).split( '.' );
  402. if ( s[ 0 ].length > 3 ) {
  403. s[ 0 ] = s[ 0 ].replace( /\B(?=(?:\d{3})+(?!\d))/g, sep );
  404. }
  405. if ( ( s[ 1 ] || '' ).length < prec ) {
  406. s[ 1 ] = s[ 1 ] || '';
  407. s[ 1 ] += new Array( prec - s[ 1 ].length + 1 ).join( '0' );
  408. }
  409. return s.join( dec );
  410. },
  411. /**
  412. * Empty check similar to PHP.
  413. *
  414. * @link http://locutus.io/php/empty/
  415. * @since 1.2.6
  416. */
  417. empty: function(mixedVar) {
  418. var undef;
  419. var key;
  420. var i;
  421. var len;
  422. var emptyValues = [undef, null, false, 0, '', '0'];
  423. for ( i = 0, len = emptyValues.length; i < len; i++ ) {
  424. if (mixedVar === emptyValues[i]) {
  425. return true;
  426. }
  427. }
  428. if ( typeof mixedVar === 'object' ) {
  429. for ( key in mixedVar ) {
  430. if ( mixedVar.hasOwnProperty( key ) ) {
  431. return false;
  432. }
  433. }
  434. return true;
  435. }
  436. return false;
  437. },
  438. /**
  439. * Debug output helper.
  440. *
  441. * @since 1.3.8
  442. * @param msg
  443. */
  444. debug: function( msg ) {
  445. if ( wpf.isDebug() ) {
  446. if ( typeof msg === 'object' || msg.constructor === Array ) {
  447. console.log( 'WPForms Debug:' );
  448. console.log( msg )
  449. } else {
  450. console.log( 'WPForms Debug: '+msg );
  451. }
  452. }
  453. },
  454. /**
  455. * Is debug mode.
  456. *
  457. * @since 1.3.8
  458. */
  459. isDebug: function() {
  460. return ( ( window.location.hash && '#wpformsdebug' === window.location.hash ) || wpforms_builder.debug );
  461. },
  462. /**
  463. * Focus the input/textarea and put the caret at the end of the text.
  464. *
  465. * @since 1.4.1
  466. */
  467. focusCaretToEnd: function( el ) {
  468. el.focus();
  469. var $thisVal = el.val();
  470. el.val('').val($thisVal);
  471. },
  472. /**
  473. * Creates a object from form elements.
  474. *
  475. * @since 1.4.5
  476. */
  477. formObject: function( el ) {
  478. var form = jQuery( el ),
  479. fields = form.find( '[name]' ),
  480. json = {},
  481. arraynames = {};
  482. for ( var v = 0; v < fields.length; v++ ){
  483. var field = jQuery( fields[v] ),
  484. name = field.prop( 'name' ).replace( /\]/gi,'' ).split( '[' ),
  485. value = field.val(),
  486. lineconf = {};
  487. if ( ( field.is( ':radio' ) || field.is( ':checkbox' ) ) && ! field.is( ':checked' ) ) {
  488. continue;
  489. }
  490. for ( var i = name.length-1; i >= 0; i-- ) {
  491. var nestname = name[i];
  492. if ( typeof nestname === 'undefined' ) {
  493. nestname = '';
  494. }
  495. if ( nestname.length === 0 ){
  496. lineconf = [];
  497. if ( typeof arraynames[name[i-1]] === 'undefined' ) {
  498. arraynames[name[i-1]] = 0;
  499. } else {
  500. arraynames[name[i-1]] += 1;
  501. }
  502. nestname = arraynames[name[i-1]];
  503. }
  504. if ( i === name.length-1 ){
  505. if ( value ) {
  506. if ( value === 'true' ) {
  507. value = true;
  508. } else if ( value === 'false' ) {
  509. value = false;
  510. }else if ( ! isNaN( parseFloat( value ) ) && parseFloat( value ).toString() === value ) {
  511. value = parseFloat( value );
  512. } else if ( typeof value === 'string' && ( value.substr( 0,1 ) === '{' || value.substr( 0,1 ) === '[' ) ) {
  513. try {
  514. value = JSON.parse( value );
  515. } catch (e) {}
  516. } else if ( typeof value === 'object' && value.length && field.is( 'select' ) ){
  517. var new_val = {};
  518. for ( var i = 0; i < value.length; i++ ){
  519. new_val[ 'n' + i ] = value[ i ];
  520. }
  521. value = new_val;
  522. }
  523. }
  524. lineconf[nestname] = value;
  525. } else {
  526. var newobj = lineconf;
  527. lineconf = {};
  528. lineconf[nestname] = newobj;
  529. }
  530. }
  531. jQuery.extend( true, json, lineconf );
  532. }
  533. return json;
  534. },
  535. /**
  536. * Initialize WPForms admin area tooltips.
  537. *
  538. * @since 1.4.8
  539. */
  540. initTooltips: function() {
  541. if ( typeof jQuery.fn.tooltipster === 'undefined' ) {
  542. return;
  543. }
  544. jQuery( '.wpforms-help-tooltip' ).tooltipster( {
  545. contentAsHTML: true,
  546. position: 'right',
  547. maxWidth: 300,
  548. multiple: true,
  549. interactive: true,
  550. debug: false,
  551. IEmin: 11,
  552. } );
  553. },
  554. /**
  555. * Restore WPForms admin area tooltip's title.
  556. *
  557. * @since 1.6.5
  558. *
  559. * @param {mixed} $scope Searching scope.
  560. */
  561. restoreTooltips: function( $scope ) {
  562. $scope = typeof $scope !== 'undefined' && $scope && $scope.length > 0 ? $scope.find( '.wpforms-help-tooltip' ) : jQuery( '.wpforms-help-tooltip' );
  563. $scope.each( function() {
  564. var $this = jQuery( this );
  565. if ( jQuery.tooltipster.instances( this ).length !== 0 ) {
  566. // Restoring title.
  567. $this.attr( 'title', $this.tooltipster( 'content' ) );
  568. }
  569. } );
  570. },
  571. /**
  572. * Validate a URL.
  573. * source: `https://github.com/segmentio/is-url/blob/master/index.js`
  574. *
  575. * @since 1.5.8
  576. *
  577. * @param {string} url URL for checking.
  578. *
  579. * @returns {boolean} True if `url` is a valid URL.
  580. */
  581. isURL: function( url ) {
  582. /**
  583. * RegExps.
  584. * A URL must match #1 and then at least one of #2/#3.
  585. * Use two levels of REs to avoid REDOS.
  586. */
  587. var protocolAndDomainRE = /^(?:http(?:s?):)?\/\/(\S+)/;
  588. var localhostDomainRE = /^localhost[\:?\d]*(?:[^\:?\d]\S*)?$/;
  589. var nonLocalhostDomainRE = /^[^\s\.]+\.\S{2,}$/;
  590. if ( typeof url !== 'string' ) {
  591. return false;
  592. }
  593. var match = url.match( protocolAndDomainRE );
  594. if ( ! match ) {
  595. return false;
  596. }
  597. var everythingAfterProtocol = match[1];
  598. if ( ! everythingAfterProtocol ) {
  599. return false;
  600. }
  601. if ( localhostDomainRE.test( everythingAfterProtocol ) || nonLocalhostDomainRE.test( everythingAfterProtocol ) ) {
  602. return true;
  603. }
  604. return false;
  605. },
  606. /**
  607. * Sanitize HTML.
  608. * Uses: `https://github.com/cure53/DOMPurify`
  609. *
  610. * @since 1.5.9
  611. *
  612. * @param {string} string HTML to sanitize.
  613. *
  614. * @returns {string} Sanitized HTML.
  615. */
  616. sanitizeHTML: function( string ) {
  617. var purify = window.DOMPurify;
  618. if ( typeof purify === 'undefined' ) {
  619. return string;
  620. }
  621. if ( typeof string !== 'string' ) {
  622. string = string.toString();
  623. }
  624. return purify.sanitize( string );
  625. },
  626. /**
  627. * Encode HTML entities.
  628. * Uses: `https://stackoverflow.com/a/18750001/9745718`
  629. *
  630. * @since 1.6.3
  631. *
  632. * @param {string} string HTML to sanitize.
  633. *
  634. * @returns {string} String with encoded HTML entities.
  635. */
  636. encodeHTMLEntities: function( string ) {
  637. if ( typeof string !== 'string' ) {
  638. string = string.toString();
  639. }
  640. return string.replace( /[\u00A0-\u9999<>&]/gim, function( i ) {
  641. return '&#' + i.charCodeAt( 0 ) + ';';
  642. } );
  643. },
  644. /**
  645. * Radio Group for Checkboxes.
  646. *
  647. * @since 1.6.6
  648. */
  649. initRadioGroupForCheckboxes: function() {
  650. var $ = jQuery;
  651. $( document ).on( 'change', 'input[type="checkbox"].wpforms-radio-group', function() {
  652. var $input = $( this ),
  653. inputId = $input.attr( 'id' );
  654. if ( ! $input.prop( 'checked' ) ) {
  655. return;
  656. }
  657. var groupName = $input.data( 'radio-group' ),
  658. $group = $( '.wpforms-radio-group-' + groupName ),
  659. $item;
  660. $group.each( function() {
  661. $item = $( this );
  662. if ( $item.attr( 'id' ) !== inputId ) {
  663. $item.prop( 'checked', false );
  664. }
  665. } );
  666. } );
  667. },
  668. /**
  669. * Pluck a certain field out of each object in a list.
  670. *
  671. * JS implementation of the `wp_list_pluck()`.
  672. *
  673. * @since 1.6.8
  674. *
  675. * @param {Array} arr Array of objects.
  676. * @param {string} column Column.
  677. *
  678. * @returns {Array} Array with extracted column values.
  679. */
  680. listPluck: function( arr, column ) {
  681. return arr.map( function( x ) {
  682. if ( typeof x !== 'undefined' ) {
  683. return x[ column ];
  684. }
  685. return x;
  686. } );
  687. },
  688. };
  689. wpf.init();