diff --git a/README.md b/README.md index d69929a..e3e1599 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,10 @@ Email sender registrer for WordPress ## Changelog +### 1.4.5 +* Added: optional disable for cron file logs via `BEA_SENDER_FILE_LOGGING` or the `bea_sender_file_logging_enabled` filter (`bea_sender_is_file_logging_enabled()` in the main plugin file). +* Changed: `Bea_Log` accepts a `$write_enabled` flag; when rotation runs, rotated files may be compressed to `.zip` when `ZipArchive` is available. + ### 1.4.4 * Changed: use native `wp_mail` function to send emails diff --git a/bea_sender.php b/bea_sender.php index 88a52a7..1a23355 100644 --- a/bea_sender.php +++ b/bea_sender.php @@ -1,20 +1,20 @@ bea_s_campaigns = $wpdb->prefix.'bea_s_campaigns'; -$wpdb->bea_s_receivers = $wpdb->prefix.'bea_s_receivers'; -$wpdb->bea_s_re_ca = $wpdb->prefix.'bea_s_re_ca'; -$wpdb->bea_s_contents = $wpdb->prefix.'bea_s_contents'; -$wpdb->bea_s_attachments = $wpdb->prefix.'bea_s_attachments'; +$wpdb->bea_s_campaigns = $wpdb->prefix . 'bea_s_campaigns'; +$wpdb->bea_s_receivers = $wpdb->prefix . 'bea_s_receivers'; +$wpdb->bea_s_re_ca = $wpdb->prefix . 'bea_s_re_ca'; +$wpdb->bea_s_contents = $wpdb->prefix . 'bea_s_contents'; +$wpdb->bea_s_attachments = $wpdb->prefix . 'bea_s_attachments'; // Add tables to the index of tables for WordPress $wpdb->tables[] = 'bea_s_campaigns'; @@ -22,68 +22,108 @@ $wpdb->tables[] = 'bea_s_re_ca'; $wpdb->tables[] = 'bea_s_contents'; -define('BEA_SENDER_URL', plugin_dir_url ( __FILE__ )); -define('BEA_SENDER_DIR', plugin_dir_path( __FILE__ )); -define( 'BEA_SENDER_VER', '1.4.4' ); +define( 'BEA_SENDER_URL', plugin_dir_url( __FILE__ ) ); +define( 'BEA_SENDER_DIR', plugin_dir_path( __FILE__ ) ); +define( 'BEA_SENDER_VER', '1.4.5' ); define( 'BEA_SENDER_PPP', '10' ); define( 'BEA_SENDER_DEFAULT_COUNTER', 100 ); define( 'BEA_SENDER_OPTION_NAME', 'bea_s-main' ); define( 'BEA_SENDER_EXPORT_OPTION_NAME', 'bea_s-export' ); +/** + * Whether Sender cron jobs write Bea_Log files under WP_CONTENT_DIR. + * + * Disable with `define( 'BEA_SENDER_FILE_LOGGING', false );` in wp-config.php, + * or `add_filter( 'bea_sender_file_logging_enabled', '__return_false' );`. + * + * @return bool + */ +function bea_sender_is_file_logging_enabled() { + if ( defined( 'BEA_SENDER_FILE_LOGGING' ) ) { + return (bool) BEA_SENDER_FILE_LOGGING; + } + + return (bool) apply_filters( 'bea_sender_file_logging_enabled', true ); +} + // Function for easy load files -function _bea_sender_load_files($dir, $files, $prefix = '') { - foreach ($files as $file) { - if ( is_file($dir . $prefix . $file . ".php") ) { - require_once($dir . $prefix . $file . ".php"); +function _bea_sender_load_files( $dir, $files, $prefix = '' ) { + foreach ( $files as $file ) { + if ( is_file( $dir . $prefix . $file . '.php' ) ) { + require_once $dir . $prefix . $file . '.php'; } } } // Utils -_bea_sender_load_files( BEA_SENDER_DIR . 'inc/utils/', array( - 'campaign', - 'content', - 'attachment', - 'receiver', - 'sender', - 'bounce.email', - 'export', - 'receivers' -), 'class.' ); +_bea_sender_load_files( + BEA_SENDER_DIR . 'inc/utils/', + array( + 'campaign', + 'content', + 'attachment', + 'receiver', + 'sender', + 'bounce.email', + 'export', + 'receivers', + ), + 'class.' +); // Admin -if( is_admin( ) ) { +if ( is_admin() ) { - if( !class_exists( 'WP_List_Table' ) ) { - require (ABSPATH.'/wp-admin/includes/class-wp-list-table.php'); + if ( ! class_exists( 'WP_List_Table' ) ) { + require ABSPATH . '/wp-admin/includes/class-wp-list-table.php'; } _bea_sender_load_files( BEA_SENDER_DIR . 'inc/', array( 'admin' ), 'class.' ); - _bea_sender_load_files( BEA_SENDER_DIR . 'inc/utils/', array( - 'admin.table', - 'admin.table.single', - 'admin.bounce.tools' - ), 'class.' ); + _bea_sender_load_files( + BEA_SENDER_DIR . 'inc/utils/', + array( + 'admin.table', + 'admin.table.single', + 'admin.bounce.tools', + ), + 'class.' + ); } // Inc -_bea_sender_load_files( BEA_SENDER_DIR . 'inc/', array( - 'client', - 'cron', -), 'class.' ); +_bea_sender_load_files( + BEA_SENDER_DIR . 'inc/', + array( + 'client', + 'cron', + ), + 'class.' +); // Libs -_bea_sender_load_files( BEA_SENDER_DIR . 'inc/libs/wordpress-settings-api/', array( - 'settings-api', -), 'class.' ); - -_bea_sender_load_files( BEA_SENDER_DIR . 'inc/libs/php-bounce/', array( - 'phpmailer-bmh', -), 'class.' ); - -_bea_sender_load_files( BEA_SENDER_DIR . 'inc/libs/', array( - 'log', -), 'class-' ); +_bea_sender_load_files( + BEA_SENDER_DIR . 'inc/libs/wordpress-settings-api/', + array( + 'settings-api', + ), + 'class.' +); + +_bea_sender_load_files( + BEA_SENDER_DIR . 'inc/libs/php-bounce/', + array( + 'phpmailer-bmh', + ), + 'class.' +); + +_bea_sender_load_files( + BEA_SENDER_DIR . 'inc/libs/', + array( + 'log', + ), + 'class-' +); // Create tables on activation register_activation_hook( __FILE__, array( 'Bea_Sender_Client', 'activation' ) ); @@ -91,22 +131,26 @@ function _bea_sender_load_files($dir, $files, $prefix = '') { add_action( 'plugins_loaded', 'Bea_sender_init' ); -function Bea_sender_init( ) { +function Bea_sender_init() { global $bea_sender, $bea_send_counter; - $bea_send_counter = apply_filters( 'bea_send_counter', BEA_SENDER_DEFAULT_COUNTER ); - $bea_sender['client'] = new Bea_Sender_Client( ); + $bea_send_counter = apply_filters( 'bea_send_counter', BEA_SENDER_DEFAULT_COUNTER ); + $bea_sender['client'] = new Bea_Sender_Client(); new Bea_Sender_Cron(); - if( is_admin( ) ) { - $bea_sender['admin'] = new Bea_Sender_Admin( ); - $bea_sender['admin_bounce_tools'] = new BEA_Admin_Settings_Main( ); + if ( is_admin() ) { + $bea_sender['admin'] = new Bea_Sender_Admin(); + $bea_sender['admin_bounce_tools'] = new BEA_Admin_Settings_Main(); } if ( defined( 'WP_CLI' ) ) { - _bea_sender_load_files( BEA_SENDER_DIR . 'inc/cli/', array( - 'sender-command', - ), 'class.' ); + _bea_sender_load_files( + BEA_SENDER_DIR . 'inc/cli/', + array( + 'sender-command', + ), + 'class.' + ); \WP_CLI::add_command( 'bea-sender-mail', Bea_Sender_Command::class ); } diff --git a/inc/libs/class-log.php b/inc/libs/class-log.php index e8d58dc..53ae647 100644 --- a/inc/libs/class-log.php +++ b/inc/libs/class-log.php @@ -1,5 +1,5 @@ file_path = $file_path; // File extention - if( isset( $file_extention ) ) { + if ( isset( $file_extention ) ) { $this->file_extention = $file_extention; } // Retention size - if( isset( $retention_size ) && !empty( $retention_size ) && (int)$retention_size > 0 ) { + if ( isset( $retention_size ) && ! empty( $retention_size ) && (int) $retention_size > 0 ) { $this->retention_size = $retention_size; } - $this->is_configured = true; + $this->is_configured = (bool) $write_enabled; } /** @@ -45,36 +51,72 @@ function __construct( $file_path, $file_extention = '.log' , $retention_size = ' * @param $message : the message to log * @return boolean : true on success * @author Nicolas Juen - * */ public function log_this( $message, $type = '' ) { - if( $this->is_configured === false ) { + if ( ! $this->is_configured ) { return false; } // Add the type - if( empty( $type ) ) { + if ( empty( $type ) ) { $type = self::gravity_7; } // Make the file path - $file_path = $this->file_path.$this->file_extention; + $file_path = $this->file_path . $this->file_extention; // If the file exists - if( is_file( $file_path ) ) { + if ( is_file( $file_path ) ) { // Get file size $size = filesize( $file_path ); // Check size - if( $size > $this->retention_size ) { - // Rename the file - rename( $file_path, $this->file_path.'-'.date( 'Y-m-d-H-i-s' ).$this->file_extention ); + if ( $size > $this->retention_size ) { + $rotated_path = $this->file_path . '-' . date( 'Y-m-d-H-i-s' ) . $this->file_extention; + if ( rename( $file_path, $rotated_path ) ) { + $this->maybe_zip_rotated_file( $rotated_path ); + } } } // Log the error - error_log( date('[d-m-Y H:i:s]').'['.$type.'] '.$message."\n", 3, $file_path ); + error_log( date( '[d-m-Y H:i:s]' ) . '[' . $type . '] ' . $message . "\n", 3, $file_path ); return true; } + + /** + * Compress a rotated log into a sibling .zip and remove the plain file on success. + * + * @param string $rotated_path Absolute path to the rotated log file. + */ + private function maybe_zip_rotated_file( $rotated_path ) { + if ( ! class_exists( 'ZipArchive' ) ) { + return; + } + + if ( ! is_string( $rotated_path ) || '' === $rotated_path || ! is_file( $rotated_path ) ) { + return; + } + + $zip_path = $rotated_path . '.zip'; + $zip = new ZipArchive(); + + if ( true !== $zip->open( $zip_path, ZipArchive::CREATE | ZipArchive::OVERWRITE ) ) { + return; + } + + $zip->addFile( $rotated_path, basename( $rotated_path ) ); + + if ( ! $zip->close() ) { + if ( is_file( $zip_path ) ) { + unlink( $zip_path ); + } + return; + } + + if ( is_file( $zip_path ) && filesize( $zip_path ) > 0 ) { + unlink( $rotated_path ); + } + } } -} \ No newline at end of file +} diff --git a/inc/utils/class.bounce.email.php b/inc/utils/class.bounce.email.php index 85e0cc7..4730112 100644 --- a/inc/utils/class.bounce.email.php +++ b/inc/utils/class.bounce.email.php @@ -5,7 +5,7 @@ class Bea_Sender_BounceEmail { private $bmh; - private static $locked = false; + private static $locked = false; private static $lock_file = '/lock-bounce.lock'; /** @@ -17,13 +17,13 @@ class Bea_Sender_BounceEmail { public function __construct() { $this->bmh = new PHPMailerBMH(); - $this->log = new Bea_Log( WP_CONTENT_DIR.'/bea-sender-bounce-cron' ); + $this->log = new Bea_Log( WP_CONTENT_DIR . '/bea-sender-bounce-cron', '.log', '', bea_sender_is_file_logging_enabled() ); } public function bounce_init() { // If we are already locked, stop the process - if ( !self::lock() ) { + if ( ! self::lock() ) { return false; } @@ -45,7 +45,7 @@ public function bounce_init() { 'movehard' => false, 'hardmailbox' => 'INBOX.hard', 'movesoft' => false, - 'softmailbox' => 'INBOX.soft' + 'softmailbox' => 'INBOX.soft', ); // Parse the args @@ -62,7 +62,6 @@ public function bounce_init() { return false; } - // BHM setup $this->bmh->mailhost = $host['mailhost']; // your mail server @@ -86,7 +85,7 @@ public function bounce_init() { // default is false $this->bmh->softMailbox = $host['softmailbox']; // default is 'INBOX.soft' - NOTE: must start with 'INBOX.' - //$this->bmh->deleteMsgDate = '2009-01-05'; // format must be as + // $this->bmh->deleteMsgDate = '2009-01-05'; // format must be as // 'yyyy-mm-dd' // Log @@ -95,7 +94,7 @@ public function bounce_init() { $this->bmh->MailboxOpen(); $this->bmh->actionFunction = array( $this, - 'callback_action' + 'callback_action', ); $this->log->log_this( 'Process mailbox' ); @@ -106,11 +105,13 @@ public function bounce_init() { self::unlock(); } - /* This is a sample callback function for PHPMailer-BMH (Bounce Mail Handler). + /* + This is a sample callback function for PHPMailer-BMH (Bounce Mail Handler). * This callback function will echo the results of the BMH processing. */ - /* Callback (action) function + /* + Callback (action) function * @param int $msgnum the message number returned by Bounce Mail * Handler * @param string $bounce_type the bounce type: @@ -143,17 +144,21 @@ public function callback_action( $msgnum, $bounce_type, $email, $subject, $xhead if ( $receiver_result ) { $this->log->log_this( sprintf( '%s found on database', $email ) ); $wpdb->update( - $wpdb->bea_s_receivers, array( + $wpdb->bea_s_receivers, + array( 'current_status' => 'invalid', 'bounce_cat' => $rule_cat, 'bounce_type' => $bounce_type, - 'bounce_no' => $rule_no - ), array( 'email' => $email ), array( + 'bounce_no' => $rule_no, + ), + array( 'email' => $email ), + array( + '%s', '%s', '%s', '%s', - '%s' - ), array( '%s' ) + ), + array( '%s' ) ); } @@ -164,12 +169,16 @@ public function callback_action( $msgnum, $bounce_type, $email, $subject, $xhead if ( $re_ca__result ) { $this->log->log_this( sprintf( '%s found on the _re_ca table', $email ) ); $wpdb->update( - $wpdb->bea_s_re_ca, array( 'current_status' => 'bounced' ), array( + $wpdb->bea_s_re_ca, + array( 'current_status' => 'bounced' ), + array( 'id_campaign' => $xheader, - 'id_receiver' => $receiver_id - ), array( '%s' ), array( + 'id_receiver' => $receiver_id, + ), + array( '%s' ), + array( + '%d', '%d', - '%d' ) ); } @@ -202,7 +211,7 @@ private static function lock() { } // If we are already locked, stop now - if ( fopen( WP_CONTENT_DIR . self::$lock_file, "x" ) ) { + if ( fopen( WP_CONTENT_DIR . self::$lock_file, 'x' ) ) { self::$locked = true; return true; } @@ -224,5 +233,4 @@ private static function unlock() { // Unlock the file self::$locked = false; } - } diff --git a/inc/utils/class.sender.php b/inc/utils/class.sender.php index 94ec13c..0d76f24 100644 --- a/inc/utils/class.sender.php +++ b/inc/utils/class.sender.php @@ -1,8 +1,8 @@ log = new Bea_Log( WP_CONTENT_DIR.'/bea-sender-email-cron' ); + function __construct() { + $this->log = new Bea_Log( WP_CONTENT_DIR . '/bea-sender-email-cron', '.log', '', bea_sender_is_file_logging_enabled() ); } /** * @return array|bool * @author Nicolas Juen */ - public function init( ) { - if( !self::lock() ) { + public function init() { + if ( ! self::lock() ) { return false; } $this->log->log_this( 'Start sending' ); - if( !$this->getCampaigns( ) ) { + if ( ! $this->getCampaigns() ) { $this->log->log_this( 'No campaign to send, exit.' ); // Unlock the file self::unlock(); } - return $this->sendCampaigns( ); + return $this->sendCampaigns(); } /** * @return bool * @author Nicolas Juen */ - private function getCampaigns( ) { + private function getCampaigns() { /* @var $wpdb wpdb */ global $wpdb; - $cols = $wpdb->get_col( "SELECT id FROM $wpdb->bea_s_campaigns WHERE current_status IN( '".implode( "','", Bea_Sender_Campaign::getAuthStatuses( ) )."' ) AND scheduled_from <= '".current_time( 'mysql' )."' ORDER BY add_date ASC" ); + $cols = $wpdb->get_col( "SELECT id FROM $wpdb->bea_s_campaigns WHERE current_status IN( '" . implode( "','", Bea_Sender_Campaign::getAuthStatuses() ) . "' ) AND scheduled_from <= '" . current_time( 'mysql' ) . "' ORDER BY add_date ASC" ); $this->log->log_this( sprintf( '%d campaigns to send', count( $cols ) ) ); - if( !isset( $cols ) || empty( $cols ) ) { - $this->campaigns = array( ); + if ( ! isset( $cols ) || empty( $cols ) ) { + $this->campaigns = array(); return false; } $this->campaigns = array_map( 'absint', $cols ); @@ -59,19 +59,19 @@ private function getCampaigns( ) { * @return array * @author Nicolas Juen */ - private function sendCampaigns( ) { - $results = array( ); + private function sendCampaigns() { + $results = array(); do_action( 'bea_sender_before_send' ); - foreach( $this->campaigns as $campaign_id ) { + foreach ( $this->campaigns as $campaign_id ) { $campaign = new Bea_Sender_Campaign( $campaign_id ); - if( $campaign->isData( ) !== true ) { + if ( $campaign->isData() !== true ) { continue; } $this->log->log_this( sprintf( 'Send %s campaign', $campaign_id ) ); do_action( 'bea_sender_before_send_campaign', $campaign_id, $campaign ); // Make the sending - $results[] = $campaign->makeSend( ); + $results[] = $campaign->makeSend(); do_action( 'bea_sender_after_send_campaign', $campaign_id, $campaign ); } @@ -80,8 +80,8 @@ private function sendCampaigns( ) { // Unlock the file self::unlock(); - $this->log->log_this( 'End campaigns '.var_export( $results, true ) ); - + $this->log->log_this( 'End campaigns ' . var_export( $results, true ) ); + return $results; } @@ -101,12 +101,12 @@ private static function lock() { clearstatcache(); - $lock_file_path = WP_CONTENT_DIR.self::$lock_file; - - if( is_file( $lock_file_path ) ) { + $lock_file_path = WP_CONTENT_DIR . self::$lock_file; + + if ( is_file( $lock_file_path ) ) { // Check if lock file is older than 24 hours (86400 seconds) $file_age = time() - filemtime( $lock_file_path ); - if( $file_age > 86400 ) { + if ( $file_age > 86400 ) { // File is too old, remove it to avoid stale locks unlink( $lock_file_path ); clearstatcache(); // Clear cache after file deletion @@ -116,14 +116,14 @@ private static function lock() { return false; } } - + // If we are already locked, stop now - if( $lock_file_handle = fopen( $lock_file_path, "x" ) ) { - fclose($lock_file_handle); + if ( $lock_file_handle = fopen( $lock_file_path, 'x' ) ) { + fclose( $lock_file_handle ); self::$locked = true; return true; } - + return false; } @@ -132,11 +132,11 @@ private static function lock() { */ private static function unlock() { // Remove the file if needed - if( is_file( WP_CONTENT_DIR.self::$lock_file ) ) { - unlink( WP_CONTENT_DIR.self::$lock_file ); + if ( is_file( WP_CONTENT_DIR . self::$lock_file ) ) { + unlink( WP_CONTENT_DIR . self::$lock_file ); } - + // Unlock the file self::$locked = false; - } + } }