Plugin Directory

Changeset 3759933


Ignore:
Timestamp:
10/22/2026 12:53:32 PM (3 weeks ago)
Author:
supsystic.com
Message:

Contact Form Free 1.11.1

Location:
contact-form-by-supsystic
Files:
2 added
34 edited
1 copied

Legend:

Unmodified
Added
Removed
  • contact-form-by-supsystic/tags/1.11.1/cfs.php

    r3750257 r3759933  
    55 * Plugin URI: https://supsystic.com/plugins/contact-form-plugin/
    66 * Description: Drag-and-drop contact form builder with ready templates, 25+ field types, reCAPTCHA, saved submissions and form statistics. No coding needed.
    7  * Version: 1.10.7
     7 * Version: 1.11.1
    88 * Author: supsystic.com
    99 * Author URI: https://supsystic.com
  • contact-form-by-supsystic/tags/1.11.1/classes/controller.php

    r3679442 r3759933  
    104104  public function __call($name, $arguments)
    105105  {
    106     // delete()/clear() wipe the entire table when called with no/null arguments,
    107     // which is exactly how they get invoked here (AJAX dispatch never actually
    108     // passes $arguments through). Neither is reliably listed in every controller's
    109     // getPermissions(), and havePermissions() defaults to "allow" for any action
    110     // name it doesn't recognize - so without this check, any visitor could delete
    111     // all data in a table simply by requesting action=delete or action=clear.
     106    // delete()/clear() wipe the entire table when called with no/null arguments.
     107    // Request dispatcher never routes actions here (frameCfs::havePermissions() accepts
     108    // only real declared controller methods), but keep this guard for any other caller.
    112109    if (in_array(strtolower($name), ['delete', 'clear'], true)) {
    113110      return false;
    … …  
    136133                );
    137134         * Can be used on of sub-array - CFS_METHODS or CFS_USERLEVELS
     135         * Methods that are not listed here and not listed in getPublicMethods() can not be called by request at all.
    138136         */
    139137  public function getPermissions()
    … …  
    142140  }
    143141  /**
    144    * Methods that require nonce to be generated
     142   * Methods that can be called by request from any visitor, including not logged in users.
     143   * Each of them must validate request on it's own (own nonce, hash, etc.).
    145144   * If need - should be redefined in each controller where it required.
    146145   * @return array
    147146   */
     147  public function getPublicMethods()
     148  {
     149    return [];
     150  }
     151  /**
     152   * Methods from getPermissions() that are opened by direct links from outside of plugin pages
     153   * (for example - OAuth callbacks), so they can not carry plugin nonce.
     154   * All other methods from getPermissions() always require valid plugin nonce.
     155   * @return array
     156   */
     157  public function getNonceExemptMethods()
     158  {
     159    return [];
     160  }
     161  /**
     162   * Public methods that require plugin nonce too.
     163   * Methods from getPermissions() require it anyway, @see frameCfs::havePermissions().
     164   * @return array
     165   */
    148166  public function getNoncedMethods()
    149167  {
    150168    return [];
     169  }
     170  /**
     171   * Check that current request was sent by plugin administrator from plugin pages
     172   * @return bool
     173   */
     174  protected function _isAdminRequest()
     175  {
     176    return frameCfs::_()->getModule('user')->isAdmin() && frameCfs::_()->verifyNonce();
    151177  }
    152178  public function getModule()
    … …  
    234260  {
    235261    $res = new responseCfs();
    236     if ($this->getModel()->removeGroup(reqCfs::getVar('listIds', 'post'))) {
     262    // Inherited by all controllers - so check access here too, not only in request dispatcher
     263    if (!$this->_isAdminRequest()) {
     264      $res->pushError(__('You have no permissions to do this', CFS_LANG_CODE));
     265    } elseif ($this->getModel()->removeGroup(reqCfs::getVar('listIds', 'post'))) {
    237266      $res->addMessage(__('Done', CFS_LANG_CODE));
    238267    } else {
    … …  
    244273  {
    245274    $res = new responseCfs();
    246     if ($this->getModel()->clear()) {
     275    if (!$this->_isAdminRequest()) {
     276      $res->pushError(__('You have no permissions to do this', CFS_LANG_CODE));
     277    } elseif ($this->getModel()->clear()) {
    247278      $res->addMessage(__('Done', CFS_LANG_CODE));
    248279    } else {
  • contact-form-by-supsystic/tags/1.11.1/classes/frame.php

    r3641551 r3759933  
    2323  private $_mod = '';
    2424  private $_action = '';
     25  /**
     26   * PRO before 1.5.0 does not declare it's public and nonce exempt methods, @see _isLegacyPro()
     27   */
     28  private static $_legacyProPublicMethods = [
     29    'add_fields' => ['uploadFile', 'getFile', 'removeFile'],
     30    'wp_subscribe' => ['confirm'],
     31  ];
     32  private static $_legacyProNonceExemptMethods = [
     33    'campaignmonitor' => ['saveAccessToken'],
     34    'constantcontact' => ['saveAccessToken'],
     35    'verticalresponse' => ['saveAccessToken'],
     36    // Read only exports - old PRO opens them by links without nonce
     37    'ab_testing' => ['getListForTbl'],
     38    'wp_subscribe' => ['getWpCsvList'],
     39    'statistics' => ['getCsv'],
     40  ];
     41  private static $_legacyPro = null;
    2542  /**
    2643   * Object with result of executing non-ajax module request
    … …  
    194211  }
    195212  /**
    196    * Check permissions for action in controller by $code
     213   * Check permissions for action in controller by $code.
     214   * Access is denied by default: an action can be dispatched only if its controller declares it,
     215   * either as public (getPublicMethods() - such method must validate the request on its own),
     216   * or for some user level (getPermissions()). Every action that is not public requires
     217   * a valid plugin nonce too, except actions from getNonceExemptMethods().
    197218   * @param string $code Code of controller that need to be checked
    198219   * @param string $action Action that need to be checked
    … …  
    201222  public function havePermissions($code, $action)
    202223  {
    203     $res = true;
     224    $controller = $this->_getDispatchController($code, $action);
     225    if (!$controller) {
     226      return false;
     227    }
     228    $action = strtolower($action);
     229    if ($this->_methodListed($action, $this->_getPublicMethods($code, $controller))) {
     230      // Public methods can be additionally enrolled for the plugin nonce
     231      return !$this->_methodListed($action, $controller->getNoncedMethods()) || $this->verifyNonce();
     232    }
     233    $requiredLevels = $this->_getRequiredUserLevels($controller->getPermissions(), $action);
     234    if (empty($requiredLevels)) {
     235      // Action was not declared by controller - deny it
     236      return false;
     237    }
     238    $currentUserPosition = $this->getModule('user')->getCurrentUserPosition();
     239    // For multi-sites network admin role is undefined, let's do this here
     240    if (is_multisite() && is_admin() && is_super_admin()) {
     241      $currentUserPosition = CFS_ADMIN;
     242    }
     243    foreach ($requiredLevels as $levels) {
     244      if (!in_array($currentUserPosition, $levels, true)) {
     245        return false;
     246      }
     247    }
     248    if (!$this->_methodListed($action, $this->_getNonceExemptMethods($code, $controller)) && !$this->verifyNonce()) {
     249      return false;
     250    }
     251    return true;
     252  }
     253  /**
     254   * PRO add-on can be older than this plugin: before 1.5.0 it did not declare access for it's methods
     255   * @return bool
     256   */
     257  protected function _isLegacyPro()
     258  {
     259    if (self::$_legacyPro === null) {
     260      self::$_legacyPro = function_exists('getProPlugCodeCfs') && !defined('CFS_PRO_DECLARES_ACCESS');
     261    }
     262    return self::$_legacyPro;
     263  }
     264  protected function _getPublicMethods($code, $controller)
     265  {
     266    $methods = (array) $controller->getPublicMethods();
     267    if ($this->_isLegacyPro() && isset(self::$_legacyProPublicMethods[$code])) {
     268      $methods = array_merge($methods, self::$_legacyProPublicMethods[$code]);
     269    }
     270    return $methods;
     271  }
     272  protected function _getNonceExemptMethods($code, $controller)
     273  {
     274    $methods = (array) $controller->getNonceExemptMethods();
     275    if ($this->_isLegacyPro() && isset(self::$_legacyProNonceExemptMethods[$code])) {
     276      $methods = array_merge($methods, self::$_legacyProNonceExemptMethods[$code]);
     277    }
     278    return $methods;
     279  }
     280  /**
     281   * Check if action is allowed for any visitor, including not logged in users
     282   * @param string $code Code of controller that need to be checked
     283   * @param string $action Action that need to be checked
     284   * @return bool
     285   */
     286  public function isPublicAction($code, $action)
     287  {
     288    $controller = $this->_getDispatchController($code, $action);
     289    return $controller && $this->_methodListed(strtolower($action), $this->_getPublicMethods($code, $controller));
     290  }
     291  /**
     292   * Verify plugin nonce from current request
     293   * @return bool
     294   */
     295  public function verifyNonce()
     296  {
     297    $nonce = isset($_REQUEST['_wpnonce']) && is_string($_REQUEST['_wpnonce']) ? sanitize_text_field(wp_unslash($_REQUEST['_wpnonce'])) : '';
     298    if (empty($nonce)) {
     299      return false;
     300    }
     301    return wp_verify_nonce($nonce, 'cfs_nonce') || wp_verify_nonce($nonce, 'cfs_nonce_frontend');
     302  }
     303  /**
     304   * Get controller of module, if action can be dispatched to it: it should be real public controller method,
     305   * not some name that will be routed through controllerCfs::__call() to it's model.
     306   * @param string $code Module code
     307   * @param string $action Action name
     308   * @return controllerCfs|null
     309   */
     310  protected function _getDispatchController($code, $action)
     311  {
    204312    $mod = $this->getModule($code);
    205     $action = strtolower($action);
    206     if ($mod) {
    207       $permissions = $mod->getController()->getPermissions();
    208       if (!empty($permissions)) {
    209         // Special permissions
    210         if (isset($permissions[CFS_METHODS]) && !empty($permissions[CFS_METHODS])) {
    211           foreach ($permissions[CFS_METHODS] as $method => $permissions) {
    212             // Make case-insensitive
    213             $permissions[CFS_METHODS][strtolower($method)] = $permissions;
    214           }
    215           if (array_key_exists($action, $permissions[CFS_METHODS])) {
    216             // Permission for this method exists
    217             $currentUserPosition = frameCfs::_()->getModule('user')->getCurrentUserPosition();
    218             if ((is_array($permissions[CFS_METHODS][$action]) && !in_array($currentUserPosition, $permissions[CFS_METHODS][$action])) || (!is_array($permissions[CFS_METHODS][$action]) && $permissions[CFS_METHODS][$action] != $currentUserPosition)) {
    219               $res = false;
    220             }
    221           }
    222         }
    223         if (isset($permissions[CFS_USERLEVELS]) && !empty($permissions[CFS_USERLEVELS])) {
    224           $currentUserPosition = frameCfs::_()->getModule('user')->getCurrentUserPosition();
    225           // For multi-sites network admin role is undefined, let's do this here
    226           if (is_multisite() && is_admin() && is_super_admin()) {
    227             $currentUserPosition = CFS_ADMIN;
    228           }
    229           foreach ($permissions[CFS_USERLEVELS] as $userlevel => $methods) {
    230             if (is_array($methods)) {
    231               $lowerMethods = array_map('strtolower', $methods); // Make case-insensitive
    232               if (in_array($action, $lowerMethods)) {
    233                 // Permission for this method exists
    234                 if ($currentUserPosition != $userlevel) {
    235                   $res = false;
    236                 }
    237                 break;
    238               }
    239             } else {
    240               $lowerMethod = strtolower($methods); // Make case-insensitive
    241               if ($lowerMethod == $action) {
    242                 // Permission for this method exists
    243                 if ($currentUserPosition != $userlevel) {
    244                   $res = false;
    245                 }
    246                 break;
    247               }
    248             }
    249           }
    250         }
    251       }
    252       if ($res) {
    253         // Additional check for nonces
    254         $noncedMethods = $mod->getController()->getNoncedMethods();
    255         if (!empty($noncedMethods)) {
    256           $noncedMethods = array_map('strtolower', $noncedMethods);
    257           if (in_array($action, $noncedMethods)) {
    258             $nonce = isset($_REQUEST['_wpnonce']) ? $_REQUEST['_wpnonce'] : reqCfs::getVar('_wpnonce');
    259             if (is_admin()) {
    260               if (!wp_verify_nonce($nonce, 'cfs_nonce')) {
    261                 $res = false;
    262               }
    263             } else {
    264               if (!wp_verify_nonce($nonce, 'cfs_nonce_frontend')) {
    265                 $res = false;
    266               }
    267             }
    268           }
     313    $controller = $mod ? $mod->getController() : null;
     314    if (!$controller || empty($action) || !is_string($action) || !method_exists($controller, $action)) {
     315      return null;
     316    }
     317    $method = new ReflectionMethod($controller, $action);
     318    if (!$method->isPublic() || $method->isStatic()) {
     319      return null;
     320    }
     321    return $controller;
     322  }
     323  protected function _methodListed($action, $methods)
     324  {
     325    return !empty($methods) && is_array($methods) && in_array($action, array_map('strtolower', $methods), true);
     326  }
     327  /**
     328   * Get user levels required for action from controller permissions
     329   * @param array $permissions Result of controllerCfs::getPermissions()
     330   * @param string $action Action name in lower case
     331   * @return array List of conditions, each one - array of user levels, any of them pass condition. Empty if action is not declared.
     332   */
     333  protected function _getRequiredUserLevels($permissions, $action)
     334  {
     335    $res = [];
     336    if (empty($permissions) || !is_array($permissions)) {
     337      return $res;
     338    }
     339    if (isset($permissions[CFS_METHODS]) && is_array($permissions[CFS_METHODS])) {
     340      foreach ($permissions[CFS_METHODS] as $method => $levels) {
     341        if (strtolower($method) == $action) {
     342          $res[] = (array) $levels;
     343          break;
     344        }
     345      }
     346    }
     347    if (isset($permissions[CFS_USERLEVELS]) && is_array($permissions[CFS_USERLEVELS])) {
     348      foreach ($permissions[CFS_USERLEVELS] as $userlevel => $methods) {
     349        if ($this->_methodListed($action, (array) $methods)) {
     350          $res[] = [$userlevel];
     351          break;
    269352        }
    270353      }
    … …  
    335418        case 'ajax':
    336419          add_action('wp_ajax_' . $this->_action, [$mod->getController(), $this->_action]);
    337           add_action('wp_ajax_nopriv_' . $this->_action, [$mod->getController(), $this->_action]);
     420          // Not logged in visitors can reach only actions that controller declared as public
     421          if ($this->isPublicAction($this->_mod, $this->_action)) {
     422            add_action('wp_ajax_nopriv_' . $this->_action, [$mod->getController(), $this->_action]);
     423          }
    338424          break;
    339425        default:
  • contact-form-by-supsystic/tags/1.11.1/classes/utils.php

    r3679442 r3759933  
    735735    }
    736736    return $str;
     737  }
     738  /**
     739   * Make value safe to be placed in email header: line breaks there start new header.
     740   * @param mixed $value Header value, or array of values
     741   * @return mixed
     742   */
     743  public static function cleanMailHeader($value)
     744  {
     745    if (is_array($value)) {
     746      return array_map([__CLASS__, 'cleanMailHeader'], $value);
     747    }
     748    if (!is_scalar($value)) {
     749      return '';
     750    }
     751    return trim(preg_replace('/[\r\n\t\0\x0B\x0C]+/', ' ', (string) $value));
     752  }
     753  /**
     754   * Sendmail path is passed to shell by mailer, so allow only absolute path to sendmail-compatible binary,
     755   * with few safe flags.
     756   * @param string $path
     757   * @return bool
     758   */
     759  public static function isValidSendmailPath($path)
     760  {
     761    if (!is_string($path)) {
     762      return false;
     763    }
     764    $path = trim($path, ' ');
     765    if ($path === '') {
     766      return true; // Default from php.ini will be used
     767    }
     768    // Only printable chars, parts are separated by spaces only
     769    if (preg_match('/[^\x20-\x7E]/', $path)) {
     770      return false;
     771    }
     772    $parts = preg_split('/ +/', $path);
     773    $bin = array_shift($parts);
     774    if (!preg_match('#^(?:[A-Za-z]:)?[\\\\/][A-Za-z0-9._\\\\/-]+$#', $bin) || strpos($bin, '..') !== false) {
     775      return false;
     776    }
     777    $binName = basename(str_replace('\\', '/', $bin));
     778    if (!preg_match('/^(sendmail|msmtp|ssmtp|esmtp|exim4?|qmail-inject|mini_sendmail|nullmailer-inject)(\.exe)?$/i', $binName)) {
     779      return false;
     780    }
     781    $emailRe = '/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+$/';
     782    for ($i = 0; $i < count($parts); $i++) {
     783      $flag = $parts[$i];
     784      if (in_array($flag, ['-t', '-i', '-oi', '-ti'], true)) {
     785        continue;
     786      }
     787      if (strpos($flag, '-f') === 0 && strlen($flag) > 2 && preg_match($emailRe, substr($flag, 2))) {
     788        continue;
     789      }
     790      if ($flag === '-f' && isset($parts[$i + 1]) && preg_match($emailRe, $parts[$i + 1])) {
     791        $i++;
     792        continue;
     793      }
     794      return false;
     795    }
     796    return true;
    737797  }
    738798  public static function encodeArrayTxt($arr, $forceOrd = false)
  • contact-form-by-supsystic/tags/1.11.1/config.php

    r3750170 r3759933  
    5353
    5454define('CFS_PLUGIN_INSTALLED', true);
    55 define('CFS_VERSION', '1.10.7');
     55define('CFS_VERSION', '1.11.1');
    5656define('CFS_USER', 'user');
    5757
  • contact-form-by-supsystic/tags/1.11.1/js/core.js

    r3491826 r3759933  
    8484  var dataType = params.dataType ? params.dataType : 'json';
    8585  // Set plugin orientation
     86  // Admin actions require plugin nonce - add it if request does not have it's own one
     87  var adminNonce = cfsGetAdminNonce();
    8688  if (typeof data == 'string') {
    8789    data += '&pl=' + CFS_DATA.CFS_CODE;
    8890    data += '&reqType=ajax';
     91    if (adminNonce && !/(^|&)_wpnonce=/.test(data)) data += '&_wpnonce=' + encodeURIComponent(adminNonce);
    8992  } else {
    9093    data['pl'] = CFS_DATA.CFS_CODE;
    9194    data['reqType'] = 'ajax';
     95    if (adminNonce && typeof data['_wpnonce'] == 'undefined') data['_wpnonce'] = adminNonce;
    9296  }
    9397
    … …  
    194198}
    195199
     200/**
     201 * Plugin nonce for admin requests, it is not available on frontend
     202 */
     203function cfsGetAdminNonce() {
     204  if (typeof CFS_NONCE != 'undefined' && CFS_NONCE && CFS_NONCE.cfs_nonce) return CFS_NONCE.cfs_nonce;
     205  if (typeof CFS_DATA != 'undefined' && CFS_DATA && CFS_DATA.cfsNonce) return CFS_DATA.cfsNonce;
     206  return false;
     207}
    196208function getDialogElementCfs() {
    197209  return jQuery('<div/>').appendTo(jQuery('body'));
  • contact-form-by-supsystic/tags/1.11.1/js/core.min.js

    r3403065 r3759933  
    11
    2 if(typeof(CFS_DATA)=="undefined"){var CFS_DATA={}}if(isNumber(CFS_DATA.animationSpeed)){CFS_DATA.animationSpeed=parseInt(CFS_DATA.animationSpeed)}else{if(jQuery.inArray(CFS_DATA.animationSpeed,["fast","slow"])==-1){CFS_DATA.animationSpeed="fast"}}CFS_DATA.showSubscreenOnCenter=parseInt(CFS_DATA.showSubscreenOnCenter);var sdLoaderImgCfs='<img src="'+CFS_DATA.loader+'" />';var g_cfsAnimationSpeed=300;jQuery.fn.showLoaderCfs=function(){return jQuery(this).html(sdLoaderImgCfs)};jQuery.fn.appendLoaderCfs=function(){jQuery(this).append(sdLoaderImgCfs)};jQuery.sendFormCfs=function(a){return jQuery("<br />").sendFormCfs(a)};jQuery.fn.sendFormCfs=function(e){var b=null;if(!e){e={fid:false,msgElID:false,onSuccess:false}}if(e.fid){b=jQuery("#"+fid)}else{b=jQuery(this)}var c=(jQuery(b).tagName()=="FORM");var f=new Array();if(e.data){f=e.data}else{if(c){f=jQuery(b).serialize()}}if(e.appendData){var h=typeof(f)=="string";var k=[];for(var g in e.appendData){if(h){k.push(g+"="+e.appendData[g])}else{f[g]=e.appendData[g]}}if(h){f+="&"+k.join("&")}}var d=null;if(e.msgElID){if(e.msgElID=="noMessages"){d=false}else{if(typeof(e.msgElID)=="object"){d=e.msgElID}else{d=jQuery("#"+e.msgElID)}}}if(typeof(e.inputsWraper)=="string"){b=jQuery("#"+e.inputsWraper);c=true}if(c&&b){jQuery(b).find("*").removeClass("cfsInputError")}if(d&&!e.btn){jQuery(d).removeClass("cfsSuccessMsg").removeClass("cfsErrorMsg");if(!e.btn){jQuery(d).showLoaderCfs()}}if(e.btn){jQuery(e.btn).attr("disabled","disabled");e.btnIconElement=jQuery(e.btn).find(".fa").length?jQuery(e.btn).find(".fa"):jQuery(e.btn);if(jQuery(e.btn).find(".fa").length){e.btnIconElement.data("prev-class",e.btnIconElement.attr("class")).attr("class","fa fa-spinner fa-spin")}}var a="";if(typeof(e.url)!="undefined"){a=e.url}else{if(typeof(ajaxurl)=="undefined"){a=CFS_DATA.ajaxurl}else{a=ajaxurl}}jQuery(".cfsErrorForField").hide(CFS_DATA.animationSpeed);var j=e.dataType?e.dataType:"json";if(typeof(f)=="string"){f+="&pl="+CFS_DATA.CFS_CODE;f+="&reqType=ajax"}else{f.pl=CFS_DATA.CFS_CODE;f.reqType="ajax"}jQuery.ajax({url:a,data:f,type:"POST",dataType:j,success:function(i){toeProcessAjaxResponseCfs(i,d,b,c,e);if(e.clearMsg){setTimeout(function(){if(d){jQuery(d).animateClear()}},typeof(e.clearMsg)=="boolean"?5000:e.clearMsg)}}})};jQuery.fn.animateClear=function(){var a=jQuery("<span>"+jQuery(this).html()+"</span>");jQuery(this).html(a);jQuery(a).hide(CFS_DATA.animationSpeed,function(){jQuery(a).remove()})};jQuery.fn.animateRemoveCfs=function(a,b){a=a==undefined?CFS_DATA.animationSpeed:a;jQuery(this).hide(a,function(){jQuery(this).remove();if(typeof(b)==="function"){b()}})};function toeProcessAjaxResponseCfs(h,e,c,d,f){if(typeof(f)=="undefined"){f={}}if(typeof(e)=="string"){e=jQuery("#"+e)}if(e){jQuery(e).html("")}if(f.btn){jQuery(f.btn).removeAttr("disabled");if(f.btnIconElement){f.btnIconElement.attr("class",f.btnIconElement.data("prev-class"))}}if(typeof(h)=="object"){if(h.error){if(e){jQuery(e).removeClass("cfsSuccessMsg").addClass("cfsErrorMsg")}var j=[];for(var a in h.errors){if(d){var b=jQuery(c).find('[name*="'+a+'"]');b.addClass("cfsInputError");if(b.attr("placeholder")){}if(!b.data("keyup-error-remove-binded")){b.keydown(function(){jQuery(this).removeClass("cfsInputError")}).data("keyup-error-remove-binded",1)}}if(jQuery(".cfsErrorForField.toe_"+nameToClassId(a)+"").exists()){jQuery(".cfsErrorForField.toe_"+nameToClassId(a)+"").show().html(h.errors[a])}else{if(e){jQuery(e).append(h.errors[a]).append("<br />")}else{j.push(h.errors[a])}}}if(j.length&&f.btn&&jQuery.fn.dialog&&!e){jQuery('<div title="'+toeLangCfs("Really small warning :)")+'" />').html(j.join("<br />")).appendTo("body").dialog({modal:true,width:"500px"})}}else{if(h.messages.length){if(e){jQuery(e).removeClass("cfsErrorMsg").addClass("cfsSuccessMsg");for(var g=0;g<h.messages.length;g++){jQuery(e).append(h.messages[g]).append("<br />")}}}}}if(f.onSuccess&&typeof(f.onSuccess)=="function"){f.onSuccess(h)}}function getDialogElementCfs(){return jQuery("<div/>").appendTo(jQuery("body"))}function toeOptionCfs(a){if(CFS_DATA.options&&CFS_DATA.options[a]){return CFS_DATA.options[a]}return false}function toeLangCfs(a){if(CFS_DATA.siteLang&&CFS_DATA.siteLang[a]){return CFS_DATA.siteLang[a]}return a}function toePagesCfs(a){if(typeof(CFS_DATA)!="undefined"&&CFS_DATA[a]){return CFS_DATA[a]}return false}function toeOptTimeoutHideDescriptionCfs(){jQuery("#cfsOptDescription").removeAttr("toeFixTip");setTimeout(function(){if(!jQuery("#cfsOptDescription").attr("toeFixTip")){toeOptHideDescriptionCfs()}},500)}function toeOptShowDescriptionCfs(b,a,d,c){if(typeof(b)!="undefined"&&b!=""){if(!jQuery("#cfsOptDescription").length){jQuery("body").append('<div id="cfsOptDescription"></div>')}if(c){jQuery("#cfsOptDescription").css("right",jQuery(window).width()-(a-10))}else{jQuery("#cfsOptDescription").css("left",a+10)}jQuery("#cfsOptDescription").css("top",d);jQuery("#cfsOptDescription").show(200);jQuery("#cfsOptDescription").html(b)}}function toeOptHideDescriptionCfs(){jQuery("#cfsOptDescription").removeAttr("toeFixTip");jQuery("#cfsOptDescription").hide(200)}function toeInArrayCfs(c,b){if(b){for(var a in b){if(b[a]==c){return true}}}return false}function toeShowDialogCustomized(b,a){a=jQuery.extend({resizable:false,width:500,height:300,closeOnEscape:true,open:function(c,d){jQuery(".ui-dialog-titlebar").css({"background-color":"#222222","background-image":"none",border:"none",margin:"0",padding:"0","border-radius":"0",color:"#CFCFCF",height:"27px"});jQuery(".ui-dialog-titlebar-close").css({background:'url("'+CFS_DATA.cssPath+'img/tb-close.png") no-repeat scroll 0 0 transparent',border:"0",width:"15px",height:"15px",padding:"0","border-radius":"0",margin:"7px 7px 0"}).html("");jQuery(".ui-dialog").css({"border-radius":"3px","background-color":"#FFFFFF","background-image":"none",padding:"1px","z-index":"300000",position:"fixed",top:"60px"});jQuery(".ui-dialog-buttonpane").css({"background-color":"#FFFFFF"});jQuery(".ui-dialog-title").css({color:"#CFCFCF",font:"12px sans-serif",padding:"6px 10px 0"});if(a.openCallback&&typeof(a.openCallback)=="function"){a.openCallback(c,d)}jQuery(".ui-widget-overlay").css({"z-index":jQuery(c.target).parents(".ui-dialog:first").css("z-index")-1,"background-image":"none"});if(a.modal&&a.closeOnBg){jQuery(".ui-widget-overlay").unbind("click").bind("click",function(){jQuery(b).dialog("close")})}}},a);return jQuery(b).dialog(a)}function toeSliderMove(a,b){var c=jQuery(a.target).attr("id");jQuery("#toeSliderDisplay_"+c).html(b.value);jQuery("#toeSliderInput_"+c).val(b.value).change()}function cfsCorrectJqueryUsed(){return(typeof(jQuery.fn.sendFormCfs)==="function")}function cfsReloadCoreJs(d,e){var c="",b=["common.js","core.js"];for(var a=0;a<b.length;a++){c+='<script type="text/javascript" class="cfsReloadedScript" src="'+CFS_DATA.jsPath+b[a]+'"><\/script>'}jQuery("head").append(c);if(d){_cfsRunClbAfterCoreReload(d,e)}}function _cfsRunClbAfterCoreReload(a,b){if(cfsCorrectJqueryUsed()){callUserFuncArray(a,b);return}setTimeout(function(){cfsCorrectJqueryUsed(a,b)},500)}function base64_decode(d){var b="";var l,j,g="";var k,h,f,e="";var c=0;var a=/[^A-Za-z0-9\+\/\=]/g;if(a.exec(d)){alert("There were invalid base64 characters in the input text.\nValid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\nExpect errors in decoding.")}d=d.replace(/[^A-Za-z0-9\+\/\=]/g,"");do{k=keyStr.indexOf(d.charAt(c++));h=keyStr.indexOf(d.charAt(c++));f=keyStr.indexOf(d.charAt(c++));e=keyStr.indexOf(d.charAt(c++));l=(k<<2)|(h>>4);j=((h&15)<<4)|(f>>2);g=((f&3)<<6)|e;b=b+String.fromCharCode(l);if(f!=64){b=b+String.fromCharCode(j)}if(e!=64){b=b+String.fromCharCode(g)}l=j=g="";k=h=f=e=""}while(c<d.length);return unescape(b)};
     2if(typeof(CFS_DATA)=="undefined"){var CFS_DATA={}}if(isNumber(CFS_DATA.animationSpeed)){CFS_DATA.animationSpeed=parseInt(CFS_DATA.animationSpeed)}else{if(jQuery.inArray(CFS_DATA.animationSpeed,["fast","slow"])==-1){CFS_DATA.animationSpeed="fast"}}CFS_DATA.showSubscreenOnCenter=parseInt(CFS_DATA.showSubscreenOnCenter);var sdLoaderImgCfs='<img src="'+CFS_DATA.loader+'" />';var g_cfsAnimationSpeed=300;jQuery.fn.showLoaderCfs=function(){return jQuery(this).html(sdLoaderImgCfs)};jQuery.fn.appendLoaderCfs=function(){jQuery(this).append(sdLoaderImgCfs)};jQuery.sendFormCfs=function(a){return jQuery("<br />").sendFormCfs(a)};jQuery.fn.sendFormCfs=function(e){var b=null;if(!e){e={fid:false,msgElID:false,onSuccess:false}}if(e.fid){b=jQuery("#"+fid)}else{b=jQuery(this)}var c=(jQuery(b).tagName()=="FORM");var f=new Array();if(e.data){f=e.data}else{if(c){f=jQuery(b).serialize()}}if(e.appendData){var h=typeof(f)=="string";var k=[];for(var g in e.appendData){if(h){k.push(g+"="+e.appendData[g])}else{f[g]=e.appendData[g]}}if(h){f+="&"+k.join("&")}}var d=null;if(e.msgElID){if(e.msgElID=="noMessages"){d=false}else{if(typeof(e.msgElID)=="object"){d=e.msgElID}else{d=jQuery("#"+e.msgElID)}}}if(typeof(e.inputsWraper)=="string"){b=jQuery("#"+e.inputsWraper);c=true}if(c&&b){jQuery(b).find("*").removeClass("cfsInputError")}if(d&&!e.btn){jQuery(d).removeClass("cfsSuccessMsg").removeClass("cfsErrorMsg");if(!e.btn){jQuery(d).showLoaderCfs()}}if(e.btn){jQuery(e.btn).attr("disabled","disabled");e.btnIconElement=jQuery(e.btn).find(".fa").length?jQuery(e.btn).find(".fa"):jQuery(e.btn);if(jQuery(e.btn).find(".fa").length){e.btnIconElement.data("prev-class",e.btnIconElement.attr("class")).attr("class","fa fa-spinner fa-spin")}}var a="";if(typeof(e.url)!="undefined"){a=e.url}else{if(typeof(ajaxurl)=="undefined"){a=CFS_DATA.ajaxurl}else{a=ajaxurl}}jQuery(".cfsErrorForField").hide(CFS_DATA.animationSpeed);var j=e.dataType?e.dataType:"json";var n=cfsGetAdminNonce();if(typeof(f)=="string"){f+="&pl="+CFS_DATA.CFS_CODE;f+="&reqType=ajax";if(n&&!/(^|&)_wpnonce=/.test(f)){f+="&_wpnonce="+encodeURIComponent(n)}}else{f.pl=CFS_DATA.CFS_CODE;f.reqType="ajax";if(n&&typeof(f._wpnonce)=="undefined"){f._wpnonce=n}}jQuery.ajax({url:a,data:f,type:"POST",dataType:j,success:function(i){toeProcessAjaxResponseCfs(i,d,b,c,e);if(e.clearMsg){setTimeout(function(){if(d){jQuery(d).animateClear()}},typeof(e.clearMsg)=="boolean"?5000:e.clearMsg)}}})};jQuery.fn.animateClear=function(){var a=jQuery("<span>"+jQuery(this).html()+"</span>");jQuery(this).html(a);jQuery(a).hide(CFS_DATA.animationSpeed,function(){jQuery(a).remove()})};jQuery.fn.animateRemoveCfs=function(a,b){a=a==undefined?CFS_DATA.animationSpeed:a;jQuery(this).hide(a,function(){jQuery(this).remove();if(typeof(b)==="function"){b()}})};function toeProcessAjaxResponseCfs(h,e,c,d,f){if(typeof(f)=="undefined"){f={}}if(typeof(e)=="string"){e=jQuery("#"+e)}if(e){jQuery(e).html("")}if(f.btn){jQuery(f.btn).removeAttr("disabled");if(f.btnIconElement){f.btnIconElement.attr("class",f.btnIconElement.data("prev-class"))}}if(typeof(h)=="object"){if(h.error){if(e){jQuery(e).removeClass("cfsSuccessMsg").addClass("cfsErrorMsg")}var j=[];for(var a in h.errors){if(d){var b=jQuery(c).find('[name*="'+a+'"]');b.addClass("cfsInputError");if(b.attr("placeholder")){}if(!b.data("keyup-error-remove-binded")){b.keydown(function(){jQuery(this).removeClass("cfsInputError")}).data("keyup-error-remove-binded",1)}}if(jQuery(".cfsErrorForField.toe_"+nameToClassId(a)+"").exists()){jQuery(".cfsErrorForField.toe_"+nameToClassId(a)+"").show().html(h.errors[a])}else{if(e){jQuery(e).append(h.errors[a]).append("<br />")}else{j.push(h.errors[a])}}}if(j.length&&f.btn&&jQuery.fn.dialog&&!e){jQuery('<div title="'+toeLangCfs("Really small warning :)")+'" />').html(j.join("<br />")).appendTo("body").dialog({modal:true,width:"500px"})}}else{if(h.messages.length){if(e){jQuery(e).removeClass("cfsErrorMsg").addClass("cfsSuccessMsg");for(var g=0;g<h.messages.length;g++){jQuery(e).append(h.messages[g]).append("<br />")}}}}}if(f.onSuccess&&typeof(f.onSuccess)=="function"){f.onSuccess(h)}}function cfsGetAdminNonce(){if(typeof(CFS_NONCE)!="undefined"&&CFS_NONCE&&CFS_NONCE.cfs_nonce){return CFS_NONCE.cfs_nonce}if(typeof(CFS_DATA)!="undefined"&&CFS_DATA&&CFS_DATA.cfsNonce){return CFS_DATA.cfsNonce}return false}function getDialogElementCfs(){return jQuery("<div/>").appendTo(jQuery("body"))}function toeOptionCfs(a){if(CFS_DATA.options&&CFS_DATA.options[a]){return CFS_DATA.options[a]}return false}function toeLangCfs(a){if(CFS_DATA.siteLang&&CFS_DATA.siteLang[a]){return CFS_DATA.siteLang[a]}return a}function toePagesCfs(a){if(typeof(CFS_DATA)!="undefined"&&CFS_DATA[a]){return CFS_DATA[a]}return false}function toeOptTimeoutHideDescriptionCfs(){jQuery("#cfsOptDescription").removeAttr("toeFixTip");setTimeout(function(){if(!jQuery("#cfsOptDescription").attr("toeFixTip")){toeOptHideDescriptionCfs()}},500)}function toeOptShowDescriptionCfs(b,a,d,c){if(typeof(b)!="undefined"&&b!=""){if(!jQuery("#cfsOptDescription").length){jQuery("body").append('<div id="cfsOptDescription"></div>')}if(c){jQuery("#cfsOptDescription").css("right",jQuery(window).width()-(a-10))}else{jQuery("#cfsOptDescription").css("left",a+10)}jQuery("#cfsOptDescription").css("top",d);jQuery("#cfsOptDescription").show(200);jQuery("#cfsOptDescription").html(b)}}function toeOptHideDescriptionCfs(){jQuery("#cfsOptDescription").removeAttr("toeFixTip");jQuery("#cfsOptDescription").hide(200)}function toeInArrayCfs(c,b){if(b){for(var a in b){if(b[a]==c){return true}}}return false}function toeShowDialogCustomized(b,a){a=jQuery.extend({resizable:false,width:500,height:300,closeOnEscape:true,open:function(c,d){jQuery(".ui-dialog-titlebar").css({"background-color":"#222222","background-image":"none",border:"none",margin:"0",padding:"0","border-radius":"0",color:"#CFCFCF",height:"27px"});jQuery(".ui-dialog-titlebar-close").css({background:'url("'+CFS_DATA.cssPath+'img/tb-close.png") no-repeat scroll 0 0 transparent',border:"0",width:"15px",height:"15px",padding:"0","border-radius":"0",margin:"7px 7px 0"}).html("");jQuery(".ui-dialog").css({"border-radius":"3px","background-color":"#FFFFFF","background-image":"none",padding:"1px","z-index":"300000",position:"fixed",top:"60px"});jQuery(".ui-dialog-buttonpane").css({"background-color":"#FFFFFF"});jQuery(".ui-dialog-title").css({color:"#CFCFCF",font:"12px sans-serif",padding:"6px 10px 0"});if(a.openCallback&&typeof(a.openCallback)=="function"){a.openCallback(c,d)}jQuery(".ui-widget-overlay").css({"z-index":jQuery(c.target).parents(".ui-dialog:first").css("z-index")-1,"background-image":"none"});if(a.modal&&a.closeOnBg){jQuery(".ui-widget-overlay").unbind("click").bind("click",function(){jQuery(b).dialog("close")})}}},a);return jQuery(b).dialog(a)}function toeSliderMove(a,b){var c=jQuery(a.target).attr("id");jQuery("#toeSliderDisplay_"+c).html(b.value);jQuery("#toeSliderInput_"+c).val(b.value).change()}function cfsCorrectJqueryUsed(){return(typeof(jQuery.fn.sendFormCfs)==="function")}function cfsReloadCoreJs(d,e){var c="",b=["common.js","core.js"];for(var a=0;a<b.length;a++){c+='<script type="text/javascript" class="cfsReloadedScript" src="'+CFS_DATA.jsPath+b[a]+'"><\/script>'}jQuery("head").append(c);if(d){_cfsRunClbAfterCoreReload(d,e)}}function _cfsRunClbAfterCoreReload(a,b){if(cfsCorrectJqueryUsed()){callUserFuncArray(a,b);return}setTimeout(function(){cfsCorrectJqueryUsed(a,b)},500)}function base64_decode(d){var b="";var l,j,g="";var k,h,f,e="";var c=0;var a=/[^A-Za-z0-9\+\/\=]/g;if(a.exec(d)){alert("There were invalid base64 characters in the input text.\nValid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\nExpect errors in decoding.")}d=d.replace(/[^A-Za-z0-9\+\/\=]/g,"");do{k=keyStr.indexOf(d.charAt(c++));h=keyStr.indexOf(d.charAt(c++));f=keyStr.indexOf(d.charAt(c++));e=keyStr.indexOf(d.charAt(c++));l=(k<<2)|(h>>4);j=((h&15)<<4)|(f>>2);g=((f&3)<<6)|e;b=b+String.fromCharCode(l);if(f!=64){b=b+String.fromCharCode(j)}if(e!=64){b=b+String.fromCharCode(g)}l=j=g="";k=h=f=e=""}while(c<d.length);return unescape(b)};
  • contact-form-by-supsystic/tags/1.11.1/modules/forms/controller.php

    r3679442 r3759933  
    622622    return ['save', 'removeGroup', 'getListForTbl', 'saveAsCopy', 'remove', 'createFromTpl', 'changeTpl'];
    623623  }
     624  /**
     625   * contact() checks it's own per-form nonce, updateNonce() just gives fresh nonce for cached pages
     626   */
     627  public function getPublicMethods()
     628  {
     629    return ['contact', 'updateNonce'];
     630  }
    624631  public function getPermissions()
    625632  {
  • contact-form-by-supsystic/tags/1.11.1/modules/forms/models/forms.php

    r3640942 r3759933  
    204204        }
    205205        if (empty($errorMsg)) {
     206          // Not mandatory email can be empty, but if it is entered - it must be valid
     207          if ($htmlType == 'email' && $value && (!is_string($value) || !is_email($value))) {
     208            $errorMsg = empty($formInvalidError) ? __('Invalid value for %s', CFS_LANG_CODE) : $formInvalidError;
     209          }
     210        }
     211        if (empty($errorMsg)) {
    206212          // Start other validation process
    207213          if (!$isDate && isset($f['min_size']) && !empty($f['min_size']) && ($minSize = (int) $f['min_size'])) {
    … …  
    315321              // This is requiredfor case when you hav select box with vals = mail1@mail.com, ... and names User Name 1, ...
    316322              // To send to it's values, not to it's names
    317               $variablesWithSelectVals['user_' . $sendName] = $fieldsData[$sendName];
     323              $variablesWithSelectVals['user_' . $sendName] = $this->_getSelectedOptionValue($f, isset($fieldsData[$sendName]) ? $fieldsData[$sendName] : '');
    318324              break;
    319325            default:
    … …  
    324330        }
    325331      }
     332      // Submitted values can be mapped to mail headers - line breaks there would start new headers
     333      $headerVariables = utilsCfs::cleanMailHeader($variables);
     334      $headerVariablesWithSelectVals = utilsCfs::cleanMailHeader($variablesWithSelectVals);
    326335      $sendForceTo = dispatcherCfs::applyFilters('sendContactTo', '', $fieldsData, $form);
    327336      foreach ($form['params']['submit'] as $s) {
    … …  
    334343        $additionalHeaders = [];
    335344        if (isset($s['enb_cc']) && (int) $s['enb_cc'] && isset($s['cc']) && !empty($s['cc'])) {
    336           $additionalHeaders[] = 'Cc: ' . utilsCfs::replaceVariables($s['cc'], $variables);
     345          $additionalHeaders[] = 'Cc: ' . utilsCfs::cleanMailHeader(utilsCfs::replaceVariables($s['cc'], $headerVariables));
    337346        }
    338347        if (isset($s['from']) && !empty($s['from'])) {
    339           $additionalHeaders[] = 'From: ' . utilsCfs::replaceVariables($s['from'], $variables);
     348          $additionalHeaders[] = 'From: ' . utilsCfs::cleanMailHeader(utilsCfs::replaceVariables($s['from'], $headerVariables));
    340349        }
    341350        if (isset($s['reply']) && !empty($s['reply'])) {
    342           $additionalHeaders[] = 'Reply-To: ' . utilsCfs::replaceVariables($s['reply'], $variables);
    343         }
    344         $to = utilsCfs::replaceVariables($to, $variablesWithSelectVals);
    345         $subject = utilsCfs::replaceVariables($s['subject'], $variables);
     351          $additionalHeaders[] = 'Reply-To: ' . utilsCfs::cleanMailHeader(utilsCfs::replaceVariables($s['reply'], $headerVariables));
     352        }
     353        $to = utilsCfs::cleanMailHeader(utilsCfs::replaceVariables($to, $headerVariablesWithSelectVals));
     354        if (empty($to)) {
     355          continue; // Recipient was taken from submitted data, and it was not valid
     356        }
     357        $subject = utilsCfs::cleanMailHeader(utilsCfs::replaceVariables($s['subject'], $headerVariables));
    346358        // Let it be only for Message field
    347359        //$msg = nl2br(utilsCfs::replaceVariables($msg, array_merge($variables, array('form_data' => $sendFormDataStr))));
    … …  
    363375    }
    364376    return true;
     377  }
     378  /**
     379   * Submitted select box value can be used as recipient email, @see sendContact(),
     380   * so it is accepted only if it is one of field options
     381   * @param array $field Select box field
     382   * @param mixed $value Submitted value
     383   * @return string Value, or empty string if there is no such option
     384   */
     385  private function _getSelectedOptionValue($field, $value)
     386  {
     387    if (!is_string($value) || $value === '' || empty($field['options']) || !is_array($field['options'])) {
     388      return '';
     389    }
     390    // Options are prepared in same way as submitted data, @see contact()
     391    foreach (dbCfs::prepareHtmlIn($field['options']) as $opt) {
     392      if (isset($opt['name']) && (string) $opt['name'] === $value) {
     393        return $value;
     394      }
     395    }
     396    return '';
    365397  }
    366398  private function _generateSendFormData($fieldsData, $form)
    … …  
    782814      for ($i = 0; $i < (int) $d['params']['opts_attrs']['txt_block_number']; $i++) {
    783815        $sendValKey = 'params_tpl_txt_val_' . $i;
    784         if (isset($d[$sendValKey])) {
    785           $d['params']['tpl']['txt_' . $i] = urldecode($d[$sendValKey]);
     816        if (isset($d[$sendValKey]) && is_string($d[$sendValKey])) {
     817          // Text is sent url-encoded, so request filter did not see real value - filter it after decode
     818          $d['params']['tpl']['txt_' . $i] = reqCfs::sanitizeString(urldecode($d[$sendValKey]));
    786819        }
    787820      }
  • contact-form-by-supsystic/tags/1.11.1/modules/forms/views/forms.php

    r3750170 r3759933  
    55{
    66  protected $_twig;
     7  /**
     8   * Tags values for current render: placeholder => value, @see _addTwigValue()
     9   */
     10  private $_twigValues = [];
     11  private $_twigValuesKey = '';
    712  private $_renderFormIter = 0;
    813  private $_lastForm = null;
    … …  
    1419    frameCfs::_()->addScript('admin.forms', $this->getModule()->getModPath() . 'js/admin.forms.js');
    1520    frameCfs::_()->addScript('admin.forms.list', $this->getModule()->getModPath() . 'js/admin.forms.list.js');
    16     frameCfs::_()->addJSVar('admin.forms.list', 'cfsTblDataUrl', uriCfs::mod('forms', 'getListForTbl', ['reqType' => 'ajax']));
     21    frameCfs::_()->addJSVar('admin.forms.list', 'cfsTblDataUrl', uriCfs::mod('forms', 'getListForTbl', ['reqType' => 'ajax', '_wpnonce' => wp_create_nonce('cfs_nonce')]));
    1722
    1823    $this->assign('addNewLink', frameCfs::_()->getModule('options')->getTabUrl('forms_add_new'));
    … …  
    114119    ];
    115120
    116     $this->assign('csvExportUrl', uriCfs::mod('forms', 'exportCsv', ['id' => $id]));
     121    $this->assign('csvExportUrl', uriCfs::mod('forms', 'exportCsv', ['id' => $id, '_wpnonce' => wp_create_nonce('cfs_nonce')]));
    117122
    118123    $this->assign('adminEmail', get_bloginfo('admin_email'));
    … …  
    123128    $this->assign('cfsAddNewUrl', $cfsAddNewUrl);
    124129    $this->assign('bgTypes', $bgType);
    125     $this->assign('previewUrl', uriCfs::mod('forms', 'getPreviewHtml', ['id' => $id]));
     130    $this->assign('previewUrl', uriCfs::mod('forms', 'getPreviewHtml', ['id' => $id, '_wpnonce' => wp_create_nonce('cfs_nonce')]));
    126131    $this->assign('form', $form);
    127132    $this->assign('fieldTypes', $fieldTypes);
    … …  
    473478  public function adjustOpacity($color, $alpha)
    474479  {
     480    $color = $this->_resolveTwigValues($color);
     481    $alpha = $this->_resolveTwigValues($alpha);
    475482    $alpha = max(0, min(1, $alpha));
    476483    $rgbColor = utilsCfs::hexToRgb($color);
    … …  
    478485    return 'rgba(' . implode(',', $rgbColor) . ')';
    479486  }
     487  public function hexToRgbaStr($hex, $alpha = 1)
     488  {
     489    return utilsCfs::hexToRgbaStr($this->_resolveTwigValues($hex), $this->_resolveTwigValues($alpha));
     490  }
    480491  public function adjustBrightness($hex, $steps)
    481492  {
     493    $hex = $this->_resolveTwigValues($hex);
     494    $steps = $this->_resolveTwigValues($steps);
    482495    // Steps should be between -255 and 255. Negative = darker, positive = lighter
    483496    $steps = max(-255, min(255, $steps));
    … …  
    525538
    526539    $form['css'] = $this->_replaceTagsWithTwig($form['css'], $form);
     540    $htmlValuesFrom = count($this->_twigValues);
    527541    $form['html'] = $this->_replaceTagsWithTwig($form['html'], $form);
    528542
    529     $form['html'] = str_replace('col-md-', 'supMd', $form['html']);
    530     $form['html'] = str_replace('col-xs-', 'supXs', $form['html']);
    531     $form['html'] = str_replace('col-sm-', 'supSm', $form['html']);
    532     $form['html'] = str_replace('col-lg-', 'supLg', $form['html']);
    533     $form['html'] = str_replace('row', 'supRow', $form['html']);
     543    $colReplaceFrom = ['col-md-', 'col-xs-', 'col-sm-', 'col-lg-', 'row'];
     544    $colReplaceTo = ['supMd', 'supXs', 'supSm', 'supLg', 'supRow'];
     545    $form['html'] = str_replace($colReplaceFrom, $colReplaceTo, $form['html']);
     546    // Values of tags in html were always processed by this replacement too
     547    foreach (array_slice(array_keys($this->_twigValues), $htmlValuesFrom) as $valueKey) {
     548      $this->_twigValues[$valueKey] = str_replace($colReplaceFrom, $colReplaceTo, $this->_twigValues[$valueKey]);
     549    }
    534550
    535551    $form['html'] .= $this->_generateImgsPreload($form);
    … …  
    540556    // it will not recognize css in js data as style whye rendering on server side,
    541557    // but will be replaced back to normal <style> tag in JS, @see js/frontend.forms.js
    542     return $this->_twig->render(
     558    $tpl =
    543559      ($replaceStyleTag ? '<span style="display: none;" id="cfsFormStylesHidden_' . $form['view_id'] . '">' : '<style type="text/css" id="' . $form['view_html_id'] . '_style">') .
    544         $form['css'] .
    545         ($replaceStyleTag ? '</span>' : '</style>') .
    546         '<div id="' .
    547         $form['view_html_id'] .
    548         '" class="cfsFormShell">' .
    549         $form['html'] .
    550         '</div>',
    551       ['forms' => $form],
    552     );
     560      $form['css'] .
     561      ($replaceStyleTag ? '</span>' : '</style>') .
     562      '<div id="' .
     563      $form['view_html_id'] .
     564      '" class="cfsFormShell">' .
     565      $form['html'] .
     566      '</div>';
     567    $obLevel = ob_get_level();
     568    try {
     569      $res = $this->_twig->render($tpl, ['forms' => $form]);
     570    } catch (Throwable $e) {
     571      // Twig cleans own output buffer only for exceptions, not for PHP errors
     572      while (ob_get_level() > $obLevel) {
     573        ob_end_clean();
     574      }
     575      // String loader uses whole template as it's name - so full Twig message would contain all template
     576      $errorMsg = $e instanceof Twig_Error ? $e->getRawMessage() : $e->getMessage();
     577      error_log('Contact Form by Supsystic: can not render form #' . $form['id'] . ': ' . $errorMsg);
     578      return frameCfs::_()->getModule('user')->isAdmin() ? esc_html(sprintf(__('Can not render form template: %s', CFS_LANG_CODE), $errorMsg)) : '';
     579    }
     580    // Put tags values only now - so they are never parsed by template engine
     581    return $this->_insertTwigValues($res);
    553582  }
    554583  private function _generateCommonFormCss($form)
    555584  {
    556585    $res = '';
    557     $res .= '#[SHELL_ID] { width: ' . $form['params']['tpl']['width'] . $form['params']['tpl']['width_measure'] . '}';
     586    // Values go through tags too, @see _replaceTagsWithTwig()
     587    $res .= '#[SHELL_ID] { width: ' . (isset($form['params']['tpl']['width']) ? '[width]' : '') . (isset($form['params']['tpl']['width_measure']) ? '[width_measure]' : '') . '}';
    558588    if (isset($form['params'], $form['params']['tpl'], $form['params']['tpl']['form_sent_msg_color']) && !empty($form['params']['tpl']['form_sent_msg_color'])) {
    559       $res .= '#[SHELL_ID] .cfsSuccessMsg { color: ' . $form['params']['tpl']['form_sent_msg_color'] . ' !important}';
     589      $res .= '#[SHELL_ID] .cfsSuccessMsg { color: [form_sent_msg_color] !important}';
    560590    }
    561591    return $res;
    … …  
    863893    foreach ($replaceFrom as $i => $v) {
    864894      $replaceFrom[$i] = '[' . $v . ']';
     895      // Tags values are users data: put them in template only as placeholders, real values are inserted
     896      // after template render, so they are never evaluated by template engine. First 4 tags - are template syntax.
     897      if ($i > 3) {
     898        $replaceTo[$i] = $this->_addTwigValue($replaceTo[$i]);
     899      }
    865900    }
    866901    return str_replace($replaceFrom, $replaceTo, $string);
     902  }
     903  /**
     904   * Register value for template and get it's placeholder
     905   * @param mixed $value
     906   * @return string Placeholder: it is safe for any template place - text, or string in Twig expression
     907   */
     908  private function _addTwigValue($value)
     909  {
     910    if (empty($this->_twigValuesKey)) {
     911      // Unpredictable, and contains only chars that will not be changed by any template replacements
     912      $this->_twigValuesKey = strtoupper(substr(md5(uniqid('', true) . wp_rand()), 0, 12));
     913    }
     914    $placeholder = '__CFSV' . $this->_twigValuesKey . '_' . count($this->_twigValues) . '__';
     915    // Same line breaks normalization as Twig lexer did, when values were part of template
     916    $this->_twigValues[$placeholder] = is_scalar($value) ? str_replace(["\r\n", "\r"], "\n", (string) $value) : '';
     917    return $placeholder;
     918  }
     919  private function _insertTwigValues($str)
     920  {
     921    // strtr() makes all replacements in one pass, so inserted values are not processed again
     922    return empty($this->_twigValues) ? $str : strtr($str, $this->_twigValues);
     923  }
     924  /**
     925   * Template functions get placeholders as arguments, @see adjustBrightness()
     926   */
     927  private function _resolveTwigValues($value)
     928  {
     929    return is_string($value) && strpos($value, '__CFSV') !== false ? $this->_insertTwigValues($value) : $value;
    867930  }
    868931  protected function _initTwig()
    … …  
    873936      }
    874937      Twig_Autoloader::register();
     938      importClassCfs('twigSecurityPolicyCfs', CFS_CLASSES_DIR . 'twigSecurityPolicy.php');
    875939      $this->_twig = new Twig_Environment(new Twig_Loader_String(), ['debug' => 0]);
     940      // Templates can be edited in admin area - so they are always rendered in sandbox
     941      $this->_twig->addExtension(new Twig_Extension_Sandbox(new twigSecurityPolicyCfs(), true));
    876942      $this->_twig->addFunction(new Twig_SimpleFunction('adjust_brightness', [$this, 'adjustBrightness']));
    877943      $this->_twig->addFunction(new Twig_SimpleFunction('adjust_opacity', [$this, 'adjustOpacity']));
    878       $this->_twig->addFunction(new Twig_SimpleFunction('hex_to_rgba_str', 'utilsCfs::hexToRgbaStr'));
     944      $this->_twig->addFunction(new Twig_SimpleFunction('hex_to_rgba_str', [$this, 'hexToRgbaStr']));
    879945    }
    880946  }
    … …  
    885951    frameCfs::_()->addScript('admin.forms', $this->getModule()->getModPath() . 'js/admin.forms.js');
    886952    frameCfs::_()->addScript('admin.forms.contacts.list', $this->getModule()->getModPath() . 'js/admin.forms.contacts.list.js');
    887     frameCfs::_()->addJSVar('admin.forms.contacts.list', 'cfsTblDataUrl', uriCfs::mod('forms', 'getContactsListForTbl', ['reqType' => 'ajax', 'fid' => $fid]));
     953    frameCfs::_()->addJSVar('admin.forms.contacts.list', 'cfsTblDataUrl', uriCfs::mod('forms', 'getContactsListForTbl', ['reqType' => 'ajax', 'fid' => $fid, '_wpnonce' => wp_create_nonce('cfs_nonce')]));
    888954    $allForms = $this->getModel('forms')->getAllFromTbl();
    889955    $allFormsForSelect = [];
  • contact-form-by-supsystic/tags/1.11.1/modules/forms/views/tpl/formsEditFormStatistics.php

    r3491826 r3759933  
    3939                        <a href="#" class="button cfsStatChartGroupBtn" data-stat-group="month" data-chart="cfsMainStats"><?php _e('Month', CFS_LANG_CODE); ?></a>
    4040                        |
    41                         <a href="<?php echo uriCfs::mod('statistics', 'getCsv'); ?>" target="_blank" class="button cfsStatExportCsv" data-chart="cfsMainStats"><?php _e('Export to CSV', CFS_LANG_CODE); ?></a>
     41                        <a href="<?php echo uriCfs::mod('statistics', 'getCsv', ['_wpnonce' => wp_create_nonce('cfs_nonce')]); ?>" target="_blank" class="button cfsStatExportCsv" data-chart="cfsMainStats"><?php _e('Export to CSV', CFS_LANG_CODE); ?></a>
    4242                        |
    4343                </span>
  • contact-form-by-supsystic/tags/1.11.1/modules/mail/mod.php

    r3491826 r3759933  
    77  public function send($to, $subject, $message, $fromName = '', $fromEmail = '', $replyToName = '', $replyToEmail = '', $additionalHeaders = [], $additionalParameters = [])
    88  {
     9    // Line breaks in any of header values would inject new mail headers
     10    list($to, $subject, $fromName, $fromEmail, $replyToName, $replyToEmail) = utilsCfs::cleanMailHeader([$to, $subject, $fromName, $fromEmail, $replyToName, $replyToEmail]);
     11    $additionalHeaders = empty($additionalHeaders) ? [] : array_filter(utilsCfs::cleanMailHeader((array) $additionalHeaders));
    912    $type = frameCfs::_()->getModule('options')->get('mail_send_engine');
    1013    $res = false;
    … …  
    6972      $this->_sendMailMailer = new PHPMailer\PHPMailer\PHPMailer(true); // create a new object
    7073      $this->_sendMailMailer->isSendmail(); // enable SendMail
    71       $sendMailPath = trim(frameCfs::_()->getModule('options')->get('sendmail_path'));
    72       if (!empty($sendMailPath)) {
     74      $sendMailPath = trim((string) frameCfs::_()->getModule('options')->get('sendmail_path'));
     75      // Value could be saved before validation was added - never pass invalid one to shell
     76      if (!empty($sendMailPath) && utilsCfs::isValidSendmailPath($sendMailPath)) {
    7377        $this->_sendMailMailer->Sendmail = $sendMailPath;
    7478      }
  • contact-form-by-supsystic/tags/1.11.1/modules/mail/views/tpl/mailAdmin.php

    r3491826 r3759933  
    66        <?php echo htmlCfs::hidden('mod', ['value' => 'mail']); ?>
    77        <?php echo htmlCfs::hidden('action', ['value' => 'testEmail']); ?>
     8        <?php echo htmlCfs::defaultNonceForAdminPanel(); ?>
    89        <button class="button button-primary">
    910                <i class="fa fa-paper-plane"></i>
    … …  
    6364        <?php echo htmlCfs::hidden('mod', ['value' => 'mail']); ?>
    6465        <?php echo htmlCfs::hidden('action', ['value' => 'saveOptions']); ?>
     66        <?php echo htmlCfs::defaultNonceForAdminPanel(); ?>
    6567        <button class="button button-primary">
    6668                <i class="fa fa-fw fa-save"></i>
  • contact-form-by-supsystic/tags/1.11.1/modules/options/models/options.php

    r3641551 r3759933  
    2222  {
    2323    if (isset(self::$_restrictedOptionValues[$optKey]) && !in_array($val, self::$_restrictedOptionValues[$optKey], true)) {
     24      return;
     25    }
     26    if (!$this->_isValidOptValue($optKey, $val)) {
    2427      return;
    2528    }
    … …  
    7073  {
    7174    if (isset($d['opt_values']) && is_array($d['opt_values']) && !empty($d['opt_values'])) {
     75      if (isset($d['opt_values']['sendmail_path']) && !$this->_isValidOptValue('sendmail_path', $d['opt_values']['sendmail_path'])) {
     76        // Field is always submitted with mail options, but matters only for Sendmail engine
     77        if (isset($d['opt_values']['mail_send_engine']) && $d['opt_values']['mail_send_engine'] === 'sendmail') {
     78          $this->pushError(__('Invalid Sendmail Path: it should be absolute path to sendmail binary, for example /usr/sbin/sendmail', CFS_LANG_CODE), 'opt_values[sendmail_path]');
     79          return false;
     80        }
     81        unset($d['opt_values']['sendmail_path']);
     82      }
    7283      dispatcherCfs::doAction('beforeSaveOpts', $d);
    7384      foreach ($d['opt_values'] as $code => $val) {
    … …  
    8091    }
    8192    return false;
     93  }
     94  private function _isValidOptValue($optKey, $val)
     95  {
     96    switch ($optKey) {
     97      case 'sendmail_path':
     98        return utilsCfs::isValidSendmailPath($val);
     99    }
     100    return true;
    82101  }
    83102  private function _updateOptsInDb()
  • contact-form-by-supsystic/tags/1.11.1/modules/statistics/controller.php

    r3491826 r3759933  
    8080    $res->ajaxExec();
    8181  }
     82  /**
     83   * add() is called from frontend and checks connect_hash on it's own.
     84   * Inherited removeGroup() and clear() are not listed anywhere - so they can not be called by request.
     85   */
     86  public function getPublicMethods()
     87  {
     88    return ['add'];
     89  }
    8290  public function getPermissions()
    8391  {
  • contact-form-by-supsystic/tags/1.11.1/modules/templates/mod.php

    r3640942 r3759933  
    9696        }
    9797        $jsData['cfsAcShow'] = $show;
     98        // All admin requests require plugin nonce, @see frameCfs::havePermissions()
     99        if (frameCfs::_()->getModule('user')->isAdmin()) {
     100          $jsData['cfsNonce'] = wp_create_nonce('cfs_nonce');
     101        }
    98102      }
    99103      $jsData = dispatcherCfs::applyFilters('jsInitVariables', $jsData);
  • contact-form-by-supsystic/tags/1.11.1/readme.txt

    r3750170 r3759933  
    44Tested up to: 7.1
    55Requires PHP: 7.3
    6 Stable tag: 1.10.7
     6Stable tag: 1.11.1
    77License: GPLv2 or later
    88License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    169169= 2026 =
    170170
     171= 1.11.1 — October 22, 2026 =
     172
     173* Maintenance release with the latest Contact Form updates.
     174
     175= 1.11.0 — October 22, 2026 =
     176
     177* General security hardening and code improvements.
     178
    171179= 1.10.7 — October 16, 2026 =
    172180
  • contact-form-by-supsystic/trunk/cfs.php

    r3750257 r3759933  
    55 * Plugin URI: https://supsystic.com/plugins/contact-form-plugin/
    66 * Description: Drag-and-drop contact form builder with ready templates, 25+ field types, reCAPTCHA, saved submissions and form statistics. No coding needed.
    7  * Version: 1.10.7
     7 * Version: 1.11.1
    88 * Author: supsystic.com
    99 * Author URI: https://supsystic.com
  • contact-form-by-supsystic/trunk/classes/controller.php

    r3679442 r3759933  
    104104  public function __call($name, $arguments)
    105105  {
    106     // delete()/clear() wipe the entire table when called with no/null arguments,
    107     // which is exactly how they get invoked here (AJAX dispatch never actually
    108     // passes $arguments through). Neither is reliably listed in every controller's
    109     // getPermissions(), and havePermissions() defaults to "allow" for any action
    110     // name it doesn't recognize - so without this check, any visitor could delete
    111     // all data in a table simply by requesting action=delete or action=clear.
     106    // delete()/clear() wipe the entire table when called with no/null arguments.
     107    // Request dispatcher never routes actions here (frameCfs::havePermissions() accepts
     108    // only real declared controller methods), but keep this guard for any other caller.
    112109    if (in_array(strtolower($name), ['delete', 'clear'], true)) {
    113110      return false;
    … …  
    136133                );
    137134         * Can be used on of sub-array - CFS_METHODS or CFS_USERLEVELS
     135         * Methods that are not listed here and not listed in getPublicMethods() can not be called by request at all.
    138136         */
    139137  public function getPermissions()
    … …  
    142140  }
    143141  /**
    144    * Methods that require nonce to be generated
     142   * Methods that can be called by request from any visitor, including not logged in users.
     143   * Each of them must validate request on it's own (own nonce, hash, etc.).
    145144   * If need - should be redefined in each controller where it required.
    146145   * @return array
    147146   */
     147  public function getPublicMethods()
     148  {
     149    return [];
     150  }
     151  /**
     152   * Methods from getPermissions() that are opened by direct links from outside of plugin pages
     153   * (for example - OAuth callbacks), so they can not carry plugin nonce.
     154   * All other methods from getPermissions() always require valid plugin nonce.
     155   * @return array
     156   */
     157  public function getNonceExemptMethods()
     158  {
     159    return [];
     160  }
     161  /**
     162   * Public methods that require plugin nonce too.
     163   * Methods from getPermissions() require it anyway, @see frameCfs::havePermissions().
     164   * @return array
     165   */
    148166  public function getNoncedMethods()
    149167  {
    150168    return [];
     169  }
     170  /**
     171   * Check that current request was sent by plugin administrator from plugin pages
     172   * @return bool
     173   */
     174  protected function _isAdminRequest()
     175  {
     176    return frameCfs::_()->getModule('user')->isAdmin() && frameCfs::_()->verifyNonce();
    151177  }
    152178  public function getModule()
    … …  
    234260  {
    235261    $res = new responseCfs();
    236     if ($this->getModel()->removeGroup(reqCfs::getVar('listIds', 'post'))) {
     262    // Inherited by all controllers - so check access here too, not only in request dispatcher
     263    if (!$this->_isAdminRequest()) {
     264      $res->pushError(__('You have no permissions to do this', CFS_LANG_CODE));
     265    } elseif ($this->getModel()->removeGroup(reqCfs::getVar('listIds', 'post'))) {
    237266      $res->addMessage(__('Done', CFS_LANG_CODE));
    238267    } else {
    … …  
    244273  {
    245274    $res = new responseCfs();
    246     if ($this->getModel()->clear()) {
     275    if (!$this->_isAdminRequest()) {
     276      $res->pushError(__('You have no permissions to do this', CFS_LANG_CODE));
     277    } elseif ($this->getModel()->clear()) {
    247278      $res->addMessage(__('Done', CFS_LANG_CODE));
    248279    } else {
  • contact-form-by-supsystic/trunk/classes/frame.php

    r3641551 r3759933  
    2323  private $_mod = '';
    2424  private $_action = '';
     25  /**
     26   * PRO before 1.5.0 does not declare it's public and nonce exempt methods, @see _isLegacyPro()
     27   */
     28  private static $_legacyProPublicMethods = [
     29    'add_fields' => ['uploadFile', 'getFile', 'removeFile'],
     30    'wp_subscribe' => ['confirm'],
     31  ];
     32  private static $_legacyProNonceExemptMethods = [
     33    'campaignmonitor' => ['saveAccessToken'],
     34    'constantcontact' => ['saveAccessToken'],
     35    'verticalresponse' => ['saveAccessToken'],
     36    // Read only exports - old PRO opens them by links without nonce
     37    'ab_testing' => ['getListForTbl'],
     38    'wp_subscribe' => ['getWpCsvList'],
     39    'statistics' => ['getCsv'],
     40  ];
     41  private static $_legacyPro = null;
    2542  /**
    2643   * Object with result of executing non-ajax module request
    … …  
    194211  }
    195212  /**
    196    * Check permissions for action in controller by $code
     213   * Check permissions for action in controller by $code.
     214   * Access is denied by default: an action can be dispatched only if its controller declares it,
     215   * either as public (getPublicMethods() - such method must validate the request on its own),
     216   * or for some user level (getPermissions()). Every action that is not public requires
     217   * a valid plugin nonce too, except actions from getNonceExemptMethods().
    197218   * @param string $code Code of controller that need to be checked
    198219   * @param string $action Action that need to be checked
    … …  
    201222  public function havePermissions($code, $action)
    202223  {
    203     $res = true;
     224    $controller = $this->_getDispatchController($code, $action);
     225    if (!$controller) {
     226      return false;
     227    }
     228    $action = strtolower($action);
     229    if ($this->_methodListed($action, $this->_getPublicMethods($code, $controller))) {
     230      // Public methods can be additionally enrolled for the plugin nonce
     231      return !$this->_methodListed($action, $controller->getNoncedMethods()) || $this->verifyNonce();
     232    }
     233    $requiredLevels = $this->_getRequiredUserLevels($controller->getPermissions(), $action);
     234    if (empty($requiredLevels)) {
     235      // Action was not declared by controller - deny it
     236      return false;
     237    }
     238    $currentUserPosition = $this->getModule('user')->getCurrentUserPosition();
     239    // For multi-sites network admin role is undefined, let's do this here
     240    if (is_multisite() && is_admin() && is_super_admin()) {
     241      $currentUserPosition = CFS_ADMIN;
     242    }
     243    foreach ($requiredLevels as $levels) {
     244      if (!in_array($currentUserPosition, $levels, true)) {
     245        return false;
     246      }
     247    }
     248    if (!$this->_methodListed($action, $this->_getNonceExemptMethods($code, $controller)) && !$this->verifyNonce()) {
     249      return false;
     250    }
     251    return true;
     252  }
     253  /**
     254   * PRO add-on can be older than this plugin: before 1.5.0 it did not declare access for it's methods
     255   * @return bool
     256   */
     257  protected function _isLegacyPro()
     258  {
     259    if (self::$_legacyPro === null) {
     260      self::$_legacyPro = function_exists('getProPlugCodeCfs') && !defined('CFS_PRO_DECLARES_ACCESS');
     261    }
     262    return self::$_legacyPro;
     263  }
     264  protected function _getPublicMethods($code, $controller)
     265  {
     266    $methods = (array) $controller->getPublicMethods();
     267    if ($this->_isLegacyPro() && isset(self::$_legacyProPublicMethods[$code])) {
     268      $methods = array_merge($methods, self::$_legacyProPublicMethods[$code]);
     269    }
     270    return $methods;
     271  }
     272  protected function _getNonceExemptMethods($code, $controller)
     273  {
     274    $methods = (array) $controller->getNonceExemptMethods();
     275    if ($this->_isLegacyPro() && isset(self::$_legacyProNonceExemptMethods[$code])) {
     276      $methods = array_merge($methods, self::$_legacyProNonceExemptMethods[$code]);
     277    }
     278    return $methods;
     279  }
     280  /**
     281   * Check if action is allowed for any visitor, including not logged in users
     282   * @param string $code Code of controller that need to be checked
     283   * @param string $action Action that need to be checked
     284   * @return bool
     285   */
     286  public function isPublicAction($code, $action)
     287  {
     288    $controller = $this->_getDispatchController($code, $action);
     289    return $controller && $this->_methodListed(strtolower($action), $this->_getPublicMethods($code, $controller));
     290  }
     291  /**
     292   * Verify plugin nonce from current request
     293   * @return bool
     294   */
     295  public function verifyNonce()
     296  {
     297    $nonce = isset($_REQUEST['_wpnonce']) && is_string($_REQUEST['_wpnonce']) ? sanitize_text_field(wp_unslash($_REQUEST['_wpnonce'])) : '';
     298    if (empty($nonce)) {
     299      return false;
     300    }
     301    return wp_verify_nonce($nonce, 'cfs_nonce') || wp_verify_nonce($nonce, 'cfs_nonce_frontend');
     302  }
     303  /**
     304   * Get controller of module, if action can be dispatched to it: it should be real public controller method,
     305   * not some name that will be routed through controllerCfs::__call() to it's model.
     306   * @param string $code Module code
     307   * @param string $action Action name
     308   * @return controllerCfs|null
     309   */
     310  protected function _getDispatchController($code, $action)
     311  {
    204312    $mod = $this->getModule($code);
    205     $action = strtolower($action);
    206     if ($mod) {
    207       $permissions = $mod->getController()->getPermissions();
    208       if (!empty($permissions)) {
    209         // Special permissions
    210         if (isset($permissions[CFS_METHODS]) && !empty($permissions[CFS_METHODS])) {
    211           foreach ($permissions[CFS_METHODS] as $method => $permissions) {
    212             // Make case-insensitive
    213             $permissions[CFS_METHODS][strtolower($method)] = $permissions;
    214           }
    215           if (array_key_exists($action, $permissions[CFS_METHODS])) {
    216             // Permission for this method exists
    217             $currentUserPosition = frameCfs::_()->getModule('user')->getCurrentUserPosition();
    218             if ((is_array($permissions[CFS_METHODS][$action]) && !in_array($currentUserPosition, $permissions[CFS_METHODS][$action])) || (!is_array($permissions[CFS_METHODS][$action]) && $permissions[CFS_METHODS][$action] != $currentUserPosition)) {
    219               $res = false;
    220             }
    221           }
    222         }
    223         if (isset($permissions[CFS_USERLEVELS]) && !empty($permissions[CFS_USERLEVELS])) {
    224           $currentUserPosition = frameCfs::_()->getModule('user')->getCurrentUserPosition();
    225           // For multi-sites network admin role is undefined, let's do this here
    226           if (is_multisite() && is_admin() && is_super_admin()) {
    227             $currentUserPosition = CFS_ADMIN;
    228           }
    229           foreach ($permissions[CFS_USERLEVELS] as $userlevel => $methods) {
    230             if (is_array($methods)) {
    231               $lowerMethods = array_map('strtolower', $methods); // Make case-insensitive
    232               if (in_array($action, $lowerMethods)) {
    233                 // Permission for this method exists
    234                 if ($currentUserPosition != $userlevel) {
    235                   $res = false;
    236                 }
    237                 break;
    238               }
    239             } else {
    240               $lowerMethod = strtolower($methods); // Make case-insensitive
    241               if ($lowerMethod == $action) {
    242                 // Permission for this method exists
    243                 if ($currentUserPosition != $userlevel) {
    244                   $res = false;
    245                 }
    246                 break;
    247               }
    248             }
    249           }
    250         }
    251       }
    252       if ($res) {
    253         // Additional check for nonces
    254         $noncedMethods = $mod->getController()->getNoncedMethods();
    255         if (!empty($noncedMethods)) {
    256           $noncedMethods = array_map('strtolower', $noncedMethods);
    257           if (in_array($action, $noncedMethods)) {
    258             $nonce = isset($_REQUEST['_wpnonce']) ? $_REQUEST['_wpnonce'] : reqCfs::getVar('_wpnonce');
    259             if (is_admin()) {
    260               if (!wp_verify_nonce($nonce, 'cfs_nonce')) {
    261                 $res = false;
    262               }
    263             } else {
    264               if (!wp_verify_nonce($nonce, 'cfs_nonce_frontend')) {
    265                 $res = false;
    266               }
    267             }
    268           }
     313    $controller = $mod ? $mod->getController() : null;
     314    if (!$controller || empty($action) || !is_string($action) || !method_exists($controller, $action)) {
     315      return null;
     316    }
     317    $method = new ReflectionMethod($controller, $action);
     318    if (!$method->isPublic() || $method->isStatic()) {
     319      return null;
     320    }
     321    return $controller;
     322  }
     323  protected function _methodListed($action, $methods)
     324  {
     325    return !empty($methods) && is_array($methods) && in_array($action, array_map('strtolower', $methods), true);
     326  }
     327  /**
     328   * Get user levels required for action from controller permissions
     329   * @param array $permissions Result of controllerCfs::getPermissions()
     330   * @param string $action Action name in lower case
     331   * @return array List of conditions, each one - array of user levels, any of them pass condition. Empty if action is not declared.
     332   */
     333  protected function _getRequiredUserLevels($permissions, $action)
     334  {
     335    $res = [];
     336    if (empty($permissions) || !is_array($permissions)) {
     337      return $res;
     338    }
     339    if (isset($permissions[CFS_METHODS]) && is_array($permissions[CFS_METHODS])) {
     340      foreach ($permissions[CFS_METHODS] as $method => $levels) {
     341        if (strtolower($method) == $action) {
     342          $res[] = (array) $levels;
     343          break;
     344        }
     345      }
     346    }
     347    if (isset($permissions[CFS_USERLEVELS]) && is_array($permissions[CFS_USERLEVELS])) {
     348      foreach ($permissions[CFS_USERLEVELS] as $userlevel => $methods) {
     349        if ($this->_methodListed($action, (array) $methods)) {
     350          $res[] = [$userlevel];
     351          break;
    269352        }
    270353      }
    … …  
    335418        case 'ajax':
    336419          add_action('wp_ajax_' . $this->_action, [$mod->getController(), $this->_action]);
    337           add_action('wp_ajax_nopriv_' . $this->_action, [$mod->getController(), $this->_action]);
     420          // Not logged in visitors can reach only actions that controller declared as public
     421          if ($this->isPublicAction($this->_mod, $this->_action)) {
     422            add_action('wp_ajax_nopriv_' . $this->_action, [$mod->getController(), $this->_action]);
     423          }
    338424          break;
    339425        default:
  • contact-form-by-supsystic/trunk/classes/utils.php

    r3679442 r3759933  
    735735    }
    736736    return $str;
     737  }
     738  /**
     739   * Make value safe to be placed in email header: line breaks there start new header.
     740   * @param mixed $value Header value, or array of values
     741   * @return mixed
     742   */
     743  public static function cleanMailHeader($value)
     744  {
     745    if (is_array($value)) {
     746      return array_map([__CLASS__, 'cleanMailHeader'], $value);
     747    }
     748    if (!is_scalar($value)) {
     749      return '';
     750    }
     751    return trim(preg_replace('/[\r\n\t\0\x0B\x0C]+/', ' ', (string) $value));
     752  }
     753  /**
     754   * Sendmail path is passed to shell by mailer, so allow only absolute path to sendmail-compatible binary,
     755   * with few safe flags.
     756   * @param string $path
     757   * @return bool
     758   */
     759  public static function isValidSendmailPath($path)
     760  {
     761    if (!is_string($path)) {
     762      return false;
     763    }
     764    $path = trim($path, ' ');
     765    if ($path === '') {
     766      return true; // Default from php.ini will be used
     767    }
     768    // Only printable chars, parts are separated by spaces only
     769    if (preg_match('/[^\x20-\x7E]/', $path)) {
     770      return false;
     771    }
     772    $parts = preg_split('/ +/', $path);
     773    $bin = array_shift($parts);
     774    if (!preg_match('#^(?:[A-Za-z]:)?[\\\\/][A-Za-z0-9._\\\\/-]+$#', $bin) || strpos($bin, '..') !== false) {
     775      return false;
     776    }
     777    $binName = basename(str_replace('\\', '/', $bin));
     778    if (!preg_match('/^(sendmail|msmtp|ssmtp|esmtp|exim4?|qmail-inject|mini_sendmail|nullmailer-inject)(\.exe)?$/i', $binName)) {
     779      return false;
     780    }
     781    $emailRe = '/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+$/';
     782    for ($i = 0; $i < count($parts); $i++) {
     783      $flag = $parts[$i];
     784      if (in_array($flag, ['-t', '-i', '-oi', '-ti'], true)) {
     785        continue;
     786      }
     787      if (strpos($flag, '-f') === 0 && strlen($flag) > 2 && preg_match($emailRe, substr($flag, 2))) {
     788        continue;
     789      }
     790      if ($flag === '-f' && isset($parts[$i + 1]) && preg_match($emailRe, $parts[$i + 1])) {
     791        $i++;
     792        continue;
     793      }
     794      return false;
     795    }
     796    return true;
    737797  }
    738798  public static function encodeArrayTxt($arr, $forceOrd = false)
  • contact-form-by-supsystic/trunk/config.php

    r3750170 r3759933  
    5353
    5454define('CFS_PLUGIN_INSTALLED', true);
    55 define('CFS_VERSION', '1.10.7');
     55define('CFS_VERSION', '1.11.1');
    5656define('CFS_USER', 'user');
    5757
  • contact-form-by-supsystic/trunk/js/core.js

    r3491826 r3759933  
    8484  var dataType = params.dataType ? params.dataType : 'json';
    8585  // Set plugin orientation
     86  // Admin actions require plugin nonce - add it if request does not have it's own one
     87  var adminNonce = cfsGetAdminNonce();
    8688  if (typeof data == 'string') {
    8789    data += '&pl=' + CFS_DATA.CFS_CODE;
    8890    data += '&reqType=ajax';
     91    if (adminNonce && !/(^|&)_wpnonce=/.test(data)) data += '&_wpnonce=' + encodeURIComponent(adminNonce);
    8992  } else {
    9093    data['pl'] = CFS_DATA.CFS_CODE;
    9194    data['reqType'] = 'ajax';
     95    if (adminNonce && typeof data['_wpnonce'] == 'undefined') data['_wpnonce'] = adminNonce;
    9296  }
    9397
    … …  
    194198}
    195199
     200/**
     201 * Plugin nonce for admin requests, it is not available on frontend
     202 */
     203function cfsGetAdminNonce() {
     204  if (typeof CFS_NONCE != 'undefined' && CFS_NONCE && CFS_NONCE.cfs_nonce) return CFS_NONCE.cfs_nonce;
     205  if (typeof CFS_DATA != 'undefined' && CFS_DATA && CFS_DATA.cfsNonce) return CFS_DATA.cfsNonce;
     206  return false;
     207}
    196208function getDialogElementCfs() {
    197209  return jQuery('<div/>').appendTo(jQuery('body'));
  • contact-form-by-supsystic/trunk/js/core.min.js

    r3403065 r3759933  
    11
    2 if(typeof(CFS_DATA)=="undefined"){var CFS_DATA={}}if(isNumber(CFS_DATA.animationSpeed)){CFS_DATA.animationSpeed=parseInt(CFS_DATA.animationSpeed)}else{if(jQuery.inArray(CFS_DATA.animationSpeed,["fast","slow"])==-1){CFS_DATA.animationSpeed="fast"}}CFS_DATA.showSubscreenOnCenter=parseInt(CFS_DATA.showSubscreenOnCenter);var sdLoaderImgCfs='<img src="'+CFS_DATA.loader+'" />';var g_cfsAnimationSpeed=300;jQuery.fn.showLoaderCfs=function(){return jQuery(this).html(sdLoaderImgCfs)};jQuery.fn.appendLoaderCfs=function(){jQuery(this).append(sdLoaderImgCfs)};jQuery.sendFormCfs=function(a){return jQuery("<br />").sendFormCfs(a)};jQuery.fn.sendFormCfs=function(e){var b=null;if(!e){e={fid:false,msgElID:false,onSuccess:false}}if(e.fid){b=jQuery("#"+fid)}else{b=jQuery(this)}var c=(jQuery(b).tagName()=="FORM");var f=new Array();if(e.data){f=e.data}else{if(c){f=jQuery(b).serialize()}}if(e.appendData){var h=typeof(f)=="string";var k=[];for(var g in e.appendData){if(h){k.push(g+"="+e.appendData[g])}else{f[g]=e.appendData[g]}}if(h){f+="&"+k.join("&")}}var d=null;if(e.msgElID){if(e.msgElID=="noMessages"){d=false}else{if(typeof(e.msgElID)=="object"){d=e.msgElID}else{d=jQuery("#"+e.msgElID)}}}if(typeof(e.inputsWraper)=="string"){b=jQuery("#"+e.inputsWraper);c=true}if(c&&b){jQuery(b).find("*").removeClass("cfsInputError")}if(d&&!e.btn){jQuery(d).removeClass("cfsSuccessMsg").removeClass("cfsErrorMsg");if(!e.btn){jQuery(d).showLoaderCfs()}}if(e.btn){jQuery(e.btn).attr("disabled","disabled");e.btnIconElement=jQuery(e.btn).find(".fa").length?jQuery(e.btn).find(".fa"):jQuery(e.btn);if(jQuery(e.btn).find(".fa").length){e.btnIconElement.data("prev-class",e.btnIconElement.attr("class")).attr("class","fa fa-spinner fa-spin")}}var a="";if(typeof(e.url)!="undefined"){a=e.url}else{if(typeof(ajaxurl)=="undefined"){a=CFS_DATA.ajaxurl}else{a=ajaxurl}}jQuery(".cfsErrorForField").hide(CFS_DATA.animationSpeed);var j=e.dataType?e.dataType:"json";if(typeof(f)=="string"){f+="&pl="+CFS_DATA.CFS_CODE;f+="&reqType=ajax"}else{f.pl=CFS_DATA.CFS_CODE;f.reqType="ajax"}jQuery.ajax({url:a,data:f,type:"POST",dataType:j,success:function(i){toeProcessAjaxResponseCfs(i,d,b,c,e);if(e.clearMsg){setTimeout(function(){if(d){jQuery(d).animateClear()}},typeof(e.clearMsg)=="boolean"?5000:e.clearMsg)}}})};jQuery.fn.animateClear=function(){var a=jQuery("<span>"+jQuery(this).html()+"</span>");jQuery(this).html(a);jQuery(a).hide(CFS_DATA.animationSpeed,function(){jQuery(a).remove()})};jQuery.fn.animateRemoveCfs=function(a,b){a=a==undefined?CFS_DATA.animationSpeed:a;jQuery(this).hide(a,function(){jQuery(this).remove();if(typeof(b)==="function"){b()}})};function toeProcessAjaxResponseCfs(h,e,c,d,f){if(typeof(f)=="undefined"){f={}}if(typeof(e)=="string"){e=jQuery("#"+e)}if(e){jQuery(e).html("")}if(f.btn){jQuery(f.btn).removeAttr("disabled");if(f.btnIconElement){f.btnIconElement.attr("class",f.btnIconElement.data("prev-class"))}}if(typeof(h)=="object"){if(h.error){if(e){jQuery(e).removeClass("cfsSuccessMsg").addClass("cfsErrorMsg")}var j=[];for(var a in h.errors){if(d){var b=jQuery(c).find('[name*="'+a+'"]');b.addClass("cfsInputError");if(b.attr("placeholder")){}if(!b.data("keyup-error-remove-binded")){b.keydown(function(){jQuery(this).removeClass("cfsInputError")}).data("keyup-error-remove-binded",1)}}if(jQuery(".cfsErrorForField.toe_"+nameToClassId(a)+"").exists()){jQuery(".cfsErrorForField.toe_"+nameToClassId(a)+"").show().html(h.errors[a])}else{if(e){jQuery(e).append(h.errors[a]).append("<br />")}else{j.push(h.errors[a])}}}if(j.length&&f.btn&&jQuery.fn.dialog&&!e){jQuery('<div title="'+toeLangCfs("Really small warning :)")+'" />').html(j.join("<br />")).appendTo("body").dialog({modal:true,width:"500px"})}}else{if(h.messages.length){if(e){jQuery(e).removeClass("cfsErrorMsg").addClass("cfsSuccessMsg");for(var g=0;g<h.messages.length;g++){jQuery(e).append(h.messages[g]).append("<br />")}}}}}if(f.onSuccess&&typeof(f.onSuccess)=="function"){f.onSuccess(h)}}function getDialogElementCfs(){return jQuery("<div/>").appendTo(jQuery("body"))}function toeOptionCfs(a){if(CFS_DATA.options&&CFS_DATA.options[a]){return CFS_DATA.options[a]}return false}function toeLangCfs(a){if(CFS_DATA.siteLang&&CFS_DATA.siteLang[a]){return CFS_DATA.siteLang[a]}return a}function toePagesCfs(a){if(typeof(CFS_DATA)!="undefined"&&CFS_DATA[a]){return CFS_DATA[a]}return false}function toeOptTimeoutHideDescriptionCfs(){jQuery("#cfsOptDescription").removeAttr("toeFixTip");setTimeout(function(){if(!jQuery("#cfsOptDescription").attr("toeFixTip")){toeOptHideDescriptionCfs()}},500)}function toeOptShowDescriptionCfs(b,a,d,c){if(typeof(b)!="undefined"&&b!=""){if(!jQuery("#cfsOptDescription").length){jQuery("body").append('<div id="cfsOptDescription"></div>')}if(c){jQuery("#cfsOptDescription").css("right",jQuery(window).width()-(a-10))}else{jQuery("#cfsOptDescription").css("left",a+10)}jQuery("#cfsOptDescription").css("top",d);jQuery("#cfsOptDescription").show(200);jQuery("#cfsOptDescription").html(b)}}function toeOptHideDescriptionCfs(){jQuery("#cfsOptDescription").removeAttr("toeFixTip");jQuery("#cfsOptDescription").hide(200)}function toeInArrayCfs(c,b){if(b){for(var a in b){if(b[a]==c){return true}}}return false}function toeShowDialogCustomized(b,a){a=jQuery.extend({resizable:false,width:500,height:300,closeOnEscape:true,open:function(c,d){jQuery(".ui-dialog-titlebar").css({"background-color":"#222222","background-image":"none",border:"none",margin:"0",padding:"0","border-radius":"0",color:"#CFCFCF",height:"27px"});jQuery(".ui-dialog-titlebar-close").css({background:'url("'+CFS_DATA.cssPath+'img/tb-close.png") no-repeat scroll 0 0 transparent',border:"0",width:"15px",height:"15px",padding:"0","border-radius":"0",margin:"7px 7px 0"}).html("");jQuery(".ui-dialog").css({"border-radius":"3px","background-color":"#FFFFFF","background-image":"none",padding:"1px","z-index":"300000",position:"fixed",top:"60px"});jQuery(".ui-dialog-buttonpane").css({"background-color":"#FFFFFF"});jQuery(".ui-dialog-title").css({color:"#CFCFCF",font:"12px sans-serif",padding:"6px 10px 0"});if(a.openCallback&&typeof(a.openCallback)=="function"){a.openCallback(c,d)}jQuery(".ui-widget-overlay").css({"z-index":jQuery(c.target).parents(".ui-dialog:first").css("z-index")-1,"background-image":"none"});if(a.modal&&a.closeOnBg){jQuery(".ui-widget-overlay").unbind("click").bind("click",function(){jQuery(b).dialog("close")})}}},a);return jQuery(b).dialog(a)}function toeSliderMove(a,b){var c=jQuery(a.target).attr("id");jQuery("#toeSliderDisplay_"+c).html(b.value);jQuery("#toeSliderInput_"+c).val(b.value).change()}function cfsCorrectJqueryUsed(){return(typeof(jQuery.fn.sendFormCfs)==="function")}function cfsReloadCoreJs(d,e){var c="",b=["common.js","core.js"];for(var a=0;a<b.length;a++){c+='<script type="text/javascript" class="cfsReloadedScript" src="'+CFS_DATA.jsPath+b[a]+'"><\/script>'}jQuery("head").append(c);if(d){_cfsRunClbAfterCoreReload(d,e)}}function _cfsRunClbAfterCoreReload(a,b){if(cfsCorrectJqueryUsed()){callUserFuncArray(a,b);return}setTimeout(function(){cfsCorrectJqueryUsed(a,b)},500)}function base64_decode(d){var b="";var l,j,g="";var k,h,f,e="";var c=0;var a=/[^A-Za-z0-9\+\/\=]/g;if(a.exec(d)){alert("There were invalid base64 characters in the input text.\nValid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\nExpect errors in decoding.")}d=d.replace(/[^A-Za-z0-9\+\/\=]/g,"");do{k=keyStr.indexOf(d.charAt(c++));h=keyStr.indexOf(d.charAt(c++));f=keyStr.indexOf(d.charAt(c++));e=keyStr.indexOf(d.charAt(c++));l=(k<<2)|(h>>4);j=((h&15)<<4)|(f>>2);g=((f&3)<<6)|e;b=b+String.fromCharCode(l);if(f!=64){b=b+String.fromCharCode(j)}if(e!=64){b=b+String.fromCharCode(g)}l=j=g="";k=h=f=e=""}while(c<d.length);return unescape(b)};
     2if(typeof(CFS_DATA)=="undefined"){var CFS_DATA={}}if(isNumber(CFS_DATA.animationSpeed)){CFS_DATA.animationSpeed=parseInt(CFS_DATA.animationSpeed)}else{if(jQuery.inArray(CFS_DATA.animationSpeed,["fast","slow"])==-1){CFS_DATA.animationSpeed="fast"}}CFS_DATA.showSubscreenOnCenter=parseInt(CFS_DATA.showSubscreenOnCenter);var sdLoaderImgCfs='<img src="'+CFS_DATA.loader+'" />';var g_cfsAnimationSpeed=300;jQuery.fn.showLoaderCfs=function(){return jQuery(this).html(sdLoaderImgCfs)};jQuery.fn.appendLoaderCfs=function(){jQuery(this).append(sdLoaderImgCfs)};jQuery.sendFormCfs=function(a){return jQuery("<br />").sendFormCfs(a)};jQuery.fn.sendFormCfs=function(e){var b=null;if(!e){e={fid:false,msgElID:false,onSuccess:false}}if(e.fid){b=jQuery("#"+fid)}else{b=jQuery(this)}var c=(jQuery(b).tagName()=="FORM");var f=new Array();if(e.data){f=e.data}else{if(c){f=jQuery(b).serialize()}}if(e.appendData){var h=typeof(f)=="string";var k=[];for(var g in e.appendData){if(h){k.push(g+"="+e.appendData[g])}else{f[g]=e.appendData[g]}}if(h){f+="&"+k.join("&")}}var d=null;if(e.msgElID){if(e.msgElID=="noMessages"){d=false}else{if(typeof(e.msgElID)=="object"){d=e.msgElID}else{d=jQuery("#"+e.msgElID)}}}if(typeof(e.inputsWraper)=="string"){b=jQuery("#"+e.inputsWraper);c=true}if(c&&b){jQuery(b).find("*").removeClass("cfsInputError")}if(d&&!e.btn){jQuery(d).removeClass("cfsSuccessMsg").removeClass("cfsErrorMsg");if(!e.btn){jQuery(d).showLoaderCfs()}}if(e.btn){jQuery(e.btn).attr("disabled","disabled");e.btnIconElement=jQuery(e.btn).find(".fa").length?jQuery(e.btn).find(".fa"):jQuery(e.btn);if(jQuery(e.btn).find(".fa").length){e.btnIconElement.data("prev-class",e.btnIconElement.attr("class")).attr("class","fa fa-spinner fa-spin")}}var a="";if(typeof(e.url)!="undefined"){a=e.url}else{if(typeof(ajaxurl)=="undefined"){a=CFS_DATA.ajaxurl}else{a=ajaxurl}}jQuery(".cfsErrorForField").hide(CFS_DATA.animationSpeed);var j=e.dataType?e.dataType:"json";var n=cfsGetAdminNonce();if(typeof(f)=="string"){f+="&pl="+CFS_DATA.CFS_CODE;f+="&reqType=ajax";if(n&&!/(^|&)_wpnonce=/.test(f)){f+="&_wpnonce="+encodeURIComponent(n)}}else{f.pl=CFS_DATA.CFS_CODE;f.reqType="ajax";if(n&&typeof(f._wpnonce)=="undefined"){f._wpnonce=n}}jQuery.ajax({url:a,data:f,type:"POST",dataType:j,success:function(i){toeProcessAjaxResponseCfs(i,d,b,c,e);if(e.clearMsg){setTimeout(function(){if(d){jQuery(d).animateClear()}},typeof(e.clearMsg)=="boolean"?5000:e.clearMsg)}}})};jQuery.fn.animateClear=function(){var a=jQuery("<span>"+jQuery(this).html()+"</span>");jQuery(this).html(a);jQuery(a).hide(CFS_DATA.animationSpeed,function(){jQuery(a).remove()})};jQuery.fn.animateRemoveCfs=function(a,b){a=a==undefined?CFS_DATA.animationSpeed:a;jQuery(this).hide(a,function(){jQuery(this).remove();if(typeof(b)==="function"){b()}})};function toeProcessAjaxResponseCfs(h,e,c,d,f){if(typeof(f)=="undefined"){f={}}if(typeof(e)=="string"){e=jQuery("#"+e)}if(e){jQuery(e).html("")}if(f.btn){jQuery(f.btn).removeAttr("disabled");if(f.btnIconElement){f.btnIconElement.attr("class",f.btnIconElement.data("prev-class"))}}if(typeof(h)=="object"){if(h.error){if(e){jQuery(e).removeClass("cfsSuccessMsg").addClass("cfsErrorMsg")}var j=[];for(var a in h.errors){if(d){var b=jQuery(c).find('[name*="'+a+'"]');b.addClass("cfsInputError");if(b.attr("placeholder")){}if(!b.data("keyup-error-remove-binded")){b.keydown(function(){jQuery(this).removeClass("cfsInputError")}).data("keyup-error-remove-binded",1)}}if(jQuery(".cfsErrorForField.toe_"+nameToClassId(a)+"").exists()){jQuery(".cfsErrorForField.toe_"+nameToClassId(a)+"").show().html(h.errors[a])}else{if(e){jQuery(e).append(h.errors[a]).append("<br />")}else{j.push(h.errors[a])}}}if(j.length&&f.btn&&jQuery.fn.dialog&&!e){jQuery('<div title="'+toeLangCfs("Really small warning :)")+'" />').html(j.join("<br />")).appendTo("body").dialog({modal:true,width:"500px"})}}else{if(h.messages.length){if(e){jQuery(e).removeClass("cfsErrorMsg").addClass("cfsSuccessMsg");for(var g=0;g<h.messages.length;g++){jQuery(e).append(h.messages[g]).append("<br />")}}}}}if(f.onSuccess&&typeof(f.onSuccess)=="function"){f.onSuccess(h)}}function cfsGetAdminNonce(){if(typeof(CFS_NONCE)!="undefined"&&CFS_NONCE&&CFS_NONCE.cfs_nonce){return CFS_NONCE.cfs_nonce}if(typeof(CFS_DATA)!="undefined"&&CFS_DATA&&CFS_DATA.cfsNonce){return CFS_DATA.cfsNonce}return false}function getDialogElementCfs(){return jQuery("<div/>").appendTo(jQuery("body"))}function toeOptionCfs(a){if(CFS_DATA.options&&CFS_DATA.options[a]){return CFS_DATA.options[a]}return false}function toeLangCfs(a){if(CFS_DATA.siteLang&&CFS_DATA.siteLang[a]){return CFS_DATA.siteLang[a]}return a}function toePagesCfs(a){if(typeof(CFS_DATA)!="undefined"&&CFS_DATA[a]){return CFS_DATA[a]}return false}function toeOptTimeoutHideDescriptionCfs(){jQuery("#cfsOptDescription").removeAttr("toeFixTip");setTimeout(function(){if(!jQuery("#cfsOptDescription").attr("toeFixTip")){toeOptHideDescriptionCfs()}},500)}function toeOptShowDescriptionCfs(b,a,d,c){if(typeof(b)!="undefined"&&b!=""){if(!jQuery("#cfsOptDescription").length){jQuery("body").append('<div id="cfsOptDescription"></div>')}if(c){jQuery("#cfsOptDescription").css("right",jQuery(window).width()-(a-10))}else{jQuery("#cfsOptDescription").css("left",a+10)}jQuery("#cfsOptDescription").css("top",d);jQuery("#cfsOptDescription").show(200);jQuery("#cfsOptDescription").html(b)}}function toeOptHideDescriptionCfs(){jQuery("#cfsOptDescription").removeAttr("toeFixTip");jQuery("#cfsOptDescription").hide(200)}function toeInArrayCfs(c,b){if(b){for(var a in b){if(b[a]==c){return true}}}return false}function toeShowDialogCustomized(b,a){a=jQuery.extend({resizable:false,width:500,height:300,closeOnEscape:true,open:function(c,d){jQuery(".ui-dialog-titlebar").css({"background-color":"#222222","background-image":"none",border:"none",margin:"0",padding:"0","border-radius":"0",color:"#CFCFCF",height:"27px"});jQuery(".ui-dialog-titlebar-close").css({background:'url("'+CFS_DATA.cssPath+'img/tb-close.png") no-repeat scroll 0 0 transparent',border:"0",width:"15px",height:"15px",padding:"0","border-radius":"0",margin:"7px 7px 0"}).html("");jQuery(".ui-dialog").css({"border-radius":"3px","background-color":"#FFFFFF","background-image":"none",padding:"1px","z-index":"300000",position:"fixed",top:"60px"});jQuery(".ui-dialog-buttonpane").css({"background-color":"#FFFFFF"});jQuery(".ui-dialog-title").css({color:"#CFCFCF",font:"12px sans-serif",padding:"6px 10px 0"});if(a.openCallback&&typeof(a.openCallback)=="function"){a.openCallback(c,d)}jQuery(".ui-widget-overlay").css({"z-index":jQuery(c.target).parents(".ui-dialog:first").css("z-index")-1,"background-image":"none"});if(a.modal&&a.closeOnBg){jQuery(".ui-widget-overlay").unbind("click").bind("click",function(){jQuery(b).dialog("close")})}}},a);return jQuery(b).dialog(a)}function toeSliderMove(a,b){var c=jQuery(a.target).attr("id");jQuery("#toeSliderDisplay_"+c).html(b.value);jQuery("#toeSliderInput_"+c).val(b.value).change()}function cfsCorrectJqueryUsed(){return(typeof(jQuery.fn.sendFormCfs)==="function")}function cfsReloadCoreJs(d,e){var c="",b=["common.js","core.js"];for(var a=0;a<b.length;a++){c+='<script type="text/javascript" class="cfsReloadedScript" src="'+CFS_DATA.jsPath+b[a]+'"><\/script>'}jQuery("head").append(c);if(d){_cfsRunClbAfterCoreReload(d,e)}}function _cfsRunClbAfterCoreReload(a,b){if(cfsCorrectJqueryUsed()){callUserFuncArray(a,b);return}setTimeout(function(){cfsCorrectJqueryUsed(a,b)},500)}function base64_decode(d){var b="";var l,j,g="";var k,h,f,e="";var c=0;var a=/[^A-Za-z0-9\+\/\=]/g;if(a.exec(d)){alert("There were invalid base64 characters in the input text.\nValid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\nExpect errors in decoding.")}d=d.replace(/[^A-Za-z0-9\+\/\=]/g,"");do{k=keyStr.indexOf(d.charAt(c++));h=keyStr.indexOf(d.charAt(c++));f=keyStr.indexOf(d.charAt(c++));e=keyStr.indexOf(d.charAt(c++));l=(k<<2)|(h>>4);j=((h&15)<<4)|(f>>2);g=((f&3)<<6)|e;b=b+String.fromCharCode(l);if(f!=64){b=b+String.fromCharCode(j)}if(e!=64){b=b+String.fromCharCode(g)}l=j=g="";k=h=f=e=""}while(c<d.length);return unescape(b)};
  • contact-form-by-supsystic/trunk/modules/forms/controller.php

    r3679442 r3759933  
    622622    return ['save', 'removeGroup', 'getListForTbl', 'saveAsCopy', 'remove', 'createFromTpl', 'changeTpl'];
    623623  }
     624  /**
     625   * contact() checks it's own per-form nonce, updateNonce() just gives fresh nonce for cached pages
     626   */
     627  public function getPublicMethods()
     628  {
     629    return ['contact', 'updateNonce'];
     630  }
    624631  public function getPermissions()
    625632  {
  • contact-form-by-supsystic/trunk/modules/forms/models/forms.php

    r3640942 r3759933  
    204204        }
    205205        if (empty($errorMsg)) {
     206          // Not mandatory email can be empty, but if it is entered - it must be valid
     207          if ($htmlType == 'email' && $value && (!is_string($value) || !is_email($value))) {
     208            $errorMsg = empty($formInvalidError) ? __('Invalid value for %s', CFS_LANG_CODE) : $formInvalidError;
     209          }
     210        }
     211        if (empty($errorMsg)) {
    206212          // Start other validation process
    207213          if (!$isDate && isset($f['min_size']) && !empty($f['min_size']) && ($minSize = (int) $f['min_size'])) {
    … …  
    315321              // This is requiredfor case when you hav select box with vals = mail1@mail.com, ... and names User Name 1, ...
    316322              // To send to it's values, not to it's names
    317               $variablesWithSelectVals['user_' . $sendName] = $fieldsData[$sendName];
     323              $variablesWithSelectVals['user_' . $sendName] = $this->_getSelectedOptionValue($f, isset($fieldsData[$sendName]) ? $fieldsData[$sendName] : '');
    318324              break;
    319325            default:
    … …  
    324330        }
    325331      }
     332      // Submitted values can be mapped to mail headers - line breaks there would start new headers
     333      $headerVariables = utilsCfs::cleanMailHeader($variables);
     334      $headerVariablesWithSelectVals = utilsCfs::cleanMailHeader($variablesWithSelectVals);
    326335      $sendForceTo = dispatcherCfs::applyFilters('sendContactTo', '', $fieldsData, $form);
    327336      foreach ($form['params']['submit'] as $s) {
    … …  
    334343        $additionalHeaders = [];
    335344        if (isset($s['enb_cc']) && (int) $s['enb_cc'] && isset($s['cc']) && !empty($s['cc'])) {
    336           $additionalHeaders[] = 'Cc: ' . utilsCfs::replaceVariables($s['cc'], $variables);
     345          $additionalHeaders[] = 'Cc: ' . utilsCfs::cleanMailHeader(utilsCfs::replaceVariables($s['cc'], $headerVariables));
    337346        }
    338347        if (isset($s['from']) && !empty($s['from'])) {
    339           $additionalHeaders[] = 'From: ' . utilsCfs::replaceVariables($s['from'], $variables);
     348          $additionalHeaders[] = 'From: ' . utilsCfs::cleanMailHeader(utilsCfs::replaceVariables($s['from'], $headerVariables));
    340349        }
    341350        if (isset($s['reply']) && !empty($s['reply'])) {
    342           $additionalHeaders[] = 'Reply-To: ' . utilsCfs::replaceVariables($s['reply'], $variables);
    343         }
    344         $to = utilsCfs::replaceVariables($to, $variablesWithSelectVals);
    345         $subject = utilsCfs::replaceVariables($s['subject'], $variables);
     351          $additionalHeaders[] = 'Reply-To: ' . utilsCfs::cleanMailHeader(utilsCfs::replaceVariables($s['reply'], $headerVariables));
     352        }
     353        $to = utilsCfs::cleanMailHeader(utilsCfs::replaceVariables($to, $headerVariablesWithSelectVals));
     354        if (empty($to)) {
     355          continue; // Recipient was taken from submitted data, and it was not valid
     356        }
     357        $subject = utilsCfs::cleanMailHeader(utilsCfs::replaceVariables($s['subject'], $headerVariables));
    346358        // Let it be only for Message field
    347359        //$msg = nl2br(utilsCfs::replaceVariables($msg, array_merge($variables, array('form_data' => $sendFormDataStr))));
    … …  
    363375    }
    364376    return true;
     377  }
     378  /**
     379   * Submitted select box value can be used as recipient email, @see sendContact(),
     380   * so it is accepted only if it is one of field options
     381   * @param array $field Select box field
     382   * @param mixed $value Submitted value
     383   * @return string Value, or empty string if there is no such option
     384   */
     385  private function _getSelectedOptionValue($field, $value)
     386  {
     387    if (!is_string($value) || $value === '' || empty($field['options']) || !is_array($field['options'])) {
     388      return '';
     389    }
     390    // Options are prepared in same way as submitted data, @see contact()
     391    foreach (dbCfs::prepareHtmlIn($field['options']) as $opt) {
     392      if (isset($opt['name']) && (string) $opt['name'] === $value) {
     393        return $value;
     394      }
     395    }
     396    return '';
    365397  }
    366398  private function _generateSendFormData($fieldsData, $form)
    … …  
    782814      for ($i = 0; $i < (int) $d['params']['opts_attrs']['txt_block_number']; $i++) {
    783815        $sendValKey = 'params_tpl_txt_val_' . $i;
    784         if (isset($d[$sendValKey])) {
    785           $d['params']['tpl']['txt_' . $i] = urldecode($d[$sendValKey]);
     816        if (isset($d[$sendValKey]) && is_string($d[$sendValKey])) {
     817          // Text is sent url-encoded, so request filter did not see real value - filter it after decode
     818          $d['params']['tpl']['txt_' . $i] = reqCfs::sanitizeString(urldecode($d[$sendValKey]));
    786819        }
    787820      }
  • contact-form-by-supsystic/trunk/modules/forms/views/forms.php

    r3750170 r3759933  
    55{
    66  protected $_twig;
     7  /**
     8   * Tags values for current render: placeholder => value, @see _addTwigValue()
     9   */
     10  private $_twigValues = [];
     11  private $_twigValuesKey = '';
    712  private $_renderFormIter = 0;
    813  private $_lastForm = null;
    … …  
    1419    frameCfs::_()->addScript('admin.forms', $this->getModule()->getModPath() . 'js/admin.forms.js');
    1520    frameCfs::_()->addScript('admin.forms.list', $this->getModule()->getModPath() . 'js/admin.forms.list.js');
    16     frameCfs::_()->addJSVar('admin.forms.list', 'cfsTblDataUrl', uriCfs::mod('forms', 'getListForTbl', ['reqType' => 'ajax']));
     21    frameCfs::_()->addJSVar('admin.forms.list', 'cfsTblDataUrl', uriCfs::mod('forms', 'getListForTbl', ['reqType' => 'ajax', '_wpnonce' => wp_create_nonce('cfs_nonce')]));
    1722
    1823    $this->assign('addNewLink', frameCfs::_()->getModule('options')->getTabUrl('forms_add_new'));
    … …  
    114119    ];
    115120
    116     $this->assign('csvExportUrl', uriCfs::mod('forms', 'exportCsv', ['id' => $id]));
     121    $this->assign('csvExportUrl', uriCfs::mod('forms', 'exportCsv', ['id' => $id, '_wpnonce' => wp_create_nonce('cfs_nonce')]));
    117122
    118123    $this->assign('adminEmail', get_bloginfo('admin_email'));
    … …  
    123128    $this->assign('cfsAddNewUrl', $cfsAddNewUrl);
    124129    $this->assign('bgTypes', $bgType);
    125     $this->assign('previewUrl', uriCfs::mod('forms', 'getPreviewHtml', ['id' => $id]));
     130    $this->assign('previewUrl', uriCfs::mod('forms', 'getPreviewHtml', ['id' => $id, '_wpnonce' => wp_create_nonce('cfs_nonce')]));
    126131    $this->assign('form', $form);
    127132    $this->assign('fieldTypes', $fieldTypes);
    … …  
    473478  public function adjustOpacity($color, $alpha)
    474479  {
     480    $color = $this->_resolveTwigValues($color);
     481    $alpha = $this->_resolveTwigValues($alpha);
    475482    $alpha = max(0, min(1, $alpha));
    476483    $rgbColor = utilsCfs::hexToRgb($color);
    … …  
    478485    return 'rgba(' . implode(',', $rgbColor) . ')';
    479486  }
     487  public function hexToRgbaStr($hex, $alpha = 1)
     488  {
     489    return utilsCfs::hexToRgbaStr($this->_resolveTwigValues($hex), $this->_resolveTwigValues($alpha));
     490  }
    480491  public function adjustBrightness($hex, $steps)
    481492  {
     493    $hex = $this->_resolveTwigValues($hex);
     494    $steps = $this->_resolveTwigValues($steps);
    482495    // Steps should be between -255 and 255. Negative = darker, positive = lighter
    483496    $steps = max(-255, min(255, $steps));
    … …  
    525538
    526539    $form['css'] = $this->_replaceTagsWithTwig($form['css'], $form);
     540    $htmlValuesFrom = count($this->_twigValues);
    527541    $form['html'] = $this->_replaceTagsWithTwig($form['html'], $form);
    528542
    529     $form['html'] = str_replace('col-md-', 'supMd', $form['html']);
    530     $form['html'] = str_replace('col-xs-', 'supXs', $form['html']);
    531     $form['html'] = str_replace('col-sm-', 'supSm', $form['html']);
    532     $form['html'] = str_replace('col-lg-', 'supLg', $form['html']);
    533     $form['html'] = str_replace('row', 'supRow', $form['html']);
     543    $colReplaceFrom = ['col-md-', 'col-xs-', 'col-sm-', 'col-lg-', 'row'];
     544    $colReplaceTo = ['supMd', 'supXs', 'supSm', 'supLg', 'supRow'];
     545    $form['html'] = str_replace($colReplaceFrom, $colReplaceTo, $form['html']);
     546    // Values of tags in html were always processed by this replacement too
     547    foreach (array_slice(array_keys($this->_twigValues), $htmlValuesFrom) as $valueKey) {
     548      $this->_twigValues[$valueKey] = str_replace($colReplaceFrom, $colReplaceTo, $this->_twigValues[$valueKey]);
     549    }
    534550
    535551    $form['html'] .= $this->_generateImgsPreload($form);
    … …  
    540556    // it will not recognize css in js data as style whye rendering on server side,
    541557    // but will be replaced back to normal <style> tag in JS, @see js/frontend.forms.js
    542     return $this->_twig->render(
     558    $tpl =
    543559      ($replaceStyleTag ? '<span style="display: none;" id="cfsFormStylesHidden_' . $form['view_id'] . '">' : '<style type="text/css" id="' . $form['view_html_id'] . '_style">') .
    544         $form['css'] .
    545         ($replaceStyleTag ? '</span>' : '</style>') .
    546         '<div id="' .
    547         $form['view_html_id'] .
    548         '" class="cfsFormShell">' .
    549         $form['html'] .
    550         '</div>',
    551       ['forms' => $form],
    552     );
     560      $form['css'] .
     561      ($replaceStyleTag ? '</span>' : '</style>') .
     562      '<div id="' .
     563      $form['view_html_id'] .
     564      '" class="cfsFormShell">' .
     565      $form['html'] .
     566      '</div>';
     567    $obLevel = ob_get_level();
     568    try {
     569      $res = $this->_twig->render($tpl, ['forms' => $form]);
     570    } catch (Throwable $e) {
     571      // Twig cleans own output buffer only for exceptions, not for PHP errors
     572      while (ob_get_level() > $obLevel) {
     573        ob_end_clean();
     574      }
     575      // String loader uses whole template as it's name - so full Twig message would contain all template
     576      $errorMsg = $e instanceof Twig_Error ? $e->getRawMessage() : $e->getMessage();
     577      error_log('Contact Form by Supsystic: can not render form #' . $form['id'] . ': ' . $errorMsg);
     578      return frameCfs::_()->getModule('user')->isAdmin() ? esc_html(sprintf(__('Can not render form template: %s', CFS_LANG_CODE), $errorMsg)) : '';
     579    }
     580    // Put tags values only now - so they are never parsed by template engine
     581    return $this->_insertTwigValues($res);
    553582  }
    554583  private function _generateCommonFormCss($form)
    555584  {
    556585    $res = '';
    557     $res .= '#[SHELL_ID] { width: ' . $form['params']['tpl']['width'] . $form['params']['tpl']['width_measure'] . '}';
     586    // Values go through tags too, @see _replaceTagsWithTwig()
     587    $res .= '#[SHELL_ID] { width: ' . (isset($form['params']['tpl']['width']) ? '[width]' : '') . (isset($form['params']['tpl']['width_measure']) ? '[width_measure]' : '') . '}';
    558588    if (isset($form['params'], $form['params']['tpl'], $form['params']['tpl']['form_sent_msg_color']) && !empty($form['params']['tpl']['form_sent_msg_color'])) {
    559       $res .= '#[SHELL_ID] .cfsSuccessMsg { color: ' . $form['params']['tpl']['form_sent_msg_color'] . ' !important}';
     589      $res .= '#[SHELL_ID] .cfsSuccessMsg { color: [form_sent_msg_color] !important}';
    560590    }
    561591    return $res;
    … …  
    863893    foreach ($replaceFrom as $i => $v) {
    864894      $replaceFrom[$i] = '[' . $v . ']';
     895      // Tags values are users data: put them in template only as placeholders, real values are inserted
     896      // after template render, so they are never evaluated by template engine. First 4 tags - are template syntax.
     897      if ($i > 3) {
     898        $replaceTo[$i] = $this->_addTwigValue($replaceTo[$i]);
     899      }
    865900    }
    866901    return str_replace($replaceFrom, $replaceTo, $string);
     902  }
     903  /**
     904   * Register value for template and get it's placeholder
     905   * @param mixed $value
     906   * @return string Placeholder: it is safe for any template place - text, or string in Twig expression
     907   */
     908  private function _addTwigValue($value)
     909  {
     910    if (empty($this->_twigValuesKey)) {
     911      // Unpredictable, and contains only chars that will not be changed by any template replacements
     912      $this->_twigValuesKey = strtoupper(substr(md5(uniqid('', true) . wp_rand()), 0, 12));
     913    }
     914    $placeholder = '__CFSV' . $this->_twigValuesKey . '_' . count($this->_twigValues) . '__';
     915    // Same line breaks normalization as Twig lexer did, when values were part of template
     916    $this->_twigValues[$placeholder] = is_scalar($value) ? str_replace(["\r\n", "\r"], "\n", (string) $value) : '';
     917    return $placeholder;
     918  }
     919  private function _insertTwigValues($str)
     920  {
     921    // strtr() makes all replacements in one pass, so inserted values are not processed again
     922    return empty($this->_twigValues) ? $str : strtr($str, $this->_twigValues);
     923  }
     924  /**
     925   * Template functions get placeholders as arguments, @see adjustBrightness()
     926   */
     927  private function _resolveTwigValues($value)
     928  {
     929    return is_string($value) && strpos($value, '__CFSV') !== false ? $this->_insertTwigValues($value) : $value;
    867930  }
    868931  protected function _initTwig()
    … …  
    873936      }
    874937      Twig_Autoloader::register();
     938      importClassCfs('twigSecurityPolicyCfs', CFS_CLASSES_DIR . 'twigSecurityPolicy.php');
    875939      $this->_twig = new Twig_Environment(new Twig_Loader_String(), ['debug' => 0]);
     940      // Templates can be edited in admin area - so they are always rendered in sandbox
     941      $this->_twig->addExtension(new Twig_Extension_Sandbox(new twigSecurityPolicyCfs(), true));
    876942      $this->_twig->addFunction(new Twig_SimpleFunction('adjust_brightness', [$this, 'adjustBrightness']));
    877943      $this->_twig->addFunction(new Twig_SimpleFunction('adjust_opacity', [$this, 'adjustOpacity']));
    878       $this->_twig->addFunction(new Twig_SimpleFunction('hex_to_rgba_str', 'utilsCfs::hexToRgbaStr'));
     944      $this->_twig->addFunction(new Twig_SimpleFunction('hex_to_rgba_str', [$this, 'hexToRgbaStr']));
    879945    }
    880946  }
    … …  
    885951    frameCfs::_()->addScript('admin.forms', $this->getModule()->getModPath() . 'js/admin.forms.js');
    886952    frameCfs::_()->addScript('admin.forms.contacts.list', $this->getModule()->getModPath() . 'js/admin.forms.contacts.list.js');
    887     frameCfs::_()->addJSVar('admin.forms.contacts.list', 'cfsTblDataUrl', uriCfs::mod('forms', 'getContactsListForTbl', ['reqType' => 'ajax', 'fid' => $fid]));
     953    frameCfs::_()->addJSVar('admin.forms.contacts.list', 'cfsTblDataUrl', uriCfs::mod('forms', 'getContactsListForTbl', ['reqType' => 'ajax', 'fid' => $fid, '_wpnonce' => wp_create_nonce('cfs_nonce')]));
    888954    $allForms = $this->getModel('forms')->getAllFromTbl();
    889955    $allFormsForSelect = [];
  • contact-form-by-supsystic/trunk/modules/forms/views/tpl/formsEditFormStatistics.php

    r3491826 r3759933  
    3939                        <a href="#" class="button cfsStatChartGroupBtn" data-stat-group="month" data-chart="cfsMainStats"><?php _e('Month', CFS_LANG_CODE); ?></a>
    4040                        |
    41                         <a href="<?php echo uriCfs::mod('statistics', 'getCsv'); ?>" target="_blank" class="button cfsStatExportCsv" data-chart="cfsMainStats"><?php _e('Export to CSV', CFS_LANG_CODE); ?></a>
     41                        <a href="<?php echo uriCfs::mod('statistics', 'getCsv', ['_wpnonce' => wp_create_nonce('cfs_nonce')]); ?>" target="_blank" class="button cfsStatExportCsv" data-chart="cfsMainStats"><?php _e('Export to CSV', CFS_LANG_CODE); ?></a>
    4242                        |
    4343                </span>
  • contact-form-by-supsystic/trunk/modules/mail/mod.php

    r3491826 r3759933  
    77  public function send($to, $subject, $message, $fromName = '', $fromEmail = '', $replyToName = '', $replyToEmail = '', $additionalHeaders = [], $additionalParameters = [])
    88  {
     9    // Line breaks in any of header values would inject new mail headers
     10    list($to, $subject, $fromName, $fromEmail, $replyToName, $replyToEmail) = utilsCfs::cleanMailHeader([$to, $subject, $fromName, $fromEmail, $replyToName, $replyToEmail]);
     11    $additionalHeaders = empty($additionalHeaders) ? [] : array_filter(utilsCfs::cleanMailHeader((array) $additionalHeaders));
    912    $type = frameCfs::_()->getModule('options')->get('mail_send_engine');
    1013    $res = false;
    … …  
    6972      $this->_sendMailMailer = new PHPMailer\PHPMailer\PHPMailer(true); // create a new object
    7073      $this->_sendMailMailer->isSendmail(); // enable SendMail
    71       $sendMailPath = trim(frameCfs::_()->getModule('options')->get('sendmail_path'));
    72       if (!empty($sendMailPath)) {
     74      $sendMailPath = trim((string) frameCfs::_()->getModule('options')->get('sendmail_path'));
     75      // Value could be saved before validation was added - never pass invalid one to shell
     76      if (!empty($sendMailPath) && utilsCfs::isValidSendmailPath($sendMailPath)) {
    7377        $this->_sendMailMailer->Sendmail = $sendMailPath;
    7478      }
  • contact-form-by-supsystic/trunk/modules/mail/views/tpl/mailAdmin.php

    r3491826 r3759933  
    66        <?php echo htmlCfs::hidden('mod', ['value' => 'mail']); ?>
    77        <?php echo htmlCfs::hidden('action', ['value' => 'testEmail']); ?>
     8        <?php echo htmlCfs::defaultNonceForAdminPanel(); ?>
    89        <button class="button button-primary">
    910                <i class="fa fa-paper-plane"></i>
    … …  
    6364        <?php echo htmlCfs::hidden('mod', ['value' => 'mail']); ?>
    6465        <?php echo htmlCfs::hidden('action', ['value' => 'saveOptions']); ?>
     66        <?php echo htmlCfs::defaultNonceForAdminPanel(); ?>
    6567        <button class="button button-primary">
    6668                <i class="fa fa-fw fa-save"></i>
  • contact-form-by-supsystic/trunk/modules/options/models/options.php

    r3641551 r3759933  
    2222  {
    2323    if (isset(self::$_restrictedOptionValues[$optKey]) && !in_array($val, self::$_restrictedOptionValues[$optKey], true)) {
     24      return;
     25    }
     26    if (!$this->_isValidOptValue($optKey, $val)) {
    2427      return;
    2528    }
    … …  
    7073  {
    7174    if (isset($d['opt_values']) && is_array($d['opt_values']) && !empty($d['opt_values'])) {
     75      if (isset($d['opt_values']['sendmail_path']) && !$this->_isValidOptValue('sendmail_path', $d['opt_values']['sendmail_path'])) {
     76        // Field is always submitted with mail options, but matters only for Sendmail engine
     77        if (isset($d['opt_values']['mail_send_engine']) && $d['opt_values']['mail_send_engine'] === 'sendmail') {
     78          $this->pushError(__('Invalid Sendmail Path: it should be absolute path to sendmail binary, for example /usr/sbin/sendmail', CFS_LANG_CODE), 'opt_values[sendmail_path]');
     79          return false;
     80        }
     81        unset($d['opt_values']['sendmail_path']);
     82      }
    7283      dispatcherCfs::doAction('beforeSaveOpts', $d);
    7384      foreach ($d['opt_values'] as $code => $val) {
    … …  
    8091    }
    8192    return false;
     93  }
     94  private function _isValidOptValue($optKey, $val)
     95  {
     96    switch ($optKey) {
     97      case 'sendmail_path':
     98        return utilsCfs::isValidSendmailPath($val);
     99    }
     100    return true;
    82101  }
    83102  private function _updateOptsInDb()
  • contact-form-by-supsystic/trunk/modules/statistics/controller.php

    r3491826 r3759933  
    8080    $res->ajaxExec();
    8181  }
     82  /**
     83   * add() is called from frontend and checks connect_hash on it's own.
     84   * Inherited removeGroup() and clear() are not listed anywhere - so they can not be called by request.
     85   */
     86  public function getPublicMethods()
     87  {
     88    return ['add'];
     89  }
    8290  public function getPermissions()
    8391  {
  • contact-form-by-supsystic/trunk/modules/templates/mod.php

    r3640942 r3759933  
    9696        }
    9797        $jsData['cfsAcShow'] = $show;
     98        // All admin requests require plugin nonce, @see frameCfs::havePermissions()
     99        if (frameCfs::_()->getModule('user')->isAdmin()) {
     100          $jsData['cfsNonce'] = wp_create_nonce('cfs_nonce');
     101        }
    98102      }
    99103      $jsData = dispatcherCfs::applyFilters('jsInitVariables', $jsData);
  • contact-form-by-supsystic/trunk/readme.txt

    r3750170 r3759933  
    44Tested up to: 7.1
    55Requires PHP: 7.3
    6 Stable tag: 1.10.7
     6Stable tag: 1.11.1
    77License: GPLv2 or later
    88License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    169169= 2026 =
    170170
     171= 1.11.1 — October 22, 2026 =
     172
     173* Maintenance release with the latest Contact Form updates.
     174
     175= 1.11.0 — October 22, 2026 =
     176
     177* General security hardening and code improvements.
     178
    171179= 1.10.7 — October 16, 2026 =
    172180
Note: See TracChangeset for help on using the changeset viewer.