Plugin Directory

Changeset 1974927


Ignore:
Timestamp:
11/15/2018 01:51:05 PM (7 years ago)
Author:
john ackers
Message:

using session, field handling cleaned up

Location:
ecampaign/branches/maintenance
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • ecampaign/branches/maintenance/Ecampaign.class.php

    r1851632 r1974927  
    1515*/
    1616
     17class EcampaignSession
     18{
     19  public
     20    $classPath,                   // path and class name of declaring class
     21    $formID = NULL,
     22    $templateFields = [],         // array of fields populated by parseTemplate()
     23    $testMode = NULL,             // determines whether messages are delivered and to where
     24    $fieldSet = NULL;             // bindings - posted values in associative array
     25 
     26  function __construct($postID, $formID)
     27  {
     28    $this->fieldSet = new stdClass();
     29    $this->fieldSet->postID = $this->postID = $postID;
     30    $this->formID = $formID;
     31  }
     32}
     33
     34
    1735include_once dirname(__FILE__) . '/EcampaignField.class.php';
    1836include_once dirname(__FILE__) . '/EcampaignLog.class.php';
     
    2139class Ecampaign
    2240{
     41  public    $session = NULL;
     42  public    $validAjaxMethods = array();  // methods in this class that ajax calls can access  protected $log ;
     43  protected $defaultTemplate;             // set by extending classes
     44  protected $submitEnabled = true;        // prevents attempts to send/sign before any (postcode) lookups
     45  protected $styleClass = 'notset';       // class attribute given to outer wrapper of form
     46  protected $testMode = NULL ;
     47 
    2348  static function help($anchor)
    2449  {
     
    2752    return "<a target='_blank' href='$path$helpFile$anchor' title='$anchor: Open ecampaign help page in another window'>". __("More help")."</a>" ;
    2853  }
    29 
    30   static $formList, $allFields = array();
    31 
    32   protected $classPath,                   // path and class name of declaring class
    33             $testMode,                    // determines whether messages are delivered and to where
    34             $cannedFields = null,         // array of hard coded fields and label names
    35             $templateFields,              // array of fields after template processing
    36             $submitEnabled = true,        // prevents attempts to send/sign before any (postcode) lookups
    37             $defaultTemplate,             // set by extending classes
    38             $styleClass = 'notset';       // class attribute given to outer wrapper of form
    39 
    40   public $validAjaxMethods = array(),     // methods in this class that ajax calls can access
    41          $fieldSet ;                      // name/value pairs recieved in POST
     54  static $allControllers=[];
     55
    4256
    4357  const ecCounter = 'ecCounter' ;    // number of people taking action, counter value is saved in metadata in post
     
    6983    sReferer = 'referer',
    7084    sPostID = 'postID',
    71 
     85    sFormID = 'formID',
     86   
    7287    sFriendEmail = 'friendEmail',
    7388    sFriendSend = 'friendSend' ,
     
    8196   * @param unknown_type $atts  all the attribute that follow </campaign>
    8297   * @param unknown_type $messageBody  the text between the <ecampaign> and </ecampaign>
    83    * @return unknown_type
    84    */
    85 
    86 
    87   function __construct()
    88   {
    89     $this->testMode = new EcampaignTestMode();
    90     $this->log = new EcampaignLog();
    91     $this->classPath = get_class($this);
     98   * @return
     99   */
     100
     101
     102  function __construct($session)
     103  {
     104    $this->session = $session;
     105    $this->log = EcampaignLog::get();
    92106    $this->bodyTrailer = "" ;
    93     if (!isset(self::$formList)) self::$formList = array();
    94   }
    95 
    96 /*
    97   function initializeCannedFieldsNext()
    98   {
    99     $this->cannedFields =
    100 
    101     " { " .self::sTo.     " label = ". __('To'). " min=10 size=70 } ".
    102     " { " .self::sSubject." label = ". __('').   " min=10 size=70 } ".
    103     " { " .self::sBody. " label = ". __('').   " min=10 size=70 } ".
    104 
    105     "";
    106   }
    107 */
    108 
    109 
     107  }
     108
     109 
    110110  function initializeCannedFields()
    111111  {
    112     $this->cannedFields = array(
     112    return array(
    113113      self::sTo           => array(__('To')),    // field lengths irrelevant
    114114      self::sSubject      => array(null,           "data-min='10' size='70'"),
     
    121121      self::sCity         => array(__('City'),     "data-min='4'  size='10'"),
    122122      self::sPostcode     => array(__('Postcode'), "data-min='4'  size='10'"),
    123       self::sUKPostcode   => array(__('Postcode'), "data-min='4'  size='10'", 'validateUKPostcode'),
     123      self::sUKPostcode   => array(__('Postcode'), "data-min='4'  size='7'", 'validateUKPostcode'),
    124124      self::sZipcode      => array(__('Zipcode'),  "data-min='5'  size='10'", 'validateZipcode'),
    125125      self::sState        => array(__('State'),    "data-min='2'  size='2'"),    // tx, ca
    126126      self::sCountry      => array(__('Country'),  "data-min='2'  size='15'"),   // us, uk
    127       self::sVeriCode     => array(__('Code'),     "data-min='4'  size='4'"),
    128       self::sCaptcha      => array(__('Captcha'),  "data-min='4'  size='4'"),
     127      self::sVeriCode     => array(__('Code'),     "data-min='4'  size='4'", 'autoSubmit'),
     128      self::sCaptcha      => array(__('Captcha'),  "data-min='4'  size='4'"), 
    129129      self::sSend         => array(__('Send')),
    130130      self::sSign         => array(__('Sign the petition')),
     
    148148   * {subject* min=20 flavor='chocolate chip' Lorem ipsum dolor sit amet}
    149149   * {body rows=8 cols='70' Ut enim ad minim veniam}
     150   *
     151   * @layout template from inside the short code or the form
     152   * @pageAttributes short code attributes which can be used to ovveride default values
     153   * @cannedFields set of built in fields with preset names and field sizes
    150154   *
    151155   * @return array of EcampaignField
     
    153157  const regexParseTemplate = '${(\w+)(\*)?((?:\s+[\w][\w-]+=(?:[%]?[\w]+|[\'\"\”][^\'\"\”]+[\'\"\”]))*)\s*([^}]*)}$' ;
    154158
    155   function parseTemplate($layout, $pageAttributes)
     159  static function parseTemplate($layout, $pageAttributes, $cannedFields)
    156160  {
    157161    $parsedFields = array();
     
    165169      $efield = new EcampaignField ;
    166170      $efield->wholeField = $parsedFields[0][$i];
     171      $efield->isCustom = false;
    167172      $noun = $parsedFields[1][$i];
    168173
    169       // use aliases for just these two fields, full name use everywhere else
     174      // map template labels to field names
    170175      if ($noun == 'name')  $noun = 'visitorName';
    171176      if ($noun == 'email') $noun = 'visitorEmail';
    172 
     177           
    173178      $efield->name = $noun ;
    174179
    175       $knownField = @$this->cannedFields[$noun];
    176 
    177       if (!isset($knownField))
     180      $knownField = array_key_exists($noun, $cannedFields) ? $cannedFields[$noun] : null ;
     181
     182      if (!$knownField)
    178183      {
    179184        $knownField = array($noun,'');  // ignore fields like %xyz we don't recognise, they will stay in the text
     
    188193      $efield->save =@ $attributeMap['save'];  $attributeMap['save'] = null;
    189194      $efield->type = @$attributeMap['type'];
    190       $efield->attributes = EcampaignField::serializeAttributes($attributeMap);
    191195      $efield->mandatory = $parsedFields[2][$i] == "*";
    192       $efield->value = $value = trim($parsedFields[4][$i]);
     196      $value = trim($parsedFields[4][$i]);
    193197      $efield->validator = @$knownField[2];
    194 
    195       if (!empty($value))
     198   
     199      // this is inconsistent behaviour but just for checkboxes
     200      // the label is taken from the value i.e. the text inside the {}
     201      // and the initial value (checked or otherwise) has to be set using the checked attribute
     202      if (in_array($noun, [Ecampaign::sCheckbox1, Ecampaign::sCheckbox2]))
     203      {
     204        $efield->type = 'checkbox';
     205        $efield->label = $value ; 
     206        $value = in_array('checked', $attributeMap) ? 'on' : 'off' ;
     207        unset($attributeMap['checked']);
     208      }     
     209      if (!empty($value))   # only if checkbox is true
    196210      {
    197211        $efield->definition = true ;
     
    203217        $efield->value = @$pageAttributes->$noun ;
    204218      }
    205 
     219      $efield->attributes = EcampaignField::serializeAttributes($attributeMap);
     220     
    206221      $templateFields[$noun] = $efield ;
    207222    }
     
    211226
    212227  /**
    213    * Generate html for one or more forms
     228   * Generate html for one instance of plugin
    214229   * @return html string
    215230   */
    216231   
    217232  function createPage($pageAttributes, $pageBody)
    218   {
    219     $this->initializeCannedFields();
    220     $this->testMode = new EcampaignTestMode($pageAttributes->testMode);
     233  {   
    221234    if (empty($pageBody))
    222       throw new Exception(__("there is no text between [ecampaign] and [/ecampaign] or the closing [/ecampaign] is missing"));
    223 
    224     if (empty($pageAttributes->campaignEmail))
    225       $pageAttributes->campaignEmail = get_option('ec_campaignEmail');
    226 
     235      throw new Exception(__("there is no text between [ecampaign] and [/ecampaign] or the closing [/ecampaign] is missing")); 
     236
     237    $cannedFields = $this->initializeCannedFields();
     238     
     239    $this->session->testMode = new EcampaignTestMode($pageAttributes->testMode);
     240    $this->session->fieldSet->referer = @$_SERVER["HTTP_REFERER"];
     241    $this->session->fieldSet->campaignEmail = $pageAttributes->campaignEmail ?
     242      $pageAttributes->campaignEmail : get_option('ec_campaignEmail'); 
     243   
    227244    $nonce = wp_create_nonce('ecampaign');
    228     $referer = @$_SERVER["HTTP_REFERER"];
    229     $postID = get_the_ID();
     245
     246    $hiddenFields =
     247    "<input type='hidden' name='_ajax_nonce'  value='{$nonce}' />
     248     <input type='hidden' name='postID'  value='{$this->session->postID}' />\n" ; //required
    230249   
    231     $hiddenFields =
    232      "<input type='hidden' name='_ajax_nonce'  value='{$nonce}' />
    233       <input type='hidden' name='referer'  value='{$referer}'/>
    234       <input type='hidden' name='postID'  value='{$postID}'/>";     
    235 
    236     $form = null ;  $pageParts = preg_split("$<hr[^/]*/>$", $pageBody);
    237 
    238     /**
    239      * Only body of message defined. using default template.
    240      */
    241 
    242     switch(count($pageParts))
    243     {
    244       case 2 :      // legacy mode, two forms, target email in top form, friends email in bottom form
    245       {
    246         $error = array() ;
    247 
    248         if (empty($pageAttributes->targetEmail))
    249           $error[] = sprintf(__('%s attribute not set.'), 'targetEmail') ;
    250 
    251         if (empty($pageAttributes->targetSubject))
    252           $error[] = sprintf(__('%s attribute not set.'), 'targetSubject') ;
    253 
    254         if (empty($pageAttributes->friendSubject))
    255           $error[] = sprintf(__('%s attribute not set.'), 'friendSubject') ;
    256 
    257         if (count($error) > 0)
    258         {
    259           $errorText = implode('</p><p>', $error);
    260           die(__('ecampaign: page not setup correctly') ."&nbsp;". self::help()."#setup"
    261           ."<p>$errorText</p>"
    262           ."<div style='border:1px solid red; padding:5px'> <code>$pageBody</code></div>");
    263         }
    264 
    265         // this is legacy mode and it's messy
    266 
    267         $this->classPath = 'EcampaignTarget'; // bodge
    268         if (empty($pageAttributes->to))      $pageAttributes->to = $pageAttributes->targetEmail ;
    269         if (empty($pageAttributes->subject)) $pageAttributes->subject = $pageAttributes->targetSubject ;
    270         $pageAttributes->body = $pageParts[0] ;
    271         // Convert the clean \r\n characters into html breaks so its in the same
    272         // format as if the form were embedded in the post.
    273         $template = preg_replace("$[\r\n]+$", "<br/>", get_option('ec_layout'));
    274         $form = self::createForm($template, "ecform ec-target", $pageAttributes, $hiddenFields);
    275 
    276         $this->classPath = 'EcampaignFriend'; // bodge
    277         $pageAttributes->subject = $pageAttributes->friendSubject ;
    278         $pageAttributes->body = $pageParts[1] . "<p>". get_permalink() . "</p>";
    279         $pageAttributes->hidden = true ;
    280         // Convert the clean \r\n characters into html breaks so its in the same
    281         // format as if the form were embedded in the post.
    282         $template = preg_replace("$[\r\n]+$", "<br/>", get_option('ec_friendsLayout'));
    283         $form .= self::createForm($template, "ecform ec-friend", $pageAttributes, $hiddenFields);
    284         $sections[] = $form;
    285         break ;
    286       }
    287 
    288       case 1 :
    289       default :
    290       {
    291         if (4 < preg_match_all('$[{}]$' ,$pageParts[0], $matches))
    292         {     // single form using the template inside the post
    293           $template = preg_replace('$[\r\n]+$', '', $pageParts[0]);  // loose existing CRLF
    294           $pageAttributes->body = null ;  // the body is contained in the template
    295         }
    296         else
    297         {
    298           $template = preg_replace("$[\r\n]+$", "<br/>", $this->defaultTemplate);
    299           $pageAttributes->body = $pageParts[0];
    300         }
    301         if (empty($pageAttributes->to))  $pageAttributes->to = $pageAttributes->targetEmail ;
    302         $form = self::createForm($template, $this->styleClass, $pageAttributes, $hiddenFields);
    303         $sections[] = $form;
    304         break ;
    305       }
    306       $hiddenFields = "" ; // only needed on first form
    307     }
    308     return "<!-- http://www.wordpress.org/plugins/ecampaign  -->\r\n" . $form ;
    309   }
    310 
    311   /**
    312    *
    313    * @param $template  Must be at least 4 fileds to be accepted.
    314    * @param $id
    315    * @param $pageAttributes
    316    * @param $hiddenFields
    317    */
    318 
    319   function createForm($template, $styleClass, $pageAttributes, $hiddenFields)
    320   {
    321     $id = EcampaignField::nextID();
    322     self::$formList[$id] = array();
    323     self::$formList[$id]['classPath'] = $this->classPath;
    324 
    325     // wrap all the lines in the template in DIV.ECROW,
    326     // this is less flexible but makes layout in IE7 less troublesome.
    327     //
    328 //    $rows = explode("\r\n", $template);
    329 //    $html = "<div class='ecrow'>" . implode("</div>\r\n<div class='ecrow'>", $rows) ."</div>" ;
    330 
    331     $templateFields = self::parseTemplate($html=$template, $pageAttributes);
     250   
     251    if (array_key_exists(self::sVeriCode, $_GET)) {
     252      $this->session->fieldSet->{self::sVeriCode} = $_GET[self::sVeriCode];
     253      $hiddenFields .= "<input type='hidden' name='autoSubmit'  value='1' />\n"; // would rather modify element atrribute
     254    }     
     255
     256    if (count(preg_split("$<hr[^/]*/>$", $pageBody)) > 1)
     257      throw new Exception(
     258          'Multiple forms within a single pair of shortcode brackets are no longer supported. '
     259          .'Please use two pairs of shortcode brackets');
     260    if (4 < preg_match_all('$[{}]$' ,$pageBody, $matches))
     261    {     // single form using the template inside the post
     262      $template = preg_replace('$[\r\n]+$', '', $pageBody);  // loose existing CRLF
     263      $pageAttributes->body = null ;  // the body is contained in the template
     264    }
     265    else
     266    {
     267      $template = preg_replace("$[\r\n]+$", "<br/>", $this->defaultTemplate);
     268      $pageAttributes->body = filter_var($pageBody, FILTER_SANITIZE_STRING);
     269    }
     270    $form = $this->createForm($template, $this->styleClass, $pageAttributes, $hiddenFields, $cannedFields);
     271
     272    return "<!-- http://www.wordpress.org/plugins/ecampaign start -->\r\n"
     273        .  $form . "\r\n"
     274        .  "<!-- http://www.wordpress.org/plugins/ecampaign end -->\r\n" ;
     275  }
     276
     277  /**
     278   *
     279   * @param $template specifies the fields
     280   * @param $styleClass class attribute given to outer wrapper of form
     281   * @param $pageAttributes contains shortcode attributes
     282   * @param $hiddenFields contains formID etc
     283   * @param $cannedFields definition of fields, label, length, etc
     284   */
     285
     286  function createForm($template, $styleClass, $pageAttributes, $hiddenFields, $cannedFields)
     287  {
     288    $templateFields = self::parseTemplate($html=$template, $pageAttributes, $cannedFields);
    332289
    333290    if (count($templateFields) == 0)
     
    338295      please go to $settingsLink and check that templates. <br/><br/>".$template));
    339296    }
    340 
     297    // populate the template //TODO, priority not right
    341298    foreach ($templateFields as $noun => $efield)
    342299    {
     300      if (!empty($this->session->fieldSet->$noun))  // put saved session values into rewritten form
     301        $efield->value = $this->session->fieldSet->$noun ;
     302
     303      $this->session->fieldSet->$noun = $efield->value ;
    343304      $snippet = $this->createField($noun, $efield, $pageAttributes, $templateFields);
    344305      if (isset($snippet))
     
    357318
    358319    unset($pageAttributes->body);       // no need to serialize it (and it can change
    359                                         // if URL attached to bottom of friends body changes)
    360     $form['pageAttributes'] = $pageAttributes ;
    361     $form['template'] = $templateFields ;
    362     $form['testMode'] = $this->testMode ;
    363 
    364     $this->updateFormList(get_the_ID(), $id, $form);
    365 
    366     $displayClass = $pageAttributes->hidden ? "hidden" : "" ;
    367     return  "<div id='$id' class='$styleClass $displayClass' >"
    368           . $html . $hiddenFields
    369           . "</div>\r\n" ;
    370   }
    371 
    372   /**
    373    * store all the form data in post meta because the ajax POST
    374    * handler doesn't have access to the page text or plugin attributes etc
    375    * Data only updated if it's changed.
    376    * Note there is double serialization.
    377    *
    378    * @param unknown_type $postID
    379    * @param unknown_type $formID
    380    * @param unknown_type $form
    381    */
    382 
    383   function updateFormList($postID, $formID, $form)
    384   {
    385 
    386     $formSerialized = serialize($form);
    387 
    388     $formListSerialized = get_post_meta($postID, 'formList', true);
    389     if (empty($formListSerialized))
    390     {
    391       $formList = array();  // creating a new list.
    392     }
    393     else
    394     {
    395       $formList = unserialize($formListSerialized);
    396     }
    397     $oldFormSerialized = @$formList[$formID];
    398     if (!empty($oldFormSerialized))
    399     {
    400       if ($formSerialized === $oldFormSerialized)
    401       {
    402         return ;  // version stored is identical
    403       }
    404     }
    405     $formList[$formID] = $formSerialized;
    406     update_post_meta($postID, 'formList', serialize($formList));
    407     $this->log->write("formUpdate", array(), "postID:$postID formID:$formID");
     320    unset($this->session->fieldSet->verificationCode);
     321                                        // if URL attached to bottom of friends body changes)   
     322    $this->session->pageAttributes = $pageAttributes ;  // will be serialized, need for form processing
     323    $this->session->templateFields = $templateFields ;  // will be serialized, need for form processing
     324
     325    $displayStyle = $pageAttributes->hidden ? "display:none" : "" ;
     326    $formID = $this->session->formID ;
     327    return  "<div id='$formID' class='$styleClass' style='$displayStyle'>"
     328    . $html . "\r\n". $hiddenFields . "\r\n</div>" ;
    408329  }
    409330
     
    415336   */
    416337  function revealNextApplicableForm($response)
    417   {
    418     $currentID = $_POST['formID'];
    419 
    420     if (!is_array(self::$formList))
    421       throw new Exception("Session data missing or corrupt");
    422 
     338  {   
     339    $lastID = NULL ;
    423340    $selector = array();
    424     $forms = self::$formList;
    425341
    426342    // restore all forms including ones that are wrapped
    427343    // in other ecampaign tags on the same page.
    428 
    429     foreach($forms as $ID => $serializedForm)
    430     {
     344   
     345    ksort(self::$allControllers);
     346    foreach(self::$allControllers as $formID => $session)
     347    {
     348     
    431349      if (isset($lastID)) // reveal all subesquent forms
    432350      {
    433         $form = unserialize($serializedForm);
    434         $ecampaign2 = _createFromClassPath($form['classPath']);
    435         // this is a bodge, copy properties from one to other class
    436         $ecampaign2->restoreForm($this->fieldSet->postID, $ID);  // overwrites self::$formList
    437         $ecampaign2->fieldSet = $this->fieldSet; // need to access posted data
    438         if ($ecampaign2->filterVisitors())
     351        $session->fieldSet = $this->session->fieldSet;  // carry over existing posted fields
     352//        if ($nextEcampaignForm->filterVisitors())       // overidden method  NEEDS FIXING
    439353        {
    440           $selector[] = "#$ID " ; // reveal this form
     354          $selector[] = "#$formID " ; // reveal this form
    441355        }
    442356      }
    443       if ($currentID == $ID)
    444         $lastID = $currentID ;
     357      if ($formID == $session->formID)
     358        $lastID = $formID ;
    445359    }
    446360    if (is_array($response))
     
    467381  {
    468382    return true ;
    469   }
    470 
    471 
    472   /**
    473    * Invoked prior to handling an ajax submit
    474    * Rearley called so not concerned about time to unserialize
    475    */
    476 
    477   function restoreForm($postID, $formID)
    478   {
    479     $formListSerialized = get_post_meta($postID, 'formList', true);
    480     if (empty($formListSerialized))
    481     {
    482       throw new Exception ("Unable to recall form list for postID $postID");
    483     }
    484     self::$allFields = array();
    485     self::$formList = unserialize($formListSerialized);
    486     foreach (self::$formList as $ID => $formSerialized)
    487     {
    488       if (empty($formSerialized))
    489       {
    490         throw new Exception ("Unable to recall form for postID $postID formID $formID");
    491       }
    492       $form =  unserialize($formSerialized);
    493       if ($formID == $ID)
    494       {
    495         $this->pageAttributes = $form['pageAttributes'];
    496         $this->templateFields = $form['template'];
    497         $this->testMode = $form['testMode'];
    498       }
    499       else
    500         self::$allFields = array_merge(self::$allFields, $form['template']);
    501     }
    502     // take values from current form and override values
    503     self::$allFields = array_merge(self::$allFields, $this->templateFields);
    504 
    505     if (empty($this->templateFields))
    506     {
    507       throw new Exception ("Unable to recall form for postID $postID formID $formID");
    508     }
    509383  }
    510384
     
    520394  function createField($noun, $efield, $pageAttributes)
    521395  {
     396    $testMode = $this->session->testMode ;
    522397    switch($noun) {
    523398
    524399      case self::sTo :
    525         $recipientsBrokenUp = self::breakupEmail(
    526           $this->testMode->isDiverted()? $pageAttributes->campaignEmail: $efield->value);
    527 
     400        $emailList = new EmailList($efield->value);
     401        $recipients = $emailList->divert($testMode, $pageAttributes->campaignEmail)->asAntiSpammedHTMLString();
    528402        $settingsUrl = admin_url("options-general.php?page=ecampaign");
    529         $helpOutOfTestMode =  $this->testMode->is(EcampaignTestMode::sNormal) ? "" : "<span id='text-test-mode'>&nbsp;[{$this->testMode->toString()} <a href='{$settingsUrl}')>change</a>]</span>"  ;
    530         $html = "<label id='lab-to'>$efield->label:</label><span id='recipients-email' class='ecinputwrap'>$recipientsBrokenUp</span>$helpOutOfTestMode" ;
    531         break ;
    532 /*
    533       case self::sSubject  :
    534         $html = $efield->writeField(null);
    535         break ;
    536 */
     403        $helpOutOfTestMode =  $testMode->isNormal() ? "" : "<span id='text-test-mode'>&nbsp;[{$testMode->toString()} <a href='{$settingsUrl}')>change</a>]</span>"  ;
     404        $html = "<label id='lab-to'>$efield->label:</label><span id='recipients-email' class='ecinputwrap'>$recipients</span>$helpOutOfTestMode" ;
     405        break ;
     406
    537407      case self::sBody :
    538408        if (isset($this->bodyTrailer))   // set in EcampaignFriends to carry url of post
    539409          $efield->value .= $this->bodyTrailer ;
    540         $efield->value = strip_tags($this->replaceParagraphTagsWithNewlines($efield->value));
     410          $efield->value = strip_tags(Ecampaign::replaceParagraphTagsWithNewlines($efield->value));
    541411        $html = $efield->writeTextArea();
    542412        unset($efield->value) ;    // to prevent if from being serialized
     
    562432
    563433      case self::sVeriCode :
    564         $efield->wrapper = 'eccode hidden';
     434        $efield->wrapper = empty($efield->value) ? 'eccode hidden' : 'eccode' ;
    565435        $html = $efield->writeField();
    566436        break ;
     
    570440      case self::sFriendSend :
    571441        $efield->attributes .= $this->submitEnabled ? "" : " disabled='disabled'" ;
    572         $efield->attributes .= " onclick=\"return ecam.onClickSubmit(this, '$this->classPath', '$noun');\" ";
     442        $efield->attributes .= " onclick=\"return ecam.onClickSubmit(this, '$noun');\" ";
    573443        $efield->name = 'submit';
    574444        $html = "<div class='ecsend'>".$efield->writeButton()."</div><div class='ecstatus'></div>" ;
     
    595465      case self::sCampaignEmail :
    596466        $efield->isCustom =false ;
    597         $html = @$efield->definition ? "" : self::breakupEmail($efield->value);
     467        $html = isset($efield->definition) ? "" : $efield->value;
    598468        break ;
    599469
    600470      case self::sSuccessMessage :  // have to remove any para tags, new lines are just ignored
    601471        $efield->isCustom =false ;
    602         $efield->value = $this->replaceParagraphTagsWithNewlines($efield->value);
     472        $efield->value = Ecampaign::replaceParagraphTagsWithNewlines($efield->value);
    603473        $html = $efield->definition ? "" : $efield->value;
    604474        break ;
    605 
    606 
     475       
    607476      case self::sCheckbox1 :
    608477      case self::sCheckbox2 :
    609         $efield->type='checkbox' ;
     478        $html = $efield->writeCheckBox() ;
     479        break ;
    610480
    611481      default :  // handles name, email, zipcode, postcode etc
    612 
    613         // this is inconsistent behaviour but just for checkboxes
    614         // the label is taken from the value i.e. the text inside the {}
    615         // and the initial value (checked or otherwise) has to be set using the checked attribute
    616 
    617         if ($efield->type=='checkbox')
    618         {
    619           $efield->label = $efield->value ;  $efield->value = null;
    620           $html = $efield->writeCheckBox();
    621           break ;
    622         }
    623         else
    624         {
    625           $html = $efield->writeField();
    626           break ;
    627         }
     482        $html = $efield->writeField();
    628483    }
    629484    return $html ;
    630485  }
    631486
    632 
    633 
    634487//  <label for="send-formName" class="labeloverlay sendoverlay labeloverlayhidden" >Name</label>
    635488
     489  function mergeBindings()
     490  {
     491    return (Object) array_merge(
     492        (array) $this->session->pageAttributes,
     493        (array) $this->session->fieldSet,
     494        (array) EcampaignField::getMap($this->session->templateFields, true)  // from $_REQUEST()
     495        );
     496  }
     497 
    636498  /**
    637499   * Convert </p> and <br/> tags into CR-LF and strip out any adjacent CR and LF that happen to be there
     
    650512  }
    651513
    652 
    653 
    654514  /**
    655515   * break up string of emails separated by commas
     
    659519   * @return string containing emails separated by comma and a space.
    660520   */
    661   static function breakupEmail($emailsBetweenCommas)
     521  static function breakupEmailUNUSED($emailsBetweenCommas)
    662522  {
    663523    $html = "" ;
     
    671531    return $html;
    672532  }
    673 
    674 
    675   /**
    676    * email addresses returned from the to: field in the browser
    677    * *all* of which are in 1 of 2 formats
    678    * 1.  mailbox@domain
    679    * 2.  first-name last-name <mailbox@domain>
    680    * and are expected to be separated by commas but not assuming that
    681    *
    682    * Cannot find any simple way of parsing emails in both formats
    683    * in one expression which doesn't involve fiddling with the parser results.
    684    *
    685    * @param $emailString
    686    * @return string containing emails separated by any whitespace character.
    687    */
    688   static function parseEmailRecipients($emailString)
    689   {
    690     $parsedFields = array();
    691     $num = preg_match_all('$<[\s]*([^>]+)[\s]*>$', $emailString, $parsedFields);
    692     if ($num >  0)
    693       return $parsedFields[1];  // ie just the email addresses inside < and >
    694 
    695     $num = preg_match_all('$[,\s]*([^<>,\s]+)$', $emailString, $parsedFields);
    696     return $parsedFields[1];    // return every word with 1 or more chars
    697   }
    698 
    699533
    700534 /**
     
    727561    $phpmailer->CharSet = apply_filters( 'wp_mail_charset', get_bloginfo( 'charset' ));
    728562    // add the originating URL so abuse can be tracked back to specific web page
    729     $phpmailer->addCustomHeader("X-ecampaign-URL: {$_SERVER["HTTP_REFERER"]}");
     563    @$phpmailer->addCustomHeader("X-ecampaign-URL: {$_SERVER["HTTP_REFERER"]}");
    730564    return $phpmailer ;
    731565  }
     
    748582        throw new Exception("$fieldName: ". __("rejected by FILTER_VALIDATE_EMAIL")." : {$recipient}");
    749583      }
    750     }
    751     else  // for older PHP versions
    752     {
    753       $validEmail = preg_match('/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9_](?:[a-zA-Z0-9_\-](?!\.)){0,61}[a-zA-Z0-9_-]?\.)+[a-zA-Z0-9_](?:[a-zA-Z0-9_\-](?!$)){0,61}[a-zA-Z0-9_]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/', $recipient);
    754       if (!validEmail)
    755         throw new Exception("$fieldName: ". __("invalid email address")." : {$recipient}");
    756584    }
    757585
     
    774602  }
    775603}
     604
     605
     606class EmailList
     607{
     608  public $items = [] ;
     609  const regexEmailList = '$(?P<name>[^@<>.,]+)\s<(?P<email>[^@\s\,<>]+@[^@\s\,<>]+)>|(?P<emailOnly>[^@\s\,<>]+@[^@\s\,<>]+)$';
     610  // https://regex101.com/r/typ8tD/ test strings
     611  function __construct($stringOfEmails)
     612  {
     613    $stringOfEmails1 = 'john.smith@ymail.com, Gillian Green <gillgreen@ymail.com>,Fred Smith<Fred@outlook.com>, <sue@sue.com>';
     614    $num = preg_match_all(self::regexEmailList, $stringOfEmails, $eList);
     615    if ($num < 1)
     616      throw new Exception(__('Unable to parse email string: '). $stringOfEmails);
     617    $this->items = EmailList::transpose($eList);
     618    // rename 'emailOnly' keys as 'email' keys
     619    foreach($this->items as $key => $ite)
     620    {
     621      if (!empty($this->items[$key]['emailOnly']))
     622      {
     623        $this->items[$key]['email'] = $this->items[$key]['emailOnly']; unset($this->items[$key]['emailOnly']);
     624      }
     625    }
     626  }
     627 
     628  static function transpose($ar) {
     629    $ar2 = [] ;
     630    foreach($ar[0] as $ik => $notused)
     631      foreach ($ar as $ok => $notused)
     632        $ar2[$ik][$ok] = $ar[$ok][$ik] ;
     633    return  $ar2 ;
     634  }
     635 
     636  function divert($testMode, $campaignEmail)
     637  {
     638    if ($testMode->isDiverted())
     639    {
     640      $this->items[0]['email'] = $campaignEmail;
     641      $this->items = array_slice($this->items, 0, 1);  // create new truncated list
     642    }
     643    return $this ;
     644  }
     645 
     646  function asString($antiSpammedHTML = False)
     647  {
     648    $items = [];
     649    foreach ($this->items as $email)
     650    {
     651      $html = empty($email['name']) ? $email['email'] : $email['name'] . " <" . $email['email'] . ">";
     652      if ($antiSpammedHTML)
     653        $html = str_replace("@", "<span class='confuseSpammers'>@</span>", htmlspecialchars($html));
     654      $items[] = $html;
     655    }
     656    return (join(',',$items));
     657  }
     658 
     659  function asAntiSpammedHTMLString()
     660  {
     661    return $this->asString(/* $antiSpammedHTML = */ True);
     662  }
     663}
  • ecampaign/branches/maintenance/EcampaignPetition.class.php

    r1700889 r1974927  
    1313class EcampaignPetition extends Ecampaign
    1414{
    15   const jsRevealNextForm = "revealNextForm" ;
    16   const passwordLength = 4 ;
    17   function __construct()
    18   {
    19     parent::__construct();
    20     $this->defaultTemplate = get_option('ec_petitionLayout');
     15  const verificationCodeLength = 4 ;
     16  function __construct($session)
     17  {
     18    parent::__construct($session);
     19    $this->session->classPath = "EcampaignPetition";
     20    $this->defaultTemplate = get_option('ec_petitionLayout');   
    2121    $this->styleClass = 'ecform ec-target';
    2222    $this->validAjaxMethods[] = "sign" ;
    2323    $this->validAjaxMethods[] = "send" ;
     24    $this->validAjaxMethods[] = "verify" ;
    2425  }
    2526
     
    2728  private function preProcess()
    2829  {
    29     $desiredFields = array_merge(array(self::sPostID, self::sVisitorName, self::sVisitorEmail, self::sCampaignEmail, self::sReferer),
    30                      array_keys($targetFields = $this->templateFields));
    31 
    32     $controlFields = array(self::sPostID => new EcampaignField(self::sPostID),
    33                            self::sReferer => new EcampaignField(self::sReferer));
    34 
    35     $fieldSet = $this->fieldSet = EcampaignField::requestPartialMap($desiredFields, array_merge($controlFields, self::$allFields));
    36 
    37     $fieldSet->permalink = get_permalink($fieldSet->postID);
    38     $fieldSet->recipients =  self::parseEmailRecipients($_POST['recipientsEmail']) ;
     30    $this->session->fieldSet = $fieldSet = $this->mergeBindings();
    3931
    4032    self::validateEmail($fieldSet->campaignEmail, "campaignEmail"); // throws exception if fails.
     
    4941    self::validateEmail($fieldSet->visitorEmail, "visitorEmail"); // throws exception if fails.
    5042
    51     $address = new EcampaignString(array($fieldSet->address1,$fieldSet->address2,$fieldSet->address3,
     43    @$address = new EcampaignString(array($fieldSet->address1,$fieldSet->address2,$fieldSet->address3,
    5244      $fieldSet->city,$fieldSet->postcode,$fieldSet->ukpostcode,
    5345      trim("{$fieldSet->state} {$fieldSet->zipcode}"),$fieldSet->country));
     
    5547    $fieldSet->postalAddress = $address->removeEmptyFields();
    5648
    57     $this->infoMap = array(
    58        "referrer: " . $fieldSet->referer,
     49    @$this->infoMap = array(
     50       "referer: " . $fieldSet->referer,
    5951       "remote: " . "{$_SERVER['REMOTE_HOST']} {$_SERVER['REMOTE_ADDR']}",
    6052       "user-agent: " . $_SERVER['HTTP_USER_AGENT']);
    61 
    62     foreach ($targetFields as $f) //save all the custom fields
     53   
     54    foreach ($this->session->templateFields as $f) //save all the custom fields   FIXME
    6355    {
    6456      if ($f->isCustom && (!empty($f->value) || isset($f->mandatory)))
     
    6658    }
    6759
    68     /* catcha and verification need to work together easily if only for test pUurposes.
     60    /* catcha and verification need to work together easily if only for test purposes.
    6961     * verification field not displayed until captcha entered so don't check captcha again
    7062     * not least because i think captcha codes only work once.
     
    7365    $userSuppliedVerificationCode = $_POST[self::sVeriCode];
    7466
    75     if (array_key_exists(self::sCaptcha, $targetFields) && empty($userSuppliedVerificationCode))
     67    if (array_key_exists(self::sCaptcha, $fieldSet) && empty($userSuppliedVerificationCode))
    7668    {
    7769      $captchadir = get_option('ec_captchadir');
     
    8577      }
    8678    }
    87 
    88     if (array_key_exists(self::sVeriCode, $targetFields))
     79   
     80    $verifiedUser = $this->log->recordExists(EcampaignLog::tVerified, $fieldSet->visitorEmail);
     81   
     82    if (!$verifiedUser && (get_option('ec_verifyEmail') || array_key_exists(self::sVeriCode, $targetFields)))
    8983    {
    9084      // nonce creation lifted from wordpress pluggable.php
    9185      // verification code is different on each site for same email addresses
    9286
    93       $i = wp_nonce_tick();
    94       $hashCodes = array();
    95       $action = $fieldSet->targetEmail . $fieldSet->visitorEmail;
    96       // Nonce generated 0-12 hours ago
    97       $hashCodes[] = substr(wp_hash($i . $action, 'nonce'), -12, self::passwordLength);
    98       // Nonce generated 12-24 hours ago
    99       $hashCodes[] = substr(wp_hash(($i - 1) . $action, 'nonce'), -12, self::passwordLength);
     87
     88      $action = get_bloginfo("name") . $fieldSet->visitorEmail;
     89      $hashCode = substr(wp_hash($action, 'nonce'), -self::verificationCodeLength);
    10090
    10191      if (empty($userSuppliedVerificationCode))
     
    10797         * @return unknown_type
    10898         */
    109         $fieldSet->code = $hashCodes[0];
     99        $fieldSet->code = $hashCode;
    110100        $success = $this->sendEmailToSiteVisitor('ec_verificationEmail', $fieldSet);
    111101        $this->log->write(EcampaignLog::tVerify, $fieldSet, "code:". $fieldSet->code);  //todo report error
    112102        return array("success" => $success, //"getCode" => true,
    113                      "callbackJS" => 'revealVerificationField',
    114                      "msg" => __("Please check your email. Please enter the random characters in the empty code field above."));
     103                     "selector" => '.eccode',
     104                     "callbackJS" => 'revealForm',
     105                     "msg" => __("Please check your email including your spam folder. We have sent you a link to verify your email address."));
    115106      }
    116107
    117       if ((0 != strcasecmp($userSuppliedVerificationCode,$hashCodes[0]))
    118       &&  (0 != strcasecmp($userSuppliedVerificationCode,$hashCodes[1])))
     108      if (0 != strcasecmp($userSuppliedVerificationCode,$hashCode))
    119109      {
    120110        return array("success" => false,
     
    129119    // and the count may fall behind.
    130120
    131 //    add_post_meta($fieldSet->postID, self::$counter, 0, true);
    132     $count = get_post_meta($fieldSet->postID, self::ecCounter, true);
    133     update_post_meta($fieldSet->postID, self::ecCounter, $count+1);
     121//    add_post_meta( ->postID, self::$counter, 0, true);
     122    $count = get_post_meta($this->session->postID, self::ecCounter, true);
     123    update_post_meta($this->session->postID, self::ecCounter, $count+1);
    134124
    135125    // setup success message
    136     $this->successMessage = $this->templateFields[self::sSuccessMessage]->value;
     126    $this->successMessage = $this->session->templateFields[self::sSuccessMessage]->value;
    137127    if (empty($this->successMessage))
    138128    {
     
    148138      return $response;
    149139
    150     $fieldSet = $this->fieldSet;
     140    $fieldSet = $this->session->fieldSet;
    151141
    152142    if (get_option('ec_preventDuplicateActions'))
     
    169159   */
    170160
     161  function verify()
     162  {
     163    return $this->send();
     164  }
     165 
    171166  function send()
    172167  {
     
    175170      return $response;
    176171
    177     $fieldSet = $this->fieldSet;
     172    $fieldSet = $this->session->fieldSet;
    178173
    179174    $mailer = self::getPhpMailer();
    180175    $mailer->Subject = $fieldSet->subject;
    181176    $mailer->From = $fieldSet->campaignEmail;  // changes 24-Jun-2016 ja
    182     $mailer->FromName = '';
    183    
     177    $mailer->FromName = get_bloginfo("name");
     178    $mailer->AddBCC($fieldSet->visitorEmail, $fieldSet->visitorName);
    184179    $mailer->AddReplyTo($fieldSet->visitorEmail, $fieldSet->visitorName);
    185     $mailer->AddBCC($fieldSet->visitorEmail, $fieldSet->visitorName);       // copy of email for site visitor
    186     //$mailer->AddBCC($fieldSet->campaignEmail, '');                                    // copy of email for campaign
    187    
    188     foreach ($fieldSet->recipients as $recipient)
    189     {
    190       self::validateEmail($recipient, "recipientEmail");
    191       $mailer->AddAddress($recipient);
    192       if (empty($fieldSet->target))
    193         $fieldSet->target = $recipient;
    194     }
     180   
     181    $emailList = new EmailList($this->session->fieldSet->to);
     182    $emailList->divert($this->session->testMode, $this->session->fieldSet->campaignEmail);
     183   
     184    foreach ($emailList->items as $recipient)
     185    {
     186      self::validateEmail($recipient['email'], "recipientEmail");
     187      empty($recipient['name']) ? $mailer->AddAddress($recipient['email']) : $mailer->AddAddress($recipient['email'], $recipient['name']);
     188    }
     189    $firstRecipient = $emailList->items[0]['email'] ;
    195190    if (get_option('ec_preventDuplicateActions'))
    196       if ($this->log->recordExists(EcampaignLog::tSend, $fieldSet->visitorEmail, $fieldSet->target, $fieldSet->postID))
    197     {
    198       $response = array("success" => false,
    199                         "msg" => __("You have already sent an email about this issue to $fieldSet->target")) ;
    200       return $response ;
    201     }
     191      if ($this->log->recordExists(EcampaignLog::tSend, $fieldSet->visitorEmail, $firstRecipient, $fieldSet->postID))
     192      {
     193        $response = array("success" => false,
     194            "msg" => __("You have already sent an email about this issue to $firstRecipient")) ;
     195        return $response ;
     196      }
     197   
    202198    $text = new EcampaignString();
    203199    $text->add($fieldSet->body)  // this has been trimmed
     
    208204    $mailer->Body = $text->asBlock();
    209205
    210     array_unshift($this->infoMap,'recipients: ' . ($recipientString = implode(', ', $fieldSet->recipients)));
    211 
    212     $delivery = $this->testMode->isSuppressed() ? 1 : ($mailer->Send() ? 2 : 0);
     206   
     207    array_unshift($this->infoMap,'recipients: ' . $recipientString = $emailList->asString(True));
     208
     209    $delivery = $this->session->testMode->isSuppressed() ? 1 : ($mailer->Send() ? 2 : 0);
    213210
    214211    if ($delivery == 0)
    215212      throw new Exception(__("unable to send email to") . " {$recipientString}, {$mailer->ErrorInfo}");
    216213
    217     if (!$this->testMode->isNormal())
    218       $this->infoMap[] = 'test mode: ' . $this->testMode->toString();
     214    if (!$this->session->testMode->isNormal())
     215      $this->infoMap[] = 'test mode: ' . $this->session->testMode->toString();
    219216
    220217    $this->log->write(EcampaignLog::tSend, $fieldSet, $this->infoMap);
     
    222219
    223220    // forward a similar version of the email to the campaign but add the checkfields that they have clicked
    224 
    225     if (true)
     221    // Disabled 2-nov-2018 because it doubles number of emails, reduces throughput etc.
     222
     223    if (false)
    226224    {
    227225      $mailer->ClearAllRecipients();
    228226      $mailer->AddAddress($fieldSet->campaignEmail);
    229       $mailer->Subject = "$fieldSet->ch1 : $fieldSet->ch2 : " . $fieldSet->subject ;
     227      $mailer->Subject = (isset($fieldSet->checkbox1)? "on:" : "  :") 
     228                       . (isset($fieldSet->checkbox2)? "on:" : "  :")
     229                       . $fieldSet->subject ;
    230230
    231231      $this->infoMap[] = " "; $this->infoMap[] = $mailer->Body;
    232232      $mailer->Body = implode("\r\n", $this->infoMap);
    233233
    234       $delivery |= $this->testMode->isSuppressed() ? 1 : ($mailer->Send() ? 2 : 0);
     234      $delivery |= $this->session->testMode->isSuppressed() ? 1 : ($mailer->Send() ? 2 : 0);
    235235
    236236      if ($delivery == 0)
     
    238238    }
    239239    $sMsg = $this->successMessage . ($delivery == 2 ? "" : " Delivery suppressed (code:$delivery) ");
    240     $response = array("success" => true,
     240/*    $response = array("success" => true,  // not used
    241241                      "msg" => $sMsg,
    242242                      "callbackJS" => self::jsRevealNextForm);
    243 
     243*/
    244244    return $this->revealNextApplicableForm(array("success" => true, "msg" => $sMsg));
    245245  }
     
    250250    $mailer = self::getPhpMailer();
    251251    $mailer->From = $fieldSet->campaignEmail ;
    252     $mailer->FromName = get_bloginfo("name");
     252    $mailer->FromName = $fieldSet->blogName = get_bloginfo("name");
    253253    $mailer->AddAddress($fieldSet->visitorEmail, $fieldSet->visitorName);
    254254
     
    259259        $fieldSet->subject = $post->post_title ;
    260260    }
     261    $fieldSet->permalink = get_permalink($this->session->postID);
     262    $fieldSet->emailVerificationLink =   $fieldSet->permalink.'?verificationCode='.$fieldSet->code;   // ja 27-sep-2018 FIXIT
     263    $fieldSet->blogname = get_bloginfo("name");
    261264    $mailer->Subject = self::replaceTokens($optionName."Subject", $fieldSet);
    262265    $mailer->Body    = self::replaceTokens($optionName."Body", $fieldSet);
    263266
    264267    if (empty($mailer->Subject) || empty($mailer->Body))
    265       return false ;
     268    {
     269      throw new Exception(__("Subject or Body of email address verfication email is empty. Check ecampaign settings"));
     270    }
    266271
    267272    $success = $mailer->Send();
     
    285290  {
    286291    $name = $match[1];
    287     return isset($this->fieldSet->$name) ? $this->fieldSet->$name :"xxxxxx" ;
     292    return isset($this->session->fieldSet->$name) ? $this->session->fieldSet->$name :"xxxxxx" ;
    288293  }
    289294
     
    302307      $list = _createFromClassPath($listClassPath);
    303308
    304       $list->subscribe($this->templateFields, $fieldSet);
     309      $list->subscribe($this->session->templateFields, $fieldSet);
    305310
    306311      $this->log->write("subscribe", $fieldSet, $this->infoMap);
Note: See TracChangeset for help on using the changeset viewer.