<?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
public $dependencyFailure;
//@@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();
// Pages that can be called using the remote processing secret parameter,
// e.g. www.mysite.com/lists/admin/?pi=myplugin&page=mypage&secret=1234567890
public $remotePages = array();
public $configArray = array();
public $importTabTitle = ''; //# title of the tab for the import page
public $needI18N = 0;
// Configuration items for the Settings page
public $settings = [];
// File system path to the plugin file
public $origin = '';
/**
* 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;
/** set to true, if this plugin provides verification of the administrator login
* the plugin will then need to implement a variety of methods, check the docs.
*/
public $authProvider = false;
/**
* The priority of this plugin.
* phplist will activate() plugins in descending priority order
*/
public $priority = 10;
/**
* Holds tablename -> real table mapping
*/
public $tables = array();
/**
* 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();
/**
* dependencyFailure holds the reason the plugin cannot be initialised
*/
/**
* 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;
}
/**
* Allows a plugin to override the default check for a new version.
*
* @param array $pluginDetails
*
* @returns string|false|null new version, or false if update not available, or null to use the default update check
*
*/
public function checkForUpdate(array $pluginDetails)
{
}
/**
* Startup code, all other objects are constructed
* returns success or failure, false means we cannot start
*/
public function activate()
{
foreach ($this->settings as $item => $itemDetails) {
$GLOBALS['default_config'][$item] = $itemDetails;
$GLOBALS['default_config'][$item]['hidden'] = false;
}
}
/**
* Return html snippet to tell about copyrights of used third party code.
* @note Plugin author is already displayed elsewhere
*/
public function displayAbout()
{
return;
}
/**
* Return i18n Language Dir so that main page content can be extended
*/
public function i18nLanguageDir()
{
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()
{
}
/**
* 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
*/
public function writeConfig($name, $value)
{
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'][$this->name.'-'.addslashes($name)]);
return 1;
}
/**
* read a value from the general config to be retrieved at a later stage
* parameters: name -> name of the variable
* returns value
*/
public function getConfig($name)
{
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;
}
/**
* 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
*/
public function displayConfig($name)
{
$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
/**
* return snippet for the Subscribe page
*/
public function displaySubscriptionChoice($pageData, $userID = 0)
{
return '';
}
public function validateSubscriptionPage($pageData)
{
return;
}
/**
* 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
*/
public function parseThankyou($pageid = 0, $userid = 0, $text = '')
{
return $text;
}
public function subscriberConfirmation($subscribepageID, $userdata = array())
{
}
//###########################################################
// Messages
/**
* displayMessages
* obsolete
* @return string
*/
public function displayMessages($msg, &$status)
{
return '';
}
//###########################################################
// Message
/**
* add a tab to the "Send a Message page" for options to be set in the plugin
* @param messageid ID of the message being displayed (should always be > 0)
* @param messagedata associative array of all data from the db for this message
* @return HTML code to slot into the form to submit to the database
*/
public function sendMessageTab($messageid = 0, $messagedata = array())
{
return '';
}
/**
* If adding a TAB to the Send a Message page, what is the TAB's name
* parameters: none
* @return short title (less than about 10 characters)
*/
public function sendMessageTabTitle($messageid = 0)
{
return '';
}
/**
* 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
*/
public function sendMessageTabInsertBefore()
{
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)
{
echo '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;
}
/**
* getMessageAttachment($messageid,$mail->Body);
* parameters: $messageid,$messagecontent
* @return array (
* 'content' => Content of the attachment
* 'filename' => name of the attached file
* 'mimetype' => mimetype of the attachment
* );
*/
public function getMessageAttachment($messageid, $content)
{
return array();
}
/**
* 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)
*/
public function mimeWrap($messageid, $body, $header, $contenttype, $destination)
{
return array(
$header,
$body,
$contenttype,
);
}
/**
* 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
* @returN email that it should go out to
*/
public function setFinalDestinationEmail($messageid, $uservalues, $email)
{
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;
}
/**
* login
* called on login
* @param none
* @return true when user is successfully logged by plugin, false instead
*/
public function login()
{
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 int $id the ID of the subscriber
*/
public function deleteUser($id)
{
}
//###########################################################
// List
/**
* purpose: return html snippet with plugin info for this list
* Currently used in lists.php
* 200711 Bas
*/
public function displayLists($list)
{
return;
}
/**
* purpose: return tablerows with list attributes for this list
* Currently used in list.php
* 200710 Bas
*/
public function displayEditList($list)
{
return;
}
/**
* purpose: process edit list page (usually save fields)
* return false if failed
* 200710 Bas
*/
public function processEditList($id)
{
return true;
}
public function processSpamComplaint($email, $date)
{
}
//###########################################################
// Subscribe page
/**
* purpose: return tablerows with subscribepage options for this list
* Currently used in spageedit.php
* 200710 Bas
*/
public function displaySubscribePageEdit($subscribePageData)
{
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;
}
/**
* purpose: process selected subscribepage options for this list
* return false if failed
* Currently used in spageedit.php
* 200710 Bas
*/
public function processSubscribePageEdit($subscribePageID)
{
return true;
}
/**
* purpose: show content for this plugin on the import page
*/
public function importContent()
{
return '';
}
/**
* validateEmailAddress
* @param string $emailaddress
* @return bool true if email address is correct
*/
public function validateEmailAddress($emailAddress)
{
return true;
}
/**
* isBlacklistedEmail
* @param string $emailaddress
* @return bool true if email address should is considered blacklisted
*/
public function isBlackListedEmail($email = '')
{
return false;
}
/**
* Allow additional processing of a link click.
*
* @param string $msgtype H or T
* @param int $fwdid id of row in linktrack_forward table
* @param int $messageid id of row in message table
* @param int $userid id of row in user table
* @param string $url url of row in linktrack_forward table
*/
public function linkClick($msgtype, $fwdid, $messageid, $userid, $url)
{
}
//#####################################
// Static functions to manage the collection of plugins
/**
* see if a plugin is enabled, static method so it can be called even if existance of plugin is unknown.
*/
public static function isEnabled($pluginName)
{
return array_key_exists($pluginName, $GLOBALS['plugins']) && $GLOBALS['plugins'][$pluginName]->enabled;
}
}