<?php
#-------------------------------------------------------------------------
# Module: Newsletter Made Simple
#        (c) to 2008 by Paul Lempkepf
#        (c) 2008 and on by Robert Campbell (calguy1000@cmsmadesimple.org)
#
#  An addon module for CMS Made Simple to provide a flexible
#  mailing list solution.
#
#-------------------------------------------------------------------------
# CMSMS - CMS Made Simple is (c) 2005 by Ted Kulp (wishy@cmsmadesimple.org)
# Visit the CMSMS Homepage at: http://www.cmsmadesimple.org
#
#-------------------------------------------------------------------------
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# However, as a special exception to the GPL, this software is distributed
# as an addon module to CMS Made Simple.  You may not use this software
# in any Non GPL version of CMS Made simple, or in any version of CMS
# Made simple that does not indicate clearly and obviously in its admin
# section that the site was built with CMS Made simple.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# Or read it online: http://www.gnu.org/licenses/licenses.html#GPL
#
#-------------------------------------------------------------------------
#END_LICENSE

require_once(__DIR__.'/function.utils.php');
require_once(__DIR__.'/defines.php');

final class NMS extends CGExtensions
{
    protected $current_message;
    var $_archive_view;
    var $attached_files;
    var $attached_images;
    var $processing_prefspage;
    var $defaultcharset = 'iso-8859-1';

    var $default_confirm_email_body = '
Thank you {$userinfo.username} for subscribing to one or more maling lists at {sitename}.  Your registration has been confirmed.  At any time you can unsubscribe from this list by following this link {$unsubscribeurl}.  You may also change your preferences by following this link: {$preferencesurl}';
  var $default_post_email_confirm_message = '
{strip}{if isset($message)}
  {if isset($error)}
    <div class="message">{$message}</div>
  {else}
    <div class="danger">{$message}</div>
  {/if}
{else}
Thank you {$username}.  Your email address {$email} has been marked as confirmed as of {$dateconfirmed|cms_date_format}.  Your uniquid (for future reference) is {$uniqueid}
{/if}{/strip}';
  var $default_unsubscribe_email_body = '
Hello {$username} <{$email}>

This message is to confirm that it is indeed you that has chosen to unsubscribe from all of the public mailing lists with us.
If this is indeed the case, please confirm this action by clicking on the following link:
{$confirm_unsubscribe_url}

If you did not instantiate this action, you can safely ignore this message.

Thank you.
';
  var $default_unsubscribe_prompt = 'Enter your email to unsubscribe';
  var $default_unsubscribe_text = '
Thank you {$username}.  An email as been sent to {$email} to confirm your wish to unsubscribe from all mailing lists on this site.  You should be receiving it shortly.  Thank you';
  var $default_post_unsubscribe_text = '
{if isset($username) && isset($email)}
Thank you {$username} <{$email}>.  You have now been unsubscribed from all public mailing lists.  We are sorry to see you go.
{else}
You have now been unsubscribed from all public mailing lists.  We are sorry to see you go.
{/if}
';
  var $default_usersettings_subject = 'User Settings Information';
  var $default_usersettings_email_body = '
Thank you {$username} <{$email}>.  You requested information as to how to change your subscription information.  Here it is:

Follow this link to adjust your settings:
{$usersettings_url}

Follow this link to completely unsubscribe from all public mailing lists:
{$unsubscribe_url}
';
  var $default_usersettings_text = '
Thank you {$userinfo.username} <{$userinfo.email}>.  A message has been sent to you via email with further instructions to allow you to adjust your settings.
';
  var $default_usersettings_text2 = '
Thank you {$username} <{$email}>, your settings have been changed.
';

  function __construct()
  {
    parent::__construct();
    $this->attached_images = [];
    $this->attached_files = [];
    $this->current_message = -1;
    $this->processing_prefspage = NULL;

    $gCms = CmsApp::get_instance();
    $smarty = $gCms->GetSmarty();
    if( !$smarty ) return;
    $smarty->registerResource('nms_content',new NMSContentResource($this));

    // at the same time, we'll register a few smarty plugins
    $smarty->register_function('nms_image',array(&$this,'function_nms_image'));
    $smarty->register_function('nms_attachment',array(&$this,'function_nms_attachment'));
    $smarty->register_function('nms_content',array(&$this,'function_nms_content'));
    $smarty->register_function('nms_getmessage',array(&$this,'function_nms_getmessage'));
  }

  function GetName() { return 'NMS'; }

  function GetDependencies() { return [ 'CGExtensions' => '1.57','CGSimpleSmarty'=>'2.1.7' ];  }

  public function get_current_message() { return $this->current_message;  }

  function GetEventDescription( $name ) { return $this->Lang('info_event_'.$name ); }
  function GetEventHelp ( $eventname ) { return $this->Lang ('help_'.$eventname); }
  function AllowAutoInstall() { return FALSE; }
  function AllowAutoUpgrade() { return FALSE; }
  function GetFriendlyName() { return $this->Lang('friendlyname'); }
  function GetVersion() { return '2.13.2'; }
  function GetHelp() { return file_get_contents(__DIR__.'/help.inc'); }
  function GetAuthor() { return 'Robert Campbell'; }
  function GetAuthorEmail() { return 'calguy1000@cmsmadesimple.org'; }
  function GetChangeLog() { return $this->ProcessTemplate('changelog.tpl'); }
  function IsPluginModule() { return TRUE; }
  function HasAdmin() { return TRUE; }
  function HandlesEvents() { return TRUE; }
  function LazyLoadAdmin() { return TRUE; }
  function GetAdminSection() { return 'extensions'; }
  function GetAdminDescription() { return $this->Lang('moddescription'); }
  function MinimumCMSVersion() { return '2.2'; }
  function InstallPostMessage() { return $this->Lang('postinstall'); }
  function UninstallPostMessage() { return $this->Lang('postuninstall'); }

  function VisibleToAdminUser()
  {
    $x =
      $this->CheckPermission('Manage NMS Users') ||
      $this->CheckPermission('Manage NMS Jobs') ||
      $this->CheckPermission('Manage NMS Lists') ||
      $this->CheckPermission('Manage NMS Messages') ||
      $this->CheckPermission('Manage NMS Preferences') ||
      $this->CheckPermission('Modify Templates') ||
      $this->CheckPermission('Modify Site Preferences');

    return $x;
  }

  function SetParameters()
  {
    $this->RegisterModulePlugin();
    $this->RestrictUnknownParams();
    $this->AddImageDir('icons');

    $this->CreateParameter('action', 'default', $this->Lang('helpaction'));

    $this->CreateParameter('mode', 'subscribe', $this->Lang('helpmode'));
    $this->SetParameterType('mode',CLEAN_STRING);

    $this->CreateParameter('archivelist_template','',$this->Lang('help_archivelist_template'));
    $this->SetParameterType('archivelist_template',CLEAN_STRING);
    $this->CreateParameter('archivemsg_template','',$this->Lang('help_archivemsg_template'));
    $this->SetParameterType('archivemsg_template',CLEAN_STRING);
    $this->CreateParameter('archivepage','',$this->Lang('help_archivepage'));
    $this->SetParameterType('archivepage',CLEAN_STRING);

    $this->CreateParameter('select', NULL, $this->Lang('helpselect'));
    $this->SetParameterType('select',CLEAN_STRING);

    $this->CreateParameter('limit','',$this->Lang('help_limit'));
    $this->SetParameterType('limit',CLEAN_INT);

    $this->CreateParameter('sortby','entered',$this->Lang('help_sortby'));
    $this->SetParameterType('sortby',CLEAN_STRING);

    $this->CreateParameter('sortorder','DESC',$this->Lang('help_sortorder'));
    $this->SetParameterType('sortorder',CLEAN_STRING);

    $this->SetParameterType('msgID',CLEAN_INT);
    $this->SetParameterType(CLEAN_REGEXP.'/nms_.*/',CLEAN_STRING);

    // internal params (users don't use these on tags)
    $this->SetParameterType('nocaptcha',CLEAN_INT);
    $this->SetParameterType('captcha',CLEAN_STRING);
    $this->CreateParameter('nocaptcha','',$this->Lang('help_nocaptcha'));
    $this->SetParameterType('message',CLEAN_STRING);
    $this->SetParameterType('error',CLEAN_INT);
    $this->SetParameterType('email',CLEAN_STRING);
    $this->SetParameterType('username',CLEAN_STRING);
    $this->SetParameterType('lists',CLEAN_STRING);
    $this->SetParameterType('submit',CLEAN_STRING);
    $this->SetParameterType('uniqueid',CLEAN_STRING);
    $this->SetParameterType('subaction',CLEAN_STRING);
    $this->SetParameterType('value',CLEAN_STRING);
    $this->SetParameterType('usersettings',CLEAN_STRING);
    $this->SetParameterType('inline',CLEAN_INT);
    $this->CreateParameter('inline',0,$this->Lang('help_inline'));

    $this->RegisterRoute('/[Nn][Mm][Ss]\/(?P<returnid>[0-9]+)\/(?P<subaction>.*?)\/(?P<value>[0-9a-f]+)$/');
  }

  /*---------------------------------------------------------
   DoAction($action, $id, $params, $returnid)
   This is the main function that gets called if your module
   is a plug-in type module.
   ---------------------------------------------------------*/
  function DoAction($action, $id, $params, $returnid='')
  {
      $gCms = CmsApp::get_instance();
      $smarty = $gCms->GetSmarty(); $smarty->assign('formid',$id);
      $smarty->assign('mod',$this);

      switch( $action ) {
      case 'delete_email':
          if ($this->CheckPermission('Manage NMS Users')) {
              $status = $this->DeleteEmail($id, $params, $returnid);
              $parms = [];
              if( !$status ) {
                  $this->parms['error'] = 1;
                  $this->parms['message'] = $this->Lang('errordeletinguser');
              }
              $this->RedirectToTab($id,'users');
          }
          else {
              $this->_DisplayErrorPage($id, $params, $returnid, $this->Lang('accessdenied'));
          }
          break;

      case 'pause_job':
          if ($this->CheckPermission('Manage NMS Jobs')) {
              $message = $this->_AdminDoPauseJob($id, $params, $returnid);
          }
          else {
              $this->_DisplayErrorPage($id, $params, $returnid, $this->Lang('accessdenied'));
          }
          break;

      case 'reset_job':
          if ($this->CheckPermission('Manage NMS Jobs')) {
              $message = $this->_AdminDoResetJob($id, $params, $returnid);
          }
          else {
              $this->_DisplayErrorPage($id, $params, $returnid, $this->Lang('accessdenied'));
          }
          break;

      case 'resume_job':
          if ($this->CheckPermission('Manage NMS Jobs')) {
              $message = $this->_AdminDoResumeJob($id, $params, $returnid);
          }
          else {
              $this->_DisplayErrorPage($id, $params, $returnid, $this->Lang('accessdenied'));
          }
          break;

          // fallback through to call the action.xxxx.php file
      default:
          parent::DoAction( $action, $id, $params, $returnid );
          break;
      }
  }


  protected function _AdminDoPauseJob($id, &$params, $returnid)
  {
      if( !isset( $params['jobid'] ) ) {
          $this->_DisplayErrorPage( $id, $params, $returnid, $this->Lang('error_insufficientparams'));
          return;
      }

      $db = $this->GetDb();
      $q = "UPDATE ".NMS_JOBS_TABLE." SET status = ? WHERE jobid = ?";
      $dbresult = $db->Execute( $q, [ 2, $params['jobid'] ] );
      $this->RedirectToTab($id,'jobs');
  }

  protected function _AdminDoResetJob($id, &$params, $returnid)
  {
      if( !isset( $params['jobid'] ) ) {
          $this->_DisplayErrorPage( $id, $params, $returnid,$this->Lang('error_insufficientparams'));
          return;
      }

      $db = $this->GetDb();

      // drop the temporary table
      $dict = NewDataDictionary( $db );
      $sqlarray = $dict->DropTableSQL( NMS_TEMP_TABLE );
      $dict->ExecuteSQLArray($sqlarray);

      $q = "UPDATE ".NMS_JOBS_TABLE." SET status = ? WHERE jobid = ?";
      $dbresult = $db->Execute( $q, [ 0, $params['jobid'] ] );

      $this->SetCurrentTab('jobs');
      $this->RedirectToTab($id);
  }

  protected function _AdminDoResumeJob($id, &$params, $returnid)
  {
      if( !isset( $params['jobid'] ) ) {
          $this->_DisplayErrorPage( $id, $params, $returnid, $this->Lang('error_insufficientparams'));
          return;
      }

      $db = $this->GetDb();
      $q = "UPDATE ".NMS_JOBS_TABLE." SET status = ? WHERE jobid = ?";
      $dbresult = $db->Execute( $q, [ 0, $params['jobid'] ] );
      $this->RedirectToTab($id,'jobs');
  }


  /**
   * Send an emmail to a specific user.
   *
   * @param string the id to use for links
   * @param array  The message info
   * @param array  An array containing the destination email, username, userid, and uniqueid.  only the email address is required.
   * @param integer The job id.
   * @return boolean
   * @ignore
   * @internal
   */
  public function _ProcessOneEmail($id,&$message,$destrow,nms_job $the_job = null)
  {
    if( !$this->_isValidEmail($destrow['email']) ) {
        $query = 'UPDATE '.NMS_USERS_TABLE.' SET error_count = COALESCE(error_count,0) + 1 WHERE email = ?';
        $db = CmsApp::get_instance()->GetDb();
        $db->Execute($query, [ $destrow['email'] ] );
        audit('',$this->GetName(),'Error sending email to '.$destrow['email'].': invalid email address');
        return FALSE;
    }

    $gCms = CmsApp::get_instance();
    $smarty = $gCms->GetSmarty();
    $config = $gCms->GetConfig();
    if( $this->processing_prefspage == NULL ) {
        //
        // Do this on processing the first message
        //
        $prefspage = $this->GetPreference('usersettings_page');
        $contentops = $gCms->GetContentOperations();
        if( $prefspage == '' ) $prefspage = $contentops->GetDefaultPageID();
        $content = $contentops->LoadContentFromAlias($prefspage);
        if( !$content ) $prefspage = $contentops->GetDefaultPageID();
        $this->processing_prefspage = $prefspage;
    }
    $prefspage = $this->processing_prefspage;

    //
    // Do this every time the messageid changes
    //
    if( $message['messageid'] != $this->current_message ) {
        $this->attached_images = [];
        $this->attached_files = [];
        $this->current_message = $message['messageid'];
    }

    // Setup the message
    $mailer = new \cms_mailer;
    $mailer->ClearAllRecipients();
    $mailer->SetSubject($message['subject']);
    $mailer->SetCharSet($this->GetPreference('message_charset'));
    $mailer->SetFromName($this->GetPreference('admin_name'));
    $mailer->SetFrom($this->GetPreference('admin_email'));
    $replyto = $message['send_as'];
    if( !$replyto) $replyto = $this->GetPreference('admin_replyto');
    if( !empty($replyto) ) $mailer->AddReplyTo($replyto);

    if( $this->GetPreference('sendheaders',1) ) {
        $mailer->AddCustomHeader('X-NMS_MessageID: '.$message['messageid']);
        $mailer->AddCustomHeader('X-NMS_ListMember: '.$destrow['email']);
        $mailer->AddCustomHeader('X-NMS-SendTime: '.microtime());
        if( isset($destrow['uniqueid']) ) $mailer->AddCustomHeader('X-NMS_MemberID: '.$destrow['uniqueid']);
        $tmp = $this->GetPreference('precedence');
        if( $tmp != '' && $tmp != 'none' ) $mailer->AddCustomHeader("Precedence: ".$tmp);
        $mailer->AddCustomHeader("X-Mailer: CMS Made Simple ".CMS_VERSION.' ('.$this->GetName()." ".$this->GetVersion().' )');
    }

    //
    // setup smarty
    //
    if( !empty($message) )$smarty->assign('message', $message);

    $smarty->assign('fielddefs',nms_fielddef::load_all());
    $smarty->assign('subject',$message['subject']);
    if( isset($destrow['username']) ) $smarty->assign('username',$destrow['username']);
    $smarty->assign('email',$destrow['email']);
    $fields = null;
    $smarty->clear_assign('fields');
    if( isset($destrow['userid']) ) {
        $fields = nms_utils::get_userfields($destrow['userid']);
        if( is_array($fields) && count($fields) ) $smarty->assign('fields',$fields);
    }

    if( isset($destrow['uniqueid']) ) {
        $smarty->assign('uniqueid',$destrow['uniqueid']);

        $link = $this->_cleanLink($this->create_url($id,'unsubscribe_email',$message['pageid'],['uniqueid'=>$destrow['uniqueid'] ] ));
        $smarty->assign('unsubscribe',$link);

        $link = $this->_cleanLink($this->create_url($id,'usersettings',$prefspage,array('uniqueid'=>$destrow['uniqueid'])));
        $smarty->assign('preferences',$link);

        $link = $this->_cleanLink($this->create_url($id,'confirm_email',$message['pageid'],array('showtemplate'=>false,'uniqueid'=>$destrow['uniqueid'])));
        $smarty->assign('confirmurl',$link);

        $link = $this->_cleanLink($this->create_url($id,'showmessage',$message['pageid'],array('msgID'=>$this->current_message,'showtemplate'=>false)));
        $smarty->assign('msg_permalink',$link);
    }

    // add smarty variables from the job
    if( is_object($the_job) ) {
        $smarty->assign('job_title',$the_job['title']);

        // add smarty variables from modules
        foreach ($this->GetModulesWithCapability('nms_vars') as $one_module) {
            $obj = cms_utils::get_module($one_module);
            if( !is_object($obj) ) continue;
            $obj->SetSmartyVars($id, $the_job['id'], $message['messageid'], $destrow['email'], $smarty, 'send');
        }
    }

    //
    // do the processing
    //
    $htmlbody = '';
    if( !$message['text_only'] ) $htmlbody = $this->ProcessTemplateFromDatabase($message['template']);
    $textbody = $this->ProcessTemplateFromData($message['message']);
    if( empty($htmlbody) && empty($textbody) ) {
        audit('',$this->GetName(),'Error sending email to '.$destrow['email'].': no content in processed message');
        return FALSE;
    }

    // Postprocess the text
    // and create absolute links
    if( !$message['text_only'] && $htmlbody ) $htmlbody = make_absolute_links($htmlbody,$config['root_url']);
    $textbody = make_absolute_links($textbody,$config['root_url']);

    //
    // attach the attachments
    //
    foreach( $this->attached_images as $obj ) {
        if( !$obj->cid ) continue;
        if( !$obj->filename ) continue;
        $mimetype = cge_utils::get_mime_type($obj->filename);
        if( empty($mimetype) || $mimetype == 'unknown' ) $mimetype = 'application/octet-stream';
        $mailer->AddEmbeddedImage($obj->filename,$obj->cid,basename($obj->filename),'base64',$mimetype);
    }

    foreach( $this->attached_files as $onefile ) {
        $fn = cms_join_path($config['uploads_path'],$onefile);
        $mailer->AddAttachment($fn,$onefile);
    }

    //
    // handle the mail contents
    //
    if( !empty($htmlbody) ) {
        $mailer->IsHTML(true);
        $mailer->SetBody($htmlbody);
        if( !empty($textbody) ) $mailer->SetAltBody($textbody);
    }
    else {
        $mailer->IsHTML(false);
        $mailer->SetBody($textbody);
    }

    //
    // Add the destination and send it
    //
    $res = $mailer->AddAddress($destrow['email']);
    if( $res ) $res = $mailer->Send();

    if( !$res || $mailer->GetErrorInfo() != '' ) {
        // error sendimg message.
        audit('',$this->GetName(),'Error sending email to '.$destrow['email'].' '.$mailer->the_mailer->ErrorInfo);
        $query = 'UPDATE '.NMS_USERS_TABLE.' SET error_count = COALESCE(error_count,0) + 1 WHERE email = ?';
        $db = $gCms->GetDb();
        $db->Execute($query,[ $destrow['email'] ]);
        return FALSE;
    }

    return TRUE;
  }

  protected function _cleanLink($strMessage)
  {
      $strMessage = str_replace('&amp;', '&',$strMessage);
      return $strMessage;
  }


  protected function DeleteEmail($id, &$params, $returnid )
  {
    $db = $this->GetDb();

    $query = "DELETE FROM ".NMS_USERS_TABLE." WHERE userid = ?";
    $dbresult = $db->Execute($query,array($params['userid']));
    if( $dbresult ) {
      $query = "DELETE FROM ".NMS_LISTUSER_TABLE." WHERE userid = ?";
      $dbresult = $db->Execute($query,array($params['userid']));

      $query = "DELETE FROM ".NMS_USER_DATA_TABLE." WHERE userid = ?";
      $dbresult = $db->Execute($query,array($params['userid']));
    }

    if ($dbresult) {
      // send an event
      $parms = [ 'id' => $params['userid'] ];
      $this->SendEvent('OnDeleteUser',$parms);
      audit($params['userid'],$this->GetName(),'User Deleted');
      return true;
    } else {
      $params['error'] = 1;
      $params['message'] = $db->GetErrorMsg();
      return false;
    }
  }


  /*---------------------------------------------------------
   _DisplayErrorPage($id, $params, $returnid, $message)
   ---------------------------------------------------------*/
  protected function _DisplayErrorPage($id, &$params, $returnid, $message='')
  {
      $smarty = CmsApp::get_instance()->GetSmarty();
      $smarty->assign('title_error', $this->Lang('error'));
      if ($message != '') $smarty->assign('message', $message);

      // Display the populated template
      echo $this->ProcessTemplate('error.tpl');
  }


  ####################
  #
  # Smarty Plugins
  #
  ####################


  /**
   * Internal function
   */
  function function_nms_content($params, &$smarty)
  {
    if( $this->current_message == NULL || $this->current_message <= 0 ) return; // do nothing
    if( !isset($params['name']) ) return; // do nothing

    $name = str_replace(' ','_',$params['name']);
    $smarty->force_compile = true;
    $result = $smarty->fetch("nms_content:$name",$this->current_message);
    $smarty->force_compile = false;

    if( isset($params['assign']) ) {
      $smarty->assign(trim($params['assign']),$result);
      return;
    }
    return $result;
  }


  /**
   * Internal function
   */
  function function_nms_image($params, &$smarty)
  {
      if( (int) $this->current_message < 1 ) return;  // do nothing
      if( !isset($params['name']) && !isset($params['urlonly']) ) return;
      $safe_name = str_replace(' ','_',$params['name']);

      $canembed = 1;
      if( isset($params['noembed']) || $this->_archive_view ) $canembed = 0;

      // Check for the src argument
      if( isset($params['src']) ) {
          $obj = new StdClass();
          $obj->filename = cge_utils::src_to_file(trim($params['src']));;
          if( !$obj->filename ) {
              $config = CmsApp::get_instance()->GetConfig();
              $tmp = cms_join_path($config['image_uploads_path'],trim($params['src']));
              if( is_file($tmp) ) $obj->filename = $tmp;
          }
          $obj->src = cge_utils::file_to_url($obj->filename);
          if( $canembed ) {
              $obj->cid = md5(basename($obj->filename));
              $this->attached_images[$safe_name] = $obj;
          }
      }
      else {
          // Check the cache
          if( is_array($this->attached_images) && isset($this->attached_images[$safe_name]) ) {
              // woot, it's cached
              $obj =& $this->attached_images[$safe_name];
          }
          else {
              // get the filename from the database
              $db = $this->GetDb();
              $config = CmsApp::get_instance()->GetConfig();
              $query = 'SELECT value FROM '.NMS_MESSAGE_CONTENT_TABLE.' WHERE name = ? AND messageid = ?';
              $filename = $db->GetOne($query,array($safe_name,$this->current_message));
              if( !$filename || $filename == -1 ) return;  // do nothing

              $dir = $config['uploads_path'];
              $src = cms_join_path($dir,$filename);
              if( !is_file( $src ) ) return; // file does not exist.)

              // add it to the cache
              $obj = new StdClass();
              $obj->filename = cge_utils::src_to_file($src);
              $obj->src = cge_utils::file_to_url($obj->filename);
              $obj->dir = $dir;
              if( $canembed ) {
                  $obj->cid = md5(basename($obj->filename));
                  $this->attached_images[$safe_name] = $obj;
              }
          }
      }

      if( isset($params['urlonly']) && !isset($obj->cid) && isset($obj->src) ) {
          $txt = $obj->src;
      }
      else if( !isset($params['urlonly']) ) {
          $txt = '';
          if( isset($obj->cid) ) {
              $txt = '<img src="cid:'.$obj->cid.'" ';
          } else if( isset($obj->src) ) {
              $txt = '<img src="'.$obj->src.'" ';
          }

          // 4.  format the output
          if( $txt ) {
              foreach( $params as $key => $value ) {
                  if( $key == 'name' ) continue;
                  if( $key == 'src' ) continue;
                  if( $key == 'prompt' ) continue;
                  if( $key == 'dir' ) continue;
                  if( $key == 'noembed' ) continue;
                  $txt .= sprintf("%s=\"%s\" ",$key,$value);
              }
              $txt .= '/>';
          }
      }

      if( isset($params['assign']) ) {
          $smarty->assign(trim($params['assign']),$txt);
          return;
      }

      return $txt;
  }


  /**
   * Internal function
   */
  function function_nms_attachment($params, &$smarty)
  {
      if( $this->current_message == NULL || $this->current_message <= 0 ) return;
      $name = \cge_param::get_string($params,'name');
      if( !$name ) return;
      $safe_name = str_replace(' ','_',$params['name']);

      // 1.  Check the cache first
      $filename = '';
      if( is_array($this->attached_files) && isset($this->attached_files[$safe_name]) ) {
          // woot, it's cached
          $filename = $this->attached_files[$safe_name];
      }
      else {
          // 2.  get the filename from the database
          $db = $this->GetDb();
          $query = 'SELECT value FROM '.NMS_MESSAGE_CONTENT_TABLE.' WHERE name = ? AND messageid = ?';
          $filename = $db->GetOne($query,array($safe_name,$this->current_message));
          if( !$filename ) return;

          // add it to the cache
          $this->attached_files[$safe_name] = $filename;
      }
      if( !$this->_archive_view ) return;

      // only archive view can output html...
      $config = \cms_config::get_instance();
      $filespec = $config['uploads_path'].'/'.$filename;
      $url = \cge_utils::file_to_url($filespec);
      if( \cge_param::exists($params,'urlonly') ) {
          $out = 'url';
      }
      else {
          $out = "<a href=\"{$url}\">{$name}</a>";
      }

      if( isset($params['assign']) ) {
          $smarty->assign(trim($params['assign']),$out);
          return;
      }

      return $out;
  }


  /**
   * Internal function
   */
  function function_nms_getmessage($params, &$smarty)
  {
    $msgid = '';
    if( isset($params['msg']) ) $msgid = (int)$params['msg'];
    if( isset($params['messageid']) ) $msgid = (int)$params['messageid'];
    // Set a member variable for use by the other nms plugins
    // so that they can know the message to work with.
    $this->current_message = $msgid;

    // retrieve the message
    $db = $this->GetDb();
    $query = 'SELECT * FROM '.NMS_MESSAGES_TABLE.' WHERE messageid = ?';
    $message = $db->GetRow($query, [$msgid] );
    if( !is_array($message) ) return;

    // process the template
    $template = $this->GetTemplate($message['template']);
    $text = $this->ProcessTemplateFromData($template);
    $this->current_message = -1;

    // return the results
    if( isset($params['assign']) ) {
      $smarty->assign(trim($params['assign']),$result);
      return;
    }
    return $text;
  }

  ####################
  #
  # End of smarty functions
  #
  ####################

  /**
   * Internal function
   */
  protected function _handle_user_bounce($fields)
  {
    if( !is_array($fields) || !isset($fields['ListMember']) || !isset($fields['MemberID']) ) return;
    $db = $this->GetDb();

    // get the bounce count, and the disabled status
    $query = 'SELECT disabled,bounce_count FROM '.NMS_USERS_TABLE.' WHERE uniqueid = ? AND email = ?';
    $row = $db->GetRow($query,array($fields['MemberID'],$fields['ListMember']));
    if( !$row ) {
      // user not found?
      // ignore this
      return;
    }
    if( !$row['bounce_count'] ) $row['bounce_count'] = 0;
    $row['bounce_count']++;

    // if the bounce count is now too high
    // make sure the user is disabled
    if( $row['bounce_count'] >= $this->GetPreference('bounce_limit',10) ) $row['disabled'] = 1;

    // update the user record
    $query = 'UPDATE '.NMS_USERS_TABLE.' SET bounce_count = ?, disabled = ? WHERE uniqueid = ? AND email = ?';
    $db->Execute($query, array($row['bounce_count'],$row['disabled'],$fields['MemberID'],$fields['ListMember']));
  }

  public function get_pretty_url($id,$action,$returnid='',$params=[],$inline=false)
  {
      $url = '';
      $str = sprintf('NMS/%d',$returnid);

      switch( $action ) {
      case 'unsubscribe':
          if( isset($params['uniqueid']) ) $url = $str.'/unsub/'.$params['uniqueid'];
          break;

      case 'unsubscribe_email':
          if( isset($params['uniqueid']) ) $url = $str.'/unsub_email/'.$params['uniqueid'];
          break;

      case 'usersettings':
          if( isset($params['uniqueid']) ) $url = $str.'/settings/'.$params['uniqueid'];
          break;

      case 'preferences':
          $url = $str.'/prefs';
          break;

      case 'confirm_email':
          if( isset($params['uniqueid']) ) $url = $str.'/confirm/'.$params['uniqueid'];
          break;

      case 'showmessage':
          if( isset($params['msgID']) ) $url = $str.'/message/'.$params['msgID'];
          break;
      }
      return $url;
  }


  /*---------------------------------------------------------
   GetLists : Public
   -
   Return an array of lists
   params arer: status (public/private), active (0, 1), populated (0,1), dynamic (0,1)
   ---------------------------------------------------------*/
  function GetLists($parms = [])
  {
      $db = $this->GetDb();
      $sig = md5(__METHOD__.serialize($parms));
      static $_out = [];
      if( !isset($_out[$sig]) ) {

          $query = null;
          $where = [];
          if( isset($parms['status']) ) {
              switch( strtolower($parms['status']) ) {
              case 'public':
                  $where[] = 'A.public = 1';
                  break;

              case 'private':
                  $where[] = 'A.public = 0';
                  break;

              default:
                  break;
              }
          }

          if( isset($parms['populated']) ) {
              switch( (int) $parms['populated'] ) {
              case 0:
                  $where[] = 'B.count = 0';
                  break;
              default:
                  $where[] = '(B.count > 0 OR COALESCE(A.dynamic,\'\') != \'\')';
                  unset($parms['dynamic']);
                  break;
              }
          }

          if( isset($parms['dynamic']) ) {
              switch( (int) $parms['dynamic'] ) {
              case 0:
                  $where[] = 'COALESCE(A.dynamic,\'\') = \'\'';
                  break;
              default:
                  $where[] = 'COALESCE(A.dynamic,\'\') != \'\'';
                  break;
              }
          }

          if( isset($parms['active']) ) {
              switch( (int) $parms['active'] ) {
              case 0:
                  $where[] = 'COALESCE(A.active,0) = 0';
                  break;
              default:
                  $where[] = 'A.active = 1';
                  break;
              }
          }

          $parms = array((int) $this->GetPreference('max_error_count',10));
          $query = 'SELECT A.*,B.count FROM '.NMS_LIST_TABLE.' A
                    LEFT JOIN
                       (SELECT L.listid,COUNT(L.userid) AS count FROM '.NMS_LISTUSER_TABLE.' L
                        LEFT JOIN '.NMS_USERS_TABLE.' U ON L.userid = U.userid AND U.disabled = 0 AND U.confirmed = 1 AND COALESCE(U.error_count) < ?
                        WHERE L.active = 1 GROUP BY L.listid) AS B ON A.listid = B.listid';
          if( count( $where ) ) {
              $query .= ' WHERE ';
              $query .= implode(' AND ',$where);
          }
          $query .= ' ORDER BY A.name';

          $tmp = $db->GetArray($query,$parms);
          $out = null;
          if( count($tmp) ) {
              $out = [];
              foreach( $tmp as $row ) {
                  $out[$row['listid']] = $row;
              }
          }
          $_out[$sig] = $out;
      }
      return $_out[$sig];
  }


  /*
   * Get Lists as an array
   * suitable to give to smarty for a select box.
   */
  public function GetListsAsArray($parms = [])
  {
      $with_count = \cge_param::get_bool($parms,'with_count');
      $prettify = (isset($parms['prettify'])) ? (int) $parms['prettify'] : 0;
      $tmp = $this->GetLists($parms);
      if( !count($tmp) ) return;

      $out = [];
      foreach( $tmp as $key => $row ) {
          $name = $row['name'];
          if( $with_count && !$row['dynamic'] ) $name .= ' ('.$row['count'].')';
          if( $prettify ) {
              if( !$row['public'] )  $str .= '('.$this->Lang('private').')';
              if( $row['dynamic'] )  $str .= '('.$this->Lang('dynamic').')';
          }
          $out[$key] = $name;
      }
      return $out;
  }

  protected function _isValidEmail($email,$checkdns = -1)
  {
      if( !is_email($email) ) return FALSE;

      if( $checkdns = -1 ) $checkdns = $this->GetPreference('check_email_dns',0);
      if( $checkdns ) {
          static $good_domains = [];
          if( function_exists('checkdnsrr') ) {
              list($junk,$domain) = explode('@',$email,2);
              if( in_array($domain,$good_domains) ) return TRUE;

              if( checkdnsrr($domain,'MX') ) {
                  $good_domains[] = $domain;
                  return TRUE;
              }
              else {
                  return FALSE;
              }
          }
      }

      return TRUE;
  }

  /*---------------------------------------------------------
   AddUser
   -
   Add a new user to one or more lists
   ---------------------------------------------------------*/
   function AddUser($email,$lists,$username='',$disabled=0,$confirmed=0)
   {
       if( empty($email) ) return $this->Lang('error_invalidemail');
       if( !is_array($lists) || count($lists) == 0 ) return $this->Lang('error_selectonelist');
       if( !is_email($email) ) return $this->Lang('error_invalidemail');

       $uniqueid = md5(uniqid(rand(),1));
       $db = &$this->GetDb();

       // Set the values for the fields in the record
       $record = [];
       $record["email"] = trim($email);
       $record["username"] = trim($username);
       $record["uniqueid"] = $uniqueid;
       $record["disabled"] = (isset($disabled)) ? $disabled : 0;
       $record["confirmed"] = $confirmed;
       $record["htmlemail"] = 1;
       $record["dateadded"] = trim($db->DBTimeStamp(time()),"'");

       $query = "SELECT userid,email FROM ".NMS_USERS_TABLE." WHERE email = ?";
       $row = $db->GetRow($query,array($record['email']));
       $userid = '';
       if ( $row ) {
           // email already exists
           $userid = $row['userid'];
           $newuser = 0;

           // update the lists he's a member of
           foreach ($lists as $listid){
               $query = 'SELECT userid FROM '.NMS_LISTUSER_TABLE.' WHERE listid = ? AND userid = ?';
               $num = $db->GetOne($query,array($listid,$userid));
               if( !$num ) {
                   $record = [];
                   $record["userid"] = $userid;
                   $record["listid"] = $listid;
                   $record["active"] = 1;
                   $record["entered"] = trim($db->DBTimeStamp(time()),"'");

                   $query = "INSERT INTO ".NMS_LISTUSER_TABLE." (userid, listid, active, entered) VALUES (?,?,?,?)";
                   $db->Execute($query, $record);
               }
           }
       }
       else {
           // insert the user
           $query = "INSERT INTO ".NMS_USERS_TABLE." (email, username, uniqueid, disabled, confirmed, htmlemail, dateadded) VALUES (?,?,?,?,?,?,?)";
           $dbresult = $db->Execute($query, $record);
           if( !$dbresult ) {
               echo $db->ErrorMsg();
               return;
           }

           // get user id back from the table
           $query = "SELECT userid FROM ".NMS_USERS_TABLE." WHERE uniqueid = ?";
           $dbresult = $db->Execute($query,array($uniqueid));

           if ($dbresult && $dbresult->RecordCount() > 0) {
               $row = $dbresult->FetchRow();
               $userid = $row['userid'];
           }

           // add him to his member lists
           foreach ($lists as $listid){
               // add to list/user table
               $record = [];
               $record["userid"] = $userid;
               $record["listid"] = $listid;
               $record["active"] = 1;
               $record["entered"] = trim($db->DBTimeStamp(time()),"'");

               $query = "INSERT INTO ".NMS_LISTUSER_TABLE." (userid, listid, active, entered) VALUES (?,?,?,?)";
               $dbresult = $db->Execute($query, $record);
           }
       }

       return '';
   }

} /* class */

// EOF
