diff --git a/.gitignore b/.gitignore index d67a566e..a8f1b1ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /public/include/config/global.inc.php /public/templates/compile/*.php +/cronjobs/logs/*.txt +/public/templates/cache/*.php diff --git a/POOLS.md b/POOLS.md index 88b986b4..9268a9d4 100644 --- a/POOLS.md +++ b/POOLS.md @@ -27,7 +27,7 @@ They have succesfully mined blocks on each of those pools listed. All pools are running on Stratum only. | Pool URL | Coin | Avg. Hashrate | Avg. Active Workers | Notes | -| -------- | ---- | ------------- | ----------------- | ----- | +| -------- | ---- | ------------- | -----------------: | ----- | | http://wdc.nordicminers.eu | Worldcoin | n/a | n/a | | | http://lky.nordicminers.eu | Luckycoin | n/a | n/a | | | http://fst.nordicminers.eu | Fastcoin | n/a | n/a | | @@ -48,7 +48,7 @@ running more or less without any issues (related to `mmcfe-ng` that is ;-)). He the most powerful pool! | Pool URL | Coin | Avg. Hashrate | Avg. Active Workers | Notes | -| -------- | ---- | ------------- | ----------------- | ----- | +| -------- | ---- | ------------- | ------------------: | ----- | | http://www.ejpool.info | Litecoin | 155 MHash | 120 | | ### Obigal @@ -65,6 +65,12 @@ Small Time Miners are running various stratum only pools for different coins. ### Feeleep75 | Pool URL | Coin | Avg. Hashrate | Avg. Active Workers | Notes | -| -------- | ---- | ------------- | ------------------- | ----- | +| -------- | ---- | ------------- | ------------------: | ----- | | http://bot.coinmine.pl | Bottlecaps | 3 - 50 MHash | n/a | PoS/PoW type coin | | http://yacp.coinmine.pl | YaCoin | 19 MHash | n/a | | + +### LiteSaber + +| Pool URL | Coin | Avg. Hashrate | Avg. Active Workers | Notes | +| -------- | ---- | ------------- | ------------------: | ----- | +| http://coinhuntr.com | Litecoin | 200 MHash | 250 | Custom Frontend template | diff --git a/README.md b/README.md index 14526524..95a26d3f 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ These people have supported this project with a donation: * [vias](https://github.com/vias79) * [WKNiGHT](https://github.com/WKNiGHT-) * [ZC](https://github.com/zccopwrx) +* Nutnut Pools running mmcfe-ng ====================== @@ -68,12 +69,13 @@ Features The following feature have been implemented so far: -* Mobile WebUI **NEW** +* Fully re-written GUI with [Smarty][2] templates +* Mobile WebUI * Reward Systems * Propotional * PPS - * (Planned) PPLNS -* Use of memcache for statistics instead of a cronjob + * PPLNS **NEW** +* Statistics are cached in Memcache by Cronjob for quick data access * Web User accounts * Re-Captcha protected registration form * Worker accounts @@ -87,9 +89,11 @@ The following feature have been implemented so far: * Auto payout * Transaction list (confirmed and unconfirmed) * Admin Panel + * Cron Monitoring Overview **NEW** * User Listing including statistics * Wallet information - * News Posts **NEW** + * User Transactions **NEW** + * News Posts * Notification system * IDLE Workers * New blocks found in pool @@ -139,3 +143,4 @@ limitations under the License. [1]: https://github.com/TheSerapher/php-mmcfe-ng/issues "Issue" + [2]: http://www.smarty.net/docs/en/ "Smarty" diff --git a/cronjobs/archive_cleanup.php b/cronjobs/archive_cleanup.php new file mode 100755 index 00000000..ebdd5b7e --- /dev/null +++ b/cronjobs/archive_cleanup.php @@ -0,0 +1,36 @@ +#!/usr/bin/php +purgeArchive()) { + $log->logError("Failed to delete archived shares, not critical but should be checked!"); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "Failed to delete archived shares"); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); + exit(1); +} + +// Cron cleanup and monitoring +require_once('cron_end.inc.php'); +?> diff --git a/cronjobs/auto_payout.php b/cronjobs/auto_payout.php index aaf715e0..548e5de8 100755 --- a/cronjobs/auto_payout.php +++ b/cronjobs/auto_payout.php @@ -22,30 +22,28 @@ limitations under the License. // Include all settings and classes require_once('shared.inc.php'); -verbose("Running auto-payouts ..."); - if ($bitcoin->can_connect() !== true) { - verbose(" unable to connect to RPC server, exiting\n"); + $log->logFatal(" unable to connect to RPC server, exiting"); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "Unable to connect to RPC server"); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); exit(1); } -// Mark this job as active -$setting->setValue('auto_payout_active', 1); - // Fetch all users with setup AP $users = $user->getAllAutoPayout(); // Quick summary -verbose(" found " . count($users) . " queued payout(s)\n"); +$log->logInfo(" found " . count($users) . " queued payout(s)"); // Go through users and run transactions if (! empty($users)) { - verbose("\tUserID\tUsername\tBalance\tThreshold\tAddress\t\t\t\t\tStatus\n\n"); + $log->logInfo("\tUserID\tUsername\tBalance\tThreshold\tAddress"); foreach ($users as $aUserData) { $aBalance = $transaction->getBalance($aUserData['id']); $dBalance = $aBalance['confirmed']; - verbose("\t" . $aUserData['id'] . "\t" . $aUserData['username'] . "\t" . $dBalance . "\t" . $aUserData['ap_threshold'] . "\t\t" . $aUserData['coin_address'] . "\t"); + $log->logInfo("\t" . $aUserData['id'] . "\t" . $aUserData['username'] . "\t" . $dBalance . "\t" . $aUserData['ap_threshold'] . "\t\t" . $aUserData['coin_address']); // Only run if balance meets threshold and can pay the potential transaction fee if ($dBalance > $aUserData['ap_threshold'] && $dBalance > $config['txfee']) { @@ -53,7 +51,7 @@ if (! empty($users)) { try { $bitcoin->validateaddress($aUserData['coin_address']); } catch (BitcoinClientException $e) { - verbose("VERIFY FAILED\n"); + $log->logError('Failed to verifu this users coin address, skipping payout'); continue; } @@ -61,7 +59,7 @@ if (! empty($users)) { try { $bitcoin->sendtoaddress($aUserData['coin_address'], $dBalance); } catch (BitcoinClientException $e) { - verbose("SEND FAILED\n"); + $log->logError('Failed to send requested balance to coin address, please check payout process'); continue; } @@ -71,24 +69,17 @@ if (! empty($users)) { $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"); - } + if (!$notification->sendNotification($aUserData['id'], 'auto_payout', $aMailData)) + $log->logError('Failed to send notification email to users address: ' . $aMailData['email']); } else { - verbose("FAILED\n"); + $log->logError('Failed to add new Debit_AP transaction in database for user ' . $user->getUserName($aUserData['id'])); } - - } else { - verbose("SKIPPED\n"); } } } else { - verbose(" no user has configured their AP > 0\n"); + $log->logDebug(" no user has configured their AP > 0"); } -// Mark this job as inactive -$setting->setValue('auto_payout_active', 0); - +// Cron cleanup and monitoring +require_once('cron_end.inc.php'); ?> diff --git a/cronjobs/blockupdate.php b/cronjobs/blockupdate.php index 65779a3a..e11063bb 100755 --- a/cronjobs/blockupdate.php +++ b/cronjobs/blockupdate.php @@ -23,33 +23,37 @@ limitations under the License. require_once('shared.inc.php'); if ( $bitcoin->can_connect() !== true ) { - verbose("Failed to connect to RPC server\n"); + $log->logFatal("Failed to connect to RPC server\n"); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "Unable to connect to RPC server"); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); exit(1); } // Fetch all unconfirmed blocks $aAllBlocks = $block->getAllUnconfirmed($config['confirmations']); -verbose("ID\tBlockhash\tConfirmations\t\n"); +$log->logInfo("ID\tHeight\tBlockhash\tConfirmations"); foreach ($aAllBlocks as $iIndex => $aBlock) { $aBlockInfo = $bitcoin->query('getblock', $aBlock['blockhash']); // Fetch this blocks transaction details to find orphan blocks $aTxDetails = $bitcoin->query('gettransaction', $aBlockInfo['tx'][0]); - verbose($aBlock['id'] . "\t" . $aBlock['blockhash'] . "\t" . $aBlock['confirmations'] . " -> " . $aBlockInfo['confirmations'] . "\t"); + $log->logInfo($aBlock['id'] . "\t" . $aBlock['height'] . "\t" . $aBlock['blockhash'] . "\t" . $aBlock['confirmations'] . " -> " . $aBlockInfo['confirmations']); if ($aTxDetails['details'][0]['category'] == 'orphan') { // We have an orphaned block, we need to invalidate all transactions for this one - if ($transaction->setOrphan($aBlock['id']) && $block->setConfirmations($aBlock['id'], -1)) { - verbose("ORPHAN\n"); + if ($block->setConfirmations($aBlock['id'], -1)) { + $log->logInfo(" Block marked as orphan"); } else { - verbose("ORPHAN_ERR"); + $log->logError(" Block became orphaned but unable to update database entries"); } continue; } if ($aBlock['confirmations'] == $aBlockInfo['confirmations']) { - verbose("SKIPPED\n"); - } else if ($block->setConfirmations($aBlock['id'], $aBlockInfo['confirmations'])) { - verbose("UPDATED\n"); - } else { - verbose("ERROR\n"); + $log->logDebug(' No update needed'); + } else if (!$block->setConfirmations($aBlock['id'], $aBlockInfo['confirmations'])) { + $log->logError(' Failed to update block confirmations'); } } + +require_once('cron_end.inc.php'); +?> diff --git a/cronjobs/cron_end.inc.php b/cronjobs/cron_end.inc.php new file mode 100644 index 00000000..03a548d2 --- /dev/null +++ b/cronjobs/cron_end.inc.php @@ -0,0 +1,28 @@ +setStatus($cron_name . "_message", "message", "OK"); +$monitoring->setStatus($cron_name . "_status", "okerror", 0); +$monitoring->setStatus($cron_name . "_runtime", "time", microtime(true) - $cron_start[$cron_name]); +$monitoring->setStatus($cron_name . "_endtime", "date", time()); +// Mark cron as running for monitoring +$monitoring->setStatus($cron_name . '_active', "yesno", 0); +?> diff --git a/cronjobs/findblock.php b/cronjobs/findblock.php index efcc9f11..0336deea 100755 --- a/cronjobs/findblock.php +++ b/cronjobs/findblock.php @@ -25,24 +25,25 @@ require_once('shared.inc.php'); // Fetch our last block found from the DB as a starting point $aLastBlock = @$block->getLast(); $strLastBlockHash = $aLastBlock['blockhash']; -if (!$strLastBlockHash) { - $strLastBlockHash = ''; -} +if (!$strLastBlockHash) $strLastBlockHash = ''; // Fetch all transactions since our last block if ( $bitcoin->can_connect() === true ){ $aTransactions = $bitcoin->query('listsinceblock', $strLastBlockHash); } else { - verbose("Aborted: " . $bitcoin->can_connect() . "\n"); + $log->logFatal('Unable to conenct to RPC server backend'); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "Unable to connect to RPC server"); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); exit(1); } // Nothing to do so bail out if (empty($aTransactions['transactions'])) { - verbose("No new RPC transactions since last block\n"); + $log->logDebug('No new RPC transactions since last block'); } else { // Table header - verbose("Blockhash\t\tHeight\tAmount\tConfirmations\tDiff\t\tTime\t\t\tStatus\n"); + $log->logInfo("Blockhash\t\tHeight\tAmount\tConfirmations\tDiff\t\tTime"); // Let us add those blocks as unaccounted foreach ($aTransactions['transactions'] as $iIndex => $aData) { @@ -51,70 +52,67 @@ if (empty($aTransactions['transactions'])) { $config['reward_type'] == 'block' ? $aData['amount'] = $aData['amount'] : $aData['amount'] = $config['reward']; $aData['height'] = $aBlockInfo['height']; $aData['difficulty'] = $aBlockInfo['difficulty']; - verbose(substr($aData['blockhash'], 0, 15) . "...\t" . + $log->logInfo(substr($aData['blockhash'], 0, 15) . "...\t" . $aData['height'] . "\t" . $aData['amount'] . "\t" . $aData['confirmations'] . "\t\t" . $aData['difficulty'] . "\t" . - strftime("%Y-%m-%d %H:%M:%S", $aData['time']) . "\t"); - if ( $block->addBlock($aData) ) { - verbose("Added\n"); - } else { - verbose("Failed" . "\n"); + strftime("%Y-%m-%d %H:%M:%S", $aData['time'])); + if (!$block->addBlock($aData) ) { + $log->logFatal('Unable to add this block to database: ' . $aData['height']); } } } } -verbose("\n"); // Now with our blocks added we can scan for their upstream shares -$aAllBlocks = $block->getAllUnaccounted('ASC'); +$aAllBlocks = $block->getAllUnsetShareId('ASC'); if (empty($aAllBlocks)) { - verbose("No new unaccounted blocks found\n"); + $log->logDebug('No new blocks without share_id found in database'); } else { // Loop through our unaccounted blocks - verbose("\nBlock ID\tBlock Height\tAmount\tShare ID\tShares\tFinder\t\t\tStatus\n"); + $log->logInfo("Block ID\t\tHeight\tAmount\tShare ID\tShares\tFinder\tType"); foreach ($aAllBlocks as $iIndex => $aBlock) { if (empty($aBlock['share_id'])) { // Fetch this blocks upstream ID - if ($share->setUpstream($block->getLastUpstreamId(), $aBlock['time'])) { + $aBlockInfo = $bitcoin->query('getblock', $aBlock['blockhash']); + if ($share->setUpstream($aBlockInfo, $block->getLastUpstreamId())) { $iCurrentUpstreamId = $share->getUpstreamId(); $iAccountId = $user->getUserId($share->getUpstreamFinder()); } else { - verbose("\nUnable to fetch blocks upstream share. Aborting!\n"); - verbose($share->getError() . "\n"); + $log->logFatal('Unable to fetch blocks upstream share, aborted:' . $share->getError()); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "Unable to fetch blocks " . $aBlock['height'] . " upstream share: " . $share->getError()); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); exit; } // Fetch share information if (!$iPreviousShareId = $block->getLastShareId()) { $iPreviousShareId = 0; - verbose("\nUnable to find highest share ID found so far\n"); - verbose("If this is your first block, this is normal\n\n"); + $log->logInfo('Unable to find highest share ID found so far, if this is your first block, this is normal.'); } $iRoundShares = $share->getRoundShares($iPreviousShareId, $iCurrentUpstreamId); // Store new information - $strStatus = "OK"; if (!$block->setShareId($aBlock['id'], $iCurrentUpstreamId)) - $strStatus = "Share ID Failed"; + $log->logError('Failed to update share ID in database for block ' . $aBlock['height']); if (!$block->setFinder($aBlock['id'], $iAccountId)) - $strStatus = "Finder Failed"; + $log->logError('Failed to update finder account ID in database for block ' . $aBlock['height']); if (!$block->setShares($aBlock['id'], $iRoundShares)) - $strStatus = "Shares Failed"; + $log->logError('Failed to update share count in database for block ' . $aBlock['height']); if ($config['block_bonus'] > 0 && !$transaction->addTransaction($iAccountId, $config['block_bonus'], 'Bonus', $aBlock['id'])) { - $strStatus = "Bonus Failed"; + $log->logError('Failed to create Bonus transaction in database for user ' . $user->getUserName($iAccountId) . ' for block ' . $aBlock['height']); } - verbose( + $log->logInfo( $aBlock['id'] . "\t\t" . $aBlock['height'] . "\t\t" . $aBlock['amount'] . "\t" . $iCurrentUpstreamId . "\t\t" . $iRoundShares . "\t" - . "[$iAccountId] " . $user->getUserName($iAccountId) . "\t\t" - . $strStatus - . "\n" + . "[$iAccountId] " . $user->getUserName($iAccountId) . "\t" + . $share->share_type ); // Notify users @@ -125,11 +123,13 @@ if (empty($aAllBlocks)) { $aMailData['subject'] = 'New Block'; $aMailData['email'] = $user->getUserEmail($user->getUserName($aData['account_id'])); $aMailData['shares'] = $iRoundShares; - $notification->sendNotification($aData['account_id'], 'new_block', $aMailData); + if (!$notification->sendNotification($aData['account_id'], 'new_block', $aMailData)) + $log->logError('Failed to notify user of new found block: ' . $user->getUserName($aData['account_id'])); } } } } } -?> +require_once('cron_end.inc.php'); +?> diff --git a/cronjobs/logs/README.md b/cronjobs/logs/README.md new file mode 100644 index 00000000..864999fd --- /dev/null +++ b/cronjobs/logs/README.md @@ -0,0 +1 @@ +Logging directory for cronjobs. diff --git a/cronjobs/notifications.php b/cronjobs/notifications.php index 61a608ab..effcf002 100755 --- a/cronjobs/notifications.php +++ b/cronjobs/notifications.php @@ -22,51 +22,49 @@ limitations under the License. // Include all settings and classes require_once('shared.inc.php'); -verbose("Running system notifications\n"); -verbose(" IDLE Worker Notifications ..."); +$log->logDebug(" IDLE Worker Notifications ..."); // Find all IDLE workers $aWorkers = $worker->getAllIdleWorkers(); if (empty($aWorkers)) { - verbose(" no idle workers found\n"); + $log->logDebug(" no idle workers found\n"); } else { - verbose(" found " . count($aWorkers) . " IDLE workers\n"); + $log->logInfo(" found " . count($aWorkers) . " IDLE workers\n"); foreach ($aWorkers as $aWorker) { $aData = $aWorker; $aData['username'] = $user->getUserName($aWorker['account_id']); $aData['subject'] = 'IDLE Worker : ' . $aWorker['username']; $aData['worker'] = $aWorker['username']; $aData['email'] = $user->getUserEmail($aData['username']); - verbose(" " . $aWorker['username'] . "..."); - if (!$notification->sendNotification($aWorker['account_id'], 'idle_worker', $aData)) { - verbose(" " . $notification->getError() . "\n"); - } else { - verbose(" sent\n"); - } + $log->logInfo(" " . $aWorker['username'] . "..."); + if (!$notification->sendNotification($aWorker['account_id'], 'idle_worker', $aData)) + $log->logError(" Failed sending notifications: " . $notification->getError() . "\n"); } } -verbose(" Reset IDLE Worker Notifications ..."); +$log->logDebug(" Reset IDLE Worker Notifications ..."); // We notified, lets check which recovered $aNotifications = $notification->getAllActive('idle_worker'); if (!empty($aNotifications)) { - verbose(" found " . count($aNotifications) . " active notification(s)\n"); + $log->logInfo(" found " . count($aNotifications) . " active notification(s)\n"); foreach ($aNotifications as $aNotification) { $aData = json_decode($aNotification['data'], true); $aWorker = $worker->getWorker($aData['id']); - verbose(" " . $aWorker['username'] . " ..."); + $log->logInfo(" " . $aWorker['username'] . " ..."); if ($aWorker['active'] == 1) { if ($notification->setInactive($aNotification['id'])) { - verbose(" updated #" . $aNotification['id'] . " for " . $aWorker['username'] . " as inactive\n"); + $log->logInfo(" updated #" . $aNotification['id'] . " for " . $aWorker['username'] . " as inactive\n"); } else { - verbose(" failed to update #" . $aNotification['id'] . " for " . $aWorker['username'] . "\n"); + $log->logInfo(" failed to update #" . $aNotification['id'] . " for " . $aWorker['username'] . "\n"); } } else { - verbose(" still inactive\n"); + $log->logInfo(" still inactive\n"); } } } else { - verbose(" no active IDLE worker notifications\n"); + $log->logDebug(" no active IDLE worker notifications\n"); } + +require_once('cron_end.inc.php'); ?> diff --git a/cronjobs/pplns_payout.php b/cronjobs/pplns_payout.php new file mode 100755 index 00000000..f7cdb6b2 --- /dev/null +++ b/cronjobs/pplns_payout.php @@ -0,0 +1,187 @@ +#!/usr/bin/php +logInfo("Please activate this cron in configuration via payout_system = pplns"); + exit(0); +} + +// Fetch all unaccounted blocks +$aAllBlocks = $block->getAllUnaccounted('ASC'); +if (empty($aAllBlocks)) { + $log->logDebug("No new unaccounted blocks found"); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "No new unaccounted blocks"); + $monitoring->setStatus($cron_name . "_status", "okerror", 0); + exit(0); +} + +$count = 0; +foreach ($aAllBlocks as $iIndex => $aBlock) { + // We support some dynamic share targets but fall back to our fixed value + // Re-calculate after each run due to re-targets in this loop + if ($config['pplns']['shares']['type'] == 'blockavg' && $block->getBlockCount() > 0) { + $pplns_target = round($block->getAvgBlockShares($config['pplns']['blockavg']['blockcount'])); + } else { + $pplns_target = $config['pplns']['shares']['default'] ; + } + + if (!$aBlock['accounted']) { + $iPreviousShareId = @$aAllBlocks[$iIndex - 1]['share_id'] ? $aAllBlocks[$iIndex - 1]['share_id'] : 0; + $iCurrentUpstreamId = $aBlock['share_id']; + if (!is_numeric($iCurrentUpstreamId)) { + $log->logFatal("Block " . $aBlock['height'] . " has no share_id associated with it, not going to continue"); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "Block " . $aBlock['height'] . " has no share_id associated with it"); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); + exit(1); + } + $iRoundShares = $share->getRoundShares($iPreviousShareId, $aBlock['share_id']); + $iNewRoundShares = 0; + $config['reward_type'] == 'block' ? $dReward = $aBlock['amount'] : $dReward = $config['reward']; + $aRoundAccountShares = $share->getSharesForAccounts($iPreviousShareId, $aBlock['share_id']); + + if ($iRoundShares >= $pplns_target) { + $log->logDebug("Matching or exceeding PPLNS target of $pplns_target with $iRoundShares"); + $aAccountShares = $share->getSharesForAccounts($aBlock['share_id'] - $pplns_target, $aBlock['share_id']); + if (empty($aAccountShares)) { + $log->logFatal("No shares found for this block, aborted! Block Height : " . $aBlock['height']); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "No shares found for this block: " . $aBlock['height']); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); + exit(1); + } + } else { + $log->logDebug("Not able to match PPLNS target of $pplns_target with $iRoundShares"); + // We need to fill up with archived shares + // Grab the full current round shares since we didn't match target + $aAccountShares = $aRoundAccountShares; + if (empty($aAccountShares)) { + $log->logFatal("No shares found for this block, aborted! Block height: " . $aBlock['height']); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "No shares found for this block: " . $aBlock['height']); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); + exit(1); + } + + // Grab only the most recent shares from Archive that fill the missing shares + $log->logInfo('Fetching ' . ($pplns_target - $iRoundShares) . ' additional shares from archive'); + if (!$aArchiveShares = $share->getArchiveShares($pplns_target - $iRoundShares)) { + $log->logError('Failed to fetch shares from archive, setting target to round total'); + $pplns_target = $iRoundShares; + } else { + // Add archived shares to users current shares, if we have any in archive + if (is_array($aArchiveShares)) { + $log->logDebug('Found shares in archive to match PPLNS target, calculating per-user shares'); + foreach($aAccountShares as $key => $aData) { + if (array_key_exists($aData['username'], $aArchiveShares)) { + $log->logDebug('Found user ' . $aData['username'] . ' in archived shares'); + $log->logDebug(' valid : ' . $aAccountShares[$key]['valid'] . ' + ' . $aArchiveShares[$aData['username']]['valid'] . ' = ' . ($aAccountShares[$key]['valid'] + $aArchiveShares[$aData['username']]['valid']) ); + $log->logDebug(' invalid : ' . $aAccountShares[$key]['invalid'] . ' + ' . $aArchiveShares[$aData['username']]['invalid'] . ' = ' . ($aAccountShares[$key]['invalid'] + $aArchiveShares[$aData['username']]['invalid']) ); + $aAccountShares[$key]['valid'] += $aArchiveShares[$aData['username']]['valid']; + $aAccountShares[$key]['invalid'] += $aArchiveShares[$aData['username']]['invalid']; + } + } + } + // We tried to fill up to PPLNS target, now we need to check the actual shares to properly payout users + foreach($aAccountShares as $key => $aData) { + $iNewRoundShares += $aData['valid']; + } + } + } + + // We filled from archive but still are not able to match PPLNS target, re-adjust + if ($iRoundShares < $iNewRoundShares) { + $log->logInfo('Adjusting round target to ' . $iNewRoundShares); + $iRoundShares = $iNewRoundShares; + } + + // Table header for account shares + $log->logInfo("ID\tUsername\tValid\tInvalid\tPercentage\tPayout\t\tDonation\tFee"); + + // Loop through all accounts that have found shares for this round + foreach ($aAccountShares as $key => $aData) { + // Payout based on PPLNS target shares, proportional payout for all users + $aData['percentage'] = number_format(round(( 100 / $pplns_target) * $aData['valid'], 8), 8); + $aData['payout'] = number_format(round(( $aData['percentage'] / 100 ) * $dReward, 8), 8); + // Defaults + $aData['fee' ] = 0; + $aData['donation'] = 0; + + if ($config['fees'] > 0) + $aData['fee'] = number_format(round($config['fees'] / 100 * $aData['payout'], 8), 8); + // Calculate donation amount, fees not included + $aData['donation'] = number_format(round($user->getDonatePercent($user->getUserId($aData['username'])) / 100 * ( $aData['payout'] - $aData['fee']), 8), 8); + + // Verbose output of this users calculations + $log->logInfo($aData['id'] . "\t" . + $aData['username'] . "\t" . + $aData['valid'] . "\t" . + $aData['invalid'] . "\t" . + $aData['percentage'] . "\t" . + $aData['payout'] . "\t" . + $aData['donation'] . "\t" . + $aData['fee']); + + // Add full round share statistics, not just PPLNS + foreach ($aRoundAccountShares as $key => $aRoundData) { + if ($aRoundData['username'] == $aData['username']) + if (!$statistics->updateShareStatistics($aRoundData, $aBlock['id'])) + $log->logError('Failed to update share statistics for ' . $aData['username']); + } + // Add new credit transaction + if (!$transaction->addTransaction($aData['id'], $aData['payout'], 'Credit', $aBlock['id'])) + $log->logFatal('Failed to insert new Credit transaction to database for ' . $aData['username']); + // Add new fee debit for this block + if ($aData['fee'] > 0 && $config['fees'] > 0) + if (!$transaction->addTransaction($aData['id'], $aData['fee'], 'Fee', $aBlock['id'])) + $log->logFatal('Failed to insert new Fee transaction to database for ' . $aData['username']); + // Add new donation debit + if ($aData['donation'] > 0) + if (!$transaction->addTransaction($aData['id'], $aData['donation'], 'Donation', $aBlock['id'])) + $log->logFatal('Failed to insert new Donation transaction to database for ' . $aData['username']); + } + + // Move counted shares to archive before this blockhash upstream share + if (!$share->moveArchive($iCurrentUpstreamId, $aBlock['id'], $iPreviousShareId)) + $log->logError('Failed to copy shares to archive table'); + // Delete all accounted shares + if (!$share->deleteAccountedShares($iCurrentUpstreamId, $iPreviousShareId)) { + $log->logFatal("Failed to delete accounted shares from $iPreviousShareId to $iCurrentUpstreamId, aborting!"); + exit(1); + } + // Mark this block as accounted for + if (!$block->setAccounted($aBlock['id'])) { + $log->logFatal("Failed to mark block as accounted! Aborting!"); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "Failed to mark block " . $aBlock['height'] . " as accounted"); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); + exit(1); + } + } +} + +require_once('cron_end.inc.php'); +?> diff --git a/cronjobs/pps_payout.php b/cronjobs/pps_payout.php index 22e5fcf7..5e6f673a 100755 --- a/cronjobs/pps_payout.php +++ b/cronjobs/pps_payout.php @@ -25,7 +25,7 @@ require_once('shared.inc.php'); // Check if we are set as the payout system if ($config['payout_system'] != 'pps') { - verbose("Please activate this cron in configuration via payout_system = pps\n"); + $log->logInfo("Please activate this cron in configuration via payout_system = pps\n"); exit(0); } @@ -35,13 +35,16 @@ if ( $bitcoin->can_connect() === true ){ if (is_array($dDifficulty) && array_key_exists('proof-of-work', $dDifficulty)) $dDifficulty = $dDifficulty['proof-of-work']; } else { - verbose("Aborted: " . $bitcoin->can_connect() . "\n"); + $log->logFatal("Aborted: " . $bitcoin->can_connect() . "\n"); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "Unable to connect to RPC server"); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); exit(1); } // Value per share calculation if ($config['reward_type'] != 'block') { -$pps_value = number_format(round($config['reward'] / (pow(2,32) * $dDifficulty) * pow(2, $config['difficulty']), 12) ,12); + $pps_value = number_format(round($config['reward'] / (pow(2,32) * $dDifficulty) * pow(2, $config['difficulty']), 12) ,12); } else { // Try to find the last block value and use that for future payouts, revert to fixed reward if none found if ($aLastBlock = $block->getLast()) { @@ -58,7 +61,7 @@ $iLastShareId = $share->getLastInsertedShareId(); // Check for all new shares, we start one higher as our last accounted share to avoid duplicates $aAccountShares = $share->getSharesForAccounts($iPreviousShareId + 1, $iLastShareId); -verbose("ID\tUsername\tInvalid\tValid\t\tPPS Value\t\tPayout\t\tDonation\tFee\t\tStatus\n"); +$log->logInfo("ID\tUsername\tInvalid\tValid\t\tPPS Value\t\tPayout\t\tDonation\tFee"); foreach ($aAccountShares as $aData) { // Take our valid shares and multiply by per share value @@ -74,65 +77,76 @@ foreach ($aAccountShares as $aData) { // Calculate donation amount $aData['donation'] = number_format(round($user->getDonatePercent($user->getUserId($aData['username'])) / 100 * ( $aData['payout'] - $aData['fee']), 8), 8); - verbose($aData['id'] . "\t" . + $log->logInfo($aData['id'] . "\t" . $aData['username'] . "\t" . $aData['invalid'] . "\t" . $aData['valid'] . "\t*\t" . $pps_value . "\t=\t" . $aData['payout'] . "\t" . $aData['donation'] . "\t" . - $aData['fee'] . "\t"); + $aData['fee']); - $strStatus = "OK"; // Add new credit transaction if (!$transaction->addTransaction($aData['id'], $aData['payout'], 'Credit_PPS')) - $strStatus = "Transaction Failed"; + $log->logError('Failed to add Credit_PPS transaction in database'); // Add new fee debit for this block if ($aData['fee'] > 0 && $config['fees'] > 0) if (!$transaction->addTransaction($aData['id'], $aData['fee'], 'Fee_PPS')) - $strStatus = "Fee Failed"; + $log->logError('Failed to add Fee_PPS transaction in database'); // Add new donation debit if ($aData['donation'] > 0) if (!$transaction->addTransaction($aData['id'], $aData['donation'], 'Donation_PPS')) - $strStatus = "Donation Failed"; - verbose($strStatus . "\n"); + $log->logError('Failed to add Donation_PPS transaction in database'); } // Store our last inserted ID for the next run $setting->setValue('pps_last_share_id', $iLastShareId); -verbose("\n\n------------------------------------------------------------------------------------\n\n"); - // Fetch all unaccounted blocks $aAllBlocks = $block->getAllUnaccounted('ASC'); if (empty($aAllBlocks)) { - verbose("No new unaccounted blocks found\n"); + $log->logDebug("No new unaccounted blocks found"); } // Go through blocks and archive/delete shares that have been accounted for foreach ($aAllBlocks as $iIndex => $aBlock) { // If we are running through more than one block, check for previous share ID $iLastBlockShare = @$aAllBlocks[$iIndex - 1]['share_id'] ? @$aAllBlocks[$iIndex - 1]['share_id'] : 0; + if (!is_numeric($aBlock['share_id'])) { + $log->logFatal("Block " . $aBlock['height'] . " has no share_id associated with it, not going to continue"); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "Block " . $aBlock['height'] . " has no share_id associated with it"); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); + exit(1); + } // Per account statistics $aAccountShares = $share->getSharesForAccounts(@$iLastBlockShare, $aBlock['share_id']); foreach ($aAccountShares as $key => $aData) { if (!$statistics->updateShareStatistics($aData, $aBlock['id'])) - verbose("Failed to update stats for this block on : " . $aData['username'] . "\n"); + $log->logError("Failed to update stats for this block on : " . $aData['username']); } // Move shares to archive - if ($config['archive_shares'] && $aBlock['share_id'] < $iLastShareId) { + if ($aBlock['share_id'] < $iLastShareId) { if (!$share->moveArchive($aBlock['share_id'], $aBlock['id'], @$iLastBlockShare)) - verbose("Archving failed\n"); + $log->logError("Archving failed"); } // Delete shares if ($aBlock['share_id'] < $iLastShareId && !$share->deleteAccountedShares($aBlock['share_id'], $iLastBlockShare)) { - verbose("\nERROR : Failed to delete accounted shares from " . $aBlock['share_id'] . " to " . $iLastBlockShare . ", aborting!\n"); + $log->logFatal("Failed to delete accounted shares from " . $aBlock['share_id'] . " to " . $iLastBlockShare . ", aborting!"); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "Failed to delete accounted shares from " . $aBlock['share_id'] . " to " . $iLastBlockShare); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); exit(1); } // Mark this block as accounted for if (!$block->setAccounted($aBlock['id'])) { - verbose("\nERROR : Failed to mark block as accounted! Aborting!\n"); + $log->logFatal("Failed to mark block as accounted! Aborting!"); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "Failed to mark block " . $aBlock['height'] . " as accounted"); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); exit(1); } } + +require_once('cron_end.inc.php'); ?> diff --git a/cronjobs/proportional_payout.php b/cronjobs/proportional_payout.php index 57b97c2c..36a9a1fe 100755 --- a/cronjobs/proportional_payout.php +++ b/cronjobs/proportional_payout.php @@ -24,18 +24,23 @@ require_once('shared.inc.php'); // Check if we are set as the payout system if ($config['payout_system'] != 'prop') { - verbose("Please activate this cron in configuration via payout_system = prop\n"); + $log->logInfo("Please activate this cron in configuration via payout_system = prop"); exit(0); } // Fetch all unaccounted blocks $aAllBlocks = $block->getAllUnaccounted('ASC'); if (empty($aAllBlocks)) { - verbose("No new unaccounted blocks found\n"); + $log->logDebug('No new unaccounted blocks found in database'); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "No new unaccounted blocks"); + $monitoring->setStatus($cron_name . "_status", "okerror", 0); exit(0); } $count = 0; +// Table header for account shares +$log->logInfo("ID\tUsername\tValid\tInvalid\tPercentage\tPayout\t\tDonation\tFee"); foreach ($aAllBlocks as $iIndex => $aBlock) { if (!$aBlock['accounted']) { $iPreviousShareId = @$aAllBlocks[$iIndex - 1]['share_id'] ? $aAllBlocks[$iIndex - 1]['share_id'] : 0; @@ -45,14 +50,13 @@ foreach ($aAllBlocks as $iIndex => $aBlock) { $config['reward_type'] == 'block' ? $dReward = $aBlock['amount'] : $dReward = $config['reward']; if (empty($aAccountShares)) { - verbose("\nNo shares found for this block\n\n"); - sleep(2); - continue; + $log->logFatal('No shares found for this block, aborted: ' . $aBlock['height']); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "No shares found for this block, aborted: " . $aBlock['height']); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); + exit(1); } - // Table header for account shares - verbose("ID\tUsername\tValid\tInvalid\tPercentage\tPayout\t\tDonation\tFee\t\tStatus\n"); - // Loop through all accounts that have found shares for this round foreach ($aAccountShares as $key => $aData) { // Payout based on shares, PPS system @@ -68,45 +72,52 @@ foreach ($aAllBlocks as $iIndex => $aBlock) { $aData['donation'] = number_format(round($user->getDonatePercent($user->getUserId($aData['username'])) / 100 * ( $aData['payout'] - $aData['fee']), 8), 8); // Verbose output of this users calculations - verbose($aData['id'] . "\t" . - $aData['username'] . "\t" . - $aData['valid'] . "\t" . - $aData['invalid'] . "\t" . - $aData['percentage'] . "\t" . - $aData['payout'] . "\t" . - $aData['donation'] . "\t" . - $aData['fee'] . "\t"); + $log->logInfo($aData['id'] . "\t" . + $aData['username'] . "\t" . + $aData['valid'] . "\t" . + $aData['invalid'] . "\t" . + $aData['percentage'] . "\t" . + $aData['payout'] . "\t" . + $aData['donation'] . "\t" . + $aData['fee']); - $strStatus = "OK"; // Update user share statistics if (!$statistics->updateShareStatistics($aData, $aBlock['id'])) - $strStatus = "Stats Failed"; + $log->logFatal('Failed to update share statistics for ' . $aData['username']); // Add new credit transaction if (!$transaction->addTransaction($aData['id'], $aData['payout'], 'Credit', $aBlock['id'])) - $strStatus = "Transaction Failed"; + $log->logFatal('Failed to insert new Credit transaction to database for ' . $aData['username']); // Add new fee debit for this block if ($aData['fee'] > 0 && $config['fees'] > 0) if (!$transaction->addTransaction($aData['id'], $aData['fee'], 'Fee', $aBlock['id'])) - $strStatus = "Fee Failed"; + $log->logFatal('Failed to insert new Fee transaction to database for ' . $aData['username']); // Add new donation debit if ($aData['donation'] > 0) if (!$transaction->addTransaction($aData['id'], $aData['donation'], 'Donation', $aBlock['id'])) - $strStatus = "Donation Failed"; - verbose("\t$strStatus\n"); + $log->logFatal('Failed to insert new Donation transaction to database for ' . $aData['username']); } // Move counted shares to archive before this blockhash upstream share - if ($config['archive_shares']) $share->moveArchive($iCurrentUpstreamId, $aBlock['id'], $iPreviousShareId); + if (!$share->moveArchive($iCurrentUpstreamId, $aBlock['id'], $iPreviousShareId)) + $log->logError('Failed to copy shares to archive'); // Delete all accounted shares if (!$share->deleteAccountedShares($iCurrentUpstreamId, $iPreviousShareId)) { - verbose("\nERROR : Failed to delete accounted shares from $iPreviousShareId to $iCurrentUpstreamId, aborting!\n"); + $log->logFatal('Failed to delete accounted shares from ' . $iPreviousShareId . ' to ' . $iCurrentUpstreamId . ', aborted'); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "Failed to delete accounted shares from " . $iPreviousShareId . " to " . $iCurrentUpstreamId); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); exit(1); } // Mark this block as accounted for if (!$block->setAccounted($aBlock['id'])) { - verbose("\nERROR : Failed to mark block as accounted! Aborting!\n"); + $log->logFatal('Failed to mark block as accounted! Aborted.'); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "Failed to mark block " . $aBlock['height'] . " as accounted"); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); + exit(1); } - - verbose("------------------------------------------------------------------------\n\n"); } } + +require_once('cron_end.inc.php'); +?> diff --git a/cronjobs/run-crons.sh b/cronjobs/run-crons.sh index c9dfd949..fcc66f70 100755 --- a/cronjobs/run-crons.sh +++ b/cronjobs/run-crons.sh @@ -12,14 +12,8 @@ PHP_BIN=$( which php ) # Path to PID file, needs to be writable by user running this PIDFILE='/tmp/mmcfe-ng-cron.pid' -# Location of our cronjobs, assume current directory -CRONHOME='.' - # List of cruns to execute -CRONS="findblock.php proportional_payout.php pps_payout.php blockupdate.php auto_payout.php tickerupdate.php notifications.php statistics.php" - -# Additional arguments to pass to cronjobs -CRONARGS="-v" +CRONS="findblock.php proportional_payout.php pplns_payout.php pps_payout.php blockupdate.php auto_payout.php tickerupdate.php notifications.php statistics.php archive_cleanup.php" # Output additional runtime information VERBOSE="0" @@ -30,6 +24,29 @@ VERBOSE="0" # # ################################################################ +# Overwrite some settings via command line arguments +while getopts "hvp:" opt; do + case "$opt" in + h|\?) + echo "Usage: $0 [-v] [-p PHP_BINARY]"; + exit 0 + ;; + v) VERBOSE=1 ;; + p) PHP_BIN=$OPTARG ;; + :) + echo "Option -$OPTARG requires an argument." >&2 + exit 1 + ;; + esac +done + +# Find scripts path +if [[ -L $0 ]]; then + CRONHOME=$( dirname $( readlink $0 ) ) +else + CRONHOME=$( dirname $0 ) +fi + # Change working director to CRONHOME if ! cd $CRONHOME 2>/dev/null; then echo "Unable to change to working directory \$CRONHOME: $CRONHOME" @@ -66,8 +83,8 @@ fi echo $PID > $PIDFILE for cron in $CRONS; do - [[ $VERBOSE == 1 ]] && echo "Running $cron, see output below for details" - $PHP_BIN $cron $CRONARGS + [[ $VERBOSE == 1 ]] && echo "Running $cron, check logfile for details" + $PHP_BIN $cron done # Remove pidfile diff --git a/cronjobs/shared.inc.php b/cronjobs/shared.inc.php index a0b1dc5d..f66be4f4 100644 --- a/cronjobs/shared.inc.php +++ b/cronjobs/shared.inc.php @@ -22,6 +22,13 @@ limitations under the License. // We need to find our include files so set this properly define("BASEPATH", "../public/"); + +/***************************************************** + * No need to change beyond this point * + *****************************************************/ +// Our cron name +$cron_name = basename($_SERVER['PHP_SELF'], '.php'); + // Our security check define("SECURITY", 1); @@ -31,16 +38,15 @@ require_once(BASEPATH . 'include/config/global.inc.php'); // We include all needed files here, even though our templates could load them themself require_once(INCLUDE_DIR . '/autoloader.inc.php'); -// Parse command line -$options = getopt("v"); -if (array_key_exists('v', $options)) { - define("VERBOSE", true); -} else { - define("VERBOSE", false); -} +// Load 3rd party logging library for running crons +$log = new KLogger ( 'logs/' . $cron_name . '.txt' , KLogger::INFO ); +$log->LogDebug('Starting ' . $cron_name); -// Command line cron functions only -function verbose($msg) { - if (VERBOSE) echo $msg; -} +// Load the start time for later runtime calculations for monitoring +$cron_start[$cron_name] = microtime(true); +// Mark cron as running for monitoring +$log->logDebug('Marking cronjob as running for monitoring'); +$monitoring->setStatus($cron_name . '_active', 'yesno', 1); +$monitoring->setStatus($cron_name . '_starttime', 'date', time()); +?> diff --git a/cronjobs/statistics.php b/cronjobs/statistics.php index 69c1db5f..3d54fd78 100755 --- a/cronjobs/statistics.php +++ b/cronjobs/statistics.php @@ -25,44 +25,38 @@ require_once('shared.inc.php'); // Fetch all cachable values but disable fetching from cache $statistics->setGetCache(false); -// Verbose output -verbose("Running statistical cache updates\n"); - // Since fetching from cache is disabled, overwrite our stats -verbose(" getRoundShares ..."); $start = microtime(true); if (!$statistics->getRoundShares()) - verbose(" update failed"); -verbose(" " . number_format(microtime(true) - $start, 2) . " seconds\n"); -verbose(" getTopContributors shares ..."); + $log->logError("getRoundShares update failed"); +$log->logInfo("getRoundShares update " . number_format(microtime(true) - $start, 2) . " seconds"); $start = microtime(true); if (!$statistics->getTopContributors('shares')) - verbose(" update failed"); -verbose(" " . number_format(microtime(true) - $start, 2) . " seconds\n"); -verbose(" getTopContributors hashes ..."); + $log->logError("getTopContributors shares update failed"); +$log->logInfo("getTopContributors shares " . number_format(microtime(true) - $start, 2) . " seconds"); $start = microtime(true); if (!$statistics->getTopContributors('hashes')) - verbose(" update failed"); -verbose(" " . number_format(microtime(true) - $start, 2) . " seconds\n"); -verbose(" getCurrentHashrate ..."); + $log->logError("getTopContributors hashes update failed"); +$log->logInfo("getTopContributors hashes " . number_format(microtime(true) - $start, 2) . " seconds"); $start = microtime(true); if (!$statistics->getCurrentHashrate()) - verbose(" update failed"); -verbose(" " . number_format(microtime(true) - $start, 2) . " seconds\n"); + $log->logError("getCurrentHashrate update failed"); +$log->logInfo("getCurrentHashrate " . number_format(microtime(true) - $start, 2) . " seconds"); +/* // Admin specific statistics, we cache the global query due to slowness -verbose(" getAllUserStats ..."); $start = microtime(true); if (!$statistics->getAllUserStats('%')) - verbose(" update failed"); -verbose(" " . number_format(microtime(true) - $start, 2) . " seconds\n"); + $log->logError("getAllUserStats update failed"); +$log->logInfo("getAllUserStats " . number_format(microtime(true) - $start, 2) . " seconds"); +*/ // Per user share statistics based on all shares submitted -verbose(" getAllUserShares ..."); $start = microtime(true); $aUserShares = $statistics->getAllUserShares(); -verbose(" " . number_format(microtime(true) - $start, 2) . " seconds"); +$log->logInfo("getAllUserShares " . number_format(microtime(true) - $start, 2) . " seconds"); foreach ($aUserShares as $aShares) { $memcache->setCache('getUserShares'. $aShares['id'], $aShares); } -verbose("\n"); + +require_once('cron_end.inc.php'); ?> diff --git a/cronjobs/tickerupdate.php b/cronjobs/tickerupdate.php index 26323738..875be2b2 100755 --- a/cronjobs/tickerupdate.php +++ b/cronjobs/tickerupdate.php @@ -25,14 +25,13 @@ require_once('shared.inc.php'); // Include additional file not set in autoloader require_once(CLASS_DIR . '/tools.class.php'); -verbose("Running scheduled updates\n"); -verbose(" Price API Call ... "); if ($price = $tools->getPrice()) { - verbose("found $price as price\n"); + $log->logInfo("Price update: found $price as price"); if (!$setting->setValue('price', $price)) - verbose("unable to update value in settings table\n"); + $log->logError("unable to update value in settings table"); } else { - verbose("failed to fetch API data: " . $tools->getError() . "\n"); + $log->logFatal("failed to fetch API data: " . $tools->getError()); } +require_once('cron_end.inc.php'); ?> diff --git a/public/include/autoloader.inc.php b/public/include/autoloader.inc.php index e3a4534b..929e2f85 100644 --- a/public/include/autoloader.inc.php +++ b/public/include/autoloader.inc.php @@ -17,18 +17,25 @@ require_once(CLASS_DIR . '/debug.class.php'); require_once(CLASS_DIR . '/bitcoin.class.php'); require_once(CLASS_DIR . '/statscache.class.php'); require_once(CLASS_DIR . '/bitcoinwrapper.class.php'); +require_once(INCLUDE_DIR . '/lib/KLogger.php'); require_once(INCLUDE_DIR . '/database.inc.php'); require_once(INCLUDE_DIR . '/smarty.inc.php'); // Load classes that need the above as dependencies require_once(CLASS_DIR . '/base.class.php'); +require_once(CLASS_DIR . '/api.class.php'); +require_once(CLASS_DIR . '/mail.class.php'); +require_once(CLASS_DIR . '/tokentype.class.php'); +require_once(CLASS_DIR . '/token.class.php'); require_once(CLASS_DIR . '/block.class.php'); require_once(CLASS_DIR . '/setting.class.php'); +require_once(CLASS_DIR . '/monitoring.class.php'); require_once(CLASS_DIR . '/user.class.php'); +require_once(CLASS_DIR . '/invitation.class.php'); require_once(CLASS_DIR . '/share.class.php'); require_once(CLASS_DIR . '/worker.class.php'); require_once(CLASS_DIR . '/statistics.class.php'); require_once(CLASS_DIR . '/transaction.class.php'); -require_once(CLASS_DIR . '/mail.class.php'); require_once(CLASS_DIR . '/notification.class.php'); require_once(CLASS_DIR . '/news.class.php'); require_once(INCLUDE_DIR . '/lib/Michelf/Markdown.php'); +require_once(INCLUDE_DIR . '/lib/scrypt.php'); diff --git a/public/include/classes/api.class.php b/public/include/classes/api.class.php new file mode 100644 index 00000000..36374162 --- /dev/null +++ b/public/include/classes/api.class.php @@ -0,0 +1,23 @@ +config['website']['api']['disabled']) { + return true; + } else { + if ($error == true) { + header('HTTP/1.1 501 Not implemented'); + die('501 Not implemented'); + } + } + } +} + +$api = new Api(); +$api->setConfig($config); diff --git a/public/include/classes/base.class.php b/public/include/classes/base.class.php index 6b3c2389..836ee3b3 100644 --- a/public/include/classes/base.class.php +++ b/public/include/classes/base.class.php @@ -15,6 +15,9 @@ class Base { public function setMysql($mysqli) { $this->mysqli = $mysqli; } + public function setMail($mail) { + $this->mail = $mail; + } public function setSmarty($smarty) { $this->smarty = $smarty; } @@ -24,6 +27,12 @@ class Base { public function setConfig($config) { $this->config = $config; } + public function setToken($token) { + $this->token = $token; + } + public function setTokenType($tokentype) { + $this->tokentype = $tokentype; + } public function setErrorMessage($msg) { $this->sError = $msg; } diff --git a/public/include/classes/block.class.php b/public/include/classes/block.class.php index 743ce67e..8ce985d1 100644 --- a/public/include/classes/block.class.php +++ b/public/include/classes/block.class.php @@ -43,6 +43,18 @@ class Block { return false; } + /** + * Get a specific block, by block height + * @param height int Block Height + * @return data array Block information from DB + **/ + public function getBlock($height) { + $stmt = $this->mysqli->prepare("SELECT * FROM $this->table WHERE height = ? LIMIT 1"); + if ($this->checkStmt($stmt) && $stmt->bind_param('i', $height) && $stmt->execute() && $result = $stmt->get_result()) + return $result->fetch_assoc(); + return false; + } + /** * Get our last, highest share ID inserted for a block * @param none @@ -55,6 +67,18 @@ class Block { return false; } + /** + * Fetch all blocks without a share ID + * @param order string Sort order, default ASC + * @return data array Array with database fields as keys + **/ + public function getAllUnsetShareId($order='ASC') { + $stmt = $this->mysqli->prepare("SELECT * FROM $this->table WHERE ISNULL(share_id) ORDER BY height $order"); + if ($this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result()) + return $result->fetch_all(MYSQLI_ASSOC); + return false; + } + /** * Fetch all unaccounted blocks * @param order string Sort order, default ASC @@ -62,12 +86,32 @@ class Block { **/ public function getAllUnaccounted($order='ASC') { $stmt = $this->mysqli->prepare("SELECT * FROM $this->table WHERE accounted = 0 ORDER BY height $order"); - if ($this->checkStmt($stmt)) { - $stmt->execute(); - $result = $stmt->get_result(); - $stmt->close(); + if ($this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result()) return $result->fetch_all(MYSQLI_ASSOC); - } + return false; + } + + /** + * Get total amount of blocks in our table + * @param noone + * @return data int Count of rows + **/ + public function getBlockCount() { + $stmt = $this->mysqli->prepare("SELECT COUNT(id) AS blocks FROM $this->table"); + if ($this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result()) + return (int)$result->fetch_object()->blocks; + return false; + } + + /** + * Fetch our average share count for the past N blocks + * @param limit int Maximum blocks to check + * @return data float Float value of average shares + **/ + public function getAvgBlockShares($limit=1) { + $stmt = $this->mysqli->prepare("SELECT AVG(x.shares) AS average FROM (SELECT shares FROM $this->table ORDER BY height DESC LIMIT ?) AS x"); + if ($this->checkStmt($stmt) && $stmt->bind_param('i', $limit) && $stmt->execute() && $result = $stmt->get_result()) + return (float)$result->fetch_object()->average; return false; } @@ -77,14 +121,9 @@ class Block { * @return data array Array with database fields as keys **/ public function getAllUnconfirmed($confirmations='120') { - $stmt = $this->mysqli->prepare("SELECT id, blockhash, confirmations FROM $this->table WHERE confirmations < ? AND confirmations > -1"); - if ($this->checkStmt($stmt)) { - $stmt->bind_param("i", $confirmations); - $stmt->execute(); - $result = $stmt->get_result(); - $stmt->close(); + $stmt = $this->mysqli->prepare("SELECT id, height, blockhash, confirmations FROM $this->table WHERE confirmations < ? AND confirmations > -1"); + if ($this->checkStmt($stmt) && $stmt->bind_param("i", $confirmations) && $stmt->execute() && $result = $stmt->get_result()) return $result->fetch_all(MYSQLI_ASSOC); - } return false; } diff --git a/public/include/classes/invitation.class.php b/public/include/classes/invitation.class.php new file mode 100644 index 00000000..822dfef3 --- /dev/null +++ b/public/include/classes/invitation.class.php @@ -0,0 +1,146 @@ +debug->append("STA " . __METHOD__, 4); + $stmt = $this->mysqli->prepare("SELECT * FROM $this->table WHERE account_id = ?"); + if ($stmt && $stmt->bind_param('i', $account_id) && $stmt->execute() && $result = $stmt->get_result()) + return $result->fetch_all(MYSQLI_ASSOC); + $this->setErrorMessage('Unable to fetch invitiations send from your account'); + $this->debug->append('Failed to fetch invitations from database: ' . $this->mysqli->errro); + return false; + } + + /** + * Count invitations sent by an account_id + * @param account_id integer Account ID + * @return mixes Integer on success, boolean on failure + **/ + public function getCountInvitations($account_id) { + $this->debug->append("STA " . __METHOD__, 4); + $stmt = $this->mysqli->prepare("SELECT count(id) AS total FROM $this->table WHERE account_id = ?"); + if ($stmt && $stmt->bind_param('i', $account_id) && $stmt->execute() && $stmt->bind_result($total) && $stmt->fetch()) + return $total; + $this->setErrorMessage('Unable to fetch invitiations send from your account'); + $this->debug->append('Failed to fetch invitations from database: ' . $this->mysqli->errro); + return false; + } + + /** + * Get a specific invitation by email address + * Used to ensure no invitation was already sent + * @param strEmail string Email address to check for + * @return bool boolean true of ralse + **/ + public function getByEmail($strEmail) { + $this->debug->append("STA " . __METHOD__, 4); + return $this->getSingle($strEmail, 'id', 'email', 's'); + } + + /** + * Get a specific token by token ID + * Used to match an invitation against a token + * @param token_id integer Token ID stored in invitation + * @return data mixed Invitation ID on success, false on error + **/ + public function getByTokenId($token_id) { + $this->debug->append("STA " . __METHOD__, 4); + return $this->getSingle($token_id, 'id', 'token_id'); + } + + /** + * Set an invitation as activated by the invitee + * @param token_id integer Token to activate + * @return bool boolean true or false + **/ + public function setActivated($token_id) { + if (!$iInvitationId = $this->getByTokenId($token_id)) { + $this->setErrorMessage('Unable to convert token ID to invitation ID'); + return false; + } + $field = array('name' => 'is_activated', 'type' => 'i', 'value' => 1); + return $this->updateSingle($iInvitationId, $field); + } + + /** + * Insert a new invitation to the database + * @param account_id integer Account ID to bind the invitation to + * @param email string Email address the invite was sent to + * @param token_id integer Token ID used during invitation + * @return bool boolean True of false + **/ + public function createInvitation($account_id, $email, $token_id) { + $this->debug->append("STA " . __METHOD__, 4); + $stmt = $this->mysqli->prepare("INSERT INTO $this->table ( account_id, email, token_id ) VALUES ( ?, ?, ?)"); + if ($stmt && $stmt->bind_param('isi', $account_id, $email, $token_id) && $stmt->execute()) + return true; + return false; + } + /** + * Send an invitation out to a user + * Uses the mail class to send mails + * @param account_id integer Sending account ID + * @param aData array Data array including mail information + * @return bool boolean True or false + **/ + public function sendInvitation($account_id, $aData) { + $this->debug->append("STA " . __METHOD__, 4); + // Check data input + if (empty($aData['email']) || !filter_var($aData['email'], FILTER_VALIDATE_EMAIL)) { + $this->setErrorMessage( 'Invalid e-mail address' ); + return false; + } + if (preg_match('/[^a-z_\.\!\?\-0-9 ]/i', $aData['message'])) { + $this->setErrorMessage('Message may only contain alphanumeric characters'); + return false; + } + // Ensure this invitation does not exist yet nor do we have an account with that email + if ($this->user->getEmail($aData['email'])) { + $this->setErrorMessage('This email is already registered as an account'); + return false; + } + if ($this->getByEmail($aData['email'])) { + $this->setErrorMessage('A pending invitation for this address already exists'); + return false; + } + if (!$aData['token'] = $this->token->createToken('invitation', $account_id)) { + $this->setErrorMessage('Unable to generate invitation token: ' . $this->token->getError()); + return false; + } + $aData['username'] = $this->user->getUserName($account_id); + $aData['subject'] = 'Pending Invitation'; + if ($this->mail->sendMail('invitations/body', $aData)) { + $aToken = $this->token->getToken($aData['token']); + if (!$this->createInvitation($account_id, $aData['email'], $aToken['id'])) { + $this->setErrorMessage('Unable to create invitation record'); + return false; + } + return true; + } else { + $this->setErrorMessage('Unable to send email to recipient'); + } + $this->setErrorMessage('Unable to send invitation'); + return false; + } +} + +// Instantiate class +$invitation = new invitation(); +$invitation->setDebug($debug); +$invitation->setMysql($mysqli); +$invitation->setMail($mail); +$invitation->setUser($user); +$invitation->setToken($oToken); +$invitation->setConfig($config); + +?> diff --git a/public/include/classes/monitoring.class.php b/public/include/classes/monitoring.class.php new file mode 100644 index 00000000..ff69007f --- /dev/null +++ b/public/include/classes/monitoring.class.php @@ -0,0 +1,49 @@ +debug = $debug; + $this->mysqli = $mysqli; + $this->table = 'monitoring'; + } + + /** + * Fetch a value from our table + * @param name string Setting name + * @return value string Value + **/ + public function getStatus($name) { + $query = $this->mysqli->prepare("SELECT * FROM $this->table WHERE name = ? LIMIT 1"); + if ($query && $query->bind_param('s', $name) && $query->execute() && $result = $query->get_result()) { + return $result->fetch_assoc(); + } else { + $this->debug->append("Failed to fetch variable $name from $this->table"); + return false; + } + return $value; + } + + /** + * Insert or update a setting + * @param name string Name of the variable + * @param value string Variable value + * @return bool + **/ + public function setStatus($name, $type, $value) { + $stmt = $this->mysqli->prepare(" + INSERT INTO $this->table (name, type, value) + VALUES (?, ?, ?) + ON DUPLICATE KEY UPDATE value = ? + "); + if ($stmt && $stmt->bind_param('ssss', $name, $type, $value, $value) && $stmt->execute()) + return true; + $this->debug->append("Failed to set $name to $value"); + return false; + } +} + +$monitoring = new Monitoring($debug, $mysqli); diff --git a/public/include/classes/share.class.php b/public/include/classes/share.class.php index 6653814a..7bcc6d14 100644 --- a/public/include/classes/share.class.php +++ b/public/include/classes/share.class.php @@ -13,9 +13,12 @@ class Share { // This defines each share public $rem_host, $username, $our_result, $upstream_result, $reason, $solution, $time; - public function __construct($debug, $mysqli, $salt) { + public function __construct($debug, $mysqli, $user, $block, $config) { $this->debug = $debug; $this->mysqli = $mysqli; + $this->user = $user; + $this->config = $config; + $this->block = $block; $this->debug->append("Instantiated Share class", 2); } @@ -70,7 +73,7 @@ class Share { count(id) as total FROM $this->table WHERE our_result = 'Y' - AND id BETWEEN ? AND ? + AND id > ? AND id <= ? "); if ($this->checkStmt($stmt)) { $stmt->bind_param('ii', $previous_upstream, $current_upstream); @@ -86,47 +89,94 @@ class Share { * Fetch all shares grouped by accounts to count share per account * @param previous_upstream int Previous found share accepted by upstream to limit results * @param current_upstream int Current upstream accepted share + * @param limit int Limit to this amount of shares for PPLNS * @return data array username, valid and invalid shares from account **/ public function getSharesForAccounts($previous_upstream=0, $current_upstream) { - $stmt = $this->mysqli->prepare("SELECT - a.id, - validT.account AS username, - sum(validT.valid) as valid, - IFNULL(sum(invalidT.invalid),0) as invalid - FROM - ( - SELECT DISTINCT - SUBSTRING_INDEX( `username` , '.', 1 ) as account, - COUNT(id) AS valid - FROM $this->table - WHERE id BETWEEN ? AND ? - AND our_result = 'Y' - GROUP BY account - ) validT - LEFT JOIN - ( - SELECT DISTINCT - SUBSTRING_INDEX( `username` , '.', 1 ) as account, - COUNT(id) AS invalid - FROM $this->table - WHERE id BETWEEN ? AND ? - AND our_result = 'N' - GROUP BY account - ) invalidT - ON validT.account = invalidT.account - INNER JOIN accounts a ON a.username = validT.account - GROUP BY a.username DESC"); - if ($this->checkStmt($stmt)) { - $stmt->bind_param('iiii', $previous_upstream, $current_upstream, $previous_upstream, $current_upstream); - $stmt->execute(); - $result = $stmt->get_result(); - $stmt->close(); + $stmt = $this->mysqli->prepare(" + SELECT + a.id, + SUBSTRING_INDEX( s.username , '.', 1 ) as username, + IFNULL(SUM(IF(our_result='Y', 1, 0)), 0) AS valid, + IFNULL(SUM(IF(our_result='N', 1, 0)), 0) AS invalid + FROM $this->table AS s + LEFT JOIN " . $this->user->getTableName() . " AS a + ON a.username = SUBSTRING_INDEX( s.username , '.', 1 ) + WHERE s.id > ? AND s.id <= ? + GROUP BY username DESC + "); + if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $previous_upstream, $current_upstream) && $stmt->execute() && $result = $stmt->get_result()) return $result->fetch_all(MYSQLI_ASSOC); + return false; + } + + /** + * Fetch the highest available share ID from archive + **/ + function getMaxArchiveShareId() { + $stmt = $this->mysqli->prepare(" + SELECT MAX(share_id) AS share_id FROM $this->tableArchive + "); + if ($this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result()) + return $result->fetch_object()->share_id; + return false; + } + + /** + * We need a certain amount of valid archived shares + * param left int Left/lowest share ID + * param right int Right/highest share ID + * return array data Returns an array with usernames as keys for easy access + **/ + function getArchiveShares($iCount) { + $iMinId = $this->getMaxArchiveShareId() - $iCount; + $iMaxId = $this->getMaxArchiveShareId(); + $stmt = $this->mysqli->prepare(" + SELECT + a.id, + SUBSTRING_INDEX( s.username , '.', 1 ) as account, + IFNULL(SUM(IF(our_result='Y', 1, 0)), 0) AS valid, + IFNULL(SUM(IF(our_result='N', 1, 0)), 0) AS invalid + FROM $this->tableArchive AS s + LEFT JOIN " . $this->user->getTableName() . " AS a + ON a.username = SUBSTRING_INDEX( s.username , '.', 1 ) + WHERE s.share_id > ? AND s.share_id <= ? + GROUP BY account DESC"); + if ($this->checkStmt($stmt) && $stmt->bind_param("ii", $iMinId, $iMaxId) && $stmt->execute() && $result = $stmt->get_result()) { + $aData = NULL; + while ($row = $result->fetch_assoc()) { + $aData[$row['account']] = $row; + } + if (is_array($aData)) return $aData; } return false; } + /** + * We keep shares only up to a certain point + * This can be configured by the user. + * @return return bool true or false + **/ + public function purgeArchive() { + if ($this->config['payout_system'] == 'pplns') { + // Fetch our last block so we can go back configured rounds + $aLastBlock = $this->block->getLast(); + // Fetch the block we need to find the share_id + $aBlock = $this->block->getBlock($aLastBlock['height'] - $this->config['archive']['maxrounds']); + // Now that we know our block, remove those shares + $stmt = $this->mysqli->prepare("DELETE FROM $this->tableArchive WHERE block_id < ? AND time < DATE_SUB(now(), INTERVAL ? MINUTE)"); + if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $aBlock['id'], $this->config['archive']['maxage']) && $stmt->execute()) + return true; + } else { + // We are not running pplns, so we just need to keep shares of the past minutes + $stmt = $this->mysqli->prepare("DELETE FROM $this->tableArchive WHERE time < DATE_SUB(now(), INTERVAL ? MINUTE)"); + if ($this->checkStmt($stmt) && $stmt->bind_param('i', $this->config['archive']['maxage']) && $stmt->execute()) + return true; + } + // Catchall + return false; + } + /** * Move accounted shares to archive table, this step is optional * @param previous_upstream int Previous found share accepted by upstream to limit results @@ -135,10 +185,11 @@ class Share { * @return bool **/ public function moveArchive($current_upstream, $block_id, $previous_upstream=0) { - $archive_stmt = $this->mysqli->prepare("INSERT INTO $this->tableArchive (share_id, username, our_result, upstream_result, block_id, time) - SELECT id, username, our_result, upstream_result, ?, time - FROM $this->table - WHERE id BETWEEN ? AND ?"); + $archive_stmt = $this->mysqli->prepare(" + INSERT INTO $this->tableArchive (share_id, username, our_result, upstream_result, block_id, time) + SELECT id, username, our_result, upstream_result, ?, time + FROM $this->table + WHERE id > ? AND id <= ?"); if ($this->checkStmt($archive_stmt) && $archive_stmt->bind_param('iii', $block_id, $previous_upstream, $current_upstream) && $archive_stmt->execute()) { $archive_stmt->close(); return true; @@ -148,7 +199,7 @@ class Share { } public function deleteAccountedShares($current_upstream, $previous_upstream=0) { - $stmt = $this->mysqli->prepare("DELETE FROM $this->table WHERE id BETWEEN ? AND ?"); + $stmt = $this->mysqli->prepare("DELETE FROM $this->table WHERE id > ? AND id <= ?"); if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $previous_upstream, $current_upstream) && $stmt->execute()) return true; // Catchall @@ -177,7 +228,48 @@ class Share { * @param last int Skips all shares up to last to find new share * @return bool **/ - public function setUpstream($last=0, $time=0) { + public function setUpstream($aBlock, $last=0) { + // Many use stratum, so we create our stratum check first + $version = pack("I*", sprintf('%08d', $aBlock['version'])); + $previousblockhash = pack("H*", swapEndian($aBlock['previousblockhash'])); + $merkleroot = pack("H*", swapEndian($aBlock['merkleroot']) ); + $time = pack("I*", $aBlock['time']); + $bits = pack("H*", swapEndian($aBlock['bits'])); + $nonce = pack("I*", $aBlock['nonce']); + $header_bin = $version . $previousblockhash . $merkleroot . $time . $bits . $nonce; + $header_hex = implode(unpack("H*", $header_bin)); + + // Stratum supported blockhash solution entry + $stmt = $this->mysqli->prepare("SELECT SUBSTRING_INDEX( `username` , '.', 1 ) AS account, id FROM $this->table WHERE solution = ? LIMIT 1"); + if ($this->checkStmt($stmt) && $stmt->bind_param('s', $aBlock['hash']) && $stmt->execute() && $result = $stmt->get_result()) { + $this->oUpstream = $result->fetch_object(); + $this->share_type = 'startum_blockhash'; + if (!empty($this->oUpstream->account) && is_int($this->oUpstream->id)) + return true; + } + + // Stratum scrypt hash check + $scrypt_hash = swapEndian(bin2hex(Scrypt::calc($header_bin, $header_bin, 1024, 1, 1, 32))); + $stmt = $this->mysqli->prepare("SELECT SUBSTRING_INDEX( `username` , '.', 1 ) AS account, id FROM $this->table WHERE solution = ? LIMIT 1"); + if ($this->checkStmt($stmt) && $stmt->bind_param('s', $scrypt_hash) && $stmt->execute() && $result = $stmt->get_result()) { + $this->oUpstream = $result->fetch_object(); + $this->share_type = 'startum_solution'; + if (!empty($this->oUpstream->account) && is_int($this->oUpstream->id)) + return true; + } + + // Failed to fetch via startum solution, try pushpoold + // Fallback to pushpoold solution type + $ppheader = sprintf('%08d', $aBlock['version']) . word_reverse($aBlock['previousblockhash']) . word_reverse($aBlock['merkleroot']) . dechex($aBlock['time']) . $aBlock['bits'] . dechex($aBlock['nonce']); + $stmt = $this->mysqli->prepare("SELECT SUBSTRING_INDEX( `username` , '.', 1 ) AS account, id FROM $this->table WHERE solution LIKE CONCAT(?, '%') LIMIT 1"); + if ($this->checkStmt($stmt) && $stmt->bind_param('s', $ppheader) && $stmt->execute() && $result = $stmt->get_result()) { + $this->oUpstream = $result->fetch_object(); + $this->share_type = 'pp_solution'; + if (!empty($this->oUpstream->account) && is_int($this->oUpstream->id)) + return true; + } + + // Still no match, try upstream result with timerange $stmt = $this->mysqli->prepare(" SELECT SUBSTRING_INDEX( `username` , '.', 1 ) AS account, id @@ -185,9 +277,27 @@ class Share { WHERE upstream_result = 'Y' AND id > ? AND UNIX_TIMESTAMP(time) >= ? + AND UNIX_TIMESTAMP(time) <= ( ? + 60 ) ORDER BY id ASC LIMIT 1"); - if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $last, $time) && $stmt->execute() && $result = $stmt->get_result()) { + if ($this->checkStmt($stmt) && $stmt->bind_param('iii', $last, $aBlock['time'], $aBlock['time']) && $stmt->execute() && $result = $stmt->get_result()) { $this->oUpstream = $result->fetch_object(); + $this->share_type = 'upstream_share'; + if (!empty($this->oUpstream->account) && is_int($this->oUpstream->id)) + return true; + } + + // We failed again, now we take ANY result matching the timestamp + $stmt = $this->mysqli->prepare(" + SELECT + SUBSTRING_INDEX( `username` , '.', 1 ) AS account, id + FROM $this->table + WHERE our_result = 'Y' + AND id > ? + AND UNIX_TIMESTAMP(time) >= ? + ORDER BY id ASC LIMIT 1"); + if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $last, $aBlock['time']) && $stmt->execute() && $result = $stmt->get_result()) { + $this->oUpstream = $result->fetch_object(); + $this->share_type = 'any_share'; if (!empty($this->oUpstream->account) && is_int($this->oUpstream->id)) return true; } @@ -208,4 +318,4 @@ class Share { } } -$share = new Share($debug, $mysqli, SALT); +$share = new Share($debug, $mysqli, $user, $block, $config); diff --git a/public/include/classes/statistics.class.php b/public/include/classes/statistics.class.php index 2ca5bfd1..e2d1848a 100644 --- a/public/include/classes/statistics.class.php +++ b/public/include/classes/statistics.class.php @@ -61,7 +61,10 @@ class Statistics { $this->debug->append("STA " . __METHOD__, 4); if ($data = $this->memcache->get(__FUNCTION__ . $limit)) return $data; $stmt = $this->mysqli->prepare(" - SELECT b.*, a.username as finder + SELECT + b.*, + a.username AS finder, + a.is_anonymous AS is_anonymous FROM " . $this->block->getTableName() . " AS b LEFT JOIN " . $this->user->getTableName() . " AS a ON b.account_id = a.id @@ -327,9 +330,13 @@ class Statistics { case 'shares': $stmt = $this->mysqli->prepare(" SELECT - COUNT(id) AS shares, - SUBSTRING_INDEX( username, '.', 1 ) AS account - FROM " . $this->share->getTableName() . " + a.donate_percent AS donate_percent, + a.is_anonymous AS is_anonymous, + COUNT(s.id) AS shares, + SUBSTRING_INDEX( s.username, '.', 1 ) AS account + FROM " . $this->share->getTableName() . " AS s + LEFT JOIN " . $this->user->getTableName() . " AS a + ON SUBSTRING_INDEX( s.username, '.', 1 ) = a.username WHERE our_result = 'Y' GROUP BY account ORDER BY shares DESC @@ -343,14 +350,18 @@ class Statistics { case 'hashes': $stmt = $this->mysqli->prepare(" SELECT - IFNULL(ROUND(COUNT(id) * POW(2," . $this->config['difficulty'] . ")/600/1000, 2), 0) AS hashrate, - SUBSTRING_INDEX( username, '.', 1 ) AS account + a.donate_percent AS donate_percent, + a.is_anonymous AS is_anonymous, + IFNULL(ROUND(COUNT(t1.id) * POW(2," . $this->config['difficulty'] . ")/600/1000, 2), 0) AS hashrate, + SUBSTRING_INDEX( t1.username, '.', 1 ) AS account FROM ( SELECT id, username FROM " . $this->share->getTableName() . " WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE) AND our_result = 'Y' UNION SELECT id, username FROM " . $this->share->getArchiveTableName() ." WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE) AND our_result = 'Y' ) AS t1 + LEFT JOIN " . $this->user->getTableName() . " AS a + ON SUBSTRING_INDEX( t1.username, '.', 1 ) = a.username GROUP BY account ORDER BY hashrate DESC LIMIT ?"); if ($this->checkStmt($stmt) && $stmt->bind_param("i", $limit) && $stmt->execute() && $result = $stmt->get_result()) @@ -374,12 +385,22 @@ class Statistics { ROUND(COUNT(s.id) * POW(2, " . $this->config['difficulty'] . ") / 3600 / 1000) AS hashrate, HOUR(s.time) AS hour FROM " . $this->share->getTableName() . " AS s, accounts AS a + WHERE time < NOW() - INTERVAL 1 HOUR + AND time > NOW() - INTERVAL 25 HOUR + AND a.username = SUBSTRING_INDEX( s.username, '.', 1 ) + AND a.id = ? + GROUP BY HOUR(time) + UNION ALL + SELECT + ROUND(COUNT(s.id) * POW(2, " . $this->config['difficulty'] . ") / 3600 / 1000) AS hashrate, + HOUR(s.time) AS hour + FROM " . $this->share->getArchiveTableName() . " AS s, accounts AS a WHERE time < NOW() - INTERVAL 1 HOUR AND time > NOW() - INTERVAL 25 HOUR AND a.username = SUBSTRING_INDEX( s.username, '.', 1 ) AND a.id = ? GROUP BY HOUR(time)"); - if ($this->checkStmt($stmt) && $stmt->bind_param("i", $account_id) && $stmt->execute() && $result = $stmt->get_result()) { + if ($this->checkStmt($stmt) && $stmt->bind_param('ii', $account_id, $account_id) && $stmt->execute() && $result = $stmt->get_result()) { $aData = array(); while ($row = $result->fetch_assoc()) { $aData[$row['hour']] = $row['hashrate']; @@ -404,6 +425,14 @@ class Statistics { IFNULL(ROUND(COUNT(s.id) * POW(2, " . $this->config['difficulty'] . ") / 3600 / 1000), 0) AS hashrate, HOUR(s.time) AS hour FROM " . $this->share->getTableName() . " AS s + WHERE time < NOW() - INTERVAL 1 HOUR + AND time > NOW() - INTERVAL 25 HOUR + GROUP BY HOUR(time) + UNION ALL + SELECT + IFNULL(ROUND(COUNT(s.id) * POW(2, " . $this->config['difficulty'] . ") / 3600 / 1000), 0) AS hashrate, + HOUR(s.time) AS hour + FROM " . $this->share->getArchiveTableName() . " AS s WHERE time < NOW() - INTERVAL 1 HOUR AND time > NOW() - INTERVAL 25 HOUR GROUP BY HOUR(time)"); diff --git a/public/include/classes/statscache.class.php b/public/include/classes/statscache.class.php index e84386da..e64d5d02 100644 --- a/public/include/classes/statscache.class.php +++ b/public/include/classes/statscache.class.php @@ -9,12 +9,17 @@ if (!defined('SECURITY')) * Can be enabled or disabled through site configuration * Also sets a default time if no time is passed to it to enforce caching **/ -class StatsCache extends Memcached { +class StatsCache { + private $cache; + public function __construct($config, $debug) { $this->config = $config; $this->debug = $debug; - if (! $config['memcache']['enabled'] ) $this->debug->append("Not storing any values in memcache"); - return parent::__construct(); + if (! $config['memcache']['enabled'] ) { + $this->debug->append("Not storing any values in memcache"); + } else { + $this->cache = new Memcached(); + } } /** @@ -26,7 +31,7 @@ class StatsCache extends Memcached { if (empty($expiration)) $expiration = $this->config['memcache']['expiration'] + rand( -$this->config['memcache']['splay'], $this->config['memcache']['splay']); $this->debug->append("Storing " . $this->config['memcache']['keyprefix'] . "$key with expiration $expiration", 3); - return parent::set($this->config['memcache']['keyprefix'] . $key, $value, $expiration); + return $this->cache->set($this->config['memcache']['keyprefix'] . $key, $value, $expiration); } /** @@ -36,7 +41,7 @@ class StatsCache extends Memcached { public function get($key, $cache_cb = NULL, &$cas_token = NULL) { if (! $this->config['memcache']['enabled']) return false; $this->debug->append("Trying to fetch key " . $this->config['memcache']['keyprefix'] . "$key from cache", 3); - if ($data = parent::get($this->config['memcache']['keyprefix'].$key)) { + if ($data = $this->cache->get($this->config['memcache']['keyprefix'].$key)) { $this->debug->append("Found key in cache", 3); return $data; } else { @@ -55,7 +60,15 @@ class StatsCache extends Memcached { if ($this->config['memcache']['enabled']) $this->set($key, $data, $expiration); return $data; } - + + /** + * This method is invoked if the called method was not realised in this class + **/ + public function __call($name, $arguments) { + if (! $this->config['memcache']['enabled']) return false; + //Invoke method $name of $this->cache class with array of $arguments + return call_user_func_array(array($this->cache, $name), $arguments); + } } $memcache = new StatsCache($config, $debug); diff --git a/public/include/classes/token.class.php b/public/include/classes/token.class.php new file mode 100644 index 00000000..c65da13c --- /dev/null +++ b/public/include/classes/token.class.php @@ -0,0 +1,60 @@ +mysqli->prepare("SELECT * FROM $this->table WHERE token = ? LIMIT 1"); + if ($stmt && $stmt->bind_param('s', $strToken) && $stmt->execute() && $result = $stmt->get_result()) + return $result->fetch_assoc(); + return false; + } + + /** + * Insert a new token + * @param name string Name of the variable + * @param value string Variable value + * @return mixed Token string on success, false on failure + **/ + public function createToken($strType, $account_id=NULL) { + $strToken = hash('sha256', $account_id.$strType.microtime()); + if (!$iToken_id = $this->tokentype->getTypeId($strType)) { + $this->setErrorMessage('Invalid token type: ' . $strType); + return false; + } + $stmt = $this->mysqli->prepare(" + INSERT INTO $this->table (token, type, account_id) + VALUES (?, ?, ?) + "); + if ($stmt && $stmt->bind_param('sii', $strToken, $iToken_id, $account_id) && $stmt->execute()) + return $strToken; + $this->setErrorMessage('Unable to create new token'); + $this->debug->append('Failed to create new token in database: ' . $this->mysqli->error); + return false; + } + + /** + * Delete a used token + * @param token string Token name + * @return bool + **/ + public function deleteToken($token) { + $stmt = $this->mysqli->prepare("DELETE FROM $this->table WHERE token = ? LIMIT 1"); + if ($stmt && $stmt->bind_param('s', $token) && $stmt->execute()) + return true; + return false; + } +} + +$oToken = new Token(); +$oToken->setDebug($debug); +$oToken->setMysql($mysqli); +$oToken->setTokenType($tokentype); diff --git a/public/include/classes/tokentype.class.php b/public/include/classes/tokentype.class.php new file mode 100644 index 00000000..d33356cb --- /dev/null +++ b/public/include/classes/tokentype.class.php @@ -0,0 +1,21 @@ +getSingle($strName, 'id', 'name', 's'); + } +} + +$tokentype = new Token_Type(); +$tokentype->setDebug($debug); +$tokentype->setMysql($mysqli); diff --git a/public/include/classes/transaction.class.php b/public/include/classes/transaction.class.php index 2d65657b..2cbe4312 100644 --- a/public/include/classes/transaction.class.php +++ b/public/include/classes/transaction.class.php @@ -48,35 +48,6 @@ class Transaction { return false; } - /** - * Sometimes transactions become orphans when a block associated to them is orphaned - * Updates the transaction types to Orphan_ - * @param block_id int Orphaned block ID - * @return bool - **/ - public function setOrphan($block_id) { - $this->debug->append("STA " . __METHOD__, 4); - $aOrphans = array( - 'Credit' => 'Orphan_Credit', - 'Fee' => 'Orphan_Fee', - 'Donation' => 'Orphan_Donation', - 'Bonus' => 'Orphan_Bonus' - ); - foreach ($aOrphans as $from => $to) { - $stmt = $this->mysqli->prepare(" - UPDATE $this->table - SET type = '$to' - WHERE type = '$from' - AND block_id = ? - "); - if (!($this->checkStmt($stmt) && $stmt->bind_param('i', $block_id) && $stmt->execute())) { - $this->debug->append("Failed to set orphan $from => $to transactions for $block_id"); - return false; - } - } - return true; - } - /** * Get all transactions from start for account_id * @param account_id int Account ID @@ -156,6 +127,7 @@ class Transaction { SELECT SUM(t.amount) AS donation, a.username AS username, + a.is_anonymous AS is_anonymous, a.donate_percent AS donate_percent FROM $this->table AS t LEFT JOIN " . $this->user->getTableName() . " AS a @@ -227,7 +199,8 @@ class Transaction { $stmt = $this->mysqli->prepare(" SELECT ROUND(IFNULL(t1.credit, 0) - IFNULL(t2.debit, 0) - IFNULL(t3.other, 0), 8) AS confirmed, - ROUND(IFNULL(t4.credit, 0) - IFNULL(t5.other, 0), 8) AS unconfirmed + ROUND(IFNULL(t4.credit, 0) - IFNULL(t5.other, 0), 8) AS unconfirmed, + ROUND(IFNULL(t6.credit, 0) - IFNULL(t7.other, 0), 8) AS orphaned FROM ( SELECT sum(t.amount) AS credit @@ -262,7 +235,7 @@ class Transaction { FROM $this->table AS t LEFT JOIN " . $this->block->getTableName() . " AS b ON t.block_id = b.id WHERE - t.type IN ('Credit','Bonus') AND b.confirmations < ? + t.type IN ('Credit','Bonus') AND b.confirmations < ? AND b.confirmations >= 0 AND t.account_id = ? ) AS t4, ( @@ -271,13 +244,31 @@ class Transaction { LEFT JOIN " . $this->block->getTableName() . " AS b ON t.block_id = b.id WHERE ( - t.type IN ('Donation','Fee') AND b.confirmations < ? + t.type IN ('Donation','Fee') AND b.confirmations < ? AND b.confirmations >= 0 ) AND t.account_id = ? - ) AS t5 + ) AS t5, + ( + SELECT sum(t.amount) AS credit + FROM $this->table AS t + LEFT JOIN " . $this->block->getTableName() . " AS b ON t.block_id = b.id + WHERE + t.type IN ('Credit','Bonus') AND b.confirmations = -1 + AND t.account_id = ? + ) AS t6, + ( + SELECT sum(t.amount) AS other + FROM $this->table AS t + LEFT JOIN " . $this->block->getTableName() . " AS b ON t.block_id = b.id + WHERE + ( + t.type IN ('Donation','Fee') AND b.confirmations = -1 + ) + AND t.account_id = ? + ) AS t7 "); if ($this->checkStmt($stmt)) { - $stmt->bind_param("iiiiiiiii", $this->config['confirmations'], $account_id, $account_id, $this->config['confirmations'], $account_id, $this->config['confirmations'], $account_id, $this->config['confirmations'], $account_id); + $stmt->bind_param("iiiiiiiiiii", $this->config['confirmations'], $account_id, $account_id, $this->config['confirmations'], $account_id, $this->config['confirmations'], $account_id, $this->config['confirmations'], $account_id, $account_id, $account_id); if (!$stmt->execute()) { $this->debug->append("Unable to execute statement: " . $stmt->error); $this->setErrorMessage("Fetching balance failed"); diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index 69ebf8de..4f9f5cd4 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -9,7 +9,6 @@ class User { private $userID = false; private $table = 'accounts'; private $user = array(); - private $tableAccountBalance = 'accountBalance'; public function __construct($debug, $mysqli, $salt, $config) { $this->debug = $debug; @@ -20,6 +19,12 @@ class User { } // get and set methods + public function setMail($mail) { + $this->mail = $mail; + } + public function setToken($token) { + $this->token= $token; + } private function setErrorMessage($msg) { $this->sError = $msg; } @@ -44,18 +49,15 @@ class User { public function getUserLocked($id) { return $this->getSingle($id, 'is_locked', 'id'); } - public function getUserToken($id) { - return $this->getSingle($id, 'token', 'id'); - } public function getUserIp($id) { return $this->getSingle($id, 'loggedIp', 'id'); } + public function getEmail($email) { + return $this->getSingle($email, 'email', 'email', 's'); + } public function getUserFailed($id) { return $this->getSingle($id, 'failed_logins', 'id'); } - public function getIdFromToken($token) { - return $this->getSingle($token, 'id', 'token', 's'); - } public function isLocked($id) { return $this->getUserLocked($id); } @@ -70,10 +72,6 @@ class User { $field = array('name' => 'is_admin', 'type' => 'i', 'value' => !$this->isAdmin($id)); return $this->updateSingle($id, $field); } - public function setUserToken($id) { - $field = array('name' => 'token', 'type' => 's', 'value' => setHash($id.time())); - return $this->updateSingle($id, $field); - } public function setUserFailed($id, $value) { $field = array( 'name' => 'failed_logins', 'type' => 'i', 'value' => $value); return $this->updateSingle($id, $field); @@ -280,7 +278,7 @@ class User { * @param donat float donation % of income * @return bool **/ - public function updateAccount($userID, $address, $threshold, $donate, $email) { + public function updateAccount($userID, $address, $threshold, $donate, $email, $is_anonymous) { $this->debug->append("STA " . __METHOD__, 4); $bUser = false; @@ -314,8 +312,8 @@ class User { $donate = min(100, max(0, floatval($donate))); // We passed all validation checks so update the account - $stmt = $this->mysqli->prepare("UPDATE $this->table SET coin_address = ?, ap_threshold = ?, donate_percent = ?, email = ? WHERE id = ?"); - if ($this->checkStmt($stmt) && $stmt->bind_param('sddsi', $address, $threshold, $donate, $email, $userID) && $stmt->execute()) + $stmt = $this->mysqli->prepare("UPDATE $this->table SET coin_address = ?, ap_threshold = ?, donate_percent = ?, email = ?, is_anonymous = ? WHERE id = ?"); + if ($this->checkStmt($stmt) && $stmt->bind_param('sddsii', $address, $threshold, $donate, $email, $is_anonymous, $userID) && $stmt->execute()) return true; // Catchall $this->setErrorMessage('Failed to update your account'); @@ -384,7 +382,16 @@ class User { **/ public function logoutUser($redirect="index.php") { $this->debug->append("STA " . __METHOD__, 4); + // Unset all of the session variables + $_SESSION = array(); + // As we're killing the sesison, also kill the cookie! + if (ini_get("session.use_cookies")) { + $params = session_get_cookie_params(); + setcookie(session_name(), '', time() - 42000, $params["path"], $params["domain"], $params["secure"], $params["httponly"]); + } + // Destroy the session. session_destroy(); + // Enforce generation of a new Session ID and delete the old session_regenerate_id(true); // Enforce a page reload header("Location: $redirect"); @@ -409,7 +416,7 @@ class User { $this->debug->append("Fetching user information for user id: $userID"); $stmt = $this->mysqli->prepare(" SELECT - id, username, pin, api_key, is_admin, email, + id, username, pin, api_key, is_admin, is_anonymous, email, IFNULL(donate_percent, '0') as donate_percent, coin_address, ap_threshold FROM $this->table WHERE id = ? LIMIT 0,1"); @@ -437,8 +444,20 @@ class User { * @param email2 string Email confirmation * @return bool **/ - public function register($username, $password1, $password2, $pin, $email1='', $email2='') { + public function register($username, $password1, $password2, $pin, $email1='', $email2='', $strToken='') { $this->debug->append("STA " . __METHOD__, 4); + if (strlen($username > 40)) { + $this->setErrorMessage('Username exceeding character limit'); + return false; + } + if (preg_match('/[^a-z_\-0-9]/i', $username)) { + $this->setErrorMessage('Username may only contain alphanumeric characters'); + return false; + } + if ($this->getEmail($email1)) { + $this->setErrorMessage( 'This e-mail address is already taken' ); + return false; + } if (strlen($password1) < 8) { $this->setErrorMessage( 'Password is too short, minimum of 8 characters required' ); return false; @@ -459,15 +478,36 @@ class User { $this->setErrorMessage( 'Invalid PIN' ); return false; } + if (isset($strToken) && !empty($strToken)) { + $aToken = $this->token->getToken($strToken); + // Circle dependency, so we create our own object here + $invitation = new Invitation(); + $invitation->setMysql($this->mysqli); + $invitation->setDebug($this->debug); + $invitation->setUser($this); + $invitation->setConfig($this->config); + if (!$invitation->setActivated($aToken['id'])) { + $this->setErrorMessage('Unable to activate your invitation'); + return false; + } + if (!$this->token->deleteToken($strToken)) { + $this->setErrorMessage('Unable to remove used token'); + return false; + } + } if ($this->mysqli->query("SELECT id FROM $this->table LIMIT 1")->num_rows > 0) { + $this->config['accounts']['confirm_email']['enabled'] ? $is_locked = 1 : $is_locked = 0; + $is_admin = 0; $stmt = $this->mysqli->prepare(" - INSERT INTO $this->table (username, pass, email, pin, api_key) - VALUES (?, ?, ?, ?, ?) + INSERT INTO $this->table (username, pass, email, pin, api_key, is_locked) + VALUES (?, ?, ?, ?, ?, ?) "); } else { + $is_locked = 0; + $is_admin = 1; $stmt = $this->mysqli->prepare(" - INSERT INTO $this->table (username, pass, email, pin, api_key, is_admin) - VALUES (?, ?, ?, ?, ?, 1) + INSERT INTO $this->table (username, pass, email, pin, api_key, is_admin, is_locked) + VALUES (?, ?, ?, ?, ?, 1, ?) "); } @@ -475,15 +515,33 @@ class User { $password_hash = $this->getHash($password1); $pin_hash = $this->getHash($pin); $apikey_hash = $this->getHash($username); + $username_clean = strip_tags($username); - if ($this->checkStmt($stmt) && $stmt->bind_param('sssss', $username, $password_hash, $email1, $pin_hash, $apikey_hash)) { - if (!$stmt->execute()) { - $this->setErrorMessage( 'Unable to register' ); - if ($stmt->sqlstate == '23000') $this->setErrorMessage( 'Username already exists' ); - return false; + if ($this->checkStmt($stmt) && $stmt->bind_param('sssssi', $username_clean, $password_hash, $email1, $pin_hash, $apikey_hash, $is_locked) && $stmt->execute()) { + if ($this->config['accounts']['confirm_email']['enabled'] && $is_admin != 1) { + if ($token = $this->token->createToken('confirm_email', $stmt->insert_id)) { + $aData['username'] = $username_clean; + $aData['token'] = $token; + $aData['email'] = $email1; + $aData['subject'] = 'E-Mail verification'; + if (!$this->mail->sendMail('register/confirm_email', $aData)) { + $this->setErrorMessage('Unable to request email confirmation'); + return false; + } + return true; + } else { + $this->setErrorMessage('Failed to create confirmation token'); + $this->debug->append('Unable to create confirm_email token: ' . $this->token->getError()); + return false; + } + } else { + return true; } - $stmt->close(); - return true; + } else { + $this->setErrorMessage( 'Unable to register' ); + $this->debug->append('Failed to insert user into DB: ' . $this->mysqli->error); + if ($stmt->sqlstate == '23000') $this->setErrorMessage( 'Username or email already registered' ); + return false; } return false; } @@ -495,9 +553,9 @@ class User { * @param new2 string New password verification * @return bool **/ - public function useToken($token, $new1, $new2) { + public function resetPassword($token, $new1, $new2) { $this->debug->append("STA " . __METHOD__, 4); - if ($id = $this->getIdFromToken($token)) { + if ($aToken = $this->token->getToken($token)) { if ($new1 !== $new2) { $this->setErrorMessage( 'New passwords do not match' ); return false; @@ -507,54 +565,46 @@ class User { return false; } $new_hash = $this->getHash($new1); - $stmt = $this->mysqli->prepare("UPDATE $this->table SET pass = ?, token = NULL WHERE id = ? AND token = ?"); - if ($this->checkStmt($stmt) && $stmt->bind_param('sis', $new_hash, $id, $token) && $stmt->execute() && $stmt->affected_rows === 1) { - return true; + $stmt = $this->mysqli->prepare("UPDATE $this->table SET pass = ? WHERE id = ?"); + if ($this->checkStmt($stmt) && $stmt->bind_param('si', $new_hash, $aToken['account_id']) && $stmt->execute() && $stmt->affected_rows === 1) { + if ($this->token->deleteToken($aToken['token'])) { + return true; + } else { + $this->setErrorMessage('Unable to invalidate used token'); + } + } else { + $this->setErrorMessage('Unable to set new password'); } } else { - $this->setErrorMessage("Unable find user for your token"); - return false; + $this->setErrorMessage('Invalid token'); } + $this->debug->append('Failed to update password:' . $this->mysqli->error); return false; } /** * Reset a password by sending a password reset mail * @param username string Username to reset password for - * @param smarty object Smarty object for mail templating * @return bool **/ - public function resetPassword($username, $smarty) { + public function initResetPassword($username) { $this->debug->append("STA " . __METHOD__, 4); // Fetch the users mail address if (empty($username)) { $this->serErrorMessage("Username must not be empty"); return false; } - if (!$email = $this->getUserEmail($username)) { + if (!$aData['email'] = $this->getUserEmail($username)) { $this->setErrorMessage("Unable to find a mail address for user $username"); return false; } - if (!$this->setUserToken($this->getUserId($username))) { - $this->setErrorMessage("Unable to setup token for password reset"); + if (!$aData['token'] = $this->token->createToken('password_reset', $this->getUserId($username))) { + $this->setErrorMessage('Unable to setup token for password reset'); return false; } - // Send password reset link - if (!$token = $this->getUserToken($this->getUserId($username))) { - $this->setErrorMessage("Unable fetch token for password reset"); - return false; - } - $smarty->assign('TOKEN', $token); - $smarty->assign('USERNAME', $username); - $smarty->assign('SUBJECT', 'Password Reset Request'); - $smarty->assign('WEBSITENAME', $this->config['website']['name']); - $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, - $smarty->fetch('templates/mail/subject.tpl'), - $smarty->fetch('templates/mail/body.tpl'), - $headers)) { + $aData['username'] = $username; + $aData['subject'] = 'Password Reset Request'; + if ($this->mail->sendMail('password/reset', $aData)) { return true; } else { $this->setErrorMessage("Unable to send mail to your address"); @@ -584,3 +634,5 @@ class User { // Make our class available automatically $user = new User($debug, $mysqli, SALT, $config); +$user->setMail($mail); +$user->setToken($oToken); diff --git a/public/include/classes/worker.class.php b/public/include/classes/worker.class.php index ec7abdc9..7643a6d1 100644 --- a/public/include/classes/worker.class.php +++ b/public/include/classes/worker.class.php @@ -135,15 +135,9 @@ class Worker { **/ public function getCountAllActiveWorkers() { $this->debug->append("STA " . __METHOD__, 4); - $stmt = $this->mysqli->prepare("SELECT COUNT(DISTINCT username) AS total FROM " . $this->share->getTableName() . " WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE)"); - if ($this->checkStmt($stmt)) { - if (!$stmt->execute()) { - return false; - } - $result = $stmt->get_result(); - $stmt->close(); + $stmt = $this->mysqli->prepare("SELECT IFNULL(COUNT(DISTINCT username), 0) AS total FROM " . $this->share->getTableName() . " WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE)"); + if ($this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result()) return $result->fetch_object()->total; - } return false; } diff --git a/public/include/config/global.inc.dist.php b/public/include/config/global.inc.dist.php index c5a298c7..0634101d 100644 --- a/public/include/config/global.inc.dist.php +++ b/public/include/config/global.inc.dist.php @@ -96,22 +96,24 @@ $config['ap_threshold']['max'] = 250; * Website specific configuration settings * * Explanation: - * title : Website title used in master template - * name : The pool name, displayed in the header and mails - * slogan : A special slogan, also displayed in the header below name - * email : `From` addresses used in notifications - * theme : Theme used for desktop browsers - * mobile : Enable/Disable mobile theme support - * mobile_theme : Theme used for mobile browsers + * title : Website title used in master template + * name : The pool name, displayed in the header and mails + * slogan : A special slogan, also displayed in the header below name + * email : `From` addresses used in notifications + * theme : Theme used for desktop browsers + * mobile : Enable/Disable mobile theme support + * mobile_theme : Theme used for mobile browsers + * api disabled : Disable the sites API functions * * Defaults: - * title = `The Pool - Mining Evolved` - * name = `The Pool` - * slogan = `Resistance is futile` - * email = `test@example.com` - * theme = `mmcFE` - * mobile = true - * mobile_theme = `mobile` + * title = `The Pool - Mining Evolved` + * name = `The Pool` + * slogan = `Resistance is futile` + * email = `test@example.com` + * theme = `mmcFE` + * mobile = true + * mobile_theme = `mobile` + * api disbabled = false **/ $config['website']['title'] = 'The Pool - Mining Evolved'; $config['website']['name'] = 'The Pool'; @@ -120,7 +122,51 @@ $config['website']['email'] = 'test@example.com'; $config['website']['theme'] = 'mmcFE'; $config['website']['mobile'] = true; $config['website']['mobile_theme'] = 'mobile'; +$config['website']['api']['disabled'] = false; +/** + * Account specific settings + * + * Explanation + * You can change some defaults on how accounts are created or registered + * By default, all newly created accounts will require an email verificaiton. + * Only after acitivating an account the user will be able to login + * + * Invitations will allow your users to invite new members to join the pool. + * After sending a mail to the invited user, they can register using the token + * created. Invitations can be enabled and disabled through the admin panel. + * Sent invitations are listed on the account invitations page. + * + * You can limit the number of registrations send per account via configuration + * variable. + * + * Options: + * confirm_email : Send confirmation mail to user after registration + * count : Maximum invitations a user is able to send + * + * Defaults: + * confirm_email : true + * count : 5 + **/ +$config['accounts']['confirm_email']['enabled'] = true; +$config['accounts']['invitations']['count'] = 5; + +/** + * Some basic access restrictions on some pages + * + * Explanation: + * Some pools would like to run a few pages for public access instead + * of enforcing a login. You can change visibility of some pages here. + * + * Options: + * 'public' : Allow guest access and authenticated user to view page + * 'private' : Only allow logged in users access to view page + * + * Defaults: + * 'private' for every page + **/ +$config['website']['acl']['statistics']['pool'] = 'private'; +$config['website']['acl']['statistics']['blocks'] = 'private'; /** * Re-Captcha settings @@ -133,7 +179,19 @@ $config['recaptcha']['private_key'] = 'YOUR_PRIVATE_RECAPTCHA_KEY'; // Currency system used in this pool, default: `LTC` $config['currency'] = 'LTC'; -// Default transaction fee, added by RPC server, default: 0.1 +/** + * Default transaction fee to apply to user transactions + * + * Explanation + * The coin daemon applies transcation fees to young coins. + * Since we are unable to find out what the exact fee was we set + * a default value here which is applied to both manual and auto payouts. + * If this is not set, no fee is applied in the transactions history but + * the user might still see them when the coins arrive. + * + * Default: + * txfee = 0.1 + **/ $config['txfee'] = 0.1; // Payout a block bonus to block finders, default: 0 (disabled) @@ -151,18 +209,47 @@ $config['block_bonus'] = 0; * Available options: * prop: Proportional payout system * pps : Pay Per Share payout system + * pplns : Pay Per Last N Shares payout system * * Default: * prop **/ $config['payout_system'] = 'prop'; -// For debugging purposes you can archive shares in the archive_shares table, default: true -$config['archive_shares'] = true; +/** + * Archiving configuration for debugging + * + * Explanation: + * By default, we don't need to archive for a long time. PPLNS and Hashrate + * calculations rely on this archive, but all shares past a certain point can + * safely be deleted. + * + * To ensure we have enough shares on stack for PPLNS, this + * is set to the past 10 rounds. Even with lucky ones in between those should + * fit the PPLNS target. On top of that, even if we have more than 10 rounds, + * we still keep the last maxage shares to ensure we can calculate hashrates. + * Both conditions need to be met in order for shares to be purged from archive. + * + * Proportional mode will only keep the past 24 hours. These are required for + * hashrate calculations to work past a round, hence 24 hours was selected as + * the default. You may want to increase the time for debugging, then add any + * integer reflecting minutes of shares to keep. + * + * Availabe Options: + * maxrounds : PPLNS, keep shares for maxrounds + * maxage : PROP and PPLNS, delete shares older than maxage minutes + * + * Default: + * maxrounds = 10 + * maxage = 60 * 24 (24h) + **/ +$config['archive']['maxrounds'] = 10; +$config['archive']['maxage'] = 60 * 24; -// URL prefix for block searches, used for block links, default: `http://explorer.litecoin.net/search?q=` -// If empty, the block link to the block information page will be removed -$config['blockexplorer'] = 'http://explorer.litecoin.net/search?q='; +// URL prefix for block searches, used for block links, default: `http://explorer.litecoin.net/block/` +// The Blockhash is appended on the templates to this URL +// If this config is empty, the block link to the block information page will be removed +$config['blockexplorer'] = 'http://explorer.litecoin.net/block/'; // Link to blockchain information, used for difficulty link, default: `http://allchains.info` // If empty, the difficulty link to the chain information will be removed @@ -171,6 +258,48 @@ $config['chaininfo'] = 'http://allchains.info'; // Pool fees applied to users in percent, default: 0 (disabled) $config['fees'] = 0; +/** + * PPLNS requires some settings to run properly. First we need to define + * a default shares count that is applied if we don't have a proper type set. + * Different dynamic types can be applied, or you can run a fixed scheme. + * + * Explanation + * + * PPLNS can run on two different payouts: fixed and blockavg. Each one + * defines a different PPLNS target. + * + * Fixed means we will be looking at the shares setup in the default + * setting. There is no automatic adjustments to the PPLNS target, + * all users will be paid out proportionally to that target. + * + * Blockavg will look at the last blockcount blocks shares and take + * the average as the PPLNS target. This will be automatically adjusted + * when difficulty changes and more blocks are available. This keeps the + * target dynamic but still traceable. + * + * If you use the fixed type it will use $config['pplns']['shares']['default'] + * for target calculations, if you use blockavg type it will use + * $config['pplns']['blockavg']['blockcount'] blocks average for target + * calculations. + * + * default : Default target shares for PPLNS + * type : Payout type used in PPLNS + * blockcount : Amount of blocks to check for avg shares + * + * Available Options: + * default : amount of shares, integeger + * type : blockavg or fixed + * blockcount : amount of blocks, any integer + * + * Defaults: + * default = 4000000 + * type = `blockavg` + * blockcount = 10 + **/ +$config['pplns']['shares']['default'] = 4000000; +$config['pplns']['shares']['type'] = 'blockavg'; +$config['pplns']['blockavg']['blockcount'] = 10; + // Pool target difficulty as set in pushpoold configuration file // Please also read this for stratum: https://github.com/TheSerapher/php-mmcfe-ng/wiki/FAQ $config['difficulty'] = 20; @@ -181,7 +310,7 @@ $config['difficulty'] = 20; * * Explanation: * - * Proportional Payout System + * Proportional + PPLNS Payout System * When running a pool on fixed mode, each block will be paid * out as defined in `reward`. If you wish to pass transaction * fees inside discovered blocks on to user, set this to `block`. @@ -217,11 +346,17 @@ $config['confirmations'] = 120; /** * Memcache configuration * + * To disable memcache set option $config['memcache']['enabled'] = false + * After disable memcache installation of memcache is not required. + * * Please note that a memcache is greatly increasing performance * when combined with the `statistics.php` cronjob. Disabling this * is not recommended in a live environment! * * Explanations + * enabled : Disable (false) memcache for debugging or enable (true) it + * host : Host IP or hostname + * port : memcache port * keyprefix : Must be changed for multiple mmcfe-ng instances on one host * expiration : Default expiration time in seconds of all cached keys. * Increase if caches expire too fast. @@ -247,19 +382,77 @@ $config['memcache']['splay'] = 15; /** * Cookie configiration * - * For multiple installations of this cookie change the cookie name + * You can configure the cookie behaviour to secure your cookies more than the PHP defaults + * + * For multiple installations of mmcfe-ng on the same domain you must change the cookie path. + * + * Explanation: + * duration: + * the amount of time, in seconds, that a cookie should persist in the users browser. + * 0 = until closed; 1440 = 24 minutes. Check your php.ini 'session.gc_maxlifetime' value + * and ensure that it is at least the duration specified here. + * + * domain: + * the only domain name that may access this cookie in the browser + * + * path: + * the highest path on the domain that can access this cookie; i.e. if running two pools + * from a single domain you might set the path /ltc/ and /ftc/ to separate user session + * cookies between the two. + * + * httponly: + * marks the cookie as accessible only through the HTTP protocol. The cookie can't be + * accessed by scripting languages, such as JavaScript. This can help to reduce identity + * theft through XSS attacks in most browsers. + * + * secure: + * marks the cookie as accessible only through the HTTPS protocol. If you have a SSL + * certificate installed on your domain name then this will stop a user accidently + * accessing the site over a HTTP connection, without SSL, exposing their session cookie. * * Default: - * path = '/' - * name = 'POOLERCOOKIE' - * domain = '' + * duration = '1440' + * domain = '' + * path = '/' + * httponly = true + * secure = false **/ -$config['cookie']['path'] = '/'; -$config['cookie']['name'] = 'POOLERCOOKIE'; +$config['cookie']['duration'] = '1440'; $config['cookie']['domain'] = ''; +$config['cookie']['path'] = '/'; +$config['cookie']['httponly'] = true; +$config['cookie']['secure'] = false; -// Disable or enable smarty cache -// This is usually not required, default: 0 -$config['cache'] = 0; - +/** + * Enable or disable the Smarty cache + * + * Explanation: + * Smarty implements a file based cache for all HTML output generated + * from dynamic scripts. It can be enabled to cache the HTML data on disk, + * future request are served from those cache files. + * + * This may or may not work as expected, in general Memcache is used to cache + * all data so rendering the page should not take too long anyway. + * + * You can test this out and enable (1) this setting but it's not guaranteed to + * work with mmcfe-ng. + * + * Ensure that the folder `templates/cache` is writable by the webserver! + * + * cache = Enable/Disable the cache + * cache_lifetime = Time to keep files in seconds before updating them + * + * Options: + * cache: + * 0 = disabled + * 1 = enabled + * cache_lifetime: + * time in seconds + * + * Defaults: + * cache = 0, disabled + * cache_lifetime = 30 seconds + **/ +$config['smarty']['cache'] = 0; +$config['smarty']['cache_lifetime'] = 30; ?> diff --git a/public/include/lib/KLogger.php b/public/include/lib/KLogger.php new file mode 100755 index 00000000..9f53788e --- /dev/null +++ b/public/include/lib/KLogger.php @@ -0,0 +1,148 @@ + + * Date : July 26, 2008 + * Comments : Originally written for use with wpSearch + * Website : http://codefury.net + * Version : 1.0 + * + * Usage: + * $log = new KLogger ( "log.txt" , KLogger::INFO ); + * $log->LogInfo("Returned a million search results"); //Prints to the log file + * $log->LogFATAL("Oh dear."); //Prints to the log file + * $log->LogDebug("x = 5"); //Prints nothing due to priority setting + */ + + class KLogger + { + + const DEBUG = 1; // Most Verbose + const INFO = 2; // ... + const WARN = 3; // ... + const ERROR = 4; // ... + const FATAL = 5; // Least Verbose + const OFF = 6; // Nothing at all. + + const LOG_OPEN = 1; + const OPEN_FAILED = 2; + const LOG_CLOSED = 3; + + /* Public members: Not so much of an example of encapsulation, but that's okay. */ + public $Log_Status = KLogger::LOG_CLOSED; + public $DateFormat = "Y-m-d G:i:s"; + public $MessageQueue; + + private $log_file; + private $priority = KLogger::INFO; + + private $file_handle; + + public function __construct( $filepath , $priority ) + { + if ( $priority == KLogger::OFF ) return; + + $this->log_file = $filepath; + $this->MessageQueue = array(); + $this->priority = $priority; + + if ( file_exists( $this->log_file ) ) + { + if ( !is_writable($this->log_file) ) + { + $this->Log_Status = KLogger::OPEN_FAILED; + $this->MessageQueue[] = "The file exists, but could not be opened for writing. Check that appropriate permissions have been set."; + return; + } + } + + if ( $this->file_handle = fopen( $this->log_file , "a" ) ) + { + $this->Log_Status = KLogger::LOG_OPEN; + $this->MessageQueue[] = "The log file was opened successfully."; + } + else + { + $this->Log_Status = KLogger::OPEN_FAILED; + $this->MessageQueue[] = "The file could not be opened. Check permissions."; + } + + return; + } + + public function __destruct() + { + if ( $this->file_handle ) + fclose( $this->file_handle ); + } + + public function LogInfo($line) + { + $this->Log( $line , KLogger::INFO ); + } + + public function LogDebug($line) + { + $this->Log( $line , KLogger::DEBUG ); + } + + public function LogWarn($line) + { + $this->Log( $line , KLogger::WARN ); + } + + public function LogError($line) + { + $this->Log( $line , KLogger::ERROR ); + } + + public function LogFatal($line) + { + $this->Log( $line , KLogger::FATAL ); + } + + public function Log($line, $priority) + { + if ( $this->priority <= $priority ) + { + $status = $this->getTimeLine( $priority ); + $this->WriteFreeFormLine ( "$status $line \n" ); + } + } + + public function WriteFreeFormLine( $line ) + { + if ( $this->Log_Status == KLogger::LOG_OPEN && $this->priority != KLogger::OFF ) + { + if (fwrite( $this->file_handle , $line ) === false) { + $this->MessageQueue[] = "The file could not be written to. Check that appropriate permissions have been set."; + } + } + } + + private function getTimeLine( $level ) + { + $time = date( $this->DateFormat ); + + switch( $level ) + { + case KLogger::INFO: + return "$time - INFO -->"; + case KLogger::WARN: + return "$time - WARN -->"; + case KLogger::DEBUG: + return "$time - DEBUG -->"; + case KLogger::ERROR: + return "$time - ERROR -->"; + case KLogger::FATAL: + return "$time - FATAL -->"; + default: + return "$time - LOG -->"; + } + } + + } + + +?> \ No newline at end of file diff --git a/public/include/lib/scrypt.php b/public/include/lib/scrypt.php new file mode 100644 index 00000000..3d501b7d --- /dev/null +++ b/public/include/lib/scrypt.php @@ -0,0 +1,536 @@ + 0 and a power of 2"); + } + if ($n > PHP_INT_MAX / 128 / $r) { + throw new Exception\InvalidArgumentException("Parameter n is too large"); + } + if ($r > PHP_INT_MAX / 128 / $p) { + throw new Exception\InvalidArgumentException("Parameter r is too large"); + } + + if (extension_loaded('Scrypt')) { + if ($length < 16) { + throw new Exception\InvalidArgumentException("Key length is too low, must be greater or equal to 16"); + } + return self::hex2bin(scrypt($password, $salt, $n, $r, $p, $length)); + } + + $b = Pbkdf2::calc('sha256', $password, $salt, 1, $p * 128 * $r); + + $s = ''; + for ($i = 0; $i < $p; $i++) { + $s .= self::scryptROMix(substr($b, $i * 128 * $r, 128 * $r), $n, $r); + } + + return Pbkdf2::calc('sha256', $password, $s, 1, $length); + } + + /** +* scryptROMix +* +* @param string $b +* @param integer $n +* @param integer $r +* @return string +* @see https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01#section-4 +*/ + protected static function scryptROMix($b, $n, $r) + { + $x = $b; + $v = array(); + for ($i = 0; $i < $n; $i++) { + $v[$i] = $x; + $x = self::scryptBlockMix($x, $r); + } + for ($i = 0; $i < $n; $i++) { + $j = self::integerify($x) % $n; + $t = $x ^ $v[$j]; + $x = self::scryptBlockMix($t, $r); + } + return $x; + } + + /** +* scryptBlockMix +* +* @param string $b +* @param integer $r +* @return string +* @see https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01#section-3 +*/ + protected static function scryptBlockMix($b, $r) + { + $x = substr($b, -64); + $even = ''; + $odd = ''; + $len = 2 * $r; + + for ($i = 0; $i < $len; $i++) { + if (PHP_INT_SIZE === 4) { + $x = self::salsa208Core32($x ^ substr($b, 64 * $i, 64)); + } else { + $x = self::salsa208Core64($x ^ substr($b, 64 * $i, 64)); + } + if ($i % 2 == 0) { + $even .= $x; + } else { + $odd .= $x; + } + } + return $even . $odd; + } + + /** +* Salsa 20/8 core (32 bit version) +* +* @param string $b +* @return string +* @see https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01#section-2 +* @see http://cr.yp.to/salsa20.html +*/ + protected static function salsa208Core32($b) + { + $b32 = array(); + for ($i = 0; $i < 16; $i++) { + list(, $b32[$i]) = unpack("V", substr($b, $i * 4, 4)); + } + + $x = $b32; + for ($i = 0; $i < 8; $i += 2) { + $a = ($x[ 0] + $x[12]); + $x[ 4] ^= ($a << 7) | ($a >> 25) & 0x7f; + $a = ($x[ 4] + $x[ 0]); + $x[ 8] ^= ($a << 9) | ($a >> 23) & 0x1ff; + $a = ($x[ 8] + $x[ 4]); + $x[12] ^= ($a << 13) | ($a >> 19) & 0x1fff; + $a = ($x[12] + $x[ 8]); + $x[ 0] ^= ($a << 18) | ($a >> 14) & 0x3ffff; + $a = ($x[ 5] + $x[ 1]); + $x[ 9] ^= ($a << 7) | ($a >> 25) & 0x7f; + $a = ($x[ 9] + $x[ 5]); + $x[13] ^= ($a << 9) | ($a >> 23) & 0x1ff; + $a = ($x[13] + $x[ 9]); + $x[ 1] ^= ($a << 13) | ($a >> 19) & 0x1fff; + $a = ($x[ 1] + $x[13]); + $x[ 5] ^= ($a << 18) | ($a >> 14) & 0x3ffff; + $a = ($x[10] + $x[ 6]); + $x[14] ^= ($a << 7) | ($a >> 25) & 0x7f; + $a = ($x[14] + $x[10]); + $x[ 2] ^= ($a << 9) | ($a >> 23) & 0x1ff; + $a = ($x[ 2] + $x[14]); + $x[ 6] ^= ($a << 13) | ($a >> 19) & 0x1fff; + $a = ($x[ 6] + $x[ 2]); + $x[10] ^= ($a << 18) | ($a >> 14) & 0x3ffff; + $a = ($x[15] + $x[11]); + $x[ 3] ^= ($a << 7) | ($a >> 25) & 0x7f; + $a = ($x[ 3] + $x[15]); + $x[ 7] ^= ($a << 9) | ($a >> 23) & 0x1ff; + $a = ($x[ 7] + $x[ 3]); + $x[11] ^= ($a << 13) | ($a >> 19) & 0x1fff; + $a = ($x[11] + $x[ 7]); + $x[15] ^= ($a << 18) | ($a >> 14) & 0x3ffff; + $a = ($x[ 0] + $x[ 3]); + $x[ 1] ^= ($a << 7) | ($a >> 25) & 0x7f; + $a = ($x[ 1] + $x[ 0]); + $x[ 2] ^= ($a << 9) | ($a >> 23) & 0x1ff; + $a = ($x[ 2] + $x[ 1]); + $x[ 3] ^= ($a << 13) | ($a >> 19) & 0x1fff; + $a = ($x[ 3] + $x[ 2]); + $x[ 0] ^= ($a << 18) | ($a >> 14) & 0x3ffff; + $a = ($x[ 5] + $x[ 4]); + $x[ 6] ^= ($a << 7) | ($a >> 25) & 0x7f; + $a = ($x[ 6] + $x[ 5]); + $x[ 7] ^= ($a << 9) | ($a >> 23) & 0x1ff; + $a = ($x[ 7] + $x[ 6]); + $x[ 4] ^= ($a << 13) | ($a >> 19) & 0x1fff; + $a = ($x[ 4] + $x[ 7]); + $x[ 5] ^= ($a << 18) | ($a >> 14) & 0x3ffff; + $a = ($x[10] + $x[ 9]); + $x[11] ^= ($a << 7) | ($a >> 25) & 0x7f; + $a = ($x[11] + $x[10]); + $x[ 8] ^= ($a << 9) | ($a >> 23) & 0x1ff; + $a = ($x[ 8] + $x[11]); + $x[ 9] ^= ($a << 13) | ($a >> 19) & 0x1fff; + $a = ($x[ 9] + $x[ 8]); + $x[10] ^= ($a << 18) | ($a >> 14) & 0x3ffff; + $a = ($x[15] + $x[14]); + $x[12] ^= ($a << 7) | ($a >> 25) & 0x7f; + $a = ($x[12] + $x[15]); + $x[13] ^= ($a << 9) | ($a >> 23) & 0x1ff; + $a = ($x[13] + $x[12]); + $x[14] ^= ($a << 13) | ($a >> 19) & 0x1fff; + $a = ($x[14] + $x[13]); + $x[15] ^= ($a << 18) | ($a >> 14) & 0x3ffff; + } + for ($i = 0; $i < 16; $i++) { + $b32[$i] = $b32[$i] + $x[$i]; + } + $result = ''; + for ($i = 0; $i < 16; $i++) { + $result .= pack("V", $b32[$i]); + } + + return $result; + } + + /** +* Salsa 20/8 core (64 bit version) +* +* @param string $b +* @return string +* @see https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01#section-2 +* @see http://cr.yp.to/salsa20.html +*/ + protected static function salsa208Core64($b) + { + $b32 = array(); + for ($i = 0; $i < 16; $i++) { + list(, $b32[$i]) = unpack("V", substr($b, $i * 4, 4)); + } + + $x = $b32; + for ($i = 0; $i < 8; $i += 2) { + $a = ($x[ 0] + $x[12]) & 0xffffffff; + $x[ 4] ^= ($a << 7) | ($a >> 25); + $a = ($x[ 4] + $x[ 0]) & 0xffffffff; + $x[ 8] ^= ($a << 9) | ($a >> 23); + $a = ($x[ 8] + $x[ 4]) & 0xffffffff; + $x[12] ^= ($a << 13) | ($a >> 19); + $a = ($x[12] + $x[ 8]) & 0xffffffff; + $x[ 0] ^= ($a << 18) | ($a >> 14); + $a = ($x[ 5] + $x[ 1]) & 0xffffffff; + $x[ 9] ^= ($a << 7) | ($a >> 25); + $a = ($x[ 9] + $x[ 5]) & 0xffffffff; + $x[13] ^= ($a << 9) | ($a >> 23); + $a = ($x[13] + $x[ 9]) & 0xffffffff; + $x[ 1] ^= ($a << 13) | ($a >> 19); + $a = ($x[ 1] + $x[13]) & 0xffffffff; + $x[ 5] ^= ($a << 18) | ($a >> 14); + $a = ($x[10] + $x[ 6]) & 0xffffffff; + $x[14] ^= ($a << 7) | ($a >> 25); + $a = ($x[14] + $x[10]) & 0xffffffff; + $x[ 2] ^= ($a << 9) | ($a >> 23); + $a = ($x[ 2] + $x[14]) & 0xffffffff; + $x[ 6] ^= ($a << 13) | ($a >> 19); + $a = ($x[ 6] + $x[ 2]) & 0xffffffff; + $x[10] ^= ($a << 18) | ($a >> 14); + $a = ($x[15] + $x[11]) & 0xffffffff; + $x[ 3] ^= ($a << 7) | ($a >> 25); + $a = ($x[ 3] + $x[15]) & 0xffffffff; + $x[ 7] ^= ($a << 9) | ($a >> 23); + $a = ($x[ 7] + $x[ 3]) & 0xffffffff; + $x[11] ^= ($a << 13) | ($a >> 19); + $a = ($x[11] + $x[ 7]) & 0xffffffff; + $x[15] ^= ($a << 18) | ($a >> 14); + $a = ($x[ 0] + $x[ 3]) & 0xffffffff; + $x[ 1] ^= ($a << 7) | ($a >> 25); + $a = ($x[ 1] + $x[ 0]) & 0xffffffff; + $x[ 2] ^= ($a << 9) | ($a >> 23); + $a = ($x[ 2] + $x[ 1]) & 0xffffffff; + $x[ 3] ^= ($a << 13) | ($a >> 19); + $a = ($x[ 3] + $x[ 2]) & 0xffffffff; + $x[ 0] ^= ($a << 18) | ($a >> 14); + $a = ($x[ 5] + $x[ 4]) & 0xffffffff; + $x[ 6] ^= ($a << 7) | ($a >> 25); + $a = ($x[ 6] + $x[ 5]) & 0xffffffff; + $x[ 7] ^= ($a << 9) | ($a >> 23); + $a = ($x[ 7] + $x[ 6]) & 0xffffffff; + $x[ 4] ^= ($a << 13) | ($a >> 19); + $a = ($x[ 4] + $x[ 7]) & 0xffffffff; + $x[ 5] ^= ($a << 18) | ($a >> 14); + $a = ($x[10] + $x[ 9]) & 0xffffffff; + $x[11] ^= ($a << 7) | ($a >> 25); + $a = ($x[11] + $x[10]) & 0xffffffff; + $x[ 8] ^= ($a << 9) | ($a >> 23); + $a = ($x[ 8] + $x[11]) & 0xffffffff; + $x[ 9] ^= ($a << 13) | ($a >> 19); + $a = ($x[ 9] + $x[ 8]) & 0xffffffff; + $x[10] ^= ($a << 18) | ($a >> 14); + $a = ($x[15] + $x[14]) & 0xffffffff; + $x[12] ^= ($a << 7) | ($a >> 25); + $a = ($x[12] + $x[15]) & 0xffffffff; + $x[13] ^= ($a << 9) | ($a >> 23); + $a = ($x[13] + $x[12]) & 0xffffffff; + $x[14] ^= ($a << 13) | ($a >> 19); + $a = ($x[14] + $x[13]) & 0xffffffff; + $x[15] ^= ($a << 18) | ($a >> 14); + } + for ($i = 0; $i < 16; $i++) { + $b32[$i] = ($b32[$i] + $x[$i]) & 0xffffffff; + } + $result = ''; + for ($i = 0; $i < 16; $i++) { + $result .= pack("V", $b32[$i]); + } + + return $result; + } + + /** +* Integerify +* +* Integerify (B[0] ... B[2 * r - 1]) is defined as the result +* of interpreting B[2 * r - 1] as a little-endian integer. +* Each block B is a string of 64 bytes. +* +* @param string $b +* @return integer +* @see https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01#section-4 +*/ + protected static function integerify($b) + { + $v = 'v'; + if (PHP_INT_SIZE === 8) { + $v = 'V'; + } + list(,$n) = unpack($v, substr($b, -64)); + return $n; + } + + /** +* Convert hex string in a binary string +* +* @param string $hex +* @return string +*/ + protected static function hex2bin($hex) + { + if (version_compare(PHP_VERSION, '5.4') >= 0) { + return hex2bin($hex); + } + $len = strlen($hex); + $result = ''; + for ($i = 0; $i < $len; $i+=2) { + $result .= chr(hexdec($hex[$i] . $hex[$i+1])); + } + return $result; + } +} + + + +/** +* Zend Framework (http://framework.zend.com/) +* +* @link http://github.com/zendframework/zf2 for the canonical source repository +* @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) +* @license http://framework.zend.com/license/new-bsd New BSD License +*/ + + + + + +/** +* PKCS #5 v2.0 standard RFC 2898 +*/ +class Pbkdf2 +{ + /** +* Generate the new key +* +* @param string $hash The hash algorithm to be used by HMAC +* @param string $password The source password/key +* @param string $salt +* @param integer $iterations The number of iterations +* @param integer $length The output size +* @throws Exception\InvalidArgumentException +* @return string +*/ + public static function calc($hash, $password, $salt, $iterations, $length) + { + if (!Hmac::isSupported($hash)) { + throw new Exception\InvalidArgumentException("The hash algorithm $hash is not supported by " . __CLASS__); + } + + $num = ceil($length / Hmac::getOutputSize($hash, Hmac::OUTPUT_BINARY)); + $result = ''; + for ($block = 1; $block <= $num; $block++) { + $hmac = hash_hmac($hash, $salt . pack('N', $block), $password, Hmac::OUTPUT_BINARY); + $mix = $hmac; + for ($i = 1; $i < $iterations; $i++) { + $hmac = hash_hmac($hash, $hmac, $password, Hmac::OUTPUT_BINARY); + $mix ^= $hmac; + } + $result .= $mix; + } + return substr($result, 0, $length); + } +} + + + +/** +* Zend Framework (http://framework.zend.com/) +* +* @link http://github.com/zendframework/zf2 for the canonical source repository +* @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com) +* @license http://framework.zend.com/license/new-bsd New BSD License +*/ + + + +/** +* PHP implementation of the RFC 2104 Hash based Message Authentication Code +*/ +class Hmac +{ + const OUTPUT_STRING = false; + const OUTPUT_BINARY = true; + + /** +* Last algorithm supported +* +* @var string|null +*/ + protected static $lastAlgorithmSupported; + + /** +* Performs a HMAC computation given relevant details such as Key, Hashing +* algorithm, the data to compute MAC of, and an output format of String, +* or Binary. +* +* @param string $key +* @param string $hash +* @param string $data +* @param bool $output +* @throws Exception\InvalidArgumentException +* @return string +*/ + public static function compute($key, $hash, $data, $output = self::OUTPUT_STRING) + { + + if (empty($key)) { + throw new Exception\InvalidArgumentException('Provided key is null or empty'); + } + + if (!$hash || ($hash !== static::$lastAlgorithmSupported && !static::isSupported($hash))) { + throw new Exception\InvalidArgumentException( + "Hash algorithm is not supported on this PHP installation; provided '{$hash}'" + ); + } + + return hash_hmac($hash, $data, $key, $output); + } + + /** +* Get the output size according to the hash algorithm and the output format +* +* @param string $hash +* @param bool $output +* @return integer +*/ + public static function getOutputSize($hash, $output = self::OUTPUT_STRING) + { + return strlen(static::compute('key', $hash, 'data', $output)); + } + + /** +* Get the supported algorithm +* +* @return array +*/ + public static function getSupportedAlgorithms() + { + return hash_algos(); + } + + /** +* Is the hash algorithm supported? +* +* @param string $algorithm +* @return bool +*/ + public static function isSupported($algorithm) + { + if ($algorithm === static::$lastAlgorithmSupported) { + return true; + } + + if (in_array(strtolower($algorithm), hash_algos(), true)) { + static::$lastAlgorithmSupported = $algorithm; + return true; + } + + return false; + } + + /** +* Clear the cache of last algorithm supported +*/ + public static function clearLastAlgorithmCache() + { + static::$lastAlgorithmSupported = null; + } +} + function swapEndian($input){ + $output = ""; + for($i=0;$i< strlen($input);$i+=2){ + $output .= substr($input, -($i+2), 2); + + + } + return $output; + + + } + + +/*for($i=0;$i < 200;$i++){ + $value = Scrypt::calc($i, $i, 1024, 1, 1, 32); + echo "scrypt ".$i." hash:". bin2hex($value)."
"; +}*/ +/* +$i = pack("H*", "01000000f615f7ce3b4fc6b8f61e8f89aedb1d0852507650533a9e3b10b9bbcc30639f279fcaa86746e1ef52d3edb3c4ad8259920d509bd073605c9bf1d59983752a6b06b817bb4ea78e011d012d59d4"); + +$value = Scrypt::calc($i, $i, 1024, 1, 1, 32); + echo "scrypt ".$i." hash:". bin2hex($value)."
"; + print_r( swapEndian(bin2hex($value))); + */ + + +// Function used for pushpoold solution checks +function word_reverse($str) { + $ret = ''; + while (strlen($str) > 0) { + $ret .= substr($str, -8, 8); + $str = substr($str, 0, -8); + } + return $ret; +} + +?> diff --git a/public/include/pages/account/confirm.inc.php b/public/include/pages/account/confirm.inc.php new file mode 100644 index 00000000..3611c5de --- /dev/null +++ b/public/include/pages/account/confirm.inc.php @@ -0,0 +1,17 @@ + 'Missing token', 'TYPE' => 'errormsg'); +} else if (!$aToken = $oToken->getToken($_GET['token'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to activate your account. Invalid token', 'TYPE' => 'errormsg'); +} else { + $user->changeLocked($aToken['account_id']); + $oToken->deleteToken($aToken['token']); + $_SESSION['POPUP'][] = array('CONTENT' => 'Account activated. Please login.'); +} +$smarty->assign('CONTENT', 'default.tpl'); +?> diff --git a/public/include/pages/account/edit.inc.php b/public/include/pages/account/edit.inc.php index 5548dd82..9ab38e49 100644 --- a/public/include/pages/account/edit.inc.php +++ b/public/include/pages/account/edit.inc.php @@ -30,7 +30,8 @@ if ($user->isAuthenticated()) { if ($continue == true) { // Send balance to address, mind fee for transaction! try { - if ($setting->getValue('auto_payout_active') == 0) { + $auto_payout = $monitoring->getStatus('auto_payout_active'); + if ($auto_payout['value'] == 0) { $bitcoin->sendtoaddress($sCoinAddress, $dBalance); } else { $_SESSION['POPUP'][] = array('CONTENT' => 'Auto-payout active, please contact site support immidiately to revoke invalid transactions.', 'TYPE' => 'errormsg'); @@ -53,14 +54,14 @@ if ($user->isAuthenticated()) { $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to wallet RPC service', 'TYPE' => 'errormsg'); } } else { - $_SESSION['POPUP'][] = array('CONTENT' => 'Insufficient funds, you need more than ' . $config['txfee'] . ' ' . $conifg['currency'] . ' to cover transaction fees', 'TYPE' => 'errormsg'); + $_SESSION['POPUP'][] = array('CONTENT' => 'Insufficient funds, you need more than ' . $config['txfee'] . ' ' . $config['currency'] . ' to cover transaction fees', 'TYPE' => 'errormsg'); } $setting->setValue('manual_payout_active', 0); } break; case 'updateAccount': - if ($user->updateAccount($_SESSION['USERDATA']['id'], $_POST['paymentAddress'], $_POST['payoutThreshold'], $_POST['donatePercent'], $_POST['email'])) { + if ($user->updateAccount($_SESSION['USERDATA']['id'], $_POST['paymentAddress'], $_POST['payoutThreshold'], $_POST['donatePercent'], $_POST['email'], $_POST['is_anonymous'])) { $_SESSION['POPUP'][] = array('CONTENT' => 'Account details updated', 'TYPE' => 'success'); } else { $_SESSION['POPUP'][] = array('CONTENT' => 'Failed to update your account: ' . $user->getError(), 'TYPE' => 'errormsg'); diff --git a/public/include/pages/account/invitations.inc.php b/public/include/pages/account/invitations.inc.php new file mode 100644 index 00000000..b12e2d4e --- /dev/null +++ b/public/include/pages/account/invitations.inc.php @@ -0,0 +1,25 @@ +isAuthenticated()) { + if (!$setting->getValue('disable_invitations')) { + if ($invitation->getCountInvitations($_SESSION['USERDATA']['id']) >= $config['accounts']['invitations']['count']) { + $_SESSION['POPUP'][] = array('CONTENT' => 'You have exceeded the allowed invitations of ' . $config['accounts']['invitations']['count'], 'TYPE' => 'errormsg'); + } else if (isset($_POST['do']) && $_POST['do'] == 'sendInvitation') { + if ($invitation->sendInvitation($_SESSION['USERDATA']['id'], $_POST['data'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Invitation sent'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to send invitation to recipient: ' . $invitation->getError(), 'TYPE' => 'errormsg'); + } + } + $aInvitations = $invitation->getInvitations($_SESSION['USERDATA']['id']); + $smarty->assign('INVITATIONS', $aInvitations); + } else { + $aInvitations = array(); + $_SESSION['POPUP'][] = array('CONTENT' => 'Invitations are disabled', 'TYPE' => 'errormsg'); + } +} +$smarty->assign('CONTENT', 'default.tpl'); +?> diff --git a/public/include/pages/account/reset_failed.inc.php b/public/include/pages/account/reset_failed.inc.php index bce9b418..39541dc2 100644 --- a/public/include/pages/account/reset_failed.inc.php +++ b/public/include/pages/account/reset_failed.inc.php @@ -8,5 +8,6 @@ if ($user->isAuthenticated()) { $user->setUserFailed($_SESSION['USERDATA']['id'], 0); header("Location: " . $_SERVER['HTTP_REFERER']); } - +// Somehow we still need to load this empty template +$smarty->assign("CONTENT", "../../global/empty.tpl"); ?> diff --git a/public/include/pages/account/transactions.inc.php b/public/include/pages/account/transactions.inc.php index db927ca7..7bcfb4f2 100644 --- a/public/include/pages/account/transactions.inc.php +++ b/public/include/pages/account/transactions.inc.php @@ -6,6 +6,6 @@ if ($user->isAuthenticated()) { $aTransactions = $transaction->getTransactions($_SESSION['USERDATA']['id']); if (!$aTransactions) $_SESSION['POPUP'][] = array('CONTENT' => 'Could not find any transaction', 'TYPE' => 'errormsg'); $smarty->assign('TRANSACTIONS', $aTransactions); - $smarty->assign('CONTENT', 'default.tpl'); } +$smarty->assign('CONTENT', 'default.tpl'); ?> diff --git a/public/include/pages/admin/monitoring.inc.php b/public/include/pages/admin/monitoring.inc.php new file mode 100644 index 00000000..a82dd78a --- /dev/null +++ b/public/include/pages/admin/monitoring.inc.php @@ -0,0 +1,108 @@ +isAuthenticated() || !$user->isAdmin($_SESSION['USERDATA']['id'])) { + header("HTTP/1.1 404 Page not found"); + die("404 Page not found"); +} + +// Fetch settings to propagate to template +$aCronStatus = array( + 'statistics' => array ( + array( 'NAME' => 'Exit Code', 'STATUS' => $monitoring->getStatus('statistics_status') ), + array( 'NAME' => 'Active', 'STATUS' => $monitoring->getStatus('statistics_active') ), + array( 'NAME' => 'Runtime', 'STATUS' => $monitoring->getStatus('statistics_runtime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('statistics_starttime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('statistics_endtime') ), + array( 'NAME' => 'Last Message', 'STATUS' => $monitoring->getStatus('statistics_message') ), + ), + 'auto_payout' => array ( + array( 'NAME' => 'Exit Code', 'STATUS' => $monitoring->getStatus('auto_payout_status') ), + array( 'NAME' => 'Active', 'STATUS' => $monitoring->getStatus('auto_payout_active') ), + array( 'NAME' => 'Runtime', 'STATUS' => $monitoring->getStatus('auto_payout_runtime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('auto_payout_starttime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('auto_payout_endtime') ), + array( 'NAME' => 'Last Message', 'STATUS' => $monitoring->getStatus('auto_payout_message') ), + ), + 'archive_cleanup' => array ( + array( 'NAME' => 'Exit Code', 'STATUS' => $monitoring->getStatus('archive_cleanup_status') ), + array( 'NAME' => 'Active', 'STATUS' => $monitoring->getStatus('archive_cleanup_active') ), + array( 'NAME' => 'Runtime', 'STATUS' => $monitoring->getStatus('archive_cleanup_runtime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('archive_cleanup_starttime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('archive_cleanup_endtime') ), + array( 'NAME' => 'Last Message', 'STATUS' => $monitoring->getStatus('archive_cleanup_message') ), + ), + 'blockupdate' => array ( + array( 'NAME' => 'Exit Code', 'STATUS' => $monitoring->getStatus('blockupdate_status') ), + array( 'NAME' => 'Active', 'STATUS' => $monitoring->getStatus('blockupdate_active') ), + array( 'NAME' => 'Runtime', 'STATUS' => $monitoring->getStatus('blockupdate_runtime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('blockupdate_starttime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('blockupdate_endtime') ), + array( 'NAME' => 'Last Message', 'STATUS' => $monitoring->getStatus('blockupdate_message') ), + ), + 'findblock' => array ( + array( 'NAME' => 'Exit Code', 'STATUS' => $monitoring->getStatus('findblock_status') ), + array( 'NAME' => 'Active', 'STATUS' => $monitoring->getStatus('findblock_active') ), + array( 'NAME' => 'Runtime', 'STATUS' => $monitoring->getStatus('findblock_runtime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('findblock_starttime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('findblock_endtime') ), + array( 'NAME' => 'Last Message', 'STATUS' => $monitoring->getStatus('findblock_message') ), + ), + 'notifications' => array ( + array( 'NAME' => 'Exit Code', 'STATUS' => $monitoring->getStatus('notifications_status') ), + array( 'NAME' => 'Active', 'STATUS' => $monitoring->getStatus('notifications_active') ), + array( 'NAME' => 'Runtime', 'STATUS' => $monitoring->getStatus('notifications_runtime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('notifications_starttime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('notifications_endtime') ), + array( 'NAME' => 'Last Message', 'STATUS' => $monitoring->getStatus('notifications_message') ), + ), + 'tickerupdate' => array ( + array( 'NAME' => 'Exit Code', 'STATUS' => $monitoring->getStatus('tickerupdate_status') ), + array( 'NAME' => 'Active', 'STATUS' => $monitoring->getStatus('tickerupdate_active') ), + array( 'NAME' => 'Runtime', 'STATUS' => $monitoring->getStatus('tickerupdate_runtime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('tickerupdate_starttime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('tickerupdate_endtime') ), + array( 'NAME' => 'Last Message', 'STATUS' => $monitoring->getStatus('tickerupdate_message') ), + ) +); +// Payout system specifics +switch ($config['payout_system']) { +case 'pplns': + $aCronStatus['pplns_payout'] = array ( + array( 'NAME' => 'Exit Code', 'STATUS' => $monitoring->getStatus('pplns_payout_status') ), + array( 'NAME' => 'Active', 'STATUS' => $monitoring->getStatus('pplns_payout_active') ), + array( 'NAME' => 'Runtime', 'STATUS' => $monitoring->getStatus('pplns_payout_runtime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('pplns_payout_starttime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('pplns_payout_endtime') ), + array( 'NAME' => 'Last Message', 'STATUS' => $monitoring->getStatus('pplns_payout_message') ), + ); + break; +case 'pps': + $aCronStatus['pps_payout'] = array( + array( 'NAME' => 'Exit Code', 'STATUS' => $monitoring->getStatus('pps_payout_status') ), + array( 'NAME' => 'Active', 'STATUS' => $monitoring->getStatus('pps_payout_active') ), + array( 'NAME' => 'Runtime', 'STATUS' => $monitoring->getStatus('pps_payout_runtime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('pps_payout_starttime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('pps_payout_endtime') ), + array( 'NAME' => 'Last Message', 'STATUS' => $monitoring->getStatus('pps_payout_message') ), + ); + break; +case 'prop': + $aCronStatus['proportional_payout'] = array( + array( 'NAME' => 'Exit Code', 'STATUS' => $monitoring->getStatus('proportional_payout_status') ), + array( 'NAME' => 'Active', 'STATUS' => $monitoring->getStatus('proportional_payout_active') ), + array( 'NAME' => 'Runtime', 'STATUS' => $monitoring->getStatus('proportional_payout_runtime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('proportional_payout_starttime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('proportional_payout_endtime') ), + array( 'NAME' => 'Last Message', 'STATUS' => $monitoring->getStatus('proportional_payout_message') ), + ); + break; +} +$smarty->assign("CRONSTATUS", $aCronStatus); + +// Tempalte specifics +$smarty->assign("CONTENT", "default.tpl"); +?> diff --git a/public/include/pages/admin/settings.inc.php b/public/include/pages/admin/settings.inc.php index dab1f096..9bc7ef3b 100644 --- a/public/include/pages/admin/settings.inc.php +++ b/public/include/pages/admin/settings.inc.php @@ -19,6 +19,7 @@ if (@$_REQUEST['do'] == 'save' && !empty($_REQUEST['data'])) { // Fetch settings to propagate to template $smarty->assign("MAINTENANCE", $setting->getValue('maintenance')); $smarty->assign("LOCKREGISTRATION", $setting->getValue('lock_registration')); +$smarty->assign("DISABLEINVITATIONS", $setting->getValue('disable_invitations')); // Tempalte specifics $smarty->assign("CONTENT", "default.tpl"); diff --git a/public/include/pages/admin/transactions.inc.php b/public/include/pages/admin/transactions.inc.php index 00345903..fe991ba5 100644 --- a/public/include/pages/admin/transactions.inc.php +++ b/public/include/pages/admin/transactions.inc.php @@ -2,10 +2,20 @@ // Make sure we are called from index.php if (!defined('SECURITY')) die('Hacking attempt'); -if ($user->isAuthenticated()) { + +// Check user to ensure they are admin +if (!$user->isAuthenticated() || !$user->isAdmin($_SESSION['USERDATA']['id'])) { + header("HTTP/1.1 404 Page not found"); + die("404 Page not found"); +} + +if (!$smarty->isCached('master.tpl', $smarty_cache_key)) { + $debug->append('No cached version available, fetching from backend', 3); $aTransactions = $transaction->getAllTransactions(@$_REQUEST['start']); if (!$aTransactions) $_SESSION['POPUP'][] = array('CONTENT' => 'Could not find any transaction', 'TYPE' => 'errormsg'); - $smarty->assign('TRANSACTIONS', $aTransactions); - $smarty->assign('CONTENT', 'default.tpl'); +} else { + $debug->append('Using cached page', 3); } +$smarty->assign('TRANSACTIONS', $aTransactions); +$smarty->assign('CONTENT', 'default.tpl'); ?> diff --git a/public/include/pages/admin/user.inc.php b/public/include/pages/admin/user.inc.php index 9bb9ecee..ecb447b9 100644 --- a/public/include/pages/admin/user.inc.php +++ b/public/include/pages/admin/user.inc.php @@ -34,10 +34,17 @@ if (@$_POST['query']) { $aBalance = $transaction->getBalance($aUser['id']); $aUser['balance'] = $aBalance['confirmed']; $aUser['hashrate'] = $statistics->getUserHashrate($aUser['id']); - $aUser['payout']['est_block'] = round(( (int)$aUser['shares'] / (int)$aRoundShares['valid'] ) * (int)$config['reward'], 3); - $aUser['payout']['est_fee'] = round(($config['fees'] / 100) * $aUser['payout']['est_block'], 3); - $aUser['payout']['est_donation'] = round((( $aUser['donate_percent'] / 100) * ($aUser['payout']['est_block'] - $aUser['payout']['est_fee'])), 3); - $aUser['payout']['est_payout'] = round($aUser['payout']['est_block'] - $aUser['payout']['est_donation'] - $aUser['payout']['est_fee'], 3); + if ($aUser['shares'] > 0) { + $aUser['payout']['est_block'] = round(( (int)$aUser['shares'] / (int)$aRoundShares['valid'] ) * (int)$config['reward'], 3); + $aUser['payout']['est_fee'] = round(($config['fees'] / 100) * $aUser['payout']['est_block'], 3); + $aUser['payout']['est_donation'] = round((( $aUser['donate_percent'] / 100) * ($aUser['payout']['est_block'] - $aUser['payout']['est_fee'])), 3); + $aUser['payout']['est_payout'] = round($aUser['payout']['est_block'] - $aUser['payout']['est_donation'] - $aUser['payout']['est_fee'], 3); + } else { + $aUser['payout']['est_block'] = 0; + $aUser['payout']['est_fee'] = 0; + $aUser['payout']['est_donation'] = 0; + $aUser['payout']['est_payout'] = 0; + } $aUsers[$iKey] = $aUser; } // Assign our variables diff --git a/public/include/pages/admin/wallet.inc.php b/public/include/pages/admin/wallet.inc.php index 479ff919..fee0fd16 100644 --- a/public/include/pages/admin/wallet.inc.php +++ b/public/include/pages/admin/wallet.inc.php @@ -9,15 +9,22 @@ if (!$user->isAuthenticated() || !$user->isAdmin($_SESSION['USERDATA']['id'])) { die("404 Page not found"); } -if ($bitcoin->can_connect() === true){ - $dBalance = $bitcoin->query('getbalance'); +if (!$smarty->isCached('master.tpl', $smarty_cache_key)) { + $debug->append('No cached version available, fetching from backend', 3); + if ($bitcoin->can_connect() === true){ + $dBalance = $bitcoin->query('getbalance'); + } else { + $dBalance = 0; + $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to wallet RPC service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg'); + } + // Fetch locked balance from transactions + $dLockedBalance = $transaction->getLockedBalance(); } else { - $dBalance = 0; - $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to wallet RPC service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg'); + $debug->append('Using cached page', 3); } $smarty->assign("BALANCE", $dBalance); -$smarty->assign("LOCKED", $transaction->getLockedBalance()); +$smarty->assign("LOCKED", $dLockedBalance); // Tempalte specifics $smarty->assign("CONTENT", "default.tpl"); diff --git a/public/include/pages/api.inc.php b/public/include/pages/api.inc.php index c2e64a60..c85ada42 100644 --- a/public/include/pages/api.inc.php +++ b/public/include/pages/api.inc.php @@ -1,8 +1,10 @@ isActive(); // Check for valid API key $id = $user->checkApiKey($_REQUEST['api_key']); diff --git a/public/include/pages/api/getblockcount.inc.php b/public/include/pages/api/getblockcount.inc.php index 2cbd06a5..6764f436 100644 --- a/public/include/pages/api/getblockcount.inc.php +++ b/public/include/pages/api/getblockcount.inc.php @@ -1,8 +1,10 @@ isActive(); // Check user token $id = $user->checkApiKey($_REQUEST['api_key']); diff --git a/public/include/pages/api/getblocksfound.inc.php b/public/include/pages/api/getblocksfound.inc.php index 00883dad..b238bdc3 100644 --- a/public/include/pages/api/getblocksfound.inc.php +++ b/public/include/pages/api/getblocksfound.inc.php @@ -1,8 +1,10 @@ isActive(); // Check user token $id = $user->checkApiKey($_REQUEST['api_key']); diff --git a/public/include/pages/api/getcurrentworkers.inc.php b/public/include/pages/api/getcurrentworkers.inc.php index 4e26cc1c..48864316 100644 --- a/public/include/pages/api/getcurrentworkers.inc.php +++ b/public/include/pages/api/getcurrentworkers.inc.php @@ -1,8 +1,10 @@ isActive(); // Check user token $id = $user->checkApiKey($_REQUEST['api_key']); diff --git a/public/include/pages/api/getdifficulty.inc.php b/public/include/pages/api/getdifficulty.inc.php index 0e9cb6f9..e84d4045 100644 --- a/public/include/pages/api/getdifficulty.inc.php +++ b/public/include/pages/api/getdifficulty.inc.php @@ -1,8 +1,10 @@ isActive(); // Check user token $id = $user->checkApiKey($_REQUEST['api_key']); diff --git a/public/include/pages/api/getestimatedtime.inc.php b/public/include/pages/api/getestimatedtime.inc.php index a48393fa..f06e28f3 100644 --- a/public/include/pages/api/getestimatedtime.inc.php +++ b/public/include/pages/api/getestimatedtime.inc.php @@ -1,8 +1,10 @@ isActive(); // Check user token $id = $user->checkApiKey($_REQUEST['api_key']); diff --git a/public/include/pages/api/getpoolhashrate.inc.php b/public/include/pages/api/getpoolhashrate.inc.php index 6f6763ec..5546d321 100644 --- a/public/include/pages/api/getpoolhashrate.inc.php +++ b/public/include/pages/api/getpoolhashrate.inc.php @@ -1,8 +1,10 @@ isActive(); // Check user token $id = $user->checkApiKey($_REQUEST['api_key']); diff --git a/public/include/pages/api/getpoolsharerate.inc.php b/public/include/pages/api/getpoolsharerate.inc.php index 8e9117f1..a87859f6 100644 --- a/public/include/pages/api/getpoolsharerate.inc.php +++ b/public/include/pages/api/getpoolsharerate.inc.php @@ -1,8 +1,10 @@ isActive(); // Check user token $id = $user->checkApiKey($_REQUEST['api_key']); diff --git a/public/include/pages/api/getpoolstatus.inc.php b/public/include/pages/api/getpoolstatus.inc.php index ece9f557..4cf0d5ef 100644 --- a/public/include/pages/api/getpoolstatus.inc.php +++ b/public/include/pages/api/getpoolstatus.inc.php @@ -3,6 +3,9 @@ // Make sure we are called from index.php if (!defined('SECURITY')) die('Hacking attempt'); +// Check if the API is activated +$api->isActive(); + // Check user token $user_id = $user->checkApiKey($_REQUEST['api_key']); diff --git a/public/include/pages/api/gettimesincelastblock.inc.php b/public/include/pages/api/gettimesincelastblock.inc.php index 532da6bd..14575364 100644 --- a/public/include/pages/api/gettimesincelastblock.inc.php +++ b/public/include/pages/api/gettimesincelastblock.inc.php @@ -1,8 +1,10 @@ isActive(); // Check user token $id = $user->checkApiKey($_REQUEST['api_key']); diff --git a/public/include/pages/api/getuserstatus.inc.php b/public/include/pages/api/getuserstatus.inc.php index ac8f6654..afdd59fc 100644 --- a/public/include/pages/api/getuserstatus.inc.php +++ b/public/include/pages/api/getuserstatus.inc.php @@ -1,8 +1,10 @@ isActive(); // Check user token $user_id = $user->checkApiKey($_REQUEST['api_key']); diff --git a/public/include/pages/api/getuserworkers.inc.php b/public/include/pages/api/getuserworkers.inc.php index 9aaca562..06eb3411 100644 --- a/public/include/pages/api/getuserworkers.inc.php +++ b/public/include/pages/api/getuserworkers.inc.php @@ -1,8 +1,10 @@ isActive(); // Check user token $user_id = $user->checkApiKey($_REQUEST['api_key']); diff --git a/public/include/pages/api/public.inc.php b/public/include/pages/api/public.inc.php index 162e9134..5a98367a 100644 --- a/public/include/pages/api/public.inc.php +++ b/public/include/pages/api/public.inc.php @@ -1,10 +1,10 @@ isActive(); // Fetch last block information $aLastBlock = $block->getLast(); diff --git a/public/include/pages/home.inc.php b/public/include/pages/home.inc.php index ea4bd8fb..349b197d 100644 --- a/public/include/pages/home.inc.php +++ b/public/include/pages/home.inc.php @@ -6,16 +6,21 @@ if (!defined('SECURITY')) die('Hacking attempt'); // Include markdown library use \Michelf\Markdown; -// Fetch active news to display -$aNews = $news->getAllActive(); -if (is_array($aNews)) { - foreach ($aNews as $key => $aData) { - // Transform Markdown content to HTML - $aNews[$key]['content'] = Markdown::defaultTransform($aData['content']); +if (!$smarty->isCached('master.tpl', $smarty_cache_key)) { + $debug->append('No cached version available, fetching from backend', 3); + // Fetch active news to display + $aNews = $news->getAllActive(); + if (is_array($aNews)) { + foreach ($aNews as $key => $aData) { + // Transform Markdown content to HTML + $aNews[$key]['content'] = Markdown::defaultTransform($aData['content']); + } } + $smarty->assign("NEWS", $aNews); +} else { + $debug->append('Using cached page', 3); } // Load news entries for Desktop site and unauthenticated users -$smarty->assign("NEWS", $aNews); $smarty->assign("CONTENT", "default.tpl"); ?> diff --git a/public/include/pages/password/change.inc.php b/public/include/pages/password/change.inc.php index 8b5f4064..919632bd 100644 --- a/public/include/pages/password/change.inc.php +++ b/public/include/pages/password/change.inc.php @@ -4,14 +4,14 @@ if (!defined('SECURITY')) die('Hacking attempt'); -if ($_POST['do'] == 'useToken') { - if ($user->useToken($_POST['token'], $_POST['newPassword'], $_POST['newPassword2'])) { +if (isset($_POST['do']) && $_POST['do'] == 'resetPassword') { + if ($user->resetPassword($_POST['token'], $_POST['newPassword'], $_POST['newPassword2'])) { $_SESSION['POPUP'][] = array('CONTENT' => 'Password reset complete! Please login.'); } else { $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); } } - // Tempalte specifics $smarty->assign("CONTENT", "default.tpl"); + ?> diff --git a/public/include/pages/password/reset.inc.php b/public/include/pages/password/reset.inc.php index 796f5811..f7334fe8 100644 --- a/public/include/pages/password/reset.inc.php +++ b/public/include/pages/password/reset.inc.php @@ -5,7 +5,7 @@ if (!defined('SECURITY')) die('Hacking attempt'); // Process password reset request -if ($user->resetPassword($_POST['username'], $smarty)) { +if ($user->initResetPassword($_POST['username'], $smarty)) { $_SESSION['POPUP'][] = array('CONTENT' => 'Please check your mail account to finish your password reset'); } else { $_SESSION['POPUP'][] = array('CONTENT' => $user->getError(), 'TYPE' => 'errormsg'); diff --git a/public/include/pages/register.inc.php b/public/include/pages/register.inc.php index d47c67ed..01b71b18 100644 --- a/public/include/pages/register.inc.php +++ b/public/include/pages/register.inc.php @@ -3,9 +3,12 @@ // Make sure we are called from index.php if (!defined('SECURITY')) die('Hacking attempt'); -if ($setting->getValue('lock_registration')) { +if ($setting->getValue('lock_registration') && $setting->getValue('disable_invitations')) { $_SESSION['POPUP'][] = array('CONTENT' => 'Account registration is currently disabled. Please try again later.', 'TYPE' => 'errormsg'); $smarty->assign("CONTENT", "disabled.tpl"); +} else if ($setting->getValue('lock_registration') && !$setting->getValue('disable_invitations') && !isset($_GET['token'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Only invited users are allowed to register.', 'TYPE' => 'errormsg'); + $smarty->assign("CONTENT", "disabled.tpl"); } else { if ($config['recaptcha']['enabled']) { require_once(INCLUDE_DIR . '/lib/recaptchalib.php'); diff --git a/public/include/pages/register/register.inc.php b/public/include/pages/register/register.inc.php index 01a27e10..ca165e3b 100644 --- a/public/include/pages/register/register.inc.php +++ b/public/include/pages/register/register.inc.php @@ -13,33 +13,37 @@ if ($config['recaptcha']['enabled']) { ); } -// Check if recaptcha is enabled, process form data if valid -if($config['recaptcha']['enabled'] && $_POST["recaptcha_response_field"] && $_POST["recaptcha_response_field"]!=''){ - if ($rsp->is_valid) { - $smarty->assign("RECAPTCHA", recaptcha_get_html($config['recaptcha']['public_key'])); - if ($setting->getValue('lock_registration')) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Account registration is currently disabled. Please try again later.', 'TYPE' => 'errormsg'); - } else if ($user->register($_POST['username'], $_POST['password1'], $_POST['password2'], $_POST['pin'], $_POST['email1'], $_POST['email2']) && !$setting->getValue('lock_registration')) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Account created, please login'); +if ($setting->getValue('disable_invitations') && $setting->getValue('lock_registration')) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Account registration is currently disabled. Please try again later.', 'TYPE' => 'errormsg'); +} else if ($setting->getValue('lock_registration') && !$setting->getValue('disable_invitations') && !isset($_POST['token'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Only invited users are allowed to register.', 'TYPE' => 'errormsg'); +} else { + // Check if recaptcha is enabled, process form data if valid + if($config['recaptcha']['enabled'] && $_POST["recaptcha_response_field"] && $_POST["recaptcha_response_field"]!=''){ + if ($rsp->is_valid) { + $smarty->assign("RECAPTCHA", recaptcha_get_html($config['recaptcha']['public_key'])); + isset($_POST['token']) ? $token = $_POST['token'] : $token = ''; + if ($user->register($_POST['username'], $_POST['password1'], $_POST['password2'], $_POST['pin'], $_POST['email1'], $_POST['email2'], $token)) { + $config['accounts']['confirm_email']['enabled'] ? $_SESSION['POPUP'][] = array('CONTENT' => 'Please check your mailbox to activate this account') : $_SESSION['POPUP'][] = array('CONTENT' => 'Account created, please login'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to create account: ' . $user->getError(), 'TYPE' => 'errormsg'); + } + } else { + $smarty->assign("RECAPTCHA", recaptcha_get_html($config['recaptcha']['public_key'], $rsp->error)); + $_SESSION['POPUP'][] = array('CONTENT' => 'Invalid Captcha, please try again. (' . $rsp->error . ')', 'TYPE' => 'errormsg'); + } + // Empty captcha + } else if ($config['recaptcha']['enabled']) { + $smarty->assign("RECAPTCHA", recaptcha_get_html($config['recaptcha']['public_key'], $rsp->error)); + $_SESSION['POPUP'][] = array('CONTENT' => 'Empty Captcha, please try again.', 'TYPE' => 'errormsg'); + // Captcha disabled + } else { + isset($_POST['token']) ? $token = $_POST['token'] : $token = ''; + if ($user->register($_POST['username'], $_POST['password1'], $_POST['password2'], $_POST['pin'], $_POST['email1'], $_POST['email2'], $token)) { + $config['accounts']['confirm_email']['enabled'] ? $_SESSION['POPUP'][] = array('CONTENT' => 'Please check your mailbox to activate this account') : $_SESSION['POPUP'][] = array('CONTENT' => 'Account created, please login'); } else { $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to create account: ' . $user->getError(), 'TYPE' => 'errormsg'); } - } else { - $smarty->assign("RECAPTCHA", recaptcha_get_html($config['recaptcha']['public_key'], $rsp->error)); - $_SESSION['POPUP'][] = array('CONTENT' => 'Invalid Captcha, please try again. (' . $rsp->error . ')', 'TYPE' => 'errormsg'); - } -// Empty captcha -} else if ($config['recaptcha']['enabled']) { - $smarty->assign("RECAPTCHA", recaptcha_get_html($config['recaptcha']['public_key'], $rsp->error)); - $_SESSION['POPUP'][] = array('CONTENT' => 'Empty Captcha, please try again.', 'TYPE' => 'errormsg'); -// Captcha disabled -} else { - if ($setting->getValue('lock_registration')) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Account registration is currently disabled. Please try again later.', 'TYPE' => 'errormsg'); - } else if ($user->register($_POST['username'], $_POST['password1'], $_POST['password2'], $_POST['pin'], $_POST['email1'], $_POST['email2']) && !$setting->getValue('lock_registration')) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Account created, please login'); - } else { - $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to create account: ' . $user->getError(), 'TYPE' => 'errormsg'); } } diff --git a/public/include/pages/statistics.inc.php b/public/include/pages/statistics.inc.php index dc27fde6..076ce66f 100644 --- a/public/include/pages/statistics.inc.php +++ b/public/include/pages/statistics.inc.php @@ -4,17 +4,23 @@ if (!defined('SECURITY')) die('Hacking attempt'); -if ($bitcoin->can_connect() === true){ - $dDifficulty = $bitcoin->query('getdifficulty'); - if (is_array($dDifficulty) && array_key_exists('proof-of-work', $dDifficulty)) - $dDifficulty = $dDifficulty['proof-of-work']; - $iBlock = $bitcoin->query('getblockcount'); +if (!$smarty->isCached('master.tpl', $smarty_cache_key)) { + $debug->append('No cached version available, fetching from backend', 3); + if ($bitcoin->can_connect() === true){ + $dDifficulty = $bitcoin->query('getdifficulty'); + if (is_array($dDifficulty) && array_key_exists('proof-of-work', $dDifficulty)) + $dDifficulty = $dDifficulty['proof-of-work']; + $iBlock = $bitcoin->query('getblockcount'); + } else { + $dDifficulty = 1; + $iBlock = 0; + $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to litecoind RPC service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg'); + } + $smarty->assign("CURRENTBLOCK", $iBlock); + $smarty->assign("DIFFICULTY", $dDifficulty); } else { - $dDifficulty = 1; - $iBlock = 0; - $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to litecoind RPC service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg'); + $debug->append('Using cached page', 3); } -$smarty->assign("CURRENTBLOCK", $iBlock); -$smarty->assign("DIFFICULTY", $dDifficulty); $smarty->assign("CONTENT", "default.tpl"); +?> diff --git a/public/include/pages/statistics/blocks.inc.php b/public/include/pages/statistics/blocks.inc.php index c65ea861..3218a9f4 100644 --- a/public/include/pages/statistics/blocks.inc.php +++ b/public/include/pages/statistics/blocks.inc.php @@ -2,15 +2,24 @@ // Make sure we are called from index.php if (!defined('SECURITY')) die('Hacking attempt'); -if (!$user->isAuthenticated()) header("Location: index.php?page=home"); // Grab the last blocks found -empty($_REQUEST['limit']) ? $iLimit = 20 : $iLimit = $_REQUEST['limit']; -$aBlocksFoundData = $statistics->getBlocksFound($iLimit); +if (!$smarty->isCached('master.tpl', $smarty_cache_key)) { + $debug->append('No cached version available, fetching from backend', 3); + // Grab the last blocks found + $iLimit = 20; + $aBlocksFoundData = $statistics->getBlocksFound($iLimit); -// Propagate content our template -$smarty->assign("BLOCKSFOUND", $aBlocksFoundData); -$smarty->assign("BLOCKLIMIT", $iLimit); + // Propagate content our template + $smarty->assign("BLOCKSFOUND", $aBlocksFoundData); + $smarty->assign("BLOCKLIMIT", $iLimit); +} else { + $debug->append('Using cached page', 3); +} -$smarty->assign("CONTENT", "default.tpl"); +if ($config['website']['acl']['statistics']['blocks'] == 'public') { + $smarty->assign("CONTENT", "default.tpl"); +} else if ($user->isAuthenticated()) { + $smarty->assign("CONTENT", "default.tpl"); +} ?> diff --git a/public/include/pages/statistics/graphs.inc.php b/public/include/pages/statistics/graphs.inc.php index f7016835..575ce36d 100644 --- a/public/include/pages/statistics/graphs.inc.php +++ b/public/include/pages/statistics/graphs.inc.php @@ -1,16 +1,19 @@ isAuthenticated()) { - $aHourlyHashRates = $statistics->getHourlyHashrateByAccount($_SESSION['USERDATA']['id']); - $aPoolHourlyHashRates = $statistics->getHourlyHashrateByPool(); +if (!$smarty->isCached('master.tpl', $smarty_cache_key)) { + $debug->append('No cached version available, fetching from backend', 3); + if ($user->isAuthenticated()) { + $aHourlyHashRates = $statistics->getHourlyHashrateByAccount($_SESSION['USERDATA']['id']); + $aPoolHourlyHashRates = $statistics->getHourlyHashrateByPool(); + } + $smarty->assign("YOURHASHRATES", @$aHourlyHashRates); + $smarty->assign("POOLHASHRATES", @$aPoolHourlyHashRates); +} else { + $debug->append('Using cached page', 3); } -// Propagate content our template -$smarty->assign("YOURHASHRATES", @$aHourlyHashRates); -$smarty->assign("POOLHASHRATES", @$aPoolHourlyHashRates); $smarty->assign("CONTENT", "default.tpl"); ?> diff --git a/public/include/pages/statistics/pool.inc.php b/public/include/pages/statistics/pool.inc.php index 1cab1009..b8872293 100644 --- a/public/include/pages/statistics/pool.inc.php +++ b/public/include/pages/statistics/pool.inc.php @@ -1,59 +1,74 @@ can_connect() === true){ - $dDifficulty = $bitcoin->getdifficulty(); - if (is_array($dDifficulty) && array_key_exists('proof-of-work', $dDifficulty)) - $dDifficulty = $dDifficulty['proof-of-work']; - $iBlock = $bitcoin->getblockcount(); +if (!$smarty->isCached('master.tpl', $smarty_cache_key)) { + $debug->append('No cached version available, fetching from backend', 3); + // Fetch data from wallet + if ($bitcoin->can_connect() === true){ + $dDifficulty = $bitcoin->getdifficulty(); + if (is_array($dDifficulty) && array_key_exists('proof-of-work', $dDifficulty)) + $dDifficulty = $dDifficulty['proof-of-work']; + $iBlock = $bitcoin->getblockcount(); + $sBlockHash = $bitcoin->query('getblockhash', $iBlock); + } else { + $dDifficulty = 1; + $iBlock = 0; + $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to wallet RPC service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg'); + } + + // Top share contributors + $aContributorsShares = $statistics->getTopContributors('shares', 15); + + // Top hash contributors + $aContributorsHashes = $statistics->getTopContributors('hashes', 15); + + // Grab the last 10 blocks found + $iLimit = 5; + $aBlocksFoundData = $statistics->getBlocksFound($iLimit); + count($aBlocksFoundData) > 0 ? $aBlockData = $aBlocksFoundData[0] : $aBlockData = array(); + + // Estimated time to find the next block + $iCurrentPoolHashrate = $statistics->getCurrentHashrate(); + + // Time in seconds, not hours, using modifier in smarty to translate + $iCurrentPoolHashrate > 0 ? $iEstTime = $dDifficulty * pow(2,32) / ($iCurrentPoolHashrate * 1000) : $iEstTime = 0; + + // Time since last block + $now = new DateTime( "now" ); + if (!empty($aBlockData)) { + $dTimeSinceLast = ($now->getTimestamp() - $aBlockData['time']); + if ($dTimeSinceLast < 0) $dTimeSinceLast = 0; + } else { + $dTimeSinceLast = 0; + } + + // Propagate content our template + $smarty->assign("ESTTIME", $iEstTime); + $smarty->assign("TIMESINCELAST", $dTimeSinceLast); + $smarty->assign("BLOCKSFOUND", $aBlocksFoundData); + $smarty->assign("BLOCKLIMIT", $iLimit); + $smarty->assign("CONTRIBSHARES", $aContributorsShares); + $smarty->assign("CONTRIBHASHES", $aContributorsHashes); + $smarty->assign("CURRENTBLOCK", $iBlock); + $smarty->assign("CURRENTBLOCKHASH", $sBlockHash); + if (count($aBlockData) > 0) { + $smarty->assign("LASTBLOCK", $aBlockData['height']); + $smarty->assign("LASTBLOCKHASH", $aBlockData['blockhash']); + } else { + $smarty->assign("LASTBLOCK", 0); + } + $smarty->assign("DIFFICULTY", $dDifficulty); + $smarty->assign("REWARD", $config['reward']); } else { - $dDifficulty = 1; - $iBlock = 0; - $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to wallet RPC service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg'); + $debug->append('Using cached page', 3); } -// Top share contributors -$aContributorsShares = $statistics->getTopContributors('shares', 15); - -// Top hash contributors -$aContributorsHashes = $statistics->getTopContributors('hashes', 15); - -// Grab the last 10 blocks found -$iLimit = 5; -$aBlocksFoundData = $statistics->getBlocksFound($iLimit); -count($aBlocksFoundData) > 0 ? $aBlockData = $aBlocksFoundData[0] : $aBlockData = array(); - -// Estimated time to find the next block -$iCurrentPoolHashrate = $statistics->getCurrentHashrate(); - -// Time in seconds, not hours, using modifier in smarty to translate -$iCurrentPoolHashrate > 0 ? $iEstTime = $dDifficulty * pow(2,32) / ($iCurrentPoolHashrate * 1000) : $iEstTime = 0; - -// Time since last block -$now = new DateTime( "now" ); -if (!empty($aBlockData)) { - $dTimeSinceLast = ($now->getTimestamp() - $aBlockData['time']); -} else { - $dTimeSinceLast = 0; -} - -// Propagate content our template -$smarty->assign("ESTTIME", $iEstTime); -$smarty->assign("TIMESINCELAST", $dTimeSinceLast); -$smarty->assign("BLOCKSFOUND", $aBlocksFoundData); -$smarty->assign("BLOCKLIMIT", $iLimit); -$smarty->assign("CONTRIBSHARES", $aContributorsShares); -$smarty->assign("CONTRIBHASHES", $aContributorsHashes); -$smarty->assign("CURRENTBLOCK", $iBlock); -count($aBlockData) > 0 ? $smarty->assign("LASTBLOCK", $aBlockData['height']) : $smarty->assign("LASTBLOCK", 0); -$smarty->assign("DIFFICULTY", $dDifficulty); -$smarty->assign("REWARD", $config['reward']); - -if ($user->isAuthenticated()) { +// Public / private page detection +if ($config['website']['acl']['statistics']['pool'] == 'public') { + $smarty->assign("CONTENT", "authenticated.tpl"); +} else if ($user->isAuthenticated() && $config['website']['acl']['statistics']['pool'] == 'private') { $smarty->assign("CONTENT", "authenticated.tpl"); } else { $smarty->assign("CONTENT", "../default.tpl"); diff --git a/public/include/smarty.inc.php b/public/include/smarty.inc.php index 8a320581..dfd1f4f6 100644 --- a/public/include/smarty.inc.php +++ b/public/include/smarty.inc.php @@ -18,8 +18,14 @@ $smarty = new Smarty; $debug->append('Define Smarty Paths', 3); $smarty->template_dir = BASEPATH . 'templates/' . THEME . '/'; $smarty->compile_dir = BASEPATH . 'templates/compile/'; +$smarty_cache_key = md5(serialize($_REQUEST) . serialize(@$_SESSION['USERDATA']['id'])); // Optional smarty caching, check Smarty documentation for details -$smarty->caching = $config['cache']; -$smarty->cache_dir = BASEPATH . "templates/cache"; +if ($config['smarty']['cache']) { + $debug->append('Enable smarty cache'); + $smarty->setCaching(Smarty::CACHING_LIFETIME_SAVED); + $smarty->cache_lifetime = $config['smarty']['cache_lifetime']; + $smarty->cache_dir = BASEPATH . "templates/cache"; + $smarty->use_sub_dirs = true; +} ?> diff --git a/public/include/smarty_globals.inc.php b/public/include/smarty_globals.inc.php index e7bfd4b5..fe1868d6 100644 --- a/public/include/smarty_globals.inc.php +++ b/public/include/smarty_globals.inc.php @@ -1,12 +1,12 @@ append('Global smarty variables', 3); +$debug->append('No cached page detected, loading smarty globals', 3); // Defaults to get rid of PHP Notice warnings $dDifficulty = 1; $aRoundShares = 1; @@ -24,7 +24,7 @@ if (@$_SESSION['AUTHENTICATED']) { $bitcoin->can_connect() === true ? $dNetworkHashrate = $bitcoin->query('getnetworkhashps') : $dNetworkHashrate = 0; // Fetch some data -$iCurrentActiveWorkers = $worker->getCountAllActiveWorkers(); +if (!$iCurrentActiveWorkers = $worker->getCountAllActiveWorkers()) $iCurrentActiveWorkers = 0; $iCurrentPoolHashrate = $statistics->getCurrentHashrate(); $iCurrentPoolShareRate = $statistics->getCurrentShareRate(); @@ -47,7 +47,9 @@ $aGlobal = array( 'blockexplorer' => $config['blockexplorer'], 'chaininfo' => $config['chaininfo'], 'config' => array( - 'website' => array( 'title' => $config['website']['title'] ), + 'website' => $config['website'], + 'accounts' => $config['accounts'], + 'disable_invitations' => $setting->getValue('disable_invitations'), 'price' => array( 'currency' => $config['price']['currency'] ), 'targetdiff' => $config['difficulty'], 'currency' => $config['currency'], @@ -83,9 +85,7 @@ if (@$_SESSION['USERDATA']['id']) { $aGlobal['userdata']['sharerate'] = $statistics->getUserSharerate($_SESSION['USERDATA']['id']); switch ($config['payout_system']) { - case 'pps': - break; - default: + case 'prop' || 'pplns': // Some estimations if (@$aRoundShares['valid'] > 0) { $aGlobal['userdata']['est_block'] = round(( (int)$aGlobal['userdata']['shares']['valid'] / (int)$aRoundShares['valid'] ) * (float)$config['reward'], 8); @@ -98,12 +98,20 @@ if (@$_SESSION['USERDATA']['id']) { $aGlobal['userdata']['est_donation'] = 0; $aGlobal['userdata']['est_payout'] = 0; } + case 'pplns': + if ($iAvgBlockShares = round($block->getAvgBlockShares($config['pplns']['blockavg']['blockcount']))) { + $aGlobal['pplns']['target'] = $iAvgBlockShares; + } else { + $aGlobal['pplns']['target'] = $config['pplns']['shares']['default']; + } + break; + case 'pps': break; } // Site-wide notifications, based on user events if ($aGlobal['userdata']['balance']['confirmed'] >= $config['ap_threshold']['max']) - $_SESSION['POPUP'][] = array('CONTENT' => 'You have exceeded your accounts balance. Please transfer some ' . $config['currency'] . "!", 'TYPE' => 'errormsg'); + $_SESSION['POPUP'][] = array('CONTENT' => 'You have exceeded the pools configured ' . $config['currency'] . ' warning threshold. Please initiate a transfer!', 'TYPE' => 'errormsg'); if ($user->getUserFailed($_SESSION['USERDATA']['id']) > 0) $_SESSION['POPUP'][] = array('CONTENT' => 'You have ' . $user->getUserFailed($_SESSION['USERDATA']['id']) . ' failed login attempts! Reset Counter', 'TYPE' => 'errormsg'); } diff --git a/public/index.php b/public/index.php index dbb28f9a..ac7c4154 100644 --- a/public/index.php +++ b/public/index.php @@ -24,13 +24,15 @@ define("BASEPATH", "./"); // Our security check define("SECURITY", 1); -// Start a session -session_start(); -$session_id = session_id(); - // Include our configuration (holding defines for the requires) if (!include_once(BASEPATH . 'include/config/global.inc.php')) die('Unable to load site configuration'); +// Start a session +session_set_cookie_params(time()+$config['cookie']['duration'], $config['cookie']['path'], $config['cookie']['domain'], $config['cookie']['secure'], $config['cookie']['httponly']); +session_start(); +setcookie(session_name(),session_id(),time()+$config['cookie']['duration'], $config['cookie']['path'], $config['cookie']['domain'], $config['cookie']['secure'], $config['cookie']['httponly']); +$session_id = session_id(); + // Load Classes, they name defines the $ variable used // We include all needed files here, even though our templates could load them themself require_once(INCLUDE_DIR . '/autoloader.inc.php'); @@ -82,7 +84,7 @@ $smarty->assign('DebuggerInfo', $debug->getDebugInfo()); // Display our page if (!@$supress_master) - $smarty->display("master.tpl", md5(serialize($_REQUEST))); + $smarty->display("master.tpl", $smarty_cache_key); // Unset any temporary values here unset($_SESSION['POPUP']); diff --git a/public/templates/mail/invitations/body.tpl b/public/templates/mail/invitations/body.tpl new file mode 100644 index 00000000..353b82e0 --- /dev/null +++ b/public/templates/mail/invitations/body.tpl @@ -0,0 +1,11 @@ + + +

Hello valued miner,


+

{$DATA.username} invited you to participate on this pool: +

http://{$smarty.server.SERVER_NAME}{$smarty.server.PHP_SELF}?page=register&token={$DATA.token}

+{if $DATA.message}

Personal message:

{$DATA.message}

{/if} +

+

Cheers,

+

Website Administration

+ + diff --git a/public/templates/mail/body.tpl b/public/templates/mail/password/reset.tpl similarity index 80% rename from public/templates/mail/body.tpl rename to public/templates/mail/password/reset.tpl index a80b579a..fe488dca 100644 --- a/public/templates/mail/body.tpl +++ b/public/templates/mail/password/reset.tpl @@ -1,8 +1,8 @@ -

Hello {$USERNAME},


+

Hello {$DATA.username},


You have requested a password reset through our online form. In order to complete the request please follow this link:

-

http://{$smarty.server.SERVER_NAME}{$smarty.server.PHP_SELF}?page=password&action=change&token={$TOKEN}

+

http://{$smarty.server.SERVER_NAME}{$smarty.server.PHP_SELF}?page=password&action=change&token={$DATA.token}

You will be asked to change your password. You can then use this new password to login to your account.

Cheers,

Website Administration

diff --git a/public/templates/mail/register/confirm_email.tpl b/public/templates/mail/register/confirm_email.tpl new file mode 100644 index 00000000..67ea72c5 --- /dev/null +++ b/public/templates/mail/register/confirm_email.tpl @@ -0,0 +1,10 @@ + + +

Hello {$DATA.username},


+

You have create a new account. In order to complete the registration process please follow this link:

+

http://{$smarty.server.SERVER_NAME}{$smarty.server.PHP_SELF}?page=account&action=confirm&token={$DATA.token}

+

+

Cheers,

+

Website Administration

+ + diff --git a/public/templates/mmcFE/about/donors/default.tpl b/public/templates/mmcFE/about/donors/default.tpl index 2d5efaf3..8d5205ac 100644 --- a/public/templates/mmcFE/about/donors/default.tpl +++ b/public/templates/mmcFE/about/donors/default.tpl @@ -12,7 +12,7 @@ {section name=donor loop=$DONORS} - {$DONORS[donor].username} + {if $DONORS[donor].is_anonymous|default:"0" == 1}anonymous{else}{$DONORS[donor].username}{/if} {$DONORS[donor].donate_percent} {$DONORS[donor].donation|number_format:"2"} diff --git a/public/templates/mmcFE/account/confirm/default.tpl b/public/templates/mmcFE/account/confirm/default.tpl new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/public/templates/mmcFE/account/confirm/default.tpl @@ -0,0 +1 @@ + diff --git a/public/templates/mmcFE/account/edit/default.tpl b/public/templates/mmcFE/account/edit/default.tpl index 482c3579..c3b28f42 100644 --- a/public/templates/mmcFE/account/edit/default.tpl +++ b/public/templates/mmcFE/account/edit/default.tpl @@ -4,13 +4,17 @@ - + - - - - + {if !$GLOBAL.config.website.api.disabled}{/if} + + + +
Username: {$GLOBAL.userdata.username}
Username: {$GLOBAL.userdata.username|escape}
User Id: {$GLOBAL.userdata.id}
API Key: {$GLOBAL.userdata.api_key}
E-Mail:
Payment Address:
Donation %: [donation amount in percent (example: 0.5)]
API Key: {$GLOBAL.userdata.api_key}
E-Mail:
Payment Address:
Donation %: [donation amount in percent (example: 0.5)]
Automatic Payout Threshold: [{$GLOBAL.config.ap_threshold.min}-{$GLOBAL.config.ap_threshold.max} {$GLOBAL.config.currency}. Set to '0' for no auto payout]
Anonymous Account : + + +
4 digit PIN: [The 4 digit PIN you chose when registering]
@@ -23,8 +27,8 @@ - - + +
Account Balance:    {$GLOBAL.userdata.balance.confirmed|escape} {$GLOBAL.config.currency}
Payout to:
{$GLOBAL.userdata.coin_address|escape}
Account Balance:    {nocache}{$GLOBAL.userdata.balance.confirmed|escape}{/nocache} {$GLOBAL.config.currency}
Payout to:
{nocache}{$GLOBAL.userdata.coin_address|escape}{/nocache}
4 digit PIN:
diff --git a/public/templates/mmcFE/account/invitations/default.tpl b/public/templates/mmcFE/account/invitations/default.tpl new file mode 100644 index 00000000..b8d6743a --- /dev/null +++ b/public/templates/mmcFE/account/invitations/default.tpl @@ -0,0 +1,43 @@ +{include file="global/block_header.tpl" ALIGN="left" BLOCK_HEADER="Invitations"} +
+ + + + + + + + + + + + + + + +
E-Mail
Message
+ +
+
+{include file="global/block_footer.tpl"} + +{include file="global/block_header.tpl" ALIGN="right" BLOCK_HEADER="Past Invitations"} + + + + + + + + + +{section name=invite loop=$INVITATIONS} + + + + + +{/section} + +
E-MailSentActivated
{$INVITATIONS[invite].email}{$INVITATIONS[invite].time|date_format:"%d/%m/%Y %H:%M:%S"}
+{include file="global/block_footer.tpl"} diff --git a/public/templates/mmcFE/account/notifications/default.tpl b/public/templates/mmcFE/account/notifications/default.tpl index 1d54729b..52140466 100644 --- a/public/templates/mmcFE/account/notifications/default.tpl +++ b/public/templates/mmcFE/account/notifications/default.tpl @@ -12,7 +12,7 @@ IDLE Worker - + @@ -20,7 +20,7 @@ New Blocks - + @@ -28,7 +28,7 @@ Auto Payout - + @@ -36,7 +36,7 @@ Manual Payout - + diff --git a/public/templates/mmcFE/account/transactions/default.tpl b/public/templates/mmcFE/account/transactions/default.tpl index 160b12e4..a7337bd6 100644 --- a/public/templates/mmcFE/account/transactions/default.tpl +++ b/public/templates/mmcFE/account/transactions/default.tpl @@ -17,15 +17,9 @@ {assign var=has_confirmed value=false} {section transaction $TRANSACTIONS} {if ( - (($TRANSACTIONS[transaction].type == 'Credit' or $TRANSACTIONS[transaction].type == 'Bonus')and $TRANSACTIONS[transaction].confirmations >= $GLOBAL.confirmations) - or ($TRANSACTIONS[transaction].type == 'Donation' and $TRANSACTIONS[transaction].confirmations >= $GLOBAL.confirmations) - or ($TRANSACTIONS[transaction].type == 'Fee' and $TRANSACTIONS[transaction].confirmations >= $GLOBAL.confirmations) - or $TRANSACTIONS[transaction].type == 'Credit_PPS' - or $TRANSACTIONS[transaction].type == 'Fee_PPS' - or $TRANSACTIONS[transaction].type == 'Donation_PPS' - or $TRANSACTIONS[transaction].type == 'Debit_AP' - or $TRANSACTIONS[transaction].type == 'Debit_MP' - or $TRANSACTIONS[transaction].type == 'TXFee' + ( ( $TRANSACTIONS[transaction].type == 'Credit' or $TRANSACTIONS[transaction].type == 'Bonus' or $TRANSACTIONS[transaction].type == 'Donation' or $TRANSACTIONS[transaction].type == 'Fee' ) and $TRANSACTIONS[transaction].confirmations >= $GLOBAL.confirmations ) + or $TRANSACTIONS[transaction].type == 'Credit_PPS' or $TRANSACTIONS[transaction].type == 'Fee_PPS' or $TRANSACTIONS[transaction].type == 'Donation_PPS' + or $TRANSACTIONS[transaction].type == 'Debit_AP' or $TRANSACTIONS[transaction].type == 'Debit_MP' or $TRANSACTIONS[transaction].type == 'TXFee' )} {assign var=has_credits value=true} @@ -67,11 +61,9 @@ {assign var=has_unconfirmed value=false} {section transaction $TRANSACTIONS} - {if ( - ($TRANSACTIONS[transaction].type == 'Credit' or $TRANSACTIONS[transaction].type == 'Bonus') and $TRANSACTIONS[transaction].confirmations < $GLOBAL.confirmations - or ($TRANSACTIONS[transaction].type == 'Donation' and $TRANSACTIONS[transaction].confirmations < $GLOBAL.confirmations) - or ($TRANSACTIONS[transaction].type == 'Fee' and $TRANSACTIONS[transaction].confirmations < $GLOBAL.confirmations) - )} + {if + (($TRANSACTIONS[transaction].type == 'Credit' or $TRANSACTIONS[transaction].type == 'Bonus' or $TRANSACTIONS[transaction].type == 'Donation' or $TRANSACTIONS[transaction].type == 'Fee') and $TRANSACTIONS[transaction].confirmations < $GLOBAL.confirmations and $TRANSACTIONS[transaction].confirmations >= 0) + } {assign var=has_unconfirmed value=true} {$TRANSACTIONS[transaction].id} @@ -117,21 +109,16 @@ {assign var=has_orphaned value=false} {section transaction $TRANSACTIONS} - {if ( - $TRANSACTIONS[transaction].type == 'Orphan_Credit' - or $TRANSACTIONS[transaction].type == 'Orphan_Donation' - or $TRANSACTIONS[transaction].type == 'Orphan_Fee' - or $TRANSACTIONS[transaction].type == 'Orphan_Bonus' - )} + {if ($TRANSACTIONS[transaction].type == 'Credit' or $TRANSACTIONS[transaction].type == 'Fee' or $TRANSACTIONS[transaction].type == 'Donation' or $TRANSACTIONS[transaction].type == 'Bonus') and $TRANSACTIONS[transaction].confirmations == -1} {$TRANSACTIONS[transaction].id} {$TRANSACTIONS[transaction].timestamp} {$TRANSACTIONS[transaction].type} {$TRANSACTIONS[transaction].coin_address} {if $TRANSACTIONS[transaction].height == 0}n/a{else}{$TRANSACTIONS[transaction].height}{/if} - {$TRANSACTIONS[transaction].amount|number_format:"8"} + {$TRANSACTIONS[transaction].amount|number_format:"8"} - {if $TRANSACTIONS[transaction].type == 'Orphan_Credit' or $TRANSACTIONS[transaction].type == 'Orphan_Bonus'} + {if $TRANSACTIONS[transaction].type == 'Credit' or $TRANSACTIONS[transaction].type == 'Bonus'} {assign var="orphan_credits" value="`$orphan_credits|default:"0"+$TRANSACTIONS[transaction].amount`"} {else} {assign var="orphan_debits" value="`$orphan_debits|default:"0"+$TRANSACTIONS[transaction].amount`"} diff --git a/public/templates/mmcFE/account/workers/default.tpl b/public/templates/mmcFE/account/workers/default.tpl index c69d1139..f26e3109 100644 --- a/public/templates/mmcFE/account/workers/default.tpl +++ b/public/templates/mmcFE/account/workers/default.tpl @@ -15,6 +15,7 @@     + {nocache} {section worker $WORKERS} {assign var="username" value="."|escape|explode:$WORKERS[worker].username:2} @@ -29,6 +30,7 @@ {/section} + {/nocache} diff --git a/public/templates/mmcFE/admin/monitoring/default.tpl b/public/templates/mmcFE/admin/monitoring/default.tpl new file mode 100644 index 00000000..f7b099b1 --- /dev/null +++ b/public/templates/mmcFE/admin/monitoring/default.tpl @@ -0,0 +1,45 @@ +{include file="global/block_header.tpl" BLOCK_HEADER="Monitoring"} + + + + + + + + + + + +{foreach $CRONSTATUS as $cron=>$v} + + + {foreach $v as $event} + + {/foreach} + +{/foreach} + +
CronjobExit CodeActiveRuntimeStart TimeEnd TimeMessage
{$cron} + {if $event.STATUS.type == 'okerror'} + {if $event.STATUS.value == 0} + OK + {else} + ERROR + {/if} + {else if $event.STATUS.type == 'message'} + {$event.STATUS.value} + {else if $event.STATUS.type == 'yesno'} + {if $event.STATUS.value == 1} + Yes + {else} + No + {/if} + {else if $event.STATUS.type == 'time'} + {$event.STATUS.value|default:"0"|number_format:"2"} seconds + {else if $event.STATUS.type == 'date'} + {$event.STATUS.value|date_format:"%m/%d %H:%M:%S"} + {else} + {$event.STATUS.value|default:""} + {/if} +
+{include file="global/block_footer.tpl"} diff --git a/public/templates/mmcFE/admin/news/default.tpl b/public/templates/mmcFE/admin/news/default.tpl index 7bd8c198..8c3ac5d1 100644 --- a/public/templates/mmcFE/admin/news/default.tpl +++ b/public/templates/mmcFE/admin/news/default.tpl @@ -17,6 +17,7 @@ {include file="global/block_footer.tpl"} +{nocache} {section name=news loop=$NEWS} {include file="global/block_header.tpl" @@ -35,4 +36,5 @@ {include file="global/block_footer.tpl"} {/section} +{/nocache} {include file="global/block_footer.tpl"} diff --git a/public/templates/mmcFE/admin/news_edit/default.tpl b/public/templates/mmcFE/admin/news_edit/default.tpl index 467d4a6c..22e945fb 100644 --- a/public/templates/mmcFE/admin/news_edit/default.tpl +++ b/public/templates/mmcFE/admin/news_edit/default.tpl @@ -10,7 +10,7 @@ Active - + @@ -18,13 +18,13 @@ Header - + Content - + diff --git a/public/templates/mmcFE/admin/settings/default.tpl b/public/templates/mmcFE/admin/settings/default.tpl index 3cd67f4e..154c39d1 100644 --- a/public/templates/mmcFE/admin/settings/default.tpl +++ b/public/templates/mmcFE/admin/settings/default.tpl @@ -16,7 +16,7 @@ @@ -26,7 +26,17 @@ + + + + Disable Invitations + + + diff --git a/public/templates/mmcFE/admin/transactions/default.tpl b/public/templates/mmcFE/admin/transactions/default.tpl index d611f6d6..c493de8b 100644 --- a/public/templates/mmcFE/admin/transactions/default.tpl +++ b/public/templates/mmcFE/admin/transactions/default.tpl @@ -21,15 +21,9 @@ {assign var=confirmed value=0} {section transaction $TRANSACTIONS} {if ( - (($TRANSACTIONS[transaction].type == 'Credit' or $TRANSACTIONS[transaction].type == 'Bonus')and $TRANSACTIONS[transaction].confirmations >= $GLOBAL.confirmations) - or ($TRANSACTIONS[transaction].type == 'Donation' and $TRANSACTIONS[transaction].confirmations >= $GLOBAL.confirmations) - or ($TRANSACTIONS[transaction].type == 'Fee' and $TRANSACTIONS[transaction].confirmations >= $GLOBAL.confirmations) - or $TRANSACTIONS[transaction].type == 'Credit_PPS' - or $TRANSACTIONS[transaction].type == 'Fee_PPS' - or $TRANSACTIONS[transaction].type == 'Donation_PPS' - or $TRANSACTIONS[transaction].type == 'Debit_AP' - or $TRANSACTIONS[transaction].type == 'Debit_MP' - or $TRANSACTIONS[transaction].type == 'TXFee' + ( ( $TRANSACTIONS[transaction].type == 'Credit' or $TRANSACTIONS[transaction].type == 'Bonus' or $TRANSACTIONS[transaction].type == 'Donation' or $TRANSACTIONS[transaction].type == 'Fee' ) and $TRANSACTIONS[transaction].confirmations >= $GLOBAL.confirmations ) + or $TRANSACTIONS[transaction].type == 'Credit_PPS' or $TRANSACTIONS[transaction].type == 'Fee_PPS' or $TRANSACTIONS[transaction].type == 'Donation_PPS' + or $TRANSACTIONS[transaction].type == 'Debit_AP' or $TRANSACTIONS[transaction].type == 'Debit_MP' or $TRANSACTIONS[transaction].type == 'TXFee' )} {assign var=confirmed value=1} @@ -74,11 +68,7 @@ {assign var=unconfirmed value=0} {section transaction $TRANSACTIONS} - {if ( - ($TRANSACTIONS[transaction].type == 'Credit' or $TRANSACTIONS[transaction].type == 'Bonus') and $TRANSACTIONS[transaction].confirmations < $GLOBAL.confirmations - or ($TRANSACTIONS[transaction].type == 'Donation' and $TRANSACTIONS[transaction].confirmations < $GLOBAL.confirmations) - or ($TRANSACTIONS[transaction].type == 'Fee' and $TRANSACTIONS[transaction].confirmations < $GLOBAL.confirmations) - )} + {if ($TRANSACTIONS[transaction].type == 'Credit' or $TRANSACTIONS[transaction].type == 'Bonus' or $TRANSACTIONS[transaction].type == 'Donation' or $TRANSACTIONS[transaction].type == 'Fee') and $TRANSACTIONS[transaction].confirmations < $GLOBAL.confirmations and $TRANSACTIONS[transaction].confirmations >= 0} {assign var=unconfirmed value=1} {$TRANSACTIONS[transaction].id} @@ -118,12 +108,7 @@ {assign var=orphaned value=0} {section transaction $TRANSACTIONS} - {if ( - $TRANSACTIONS[transaction].type == 'Orphan_Credit' - or $TRANSACTIONS[transaction].type == 'Orphan_Donation' - or $TRANSACTIONS[transaction].type == 'Orphan_Fee' - or $TRANSACTIONS[transaction].type == 'Orphan_Bonus' - )} + {if ($TRANSACTIONS[transaction].type == 'Credit' or $TRANSACTIONS[transaction].type == 'Fee' or $TRANSACTIONS[transaction].type == 'Donation' or $TRANSACTIONS[transaction].type == 'Bonus') and $TRANSACTIONS[transaction].confirmations == -1} {assign var=orphaned value=1} {$TRANSACTIONS[transaction].id} @@ -132,7 +117,7 @@ {$TRANSACTIONS[transaction].type} {$TRANSACTIONS[transaction].coin_address} {if $TRANSACTIONS[transaction].height == 0}n/a{else}{$TRANSACTIONS[transaction].height}{/if} - {$TRANSACTIONS[transaction].amount|number_format:"8"} + {$TRANSACTIONS[transaction].amount|number_format:"8"} {/if} {/section} diff --git a/public/templates/mmcFE/admin/user/default.tpl b/public/templates/mmcFE/admin/user/default.tpl index 82955741..fef10bfa 100644 --- a/public/templates/mmcFE/admin/user/default.tpl +++ b/public/templates/mmcFE/admin/user/default.tpl @@ -44,11 +44,12 @@ +{nocache} {section name=user loop=$USERS|default} {$USERS[user].id} - {$USERS[user].username} - {$USERS[user].email} + {$USERS[user].username|escape} + {$USERS[user].email|escape} {$USERS[user].shares} {$USERS[user].hashrate} {$USERS[user].payout.est_donation|number_format:"8"} @@ -70,6 +71,7 @@ {/section} +{/nocache} diff --git a/public/templates/mmcFE/global/empty.tpl b/public/templates/mmcFE/global/empty.tpl new file mode 100644 index 00000000..e69de29b diff --git a/public/templates/mmcFE/global/header.tpl b/public/templates/mmcFE/global/header.tpl index c195c18a..4ee9d9b5 100644 --- a/public/templates/mmcFE/global/header.tpl +++ b/public/templates/mmcFE/global/header.tpl @@ -4,11 +4,11 @@
- {if $GLOBAL.config.price.currency}{/if} + {if $GLOBAL.config.price.currency}{/if} - +
  • {$GLOBAL.config.currency}/{$GLOBAL.config.price.currency}: {$GLOBAL.price|default:"n/a"|number_format:"4"}    
  • {$GLOBAL.config.currency}/{$GLOBAL.config.price.currency}: {$GLOBAL.price|default:"0"|number_format:"4"}    
  • Network Hashrate: {($GLOBAL.nethashrate / 1000 / 1000 )|default:"0"|number_format:"3"} MH/s    
  • Pool Hashrate: {($GLOBAL.hashrate / 1000)|number_format:"3"} MH/s    
  • Pool Sharerate: {$GLOBAL.sharerate|number_format:"2"} Shares/s    
  • Pool Workers: {$GLOBAL.workers}    
  • Pool Workers: {$GLOBAL.workers|default:"0"}    
  • diff --git a/public/templates/mmcFE/global/navigation.tpl b/public/templates/mmcFE/global/navigation.tpl index 70619098..14a002ef 100644 --- a/public/templates/mmcFE/global/navigation.tpl +++ b/public/templates/mmcFE/global/navigation.tpl @@ -7,12 +7,14 @@
  • My Workers
  • Transactions
  • Notifications
  • + {if !$GLOBAL.config.disable_invitations}
  • Invitations
  • {/if} {/if} {if $smarty.session.AUTHENTICATED|default:"0" == 1 && $GLOBAL.userdata.is_admin == 1}
  • Admin Panel
      +
    • Monitoring
    • User Info
    • Wallet Info
    • Transactions
    • @@ -31,13 +33,21 @@ {else}
    • Statistics +
        + {if $GLOBAL.config.website.acl.statistics.pool == 'public'} +
      • Pool Stats
      • + {/if} + {if $GLOBAL.config.website.acl.statistics.blocks == 'public'} +
      • Block Stats
      • + {/if} +
      {/if}
    • Getting Started
    • Support
    • About
    • diff --git a/public/templates/mmcFE/global/sidebar_pplns.tpl b/public/templates/mmcFE/global/sidebar_pplns.tpl new file mode 100644 index 00000000..79ffec8a --- /dev/null +++ b/public/templates/mmcFE/global/sidebar_pplns.tpl @@ -0,0 +1,75 @@ +
      +
      +
      +
      +

      Dashboard

      +
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      +
      +
      diff --git a/public/templates/mmcFE/global/sidebar_prop.tpl b/public/templates/mmcFE/global/sidebar_prop.tpl index 09159318..c19c429d 100644 --- a/public/templates/mmcFE/global/sidebar_prop.tpl +++ b/public/templates/mmcFE/global/sidebar_prop.tpl @@ -62,6 +62,7 @@ {$GLOBAL.config.currency} Account Balance Confirmed{$GLOBAL.userdata.balance.confirmed|default:"0"} Unconfirmed{$GLOBAL.userdata.balance.unconfirmed|default:"0"} + Orphaned{$GLOBAL.userdata.balance.orphaned|default:"0"}
      diff --git a/public/templates/mmcFE/global/userinfo.tpl b/public/templates/mmcFE/global/userinfo.tpl index 92105bdc..45e9ad92 100644 --- a/public/templates/mmcFE/global/userinfo.tpl +++ b/public/templates/mmcFE/global/userinfo.tpl @@ -1,5 +1,5 @@ {if $GLOBAL.userdata.username|default} -

      Welcome, {$smarty.session.USERDATA.username} Active Account: {$GLOBAL.fees}% Pool Fee (You are donating {$GLOBAL.userdata.donate_percent}% of your earnings)

      +

      Welcome, {$smarty.session.USERDATA.username|escape} {if $GLOBAL.userdata.is_anonymous}Anonymous{else}Active{/if} Account: {$GLOBAL.fees|escape}% Pool Fee (You are donating {$GLOBAL.userdata.donate_percent|escape}% of your earnings)

      {else}

      Welcome guest, please register to user this pool.

      {/if} diff --git a/public/templates/mmcFE/master.tpl b/public/templates/mmcFE/master.tpl index f476f747..68777d1a 100644 --- a/public/templates/mmcFE/master.tpl +++ b/public/templates/mmcFE/master.tpl @@ -69,7 +69,7 @@
      - {include file="system/debugger.tpl"} + {nocache}{include file="system/debugger.tpl"}{/nocache}
      diff --git a/public/templates/mmcFE/password/change/default.tpl b/public/templates/mmcFE/password/change/default.tpl index 0d254677..ae3aab55 100644 --- a/public/templates/mmcFE/password/change/default.tpl +++ b/public/templates/mmcFE/password/change/default.tpl @@ -3,7 +3,7 @@ - + diff --git a/public/templates/mmcFE/register/default.tpl b/public/templates/mmcFE/register/default.tpl index 3316dfa9..7253a243 100644 --- a/public/templates/mmcFE/register/default.tpl +++ b/public/templates/mmcFE/register/default.tpl @@ -1,6 +1,9 @@ {include file="global/block_header.tpl" BLOCK_HEADER="Join our pool" BLOCK_STYLE="clear:none;"} +{if $smarty.request.token|default:""} + +{/if}
      New Password:
      New Password Repeat:
      diff --git a/public/templates/mmcFE/statistics/blocks/default.tpl b/public/templates/mmcFE/statistics/blocks/default.tpl index 539897c7..2871cd01 100644 --- a/public/templates/mmcFE/statistics/blocks/default.tpl +++ b/public/templates/mmcFE/statistics/blocks/default.tpl @@ -57,18 +57,18 @@ target and network difficulty and assuming a zero variance scenario. {else if $BLOCKSFOUND[block].confirmations == -1} Orphan {else}{$GLOBAL.confirmations - $BLOCKSFOUND[block].confirmations} left{/if} - + {/section} diff --git a/public/templates/mmcFE/statistics/blocks/small_table.tpl b/public/templates/mmcFE/statistics/blocks/small_table.tpl index 731d57ef..010b3557 100644 --- a/public/templates/mmcFE/statistics/blocks/small_table.tpl +++ b/public/templates/mmcFE/statistics/blocks/small_table.tpl @@ -14,7 +14,7 @@ {section block $BLOCKSFOUND} - + diff --git a/public/templates/mmcFE/statistics/default.tpl b/public/templates/mmcFE/statistics/default.tpl index a8e9a4c2..59603d94 100644 --- a/public/templates/mmcFE/statistics/default.tpl +++ b/public/templates/mmcFE/statistics/default.tpl @@ -19,5 +19,5 @@
      {$BLOCKSFOUND[block].finder|default:"unknown"}{if $BLOCKSFOUND[block].is_anonymous|default:"0" == 1}anonymous{else}{$BLOCKSFOUND[block].finder|default:"unknown"|escape}{/if} {$BLOCKSFOUND[block].time|date_format:"%d/%m %H:%M:%S"} {$BLOCKSFOUND[block].difficulty|number_format:"2"} {$BLOCKSFOUND[block].amount|number_format:"2"} {math assign="estshares" equation="(pow(2,32 - targetdiff) * blockdiff)" targetdiff=$GLOBAL.config.targetdiff blockdiff=$BLOCKSFOUND[block].difficulty} - {$estshares} + {$estshares|number_format} {$BLOCKSFOUND[block].shares|number_format} {math assign="percentage" equation="shares / estshares * 100" shares=$BLOCKSFOUND[block].shares estshares=$estshares} - {$percentage} + {$percentage|number_format:"2"}
      {$BLOCKSFOUND[block].height}{$BLOCKSFOUND[block].finder|default:"unknown"}{if $BLOCKSFOUND[block].is_anonymous|default:"0" == 1}anonymous{else}{$BLOCKSFOUND[block].finder|default:"unknown"|escape}{/if} {$BLOCKSFOUND[block].time|date_format:"%d/%m %H:%M:%S"} {$BLOCKSFOUND[block].shares|number_format}
      -
    • These stats are also available in JSON format HERE
    • +{if !$GLOBAL.config.website.api.disabled}
    • These stats are also available in JSON format HERE
    • {/if} {include file="global/block_footer.tpl"} diff --git a/public/templates/mmcFE/statistics/graphs/both.tpl b/public/templates/mmcFE/statistics/graphs/both.tpl index ff760be1..03832b5f 100644 --- a/public/templates/mmcFE/statistics/graphs/both.tpl +++ b/public/templates/mmcFE/statistics/graphs/both.tpl @@ -1,7 +1,6 @@ {if is_array($YOURHASHRATES) && is_array($POOLHASHRATES)}
      -{foreach from=array('area','pie') item=chartType} - +
      @@ -30,14 +29,11 @@ {/for} {for $i=0 to date('G', time() - 60 * 60)} - + {/for}
      Your vs Pool Hashrate
      {$POOLHASHRATES.$i|default:"0"}{$POOLHASHRATES.$i|default:"0"}{$POOLHASHRATES.$i - $YOURHASHRATES.$i|default:"0"}

      -{/foreach}
      -{else} -

    • No shares available to start calculations
    • {/if} diff --git a/public/templates/mmcFE/statistics/graphs/mine.tpl b/public/templates/mmcFE/statistics/graphs/mine.tpl index fe217f2f..4d4259f2 100644 --- a/public/templates/mmcFE/statistics/graphs/mine.tpl +++ b/public/templates/mmcFE/statistics/graphs/mine.tpl @@ -26,6 +26,4 @@ -{else} -

    • No shares available to start calculations
    • {/if} diff --git a/public/templates/mmcFE/statistics/graphs/pool.tpl b/public/templates/mmcFE/statistics/graphs/pool.tpl index ba2ed4e5..f8d118c7 100644 --- a/public/templates/mmcFE/statistics/graphs/pool.tpl +++ b/public/templates/mmcFE/statistics/graphs/pool.tpl @@ -26,6 +26,4 @@ -{else} -

    • No shares available to start calculations
    • {/if} diff --git a/public/templates/mmcFE/statistics/pool/authenticated.tpl b/public/templates/mmcFE/statistics/pool/authenticated.tpl index 9e41b6d3..832cecf3 100644 --- a/public/templates/mmcFE/statistics/pool/authenticated.tpl +++ b/public/templates/mmcFE/statistics/pool/authenticated.tpl @@ -22,7 +22,7 @@ {if $GLOBAL.blockexplorer} Next Network Block - {$CURRENTBLOCK + 1}    (Current: {$CURRENTBLOCK}) + {$CURRENTBLOCK + 1}    (Current: {$CURRENTBLOCK}) {else} @@ -32,7 +32,7 @@ {/if} Last Block Found - {if $GLOBAL.blockexplorer}{$LASTBLOCK|default:"0"}{else}{$LASTBLOCK|default:"0"}{/if} + {if $GLOBAL.blockexplorer}{$LASTBLOCK|default:"0"}{else}{$LASTBLOCK|default:"0"}{/if} Current Difficulty @@ -56,7 +56,7 @@ -
    • These stats are also available in JSON format HERE
    • +{if !$GLOBAL.config.website.api.disabled}
    • These stats are also available in JSON format HERE
    • {/if} {include file="global/block_footer.tpl"} diff --git a/public/templates/mmcFE/statistics/pool/contributors_hashrate.tpl b/public/templates/mmcFE/statistics/pool/contributors_hashrate.tpl index 7b294265..a7f3cd9f 100644 --- a/public/templates/mmcFE/statistics/pool/contributors_hashrate.tpl +++ b/public/templates/mmcFE/statistics/pool/contributors_hashrate.tpl @@ -17,17 +17,17 @@ {math assign="estday" equation="round(reward / ( diff * pow(2,32) / ( hashrate * 1000 ) / 3600 / 24), 3)" diff=$DIFFICULTY reward=$REWARD hashrate=$CONTRIBHASHES[contrib].hashrate} {$rank++} - {$CONTRIBHASHES[contrib].account} + {if $CONTRIBHASHES[contrib].is_anonymous|default:"0" == 1}anonymous{else}{$CONTRIBHASHES[contrib].account|escape}{/if} {$CONTRIBHASHES[contrib].hashrate|number_format} {$estday|number_format:"3"} {if $GLOBAL.config.price.currency}{($estday * $GLOBAL.price)|default:"n/a"|number_format:"2"}{/if} {/section} -{if $listed != 1} +{if $listed != 1 && $GLOBAL.userdata.username|default:""} {if $GLOBAL.userdata.hashrate > 0}{math assign="myestday" equation="round(reward / ( diff * pow(2,32) / ( hashrate * 1000 ) / 3600 / 24), 3)" diff=$DIFFICULTY reward=$REWARD hashrate=$GLOBAL.userdata.hashrate}{/if} n/a - {$GLOBAL.userdata.username} + {$GLOBAL.userdata.username|escape} {$GLOBAL.userdata.hashrate} {$myestday|number_format:"3"|default:"n/a"} {if $GLOBAL.config.price.currency}{($myestday * $GLOBAL.price)|default:"n/a"|number_format:"2"}{/if} diff --git a/public/templates/mmcFE/statistics/pool/contributors_shares.tpl b/public/templates/mmcFE/statistics/pool/contributors_shares.tpl index 444effa4..6dd36eb8 100644 --- a/public/templates/mmcFE/statistics/pool/contributors_shares.tpl +++ b/public/templates/mmcFE/statistics/pool/contributors_shares.tpl @@ -11,17 +11,17 @@ {assign var=rank value=1} {assign var=listed value=0} -{section hashrate $CONTRIBSHARES} - +{section shares $CONTRIBSHARES} + {$rank++} - {$CONTRIBSHARES[hashrate].account} - {$CONTRIBSHARES[hashrate].shares|number_format} + {if $CONTRIBSHARES[shares].is_anonymous|default:"0" == 1}anonymous{else}{$CONTRIBSHARES[shares].account|escape}{/if} + {$CONTRIBSHARES[shares].shares|number_format} {/section} -{if $listed != 1} +{if $listed != 1 && $GLOBAL.userdata.username|default:""} n/a - {$GLOBAL.userdata.username} + {$GLOBAL.userdata.username|escape} {$GLOBAL.userdata.shares.valid|number_format} {/if} diff --git a/public/templates/mobile/global/sidebar_pplns.tpl b/public/templates/mobile/global/sidebar_pplns.tpl new file mode 100644 index 00000000..95dcf93c --- /dev/null +++ b/public/templates/mobile/global/sidebar_pplns.tpl @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      PPLNS Target{$GLOBAL.pplns.target|number_format}
       
      Your Stats
      Hashrate{$GLOBAL.userdata.hashrate|number_format} KH/s
      Unpaid Shares
      Your Valid{$GLOBAL.userdata.shares.valid|number_format}
      Pool Valid{$GLOBAL.roundshares.valid|number_format}
      Round Shares
      Pool Valid{$GLOBAL.roundshares.valid|number_format}
      Pool Invalid{$GLOBAL.roundshares.invalid|number_format}{if $GLOBAL.roundshares.valid > 0} ({(100 / $GLOBAL.roundshares.valid * $GLOBAL.roundshares.invalid)|number_format:"2"}%){/if}
      Your Invalid{$GLOBAL.userdata.shares.invalid|number_format}{if $GLOBAL.roundshares.valid > 0} ({(100 / $GLOBAL.roundshares.valid * $GLOBAL.userdata.shares.invalid)|number_format:"2"}%){/if}
      {$GLOBAL.config.currency} Round Estimate
      Block{$GLOBAL.userdata.est_block|number_format:"3"}
      Fees{$GLOBAL.userdata.est_fee|number_format:"3"}
      Donation{$GLOBAL.userdata.est_donation|number_format:"3"}
      Payout{$GLOBAL.userdata.est_payout|number_format:"3"}
       
      {$GLOBAL.config.currency} Account Balance
      Confirmed{$GLOBAL.userdata.balance.confirmed|default:"0"}
      Unconfirmed{$GLOBAL.userdata.balance.unconfirmed|default:"0"}
      diff --git a/public/templates/mobile/statistics/default.tpl b/public/templates/mobile/statistics/default.tpl index 994e60a3..7b03653d 100644 --- a/public/templates/mobile/statistics/default.tpl +++ b/public/templates/mobile/statistics/default.tpl @@ -18,4 +18,4 @@ -
    • These stats are also available in JSON format HERE
    • +{if !$GLOBAL.config.website.api.disabled}
    • These stats are also available in JSON format HERE
    • {/if} diff --git a/public/templates/mobile/statistics/pool/authenticated.tpl b/public/templates/mobile/statistics/pool/authenticated.tpl index 0889347c..a7025779 100644 --- a/public/templates/mobile/statistics/pool/authenticated.tpl +++ b/public/templates/mobile/statistics/pool/authenticated.tpl @@ -27,7 +27,7 @@ {if $GLOBAL.blockexplorer} Next Network Block - {$CURRENTBLOCK + 1}    (Current: {$CURRENTBLOCK}) + {$CURRENTBLOCK + 1}    (Current: {$CURRENTBLOCK}) {else} @@ -37,7 +37,7 @@ {/if} Last Block Found - {if $GLOBAL.blockexplorer}{$LASTBLOCK|default:"0"}{else}{$LASTBLOCK|default:"0"}{/if} + {if $GLOBAL.blockexplorer}{$LASTBLOCK|default:"0"}{else}{$LASTBLOCK|default:"0"}{/if} {if $GLOBAL.chaininfo} diff --git a/sql/mmcfe_ng_structure.sql b/sql/000_base_structure.sql similarity index 100% rename from sql/mmcfe_ng_structure.sql rename to sql/000_base_structure.sql diff --git a/sql/001_unique_email.sql b/sql/001_unique_email.sql new file mode 100644 index 00000000..30d2882e --- /dev/null +++ b/sql/001_unique_email.sql @@ -0,0 +1 @@ +ALTER TABLE `accounts` ADD UNIQUE (`email`); diff --git a/sql/002_monitoring.sql b/sql/002_monitoring.sql new file mode 100644 index 00000000..99d44c2f --- /dev/null +++ b/sql/002_monitoring.sql @@ -0,0 +1,21 @@ +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET time_zone = "+00:00"; + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; + + +CREATE TABLE IF NOT EXISTS `monitoring` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(30) NOT NULL, + `type` varchar(15) NOT NULL, + `value` varchar(25) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Monitoring events from cronjobs'; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/sql/003_accounts_anonymous.sql b/sql/003_accounts_anonymous.sql new file mode 100644 index 00000000..167ad5aa --- /dev/null +++ b/sql/003_accounts_anonymous.sql @@ -0,0 +1 @@ +ALTER TABLE `accounts` ADD `is_anonymous` BOOLEAN NOT NULL DEFAULT FALSE AFTER `is_admin` ; diff --git a/sql/004_tokens_invitations.sql b/sql/004_tokens_invitations.sql new file mode 100644 index 00000000..3ed5a8dc --- /dev/null +++ b/sql/004_tokens_invitations.sql @@ -0,0 +1,68 @@ +SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; +SET time_zone = "+00:00"; + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; + + +CREATE TABLE IF NOT EXISTS `tokens` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `account_id` int(11) NOT NULL, + `token` varchar(65) NOT NULL, + `type` tinyint(4) NOT NULL, + `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `token` (`token`), + KEY `account_id` (`account_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET time_zone = "+00:00"; + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; + + +CREATE TABLE IF NOT EXISTS `token_types` ( + `id` tinyint(4) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(25) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ; + +INSERT INTO `token_types` (`id`, `name`) VALUES +(1, 'password_reset'), +(2, 'confirm_email'), +(3, 'invitation'); + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET time_zone = "+00:00"; + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; + + +CREATE TABLE IF NOT EXISTS `invitations` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `account_id` int(11) unsigned NOT NULL, + `email` varchar(50) CHARACTER SET utf8 NOT NULL, + `token_id` int(11) NOT NULL, + `is_activated` tinyint(1) NOT NULL DEFAULT '0', + `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;