<?php

/**
 * phplistPlugin main class to extend for plugins 
 */
 
/**
 * a plugin for phpList should extend this class to work. Once you have extended
 * you can implement all the hooks provided to manipulate the functionality of phpList
 * in many places.
 */

require_once dirname(__FILE__).'/accesscheck.php';

class phplistPlugin
{

  public $name = 'Default Plugin';
  public $version = 'unknown';
  public $authors = '';
  public $description = 'No description';
  public $documentationUrl = '';## link to documentation for this plugin (eg https://resources.phplist.com/plugin/pluginname
  public $enabled = 1; // use directly, can be privitsed later and calculated with __get and __set
  public $system_root = ''; ## root dir of the phpList admin directory

  #@@Some ideas to implement this:
  # * Start each method with if (!$this->enabled) return parent :: parentMethod($args);
  # * Don't add to manage Global plugins if disabled
  public $coderoot = './PLUGIN_ROOTDIR/defaultplugin/'; # coderoot relative to the phplist admin directory
  # optional configuration variables
  public $configvars = array();
  # config var    array( type, name [array values]));
  public $DBstruct = array();

  # These files can be called from the commandline
  # The variable holds an array of page names, (the file name without .php) and the files must be in the $coderoot directory.
  public $commandlinePluginPages = array();

  # An array of page names that can be called as public pages, e.g. www.mysite.com/lists/?pi=myplugin&p=mypage
  # The page name is the file name without .php. The files must be in the $coderoot directory
  public $publicPages = array();

  public $configArray = array();

  public $importTabTitle = ''; ## title of the tab for the import page

  public $needI18N = 0;

  ## set to true, if this plugin provides the WYSIWYG editor for the send page
  /* the plugin will then need to implement:
   * 
   * function editor($fieldname,$fieldvalue)
   * 
   * which returns the HTML for the editor.
   */
  public $editorProvider = false;

  public $tables = array(); // will hold tablename -> real table mapping

  /* array of pages in this plugin to add to the main menu
   * 
   * example format: 
   *      array(
   *          'page' => array('category' => 'subscribers'),
   *      )
   * 
   * valid categories are:
   * 
   * subscribers
   * campaigns
   * statistics
   * system
   * config
   * develop (will only show up in "dev" mode)
   * info
   * 
   */
   
  public $topMenuLinks = array();

  /* titles of pages in the plugin, this is used in the listing of the pages in the menu
   * 
   * example: 
   *    array(
   *      'page' => 'Title of page'
   *    )
   */

  public $pageTitles = array();

  /* dependency check
   * 
   * provides tests to determine whether this plugin can be used
   * example:
   *    array(
   *        'description of dependency' => condition for plugin to be enabled
   *    )
   */

  public function dependencyCheck()
  {
      return array(
        'phpList version' => version_compare(VERSION, '3.0.12') >= 0,
      );
  }

  /** 
   * name of this plugin
   */

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

  /* 
   * constructor
   * plugins should not run SQL queries as construction time
   * use the "activate" function instead
   * that way you can use processDBerror to handle DB errors
   */

  public function __construct()
  {
      $this->phplistplugin();
  }

  public function phplistplugin()
  {
        # constructor
    # Startup code, other objects might not be constructed yet
    #print ("<BR>Construct " . $this->name);
    ## try to prepend PLUGIN ROOTDIR, if necessary
    if (!is_dir($this->coderoot)) {
        $this->coderoot = PLUGIN_ROOTDIR.'/'.$this->coderoot;
    }
    ## always enable in dev mode
    if (!empty($GLOBALS['developer_email'])) {
        $this->enabled = 1;
    }
        $this->importTabTitle = $this->name;
        $this->system_root = dirname(__FILE__);
        $this->version = $this->getVersion();
    ## map table names
    $me = new ReflectionObject($this);
        foreach ($this->DBstruct as $table => $structure) {
            $this->tables[$table] = $GLOBALS['table_prefix'].$me->getName().'_'.$table;
        }
    }

    public function getVersion()
    {
        $version = array();
        $me = new ReflectionObject($this);

    ## interesting trick from Dokuwiki inc/infoutils.php
    if (is_dir(dirname($me->getFileName()).'/../.git')) {
        $version['type'] = 'Git';
        $version['date'] = 'unknown';

        $inventory = dirname($me->getFileName()).'/../.git/logs/HEAD';
        if (is_file($inventory)) {
            $sz = filesize($inventory);
            $seek = max(0, $sz - 2000); // read from back of the file
            $fh = fopen($inventory, 'rb');
            fseek($fh, $seek);
            $chunk = fread($fh, 2000);
            fclose($fh);
            $chunk = trim($chunk);
            $chunk = @array_pop(explode("\n", $chunk));   //last log line
            $chunk = @array_shift(explode("\t", $chunk)); //strip commit msg
            $chunk = explode(' ', $chunk);
            array_pop($chunk); //strip timezone
            $date = date('Y-m-d', array_pop($chunk));
            if ($date) {
                $version['date'] = $date;
            }
        }

        return $version['type'].' - '.$version['date'];
    }

        return $this->version;
    }

    public function initialise()
    {
        global $table_prefix;
        $me = new ReflectionObject($this);
        $plugin_initialised = getConfig(md5('plugin-'.$me->getName().'-initialised'));
        if (empty($plugin_initialised)) {
            foreach ($this->DBstruct as $table => $structure) {
                if (!Sql_Table_exists($table_prefix.$me->getName().'_'.$table)) {
                    #  print s('Creating table').' '.$table . '<br/>';
          Sql_Create_Table($table_prefix.$me->getName().'_'.$table, $structure);
                }
            }
            saveConfig(md5('plugin-'.$me->getName().'-initialised'), time(), 0);
        }
    }

    public function upgrade($previous)
    {
        return true;
    }

    public function activate()
    {
        # Startup code, all other objects are constructed
    # returns success or failure, false means we cannot start
    if (isset($this->settings)) {
        foreach ($this->settings as $item => $itemDetails) {
            $GLOBALS['default_config'][$item] = $itemDetails;
            $GLOBALS['default_config'][$item]['hidden'] = false;
        }
    }
    }

    public function displayAbout()
    {
        # Return html snippet to tell about coopyrights of used third party code.
    # author is already displayed
    return;
    }

    public function i18nLanguageDir()
    {
        # Return i18n Language Dir so that main page content can be extended
    return;
    }

    public function pageTitle($page)
    {
        if (isset($this->pageTitles[$page])) {
            return s($this->pageTitles[$page]);
        }

        return $this->name.' : '.$page;
    }

    public function pageTitleHover($page)
    {
        if (isset($this->pageTitleHover[$page])) {
            return s($this->pageTitleHover[$page]);
        }

        return $this->name.' : '.$page;
    }

  /** deleteSent - wipe DB entries marking a campaign sent for subscribers
   * this is used in DEV mode only.
   */
  public function deleteSent()
  {
  }

    public function writeConfig($name, $value)
    {
        #  write a value to the general config to be retrieved at a later stage
    # parameters: name -> name of the variable
    #             value -> value of the variablesiable, can be a scalar, array or object
    # returns success or failure    $store = '';
    if (is_object($value) || is_array($value)) {
        $store = 'SER:'.serialize($value);
    } else {
        $store = $value;
    }
        Sql_Query(sprintf('replace into %s set item = "%s-%s",value="%s",editable=0', $GLOBALS['tables']['config'], $this->name, addslashes($name), addslashes($store)));
    ## force refresh of config in session
    unset($_SESSION['config']);

        return 1;
    }

    public function getConfig($name)
    {
        # read a value from the general config to be retrieved at a later stage
    # parameters: name -> name of the variable
    # returns value

    if (isset($_SESSION['config'][$this->name.'-'.addslashes($name)])) {
        return $_SESSION['config'][$this->name.'-'.addslashes($name)];
    }

        $req = Sql_Fetch_Array_Query(sprintf('select value from  %s where item = "%s-%s"', $GLOBALS['tables']['config'], $this->name, addslashes($name)));
        $result = stripslashes($req[0]);
        if (!empty($result) && strpos('SER:', $result) == 1) {
            $result = substr($result, 4);
            $value = unserialize($result);
        } else {
            $value = $result;
        }
        $_SESSION['config'][$this->name.'-'.addslashes($name)] = $value;

        return $result;
    }

    public function displayConfig($name)
    {
        ## displayConfig
    # purpose: display input for a config variable in the backend
    # parameters:
    # name -> name of the config variable, as found in $this->configvars
    # return, HTML snippet of input to slot into a form
    $name = trim(strtolower($name));
        $name = preg_replace('/\W/', '', $name);
        $type = $this->configvars[$name][0];
        $label = $this->configvars[$name][1];
        $currentvalue = $this->getConfig($name);
        $html = '';
        switch ($type) {
      case 'attributeselect' :
        $html = sprintf('<select name="%s"><option value=""> --%s</option>', $name, $GLOBALS['I18N']->get('choose'));
        $req = Sql_Query(sprintf('select * from %s', $GLOBALS['tables']['attribute']));
        while ($row = Sql_Fetch_Array($req)) {
            $html .= sprintf('<option value="%d" %s>%s</option>', $row['id'], $row['id'] == $currentvalue ? 'selected="selected"' : '', substr(htmlspecialchars($row['name']), 0, 25));
        }
        $html .= '</select>';

        return $html;
      case 'radio' :
        $values = $this->configvars[$name][2];
        foreach ($values as $key => $label) {
            $html .= sprintf('<input type="radio" name="%s" value="%s" %s> %s', $name, $key, $currentvalue == $key ? 'checked="checked"' : '', $label);
        }

        return $html;
      case 'textarea' :
        $html = sprintf('<textarea name="%s" rows="10" cols="40" wrap="virtual">%s </textarea>', $name, htmlspecialchars($currentvalue));

        return $html;
      case 'text' :
      default :
        $html = sprintf('<input type="text" name="%s" value="%s" size="45">', $name, htmlspecialchars($currentvalue));

        return $html;
    }
    }

  ############################################################
  # Main interface hooks

  public function adminmenu()
  {
      return array(
      # page, description
      'main' => 'Main Page',
      'helloworld' => 'Hello World page',
    );
  }

  ############################################################
  # Frontend

  public function displaySubscriptionChoice($pageData, $userID = 0)
  {
      # return snippet for the Subscribe page
    return '';
  }

    public function validateSubscriptionPage($pageData)
    {
        return;
    }

    public function parseThankyou($pageid = 0, $userid = 0, $text = '')
    {
        # parse the text of the thankyou page
    # parameters:
    #  pageid -> id of the subscribe page
    #  userid -> id of the user
    #  text -> current text of the page
    # returns parsed text
    return $text;
    }

    public function subscriberConfirmation($subscribepageID, $userdata = array())
    {
    }

  ############################################################
  # Messages

  /* displayMessages
   *  obsolete
   * @return string
   */

  public function displayMessages($msg, &$status)
  {
      return '';
  }

  ############################################################
  # Message

  public function sendMessageTab($messageid = 0, $messagedata = array())
  {
      ## add a tab to the "Send a Message page" for options to be set in the plugin
    # parameters:
    #    messageid = ID of the message being displayed (should always be > 0)
    #    messagedata = associative array of all data from the db for this message
    # returns: HTML code to slot into the form to submit to the database
    return '';
  }

    public function sendMessageTabTitle($messageid = 0)
    {
        ## If adding a TAB to the Send a Message page, what is the TAB's name
    # parameters: none
    # returns: short title (less than about 10 characters)
    return '';
    }

    public function sendMessageTabInsertBefore()
    {
        ## If adding a TAB to the Send a Message page, try to insert the tab before the one returned here by title
        # parameters: none
        # returns: tab title to insert before
        return false;
    }

  /** sendMessageTabSave
   * called before a campaign is being saved. All data will be saved by phpList, 
   * but you can capture it here and save it elsewhere if you need to.
   * 
   * @param messageid integer: id of the campaign
   * @param messagedata array: associative array with all data
   */
  public function sendMessageTabSave($messageid = 0, $data = array())
  {
      return '';
  }

    public function sendFormats()
    {
        ## sendFormats();
    # parameters: none
    # returns array of "shorttag" => "description" of possible formats this plugin can provide
    # this will be listed in the "Send As" list of radio buttons, so that an editor can choose the format
    # prefix the shorttag with _ to suppress it from the send page (for internal use)
    return array();
    }

  /**
   * Called when viewing a message
   * The plugin can return a caption and a value that will be displayed as an additional row.
   * Each can include HTML and should be html encoded.
   * 
   * @param messageid integer: id of the campaign
   * @param messagedata array: associative array of message data
   *
   * @return array 2-element array, [0] caption, [1] value.
   *         or false if the plugin does not want to display a row.
   */
  public function viewMessage($messageid, array $messagedata)
  {
      return false;
  }

  /*
   * HelloWorld
   * just a simple check
   */

  public function HelloWorld($params)
  {
      print 'Hello to you from '.$this->name;
  }

  ############################################################
  # Processqueue

  /* canSend
   *
   * can this message be sent to this subscriber
   * if false is returned, the message will be identified as sent to the subscriber
   * and never tried again
   * 
   * @param $messagedata array of all message data
   * @param $userdata array of all user data
   * returns bool: true, send it, false don't send it
 */

  public function canSend($messagedata, $subscriberdata)
  {
      return true; //@@@
  }

  /* throttleSend
   *
   * can this message be sent to this subscriber
   * if false is returned, the message will be identified as failed
   * and re-tried in the next queue run
   * 
   * @param $messagedata array of all message data
   * @param $userdata array of all user data
   * returns bool: true, do not send it, false send it
 */

  public function throttleSend($messagedata, $subscriberdata)
  {
      return false;
  }

  /* throttleDomainMap
   *
   * Return the mapping of the domain for throttling
   * Only the first plugin that returns a non-false value will be used
   * 
   * @param string $domain domain to map
   * @return string mapping or false if not mapped
 */
  public function throttleDomainMap($domain)
  {
      return false;
  }

  /**
   * messageStatusLimitReached.
   *
   * @param $recentlySent integer 
   *
   * @return text to display in the status panel of the active message
   */
  public function messageStatusLimitReached($recentlySent)
  {
      return '';
  }

  /** messageStatus
   * @param int $id messageid
   * @param string $status message status
   *
   * @return possible additional text to display
   */
  public function messageStatus($id, $status)
  {
      return '';
  }

  /* 
   * Allows plugins to modify the fields of the loaded campaign.
   * Those fields will then be customised for each subscriber
   *
   * @param integer $messageid: ID of the message
   * @param array   &$message: associative array of message data
   * @return void
   */

  public function processPrecachedCampaign($messageid, array &$message)
  {
  }

  /** 
   * handle the moment that sending a campaign has finished.
   * this is after the campaign has been potentially duplicated for repetition.
   * 
   * @param int $messageid ID of the campaign
   * @param array messagedata associative array with the current message data
   */
  public function processSendingCampaignFinished($messageid, array $messagedata)
  {
  }

  /* 
   * parseOutgoingTextMessage
   * @param integer messageid: ID of the message
   * @param string  content: entire text content of a message going out
   * @param string  destination: destination email
   * @param array   userdata: associative array with data about user
   * @return string parsed content
   */

  public function parseOutgoingTextMessage($messageid, $content, $destination, $userdata = null)
  {
      return $content;
  }

  /* 
   * parseOutgoingHTMLMessage
   * @param integer messageid: ID of the message
   * @param string  content: entire text content of a message going out
   * @param string  destination: destination email
   * @param array   userdata: associative array with data about user
   * @return string parsed content
   */

  public function parseOutgoingHTMLMessage($messageid, $content, $destination, $userdata = null)
  {
      return $content;
  }

    public function getMessageAttachment($messageid, $content)
    {
        ###getMessageAttachment($messageid,$mail->Body);
    # parameters: $messageid,$messagecontent
    # returns array (
    #  'content' => Content of the attachment
    #  'filename' => name of the attached file
    #  'mimetype' => mimetype of the attachment
    # );
    return array();
    }

    public function mimeWrap($messageid, $body, $header, $contenttype, $destination)
    {
        ### mimeWrap
    # purpose: wrap the actual contents of the message in another MIME layer
    # Designed to ENCRYPT the fully expanded message just before sending
    # Designed to be called by phplistmailer
    # parameters:
    #   messageid: message being sent
    #   body: current body of message
    #   header: current header of message, except for the Content-Type
    #   contenttype: Content-Type of message
    #   destination: email that this message is going out to
    # returns array(newheader,newbody,newcontenttype)
    return array(
      $header,
      $body,
      $contenttype,
    );
    }

    public function setFinalDestinationEmail($messageid, $uservalues, $email)
    {
        ### setFinalDestinationEmail
    # purpose: change the actual recipient based on user Attribute values:
    # parameters:
    #   messageid: message being sent
    #   uservalues: array of "attributename" => "attributevalue" of all user attributes
    #   email: email that this message is current set to go out to
    # returns: email that it should go out to
    return $email;
    }

  /**
   * messageHeaders.
   *
   * return headers for the message to be added, as "key => val"
   *
   * @param object $mail
   *
   * @return array (headeritem => headervalue)
   */
  public function messageHeaders($mail)
  {
      return array();
  }

  /** parseFinalMessage
   * purpose: create the actual message, based on the text and html content as prepared by phplist
   * parameters:
   * sendformat: the send format chosen by the admin
   *    if this is not one of the sendFormats() set up for this plugin, return 0
   * htmlmessage: contents of HTML version of message
   * textmessage: contents of Text version of message
   * mail:  mail object that is going to be send.
   * 
   ### you can alter the outgoing mail by calling the required methods of the mail object
   * returns 1 if the message has been dealt with successfully and 0 if not
   */
  public function parseFinalMessage($sendformat, $htmlmessage, $textmessage, &$mail, $messageid)
  {
      return 0;
  }
  
  
  /**
    * 
    * process the success or failure of sending an email in $sendformat
    * 
    * @deprecated, no longer used since 3.2.4
    * 
    */

    public function processSuccesFailure($messageid, $sendformat, $userdata, $success = true)
    {
    }

  /**
   * processSendSuccess.
   * 
   * called when sending of this messageid to this subscriber was successful
   * 
   * @param messageid integer 
   * @param userdata array
   * @param isTest boolean, true when testmessage
   */
  public function processSendSuccess($messageid, $userdata, $isTest = false)
  {
  }

  /**
   * processSendFailed.
   * 
   * called when sending of this messageid to this subscriber failed
   * 
   * @param messageid integer 
   * @param userdata array
   * @param isTest boolean, true when testmessage
   */
  public function processSendFailed($messageid, $userdata, $isTest = false)
  {
  }

  /*
   * processSendStats
   * called when sending a campaign has finished
   * @param integer $sent - amount sent
   * @param integer $invalid - amount invalid
   * @param integer $failed_sent - amount failed
   * @param integer $unconfirmed - amount unconfirmed
   *
   * receive stats from phpList for further handling
   * @return null
   */

  public function processSendStats($sent = 0, $invalid = 0, $failed_sent = 0, $unconfirmed = 0, $counters = array())
  {
      return;
  }

  /* sendReport
   * @param string $subject
   * @param string $message
   * @return bool -> true if report has been processed and dealt with
   */

  public function sendReport($subject, $message)
  {
      return false;
  }

  /* sendError
   * @param string $subject
   * @param string $to
   * @param string $message
   * @return null
   */

  public function sendError($subject, $to = '', $message = '')
  {
  }

  /* processDBerror
   * @param integer $error number
   * @return null
   */

  public function processDBerror($errorid)
  {
  }

  /* importReport
   * called when a subscriber import has finished
   * @param string $report
   * @return null
   */

  public function importReport($report)
  {
  }

  /* processError
   * @param string msg
   */

  public function processError($msg)
  {
  }

  /* processQueueStart
   * called at the beginning of processQueue, after the process was locked
   * @param none
   * @return null
   */

  public function processQueueStart()
  {
  }

  /* allowProcessQueue
   * called at the beginning of processQueue
   * if this returns anything but "true" processing will be cancelled
   * @param none
   * @return bool
   */

  public function allowProcessQueue()
  {
      return true;
  }

  /* sendtestAllowed 
   * called when trying to send a test email
   * return false is sending a test email is not allowed
   * @param array messagedata 
   * @return bool;
   */

  public function sendTestAllowed($messagedata)
  {
      return true;
  }

  /*
   * campaignStarted
   * called when sending of a campaign starts
   * @param array messagedata - associative array with all data for campaign
   * @return null
   */

  public function campaignStarted($messagedata = array())
  {
  }

  /* allowMessageToBeQueued
   * called to verify that the message can be added to the queue
   * @param array messagedata - associative array with all data for campaign
   * @return empty string if allowed, or error string containing reason for not allowing
   */

  public function allowMessageToBeQueued($messagedata = array())
  {
      return '';
  }

  /* messageQueued
   * called when a message is placed in the queue
   * @param integer id message id
   * @return null
   */

  public function messageQueued($id)
  {
  }

  /* messageReQueued
   * called when a message is placed back in the queue
   * @param integer id message id
   * @return null
   */

  public function messageReQueued($id)
  {
  }

  /* messageQueueFinished
   * called when a sending of the queue has finished
   * @return null
   */

  public function messageQueueFinished()
  {
  }

  /* logEvent
   * @param string msg message to log
   * @return true when dealt with or false to pass on
   */

  public function logEvent($msg = '')
  {
      return false;
  }

  /* logout
   * called on logout
   * @param none
   * @return null
   */

  public function logout()
  {
      return '';
  }

  /**
   * cronJobs.
   *
   * @param null
   *
   * @return array of cronjobs to call for this plugin
   * 
   * eg
   * array(
   *    array( // cronJob1
   *     'page' => 'pagetocall',
   *     'frequency' => (int) minutes, // how often to call
   *    ),
   *    ...
   *    ...
   *    array( // cronJobN
   *     'page' => 'anotherpagetocall',
   *     'frequency' => (int) minutes, // how often to call
   *    ),
   * );
   */
  public function cronJobs()
  {
      return array();
  }


    /** 
     * displayUsers
     * 
     * add columns for this plugin to WebblerListing
     * Currently used in users.php and members.php
     * 
     * @param (array) user - associative array of user data
     * @param string $rowid - $element of the WebblerListing
     * @param WebblerListing $list - listing object to add to
     * 
     * @note, unclear if this actually works. Better not to use it.
     */

  public function displayUsers($user, $rowid, $list)
  {
  }

    /**
     * deleteUser
     * 
     * allow plugins to delete their data when deleting a user
     * 
     * @param integer $id the ID of the subscriber
     */

    public function deleteUser($id)
    {
    }

  ############################################################
  # List

  public function displayLists($list)
  {
      # purpose: return html snippet with plugin info for this list
    # Currently used in lists.php
    # 200711 Bas
    return;
  }

    public function displayEditList($list)
    {
        # purpose: return tablerows with list attributes for this list
    # Currently used in list.php
    # 200710 Bas
    return;
    }

    public function processEditList($id)
    {
        # purpose: process edit list page (usually save fields)
    # return false if failed
    # 200710 Bas
    return true;
    }

    public function processSpamComplaint($email, $date)
    {
    }

  ############################################################
  # Subscribe page

  public function displaySubscribePageEdit($subscribePageData)
  {
      # purpose: return tablerows with subscribepage options for this list
    # Currently used in spageedit.php
    # 200710 Bas
    return;
  }

  /*
   * unsubscribePage
   * @param email string - email to unsubscribe
   * @return true when processed, which will then override the standard subscribe page
   */

  public function unsubscribePage($email)
  {
      return false;
  }

    public function processSubscribePageEdit($subscribePageID)
    {
        # purpose: process selected subscribepage options for this list
    # return false if failed
    # Currently used in spageedit.php
    # 200710 Bas
    return true;
    }

    public function importContent()
    {
        # purpose: show content for this plugin on the import page
    return '';
    }

  /*
   * validateEmailAddress
   * @param string $emailaddress
   * @return bool true if email address is correct
   */

   public function validateEmailAddress($emailAddress)
   {
       return true;
   }

  ######################################
  # Static functions to manage the collection of plugins

  public static function isEnabled($pluginName)
  {
      # see if a plugin is enabled, static method so it can be called even if existance of plugin is unknown.
    return array_key_exists($pluginName, $GLOBALS['plugins']) && $GLOBALS['plugins'][$pluginName]->enabled;
  }
};
