diff --git a/cronjobs/auto_payout.php b/cronjobs/auto_payout.php index 36c8a873..63b06d08 100755 --- a/cronjobs/auto_payout.php +++ b/cronjobs/auto_payout.php @@ -61,10 +61,19 @@ if (! empty($users)) { // Create transaction record if ($transaction->addTransaction($aUserData['id'], $dBalance, 'Debit_AP', NULL, $aUserData['coin_address'], 0.1)) { - verbose("OK\n"); + // Notify user via mail + $aMailData['email'] = $user->getUserEmail($user->getUserName($aUserData['id'])); + $aMailData['subject'] = 'Auto Payout Completed'; + $aMailData['amount'] = $dBalance; + if (!$notification->sendNotification($aUserData['id'], 'auto_payout', $aMailData)) { + verbose("NOTIFY FAILED\n"); + } else { + verbose("OK\n"); + } } else { verbose("FAILED\n"); } + } else { verbose("SKIPPED\n"); } diff --git a/cronjobs/findblock.php b/cronjobs/findblock.php index 2f978eed..ead4b61c 100755 --- a/cronjobs/findblock.php +++ b/cronjobs/findblock.php @@ -112,6 +112,16 @@ foreach ($aAllBlocks as $iIndex => $aBlock) { . $strStatus . "\n" ); + + // Notify users + $aAccounts = $notification->getNotificationAccountIdByType('new_block'); + foreach ($aAccounts as $account_id) { + $aMailData['height'] = $aBlock['height']; + $aMailData['subject'] = 'New Block'; + $aMailData['email'] = $user->getUserEmail($user->getUserName($account_id)); + $aMailData['shares'] = $iRoundShares; + $notification->sendNotification($account_id, 'new_block', $aMailData); + } } } ?> diff --git a/cronjobs/notifications.php b/cronjobs/notifications.php index aa2bf8a9..a6b8b101 100755 --- a/cronjobs/notifications.php +++ b/cronjobs/notifications.php @@ -32,20 +32,13 @@ if (empty($aWorkers)) { $aData['username'] = $user->getUserName($aWorker['account_id']); $aData['subject'] = 'IDLE Worker : ' . $aWorker['username']; $aData['email'] = $user->getUserEmail($aData['username']); - if ( $notification->isNotified($aData) ) { - verbose("Worker already notified\n"); - continue; - } - if ($notification->addNotification('idle_worker', $aData) && $notification->sendMail($aData['email'], 'idle_worker', $aData)) { - verbose ("Notified " . $aData['email'] . " for IDLE worker " . $aWorker['username'] . "\n"); - } else { - verbose("Unable to send notification: " . $notification->getError() . "\n"); - } + if (!$notification->sendNotification($aWorker['account_id'], 'idle_worker', $aData)) + verbose($notification->getError() . "\n"); } } // We notified, lets check which recovered -$aNotifications = $notification->getAllActive(); +$aNotifications = $notification->getAllActive('idle_worker'); if (!empty($aNotifications)) { foreach ($aNotifications as $aNotification) { $aData = json_decode($aNotification['data'], true); diff --git a/public/include/classes/mail.class.php b/public/include/classes/mail.class.php index 2800500c..e34423c5 100644 --- a/public/include/classes/mail.class.php +++ b/public/include/classes/mail.class.php @@ -16,6 +16,9 @@ class Mail { public function setSmarty($smarty) { $this->smarty = $smarty; } + public function setUser($user) { + $this->user = $user; + } public function setConfig($config) { $this->config = $config; } @@ -35,14 +38,14 @@ class Mail { return true; } - public function sendMail($email, $template, $aData) { + public function sendMail($template, $aData) { $this->smarty->assign('WEBSITENAME', $this->config['website']['name']); $this->smarty->assign('SUBJECT', $aData['subject']); $this->smarty->assign('DATA', $aData); $headers = 'From: Website Administration <' . $this->config['website']['email'] . ">\n"; $headers .= "MIME-Version: 1.0\n"; $headers .= "Content-Type: text/html; charset=ISO-8859-1\r\n"; - if (mail($email, + if (mail($aData['email'], $this->smarty->fetch(BASEPATH . 'templates/mail/subject.tpl'), $this->smarty->fetch(BASEPATH . 'templates/mail/' . $template . '.tpl'), $headers)) { @@ -61,5 +64,4 @@ $mail->setDebug($debug); $mail->setMysql($mysqli); $mail->setSmarty($smarty); $mail->setConfig($config); - ?> diff --git a/public/include/classes/notification.class.php b/public/include/classes/notification.class.php index 983db437..b38bac2e 100644 --- a/public/include/classes/notification.class.php +++ b/public/include/classes/notification.class.php @@ -6,6 +6,7 @@ if (!defined('SECURITY')) class Notification extends Mail { var $table = 'notifications'; + var $tableSettings = 'notification_settings'; public function setInactive($id) { $field = array( @@ -22,9 +23,10 @@ class Notification extends Mail { * @param field string Field to update * @return bool **/ - private function updateSingle($id, $field) { + private function updateSingle($id, $field, $table='') { + if (empty($table)) $table = $this->table; $this->debug->append("STA " . __METHOD__, 4); - $stmt = $this->mysqli->prepare("UPDATE $this->table SET " . $field['name'] . " = ? WHERE id = ? LIMIT 1"); + $stmt = $this->mysqli->prepare("UPDATE $table SET " . $field['name'] . " = ? WHERE id = ? LIMIT 1"); if ($this->checkStmt($stmt) && $stmt->bind_param($field['type'].'i', $field['value'], $id) && $stmt->execute()) return true; $this->debug->append("Unable to update " . $field['name'] . " with " . $field['value'] . " for ID $id"); @@ -35,22 +37,23 @@ class Notification extends Mail { * so we can avoid duplicate entries **/ public function isNotified($aData) { + $this->debug->append("STA " . __METHOD__, 4); $data = json_encode($aData); $stmt = $this->mysqli->prepare("SELECT id FROM $this->table WHERE data = ? AND active = 1 LIMIT 1"); if ($stmt && $stmt->bind_param('s', $data) && $stmt->execute() && $stmt->store_result() && $stmt->num_rows == 1) return true; // Catchall // Does not seem to have a notification set - $this->setErrorMessage("Unable to run query: " . $this->mysqli->error); return false; } /** * Get all active notifications **/ - public function getAllActive() { - $stmt =$this->mysqli->prepare("SELECT id, data FROM $this->table WHERE active = 1 LIMIT 1"); - if ($stmt && $stmt->execute() && $result = $stmt->get_result()) + public function getAllActive($strType) { + $this->debug->append("STA " . __METHOD__, 4); + $stmt =$this->mysqli->prepare("SELECT id, data FROM $this->table WHERE active = 1 AND type = ?"); + if ($stmt && $stmt->bind_param('s', $strType) && $stmt->execute() && $result = $stmt->get_result()) return $result->fetch_all(MYSQLI_ASSOC); // Catchall return false; @@ -61,14 +64,120 @@ class Notification extends Mail { * @param type string Type of the notification * @return bool **/ - public function addNotification($type, $data) { + public function addNotification($account_id, $type, $data) { + $this->debug->append("STA " . __METHOD__, 4); // Store notification data as json $data = json_encode($data); - $stmt = $this->mysqli->prepare("INSERT INTO $this->table (type, data, active) VALUES (?,?,1)"); - if ($stmt && $stmt->bind_param('ss', $type, $data) && $stmt->execute()) + $stmt = $this->mysqli->prepare("INSERT INTO $this->table (account_id, type, data, active) VALUES (?, ?,?,1)"); + if ($stmt && $stmt->bind_param('iss', $account_id, $type, $data) && $stmt->execute()) return true; $this->debug->append("Failed to add notification for $type with $data: " . $this->mysqli->error); - $this->setErrorMessage("Unable to add new notification"); + $this->setErrorMessage("Unable to add new notification " . $this->mysqli->error); + return false; + } + + /** + * Fetch notifications for a user account + * @param id int Account ID + * @return array Notification data + **/ + public function getNofifications($account_id) { + $this->debug->append("STA " . __METHOD__, 4); + $stmt = $this->mysqli->prepare("SELECT * FROM $this->table WHERE account_id = ? ORDER BY time DESC"); + if ($stmt && $stmt->bind_param('i', $account_id) && $stmt->execute() && $result = $stmt->get_result()) + return $result->fetch_all(MYSQLI_ASSOC); + // Catchall + return false; + } + + /** + * Fetch notification settings for user account + * @param id int Account ID + * @return array Notification settings + **/ + public function getNotificationSettings($account_id) { + $this->debug->append("STA " . __METHOD__, 4); + $stmt = $this->mysqli->prepare("SELECT * FROM $this->tableSettings WHERE account_id = ?"); + if ($stmt && $stmt->bind_param('i', $account_id) && $stmt->execute() && $result = $stmt->get_result()) { + while ($row = $result->fetch_assoc()) { + $aData[$row['type']] = $row['active']; + } + return $aData; + } + // Catchall + return false; + } + + /** + * Get all accounts that wish to receive a specific notification + * @param strType string Notification type + * @return data array User Accounts + **/ + public function getNotificationAccountIdByType($strType) { + $this->debug->append("STA " . __METHOD__, 4); + $stmt = $this->mysqli->prepare("SELECT account_id FROM $this->tableSettings WHERE type = ? AND active = 1"); + if ($stmt && $stmt->bind_param('s', $strType) && $stmt->execute() && $result = $stmt->get_result()) { + return $result->fetch_all(MYSQLI_ASSOC); + } + // Catchall + return false; + } + + /** + * Update accounts notification settings + * @param account_id int Account ID + * @param data array Data array + * @return bool + **/ + public function updateSettings($account_id, $data) { + $this->debug->append("STA " . __METHOD__, 4); + $failed = $ok = 0; + foreach ($data as $type => $active) { + // Does an entry exist already + $stmt = $this->mysqli->prepare("SELECT * FROM $this->tableSettings WHERE account_id = ? AND type = ?"); + if ($stmt && $stmt->bind_param('is', $account_id, $type) && $stmt->execute() && $stmt->store_result() && $stmt->num_rows() > 0) { + // We found a matching row + $stmt = $this->mysqli->prepare("UPDATE $this->tableSettings SET active = ? WHERE type = ? AND account_id = ?"); + if ($stmt && $stmt->bind_param('isi', $active, $type, $account_id) && $stmt->execute() && $stmt->close()) { + $ok++; + } else { + $failed++; + } + } else { + $stmt = $this->mysqli->prepare("INSERT INTO $this->tableSettings (active, type, account_id) VALUES (?,?,?)"); + if ($stmt && $stmt->bind_param('isi', $active, $type, $account_id) && $stmt->execute()) { + $ok++; + } else { + $failed++; + } + } + } + if ($failed > 0) { + $this->setErrorMessage('Failed to update ' . $failed . ' settings'); + return false; + } + return true; + } + + /** + * Send a specific notification setup in notification_settings + * @param type string Notification type + * @return bool + **/ + public function sendNotification($account_id, $strType, $aMailData) { + // Check if we notified for this event already + if ( $this->isNotified($aMailData) ) { + $this->setErrorMessage('A notification for this event has been sent already'); + return false; + } + // Check if this user wants strType notifications + $stmt = $this->mysqli->prepare("SELECT account_id FROM $this->tableSettings WHERE type = ? AND active = 1 AND account_id = ?"); + if ($stmt && $stmt->bind_param('si', $strType, $account_id) && $stmt->execute() && $stmt->bind_result($id) && $stmt->fetch()) { + if ($stmt->close() && $this->sendMail('notifications/' . $strType, $aMailData) && $this->addNotification($account_id, $strType, $aMailData)) + return true; + } else { + $this->setErrorMessage('User disabled ' . $strType . ' notifications'); + } return false; } } @@ -78,3 +187,5 @@ $notification->setDebug($debug); $notification->setMysql($mysqli); $notification->setSmarty($smarty); $notification->setConfig($config); + +?> diff --git a/public/include/pages/account/edit.inc.php b/public/include/pages/account/edit.inc.php index 1e2941bb..446c13f1 100644 --- a/public/include/pages/account/edit.inc.php +++ b/public/include/pages/account/edit.inc.php @@ -36,12 +36,17 @@ if ( ! $user->checkPin($_SESSION['USERDATA']['id'], $_POST['authPin']) && $_POST } } catch (BitcoinClientException $e) { $_SESSION['POPUP'][] = array('CONTENT' => 'Failed to send LTC, please contact site support immidiately', 'TYPE' => 'errormsg'); - $continue = false; +// $continue = false; } } // Set balance to 0, add to paid out, insert to ledger - if ($continue == true && $transaction->addTransaction($_SESSION['USERDATA']['id'], $dBalance, 'Debit_MP', NULL, $sCoinAddress)) + if ($continue == true && $transaction->addTransaction($_SESSION['USERDATA']['id'], $dBalance, 'Debit_MP', NULL, $sCoinAddress)) { $_SESSION['POPUP'][] = array('CONTENT' => 'Transaction completed', 'TYPE' => 'success'); + $aMailData['email'] = $user->getUserEmail($user->getUserName($_SESSION['USERDATA']['id'])); + $aMailData['amount'] = $dBalance; + $aMailData['subject'] = 'Manual Payout Completed'; + $notification->sendNotification($_SESSION['USERDATA']['id'], 'manual_payout', $aMailData); + } } else { $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to litecoind RPC service', 'TYPE' => 'errormsg'); } diff --git a/public/include/pages/account/notifications.inc.php b/public/include/pages/account/notifications.inc.php new file mode 100644 index 00000000..2ab9c0d0 --- /dev/null +++ b/public/include/pages/account/notifications.inc.php @@ -0,0 +1,25 @@ +updateSettings($_SESSION['USERDATA']['id'], $_REQUEST['data'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Updated notification settings'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => 'Failed to update settings', 'TYPE' => 'errormsg'); + } +} + +// Fetch notifications +$aNotifications = $notification->getNofifications($_SESSION['USERDATA']['id']); +if (!$aNotifications) $_SESSION['POPUP'][] = array('CONTENT' => 'Could not find any notifications', 'TYPE' => 'errormsg'); + +// Fetch user notification settings +$aSettings = $notification->getNotificationSettings($_SESSION['USERDATA']['id']); + +$smarty->assign('NOTIFICATIONS', $aNotifications); +$smarty->assign('SETTINGS', $aSettings); +$smarty->assign('CONTENT', 'default.tpl'); +?> diff --git a/public/templates/mail/notifications/auto_payout.tpl b/public/templates/mail/notifications/auto_payout.tpl new file mode 100644 index 00000000..6d045357 --- /dev/null +++ b/public/templates/mail/notifications/auto_payout.tpl @@ -0,0 +1,8 @@ + + +

An automated payout completed.

+

Amount: {$DATA.amount}

+
+
+ + diff --git a/public/templates/mail/idle_worker.tpl b/public/templates/mail/notifications/idle_worker.tpl similarity index 100% rename from public/templates/mail/idle_worker.tpl rename to public/templates/mail/notifications/idle_worker.tpl diff --git a/public/templates/mail/notifications/manual_payout.tpl b/public/templates/mail/notifications/manual_payout.tpl new file mode 100644 index 00000000..75198d7b --- /dev/null +++ b/public/templates/mail/notifications/manual_payout.tpl @@ -0,0 +1,8 @@ + + +

An manual payout request completed.

+

Amount: {$DATA.amount}

+
+
+ + diff --git a/public/templates/mail/notifications/new_block.tpl b/public/templates/mail/notifications/new_block.tpl new file mode 100644 index 00000000..9d17d06e --- /dev/null +++ b/public/templates/mail/notifications/new_block.tpl @@ -0,0 +1,7 @@ + + +

A new block has been discovered!

+
+
+ + diff --git a/public/templates/mmcFE/account/notifications/default.tpl b/public/templates/mmcFE/account/notifications/default.tpl new file mode 100644 index 00000000..676807d3 --- /dev/null +++ b/public/templates/mmcFE/account/notifications/default.tpl @@ -0,0 +1,80 @@ +{include file="global/block_header.tpl" ALIGN="left" BLOCK_HEADER="Notification Settings"} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeActive
IDLE Worker + + +
New Blocks + + +
Auto Payout + + +
Manual Payout + + +
+ +
+
+{include file="global/block_footer.tpl"} + +{include file="global/block_header.tpl" ALIGN="right" BLOCK_HEADER="Notification History"} +
+ {include file="global/pagination.tpl"} + + + + + + + + + + +{section notification $NOTIFICATIONS} + + + + + + +{/section} + +
IDTimeTypeActive
{$NOTIFICATIONS[notification].id}{$NOTIFICATIONS[notification].time} + {if $NOTIFICATIONS[notification].type == new_block}New Block + {else if $NOTIFICATIONS[notification].type == auto_payout}Auto Payout + {else if $NOTIFICATIONS[notification].type == idle_worker}IDLE Worker + {else if $NOTIFICATIONS[notification].type == manual_payout}Manual Payout + {/if} + + +
+
+{include file="global/block_footer.tpl"} diff --git a/public/templates/mmcFE/global/navigation.tpl b/public/templates/mmcFE/global/navigation.tpl index 6a0cdb37..3f26387a 100644 --- a/public/templates/mmcFE/global/navigation.tpl +++ b/public/templates/mmcFE/global/navigation.tpl @@ -7,6 +7,7 @@
  • My Workers
  • My Graphs
  • Transactions
  • +
  • Notifications
  • {/if} diff --git a/sql/issue_144_notification_upgrade.sql b/sql/issue_144_notification_upgrade.sql new file mode 100644 index 00000000..73985979 --- /dev/null +++ b/sql/issue_144_notification_upgrade.sql @@ -0,0 +1,17 @@ +CREATE TABLE IF NOT EXISTS `notifications` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `type` varchar(25) NOT NULL, + `data` varchar(255) NOT NULL, + `active` tinyint(1) NOT NULL DEFAULT '1', + `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `account_id` int(10) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `active` (`active`), + KEY `data` (`data`), + KEY `account_id` (`account_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +CREATE TABLE IF NOT EXISTS `notification_settings` ( + `type` varchar(15) NOT NULL, + `account_id` int(11) NOT NULL, + `active` tinyint(1) NOT NULL DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/sql/mmcfe_ng_structure.sql b/sql/mmcfe_ng_structure.sql index 039b9a04..1e21bbf4 100644 --- a/sql/mmcfe_ng_structure.sql +++ b/sql/mmcfe_ng_structure.sql @@ -3,7 +3,7 @@ -- http://www.phpmyadmin.net -- -- Host: localhost --- Generation Time: Jun 07, 2013 at 03:39 PM +-- Generation Time: Jun 09, 2013 at 04:08 PM -- Server version: 5.5.31-0ubuntu0.13.04.1 -- PHP Version: 5.4.9-4ubuntu2 @@ -65,7 +65,7 @@ CREATE TABLE IF NOT EXISTS `blocks` ( PRIMARY KEY (`id`), UNIQUE KEY `height` (`height`,`blockhash`), KEY `time` (`time`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Discovered blocks persisted from Litecoin Service'; +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Discovered blocks persisted from Litecoin Service'; -- -------------------------------------------------------- @@ -74,14 +74,28 @@ CREATE TABLE IF NOT EXISTS `blocks` ( -- CREATE TABLE IF NOT EXISTS `notifications` ( - `id` int(11) NOT NULL AUTO_INCREMENT, + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `type` varchar(25) NOT NULL, `data` varchar(255) NOT NULL, `active` tinyint(1) NOT NULL DEFAULT '1', - `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `account_id` int(10) unsigned DEFAULT NULL, PRIMARY KEY (`id`), KEY `active` (`active`), - KEY `data` (`data`) + KEY `data` (`data`), + KEY `account_id` (`account_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `notification_settings` +-- + +CREATE TABLE IF NOT EXISTS `notification_settings` ( + `type` varchar(15) NOT NULL, + `account_id` int(11) NOT NULL, + `active` tinyint(1) NOT NULL DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -------------------------------------------------------- @@ -153,7 +167,7 @@ CREATE TABLE IF NOT EXISTS `shares_archive` ( PRIMARY KEY (`id`), UNIQUE KEY `share_id` (`share_id`), KEY `time` (`time`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Archive shares for potential later debugging purposes'; +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Archive shares for potential later debugging purposes'; -- -------------------------------------------------------- @@ -170,7 +184,7 @@ CREATE TABLE IF NOT EXISTS `statistics_shares` ( PRIMARY KEY (`id`), KEY `account_id` (`account_id`), KEY `block_id` (`block_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -------------------------------------------------------- @@ -190,7 +204,7 @@ CREATE TABLE IF NOT EXISTS `transactions` ( KEY `block_id` (`block_id`), KEY `account_id` (`account_id`), KEY `type` (`type`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;