From a06d64e1fb193a53f8967164f01380add6b84cb1 Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Thu, 10 Oct 2013 17:06:01 +0200 Subject: [PATCH 01/19] [IMPROVED] Added case-insensitive login * [IMPROVED] Added Username/Email password reset Fixes #709 --- public/include/classes/user.class.php | 41 ++++++++++++--------- public/templates/mmcFE/password/default.tpl | 2 +- public/templates/mpos/password/default.tpl | 2 +- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index e01b24af..d811c708 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -46,11 +46,11 @@ class User { public function getUserNameByEmail($email) { return $this->getSingle($email, 'username', 'email', 's'); } - public function getUserId($username) { - return $this->getSingle($username, 'id', 'username', 's'); + public function getUserId($username, $lower=false) { + return $this->getSingle($username, 'id', 'username', 's', $lower); } - public function getUserEmail($username) { - return $this->getSingle($username, 'email', 'username', 's'); + public function getUserEmail($username, $lower=false) { + return $this->getSingle($username, 'email', 'username', 's', $lower); } public function getUserNoFee($id) { return $this->getSingle($id, 'no_fees', 'id'); @@ -130,7 +130,7 @@ class User { return false; } if (filter_var($username, FILTER_VALIDATE_EMAIL)) { - $this->debug->append("Username is an e-mail", 2); + $this->debug->append("Username is an e-mail: $username", 2); if (!$username = $this->getUserNameByEmail($username)) { $this->setErrorMessage("Invalid username or password."); return false; @@ -179,9 +179,12 @@ class User { * @param type string Type of value * @return array Return result **/ - private function getSingle($value, $search='id', $field='id', $type="i") { + private function getSingle($value, $search='id', $field='id', $type="i", $lower=false) { $this->debug->append("STA " . __METHOD__, 4); - $stmt = $this->mysqli->prepare("SELECT $search FROM $this->table WHERE $field = ? LIMIT 1"); + $sql = "SELECT $search FROM $this->table WHERE"; + $lower ? $sql .= " LOWER($field) = LOWER(?)" : $sql .= " $field = ?"; + $sql .= " LIMIT 1"; + $stmt = $this->mysqli->prepare($sql); if ($this->checkStmt($stmt)) { $stmt->bind_param($type, $value); $stmt->execute(); @@ -388,16 +391,13 @@ class User { $this->debug->append("STA " . __METHOD__, 4); $user = array(); $password_hash = $this->getHash($password); - $stmt = $this->mysqli->prepare("SELECT username, id, is_admin FROM $this->table WHERE username=? AND pass=? LIMIT 1"); - if ($this->checkStmt($stmt)) { - $stmt->bind_param('ss', $username, $password_hash); - $stmt->execute(); - $stmt->bind_result($row_username, $row_id, $row_admin); + $stmt = $this->mysqli->prepare("SELECT username, id, is_admin FROM $this->table WHERE LOWER(username) = LOWER(?) AND pass = ? LIMIT 1"); + if ($this->checkStmt($stmt) && $stmt->bind_param('ss', $username, $password_hash) && $stmt->execute() && $stmt->bind_result($row_username, $row_id, $row_admin)) { $stmt->fetch(); $stmt->close(); // Store the basic login information $this->user = array('username' => $row_username, 'id' => $row_id, 'is_admin' => $row_admin); - return $username === $row_username; + return strtolower($username) === strtolower($row_username); } return false; } @@ -638,20 +638,27 @@ class User { $this->serErrorMessage("Username must not be empty"); return false; } - if (!$aData['email'] = $this->getUserEmail($username)) { + if (filter_var($username, FILTER_VALIDATE_EMAIL)) { + $this->debug->append("Username is an e-mail: $username", 2); + if (!$username = $this->getUserNameByEmail($username)) { + $this->setErrorMessage("Invalid username or password."); + return false; + } + } + if (!$aData['email'] = $this->getUserEmail($username, true)) { $this->setErrorMessage("Unable to find a mail address for user $username"); return false; } - if (!$aData['token'] = $this->token->createToken('password_reset', $this->getUserId($username))) { + if (!$aData['token'] = $this->token->createToken('password_reset', $this->getUserId($username, true))) { $this->setErrorMessage('Unable to setup token for password reset'); return false; } - $aData['username'] = $username; + $aData['username'] = $this->getUserName($this->getUserId($username, true)); $aData['subject'] = 'Password Reset Request'; if ($this->mail->sendMail('password/reset', $aData)) { return true; } else { - $this->setErrorMessage("Unable to send mail to your address"); + $this->setErrorMessage('Unable to send mail to your address'); return false; } return false; diff --git a/public/templates/mmcFE/password/default.tpl b/public/templates/mmcFE/password/default.tpl index c82ccd31..8ed5b722 100644 --- a/public/templates/mmcFE/password/default.tpl +++ b/public/templates/mmcFE/password/default.tpl @@ -2,7 +2,7 @@
-

If you have an email set for your account, enter your username to get your password reset

+

If you have an email set for your account, enter your username or email address to get your password reset

{include file="global/block_footer.tpl"} diff --git a/public/templates/mpos/password/default.tpl b/public/templates/mpos/password/default.tpl index 7e03e788..9115932c 100644 --- a/public/templates/mpos/password/default.tpl +++ b/public/templates/mpos/password/default.tpl @@ -6,7 +6,7 @@

If you have an email set for your account, enter your username to get your password reset

- +
From cea3b29d08f88f8800ad7199a2b83ff513a32f63 Mon Sep 17 00:00:00 2001 From: nicoschtein Date: Fri, 11 Oct 2013 21:13:29 -0300 Subject: [PATCH 02/19] [FIX] Disable Block Explorer Link on Mobile Theme --- public/templates/mobile/statistics/pool/general_stats.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/templates/mobile/statistics/pool/general_stats.tpl b/public/templates/mobile/statistics/pool/general_stats.tpl index 140adaaa..d4677f3a 100644 --- a/public/templates/mobile/statistics/pool/general_stats.tpl +++ b/public/templates/mobile/statistics/pool/general_stats.tpl @@ -12,7 +12,7 @@ Current Active Workers {$GLOBAL.workers} - {if $GLOBAL.website.blockexplorer.url} + {if ! $GLOBAL.website.blockexplorer.disabled} Next Network Block {$CURRENTBLOCK + 1}    (Current: {$CURRENTBLOCK}) From d5620fd61c64cadc294b0d40b1c846f0e95eeabc Mon Sep 17 00:00:00 2001 From: nicoschtein Date: Sat, 12 Oct 2013 03:56:11 -0300 Subject: [PATCH 03/19] Increased width % for News title to fit longer text on MPOS Theme --- public/site_assets/mpos/css/layout.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/site_assets/mpos/css/layout.css b/public/site_assets/mpos/css/layout.css index 34b365b9..6a1b8d18 100644 --- a/public/site_assets/mpos/css/layout.css +++ b/public/site_assets/mpos/css/layout.css @@ -346,7 +346,7 @@ background: #ffffff; #main .module header h3 { display: block; -width: 60%; +width: 90%; float: left; } From 95f217f32da2bcbcd5a90165d305819a96d50dc1 Mon Sep 17 00:00:00 2001 From: nicoschtein Date: Sat, 12 Oct 2013 04:03:12 -0300 Subject: [PATCH 04/19] Increased width % for Pool Name header on MPOS Theme This way longer pool name can fit, and it wont affect section_title as it is one or two words long. --- public/site_assets/mpos/css/layout.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/site_assets/mpos/css/layout.css b/public/site_assets/mpos/css/layout.css index 6a1b8d18..422a07b4 100644 --- a/public/site_assets/mpos/css/layout.css +++ b/public/site_assets/mpos/css/layout.css @@ -44,7 +44,7 @@ float: left; margin: 0; font-size: 22px; display: block; -width: 23%; +width: 40%; height: 55px; font-weight: normal; text-align: left; @@ -63,7 +63,7 @@ header#header h2.section_title { text-align: left; text-indent: 4.5%; text-transform: uppercase; -width: 68%; +width: 60%; background: url(../images/header_shadow.png) no-repeat left top; } From fdba2340eb25e605f606c103483b9bb30d937e91 Mon Sep 17 00:00:00 2001 From: nicoschtein Date: Sat, 12 Oct 2013 04:12:38 -0300 Subject: [PATCH 05/19] Better set the Pool Name to auto adjust width pushing Section Title Better set the "display: inline-block;" much like breadcrumbs does. So we get just the right width to fit the Pool Name, followed by the nice shadow and then the Section Name --- public/site_assets/mpos/css/layout.css | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/public/site_assets/mpos/css/layout.css b/public/site_assets/mpos/css/layout.css index 422a07b4..d895c84b 100644 --- a/public/site_assets/mpos/css/layout.css +++ b/public/site_assets/mpos/css/layout.css @@ -43,8 +43,7 @@ header#header h1.site_title, header#header h2.section_title { float: left; margin: 0; font-size: 22px; -display: block; -width: 40%; +display: inline-block; height: 55px; font-weight: normal; text-align: left; @@ -63,7 +62,6 @@ header#header h2.section_title { text-align: left; text-indent: 4.5%; text-transform: uppercase; -width: 60%; background: url(../images/header_shadow.png) no-repeat left top; } From c31cb95d203ab9393cff7f7b713d1dc51c222113 Mon Sep 17 00:00:00 2001 From: nicoschtein Date: Sat, 12 Oct 2013 04:32:19 -0300 Subject: [PATCH 06/19] Aligned API Key on Edit Account Could be done better, but setting style on should be enough. --- public/site_assets/mpos/css/layout.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/public/site_assets/mpos/css/layout.css b/public/site_assets/mpos/css/layout.css index d895c84b..796adfe1 100644 --- a/public/site_assets/mpos/css/layout.css +++ b/public/site_assets/mpos/css/layout.css @@ -647,6 +647,15 @@ padding: 1% 0%; margin: 10px 0; } +fieldset a { +height: 20px; +padding-left: 10px; +display: block; +float: left; +width: 92%; +margin: 0 10px; +} + fieldset label { display: block; float: left; From 15d7b6d1fa68af109794af306e86e42a121f0cce Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Sat, 12 Oct 2013 19:28:56 +0200 Subject: [PATCH 07/19] [IMPROVED] Detect potential dual payout situations --- cronjobs/pplns_payout.php | 20 +++++++++++++++- cronjobs/proportional_payout.php | 23 +++++++++++++++++-- public/templates/mail/notifications/error.tpl | 10 ++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 public/templates/mail/notifications/error.tpl diff --git a/cronjobs/pplns_payout.php b/cronjobs/pplns_payout.php index f5d6f24c..d95af481 100755 --- a/cronjobs/pplns_payout.php +++ b/cronjobs/pplns_payout.php @@ -51,7 +51,7 @@ foreach ($aAllBlocks as $iIndex => $aBlock) { $pplns_target = $config['pplns']['shares']['default']; } - if (!$aBlock['accounted']) { + if (!$aBlock['accounted'] && $aBlock['height'] > $setting->getValue('last_accounted_block_height')) { $iPreviousShareId = @$aAllBlocks[$iIndex - 1]['share_id'] ? $aAllBlocks[$iIndex - 1]['share_id'] : 0; $iCurrentUpstreamId = $aBlock['share_id']; if (!is_numeric($iCurrentUpstreamId)) { @@ -183,6 +183,8 @@ foreach ($aAllBlocks as $iIndex => $aBlock) { $log->logFatal("Failed to delete accounted shares from $iPreviousShareId to $iCurrentUpstreamId, aborting!"); exit(1); } + // Store this blocks height as last accounted for + $setting->setValue('last_accounted_block_height', $aBlock['height']); // Mark this block as accounted for if (!$block->setAccounted($aBlock['id'])) { $log->logFatal("Failed to mark block as accounted! Aborting!"); @@ -191,6 +193,22 @@ foreach ($aAllBlocks as $iIndex => $aBlock) { $monitoring->setStatus($cron_name . "_status", "okerror", 1); exit(1); } + } else { + $aMailData = array( + 'email' => $setting->getValue('website_email'), + 'subject' => 'Payout processing aborted', + 'Error' => 'Potential double payout detected. All payouts halted until fixed!', + 'BlockID' => $aBlock['id'], + 'Block Height' => $aBlock['height'], + 'Block Share ID' => $aBlock['share_id'] + ); + if (!$mail->sendMail('notifications/error', $aMailData)) + $log->logError(" Failed sending notifications: " . $notification->getError() . "\n"); + $log->logFatal('Potential double payout detected. Aborted.'); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "Block height for block too low! Potential double payout detected."); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); + exit(1); } } diff --git a/cronjobs/proportional_payout.php b/cronjobs/proportional_payout.php index e6e76113..31a24beb 100755 --- a/cronjobs/proportional_payout.php +++ b/cronjobs/proportional_payout.php @@ -45,7 +45,7 @@ $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']) { + if (!$aBlock['accounted'] && $aBlock['height'] > $setting->getValue('last_accounted_block_height')) { $iPreviousShareId = @$aAllBlocks[$iIndex - 1]['share_id'] ? $aAllBlocks[$iIndex - 1]['share_id'] : 0; $iCurrentUpstreamId = $aBlock['share_id']; $aAccountShares = $share->getSharesForAccounts($iPreviousShareId, $aBlock['share_id']); @@ -82,7 +82,7 @@ foreach ($aAllBlocks as $iIndex => $aBlock) { number_format($aData['percentage'], 8) . "\t" . number_format($aData['payout'], 8) . "\t" . number_format($aData['donation'], 8) . "\t" . - number_format($aData['fee']), 8); + number_format($aData['fee'], 8)); // Update user share statistics if (!$statistics->updateShareStatistics($aData, $aBlock['id'])) @@ -111,6 +111,8 @@ foreach ($aAllBlocks as $iIndex => $aBlock) { $monitoring->setStatus($cron_name . "_status", "okerror", 1); exit(1); } + // Add block as accounted for into settings table + $setting->setValue('last_accounted_block_height', $aBlock['height']); // Mark this block as accounted for if (!$block->setAccounted($aBlock['id'])) { $log->logFatal('Failed to mark block as accounted! Aborted.'); @@ -119,7 +121,24 @@ foreach ($aAllBlocks as $iIndex => $aBlock) { $monitoring->setStatus($cron_name . "_status", "okerror", 1); exit(1); } + } else { + $log->logFatal('Possible double payout detected. Aborted.'); + $aMailData = array( + 'email' => $setting->getValue('website_email'), + 'subject' => 'Payout Failure: Double Payout', + 'Error' => 'Possible double payout detected', + 'BlockID' => $aBlock['id'], + 'Block Height' => $aBlock['height'], + 'Block Share ID' => $aBlock['share_id'] + ); + if (!$mail->sendMail('notifications/error', $aMailData)) + $log->logError(" Failed sending notifications: " . $notification->getError() . "\n"); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", 'Possible double payout detected. Aborted.'); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); + exit(1); } + exit; } require_once('cron_end.inc.php'); diff --git a/public/templates/mail/notifications/error.tpl b/public/templates/mail/notifications/error.tpl new file mode 100644 index 00000000..9aab79bf --- /dev/null +++ b/public/templates/mail/notifications/error.tpl @@ -0,0 +1,10 @@ + + +

An error occured!

+

This should never happen. Please review the error output below.

+ +{foreach from=$DATA key=text item=message} + {if $text != 'email' && $text != 'subject'} +

{$text}: {$message}

+ {/if} +{/foreach} From 1c1b31138509383e54a7cc30624e902d6c5a1296 Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Sat, 12 Oct 2013 19:54:31 +0200 Subject: [PATCH 08/19] [FIX] Set block internally accounted for earlier --- cronjobs/pplns_payout.php | 5 +++-- cronjobs/proportional_payout.php | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cronjobs/pplns_payout.php b/cronjobs/pplns_payout.php index d95af481..39ea294a 100755 --- a/cronjobs/pplns_payout.php +++ b/cronjobs/pplns_payout.php @@ -175,6 +175,9 @@ foreach ($aAllBlocks as $iIndex => $aBlock) { $log->logFatal('Failed to insert new Donation transaction to database for ' . $aData['username']); } + // Store this blocks height as last accounted for + $setting->setValue('last_accounted_block_height', $aBlock['height']); + // 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'); @@ -183,8 +186,6 @@ foreach ($aAllBlocks as $iIndex => $aBlock) { $log->logFatal("Failed to delete accounted shares from $iPreviousShareId to $iCurrentUpstreamId, aborting!"); exit(1); } - // Store this blocks height as last accounted for - $setting->setValue('last_accounted_block_height', $aBlock['height']); // Mark this block as accounted for if (!$block->setAccounted($aBlock['id'])) { $log->logFatal("Failed to mark block as accounted! Aborting!"); diff --git a/cronjobs/proportional_payout.php b/cronjobs/proportional_payout.php index 31a24beb..6ce29c15 100755 --- a/cronjobs/proportional_payout.php +++ b/cronjobs/proportional_payout.php @@ -100,6 +100,9 @@ foreach ($aAllBlocks as $iIndex => $aBlock) { $log->logFatal('Failed to insert new Donation transaction to database for ' . $aData['username']); } + // Add block as accounted for into settings table + $setting->setValue('last_accounted_block_height', $aBlock['height']); + // 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'); @@ -111,8 +114,6 @@ foreach ($aAllBlocks as $iIndex => $aBlock) { $monitoring->setStatus($cron_name . "_status", "okerror", 1); exit(1); } - // Add block as accounted for into settings table - $setting->setValue('last_accounted_block_height', $aBlock['height']); // Mark this block as accounted for if (!$block->setAccounted($aBlock['id'])) { $log->logFatal('Failed to mark block as accounted! Aborted.'); From b1001950cf7a4e1503d034eb0210abef937e00e0 Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Sat, 12 Oct 2013 19:57:18 +0200 Subject: [PATCH 09/19] [FIX] Removed debug exit from prop payout --- cronjobs/proportional_payout.php | 1 - 1 file changed, 1 deletion(-) diff --git a/cronjobs/proportional_payout.php b/cronjobs/proportional_payout.php index 6ce29c15..d863cc0d 100755 --- a/cronjobs/proportional_payout.php +++ b/cronjobs/proportional_payout.php @@ -139,7 +139,6 @@ foreach ($aAllBlocks as $iIndex => $aBlock) { $monitoring->setStatus($cron_name . "_status", "okerror", 1); exit(1); } - exit; } require_once('cron_end.inc.php'); From 8311dca422eb045365ffd43f430f0215dee581e5 Mon Sep 17 00:00:00 2001 From: andy Date: Sun, 13 Oct 2013 07:48:06 -0700 Subject: [PATCH 10/19] [FIX] Incorrectly reporting # of unconfirmed coins --- cronjobs/blockupdate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cronjobs/blockupdate.php b/cronjobs/blockupdate.php index bd489c04..0c09d50b 100755 --- a/cronjobs/blockupdate.php +++ b/cronjobs/blockupdate.php @@ -34,7 +34,7 @@ if ( $bitcoin->can_connect() !== true ) { } // Fetch all unconfirmed blocks -$aAllBlocks = $block->getAllUnconfirmed($config['confirmations']); +$aAllBlocks = $block->getAllUnconfirmed($config['network_confirmations']); $log->logInfo("ID\tHeight\tBlockhash\tConfirmations"); foreach ($aAllBlocks as $iIndex => $aBlock) { From 568bb1811f414e2fbaec1d0fb96bfae4ec61d16d Mon Sep 17 00:00:00 2001 From: nicoschtein Date: Sun, 13 Oct 2013 20:17:01 -0300 Subject: [PATCH 11/19] Added space after Pool Title on MPOS Theme --- public/site_assets/mpos/css/layout.css | 1 + 1 file changed, 1 insertion(+) diff --git a/public/site_assets/mpos/css/layout.css b/public/site_assets/mpos/css/layout.css index 796adfe1..a001b308 100644 --- a/public/site_assets/mpos/css/layout.css +++ b/public/site_assets/mpos/css/layout.css @@ -42,6 +42,7 @@ background: #222222 url(../images/header_bg.png) repeat-x; header#header h1.site_title, header#header h2.section_title { float: left; margin: 0; +padding-right: 1.8%; font-size: 22px; display: inline-block; height: 55px; From 7f6e7738d886ae806d14a232bbecde08b88a60e0 Mon Sep 17 00:00:00 2001 From: nicoschtein Date: Mon, 14 Oct 2013 14:36:14 -0300 Subject: [PATCH 12/19] [FIX] Incorrectly reporting # of unconfirmed coins Fixes #720 and fixes #721. This will work in all three cases: 1) $config['confirmations'] > $config['network_confirmations']) 2) $config['confirmations'] < $config['network_confirmations']) 3) $config['confirmations'] = $config['network_confirmations']) --- cronjobs/blockupdate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cronjobs/blockupdate.php b/cronjobs/blockupdate.php index 0c09d50b..9a1f2906 100755 --- a/cronjobs/blockupdate.php +++ b/cronjobs/blockupdate.php @@ -34,7 +34,7 @@ if ( $bitcoin->can_connect() !== true ) { } // Fetch all unconfirmed blocks -$aAllBlocks = $block->getAllUnconfirmed($config['network_confirmations']); +$aAllBlocks = $block->getAllUnconfirmed(max($config['network_confirmations'],$config['confirmations'])); $log->logInfo("ID\tHeight\tBlockhash\tConfirmations"); foreach ($aAllBlocks as $iIndex => $aBlock) { From 35303fd1a1e08b005384d2ede19b359bcd52c601 Mon Sep 17 00:00:00 2001 From: andy Date: Tue, 15 Oct 2013 13:25:18 -0700 Subject: [PATCH 13/19] [FIX] Fixes Issue #722 Firefox wrapping Pool name on long names --- public/site_assets/mpos/css/layout.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/site_assets/mpos/css/layout.css b/public/site_assets/mpos/css/layout.css index a001b308..bb36f42a 100644 --- a/public/site_assets/mpos/css/layout.css +++ b/public/site_assets/mpos/css/layout.css @@ -40,8 +40,10 @@ background: #222222 url(../images/header_bg.png) repeat-x; } header#header h1.site_title, header#header h2.section_title { +white-space: nowrap; float: left; margin: 0; +margin-right: 2.8%; padding-right: 1.8%; font-size: 22px; display: inline-block; From bee2d3a1cc566161697809614a34dc4233fbb4bb Mon Sep 17 00:00:00 2001 From: nrpatten Date: Wed, 16 Oct 2013 16:31:53 +1100 Subject: [PATCH 14/19] [FIX] Graphs not rendering properly Update jquery.visualize.js to fix graphs not working for Pool, Both. --- .../site_assets/mpos/js/jquery.visualize.js | 1236 ++++++----------- 1 file changed, 451 insertions(+), 785 deletions(-) diff --git a/public/site_assets/mpos/js/jquery.visualize.js b/public/site_assets/mpos/js/jquery.visualize.js index 1043aaca..bfeed7a9 100644 --- a/public/site_assets/mpos/js/jquery.visualize.js +++ b/public/site_assets/mpos/js/jquery.visualize.js @@ -1,795 +1,461 @@ -/** - * -------------------------------------------------------------------- - * jQuery-Plugin "visualize" - * by Scott Jehl, scott@filamentgroup.com - * http://www.filamentgroup.com - * Copyright (c) 2009 Filament Group - * Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL (filamentgroup.com/examples/gpl-license.txt) licenses. - * - * -------------------------------------------------------------------- - */ -(function($) { +/* +* -------------------------------------------------------------------- +* jQuery inputToButton plugin +* Author: Scott Jehl, scott@filamentgroup.com +* Copyright (c) 2009 Filament Group +* licensed under MIT (filamentgroup.com/examples/mit-license.txt) +* -------------------------------------------------------------------- +*/ +(function($) { $.fn.visualize = function(options, container){ - return $(this).each(function(){ - //configuration - var o = $.extend({ - type: 'bar', //also available: area, pie, line - width: $(this).width(), //height of canvas - defaults to table height - height: $(this).height(), //height of canvas - defaults to table height - appendTitle: true, //table caption text is added to chart - title: null, //grabs from table caption if null - appendKey: true, //color key is added to chart - colors: ['#be1e2d','#666699','#92d5ea','#ee8310','#8d10ee','#5a3b16','#26a4ed','#f45a90','#e9e744'], - textColors: [], //corresponds with colors array. null/undefined items will fall back to CSS - parseDirection: 'x', //which direction to parse the table data - pieMargin: 10, //pie charts only - spacing around pie - pieLabelsAsPercent: true, - pieLabelPos: 'inside', - lineWeight: 4, //for line and area - stroke weight - lineDots: false, //also available: 'single', 'double' - dotInnerColor: "#ffffff", // only used for lineDots:'double' - lineMargin: (options.lineDots?15:0), //for line and area - spacing around lines - barGroupMargin: 10, - chartId: '', - xLabelParser: null, // function to parse labels as values - valueParser: null, // function to parse values. must return a Number - chartId: '', - chartClass: '', - barMargin: 1, //space around bars in bar chart (added to both sides of bar) - yLabelInterval: 30, //distance between y labels - interaction: false // only used for lineDots != false -- triggers mouseover and mouseout on original table - },options); - - //reset width, height to numbers - o.width = parseFloat(o.width); - o.height = parseFloat(o.height); - - // reset padding if graph is not lines - if(o.type != 'line' && o.type != 'area' ) { - o.lineMargin = 0; - } - - var self = $(this); - - // scrape data from html table - var tableData = {}; - var colors = o.colors; - var textColors = o.textColors; - - - var parseLabels = function(direction){ - var labels = []; - if(direction == 'x'){ - self.find('thead tr').each(function(i){ - $(this).find('th').each(function(j){ - if(!labels[j]) { - labels[j] = []; - } - labels[j][i] = $(this).text() - }) - }); - } - else { - self.find('tbody tr').each(function(i){ - $(this).find('th').each(function(j) { - if(!labels[i]) { - labels[i] = []; - } - labels[i][j] = $(this).text() - }); - }); - } - return labels; - }; - - var fnParse = o.valueParser || parseFloat; - var dataGroups = tableData.dataGroups = []; - if(o.parseDirection == 'x'){ - self.find('tbody tr').each(function(i,tr){ - dataGroups[i] = {}; - dataGroups[i].points = []; - dataGroups[i].color = colors[i]; - if(textColors[i]){ dataGroups[i].textColor = textColors[i]; } - $(tr).find('td').each(function(j,td){ - dataGroups[i].points.push( { - value: fnParse($(td).text()), - elem: td, - tableCords: [i,j] - } ); - }); - }); - } else { - var cols = self.find('tbody tr:eq(0) td').size(); - for(var i=0; itableData.topValue) { - tableData.topValue = fnParse(item.value,10); - } - if(item.valuetopValue) topValue = parseFloat(this); + }); + return topValue; + }, + bottomValue: function(){ + var bottomValue = 0; + var allData = this.allData().join(',').split(','); + $(allData).each(function(){ + if(thistopYtotal) topYtotal = parseFloat(this); + }); + return topYtotal; + }, + totalYRange: function(){ + return this.topValue() - this.bottomValue(); + }, + xLabels: function(){ + var xLabels = []; + if(o.parseDirection == 'x'){ + self.find('tr:eq(0) th').filter(o.colFilter).each(function(){ + xLabels.push($(this).html()); + }); + } + else { + self.find('tr:gt(0) th').filter(o.rowFilter).each(function(){ + xLabels.push($(this).html()); + }); + } + return xLabels; + }, + yLabels: function(){ + var yLabels = []; + yLabels.push(bottomValue); + var numLabels = Math.round(o.height / o.yLabelInterval); + var loopInterval = Math.ceil(totalYRange / numLabels) || 1; + while( yLabels[yLabels.length-1] < topValue - loopInterval){ + yLabels.push(yLabels[yLabels.length-1] + loopInterval); + } + yLabels.push(topValue); + return yLabels; + } + }; + + return tableData; + }; + + + //function to create a chart + var createChart = { + pie: function(){ + + canvasContain.addClass('visualize-pie'); + + if(o.pieLabelPos == 'outside'){ canvasContain.addClass('visualize-pie-outside'); } + + var centerx = Math.round(canvas.width()/2); + var centery = Math.round(canvas.height()/2); + var radius = centery - o.pieMargin; + var counter = 0.0; + var toRad = function(integer){ return (Math.PI/180)*integer; }; + var labels = $('
    ') + .insertAfter(canvas); - $.each(xLabels,function(i,label) { - label = xLabels[i] = o.xLabelParser(label); - if(i === 0) { - xTopValue = label; - xBottomValue = label; - } - if(label>xTopValue) { - xTopValue = label; - } - if(label centerx) ? 'right' : 'left'; + var topBottom = (labely > centery) ? 'bottom' : 'top'; + var percentage = parseFloat((fraction*100).toFixed(2)); - var totalXRange = tableData.totalXRange = xTopValue - xBottomValue; + if(percentage){ + var labelval = (o.pieLabelsAsPercent) ? percentage + '%' : this; + var labeltext = $('' + labelval +'') + .css(leftRight, 0) + .css(topBottom, 0); + if(labeltext) + var label = $('
  • ') + .appendTo(labels) + .css({left: labelx, top: labely}) + .append(labeltext); + labeltext + .css('font-size', radius / 8) + .css('margin-'+leftRight, -labeltext.width()/2) + .css('margin-'+topBottom, -labeltext.outerHeight()/2); + + if(dataGroups[i].textColor){ labeltext.css('color', dataGroups[i].textColor); } + } + counter+=fraction; + }); + }, + + line: function(area){ + + if(area){ canvasContain.addClass('visualize-area'); } + else{ canvasContain.addClass('visualize-line'); } + + //write X labels + var xInterval = canvas.width() / (xLabels.length -1); + var xlabelsUL = $('
      ') + .width(canvas.width()) + .height(canvas.height()) + .insertBefore(canvas); + $.each(xLabels, function(i){ + var thisLi = $('
    • '+this+'
    • ') + .prepend('') + .css('left', xInterval * i) + .appendTo(xlabelsUL); + var label = thisLi.find('span:not(.line)'); + var leftOffset = label.width()/-2; + if(i == 0){ leftOffset = 0; } + else if(i== xLabels.length-1){ leftOffset = -label.width(); } + label + .css('margin-left', leftOffset) + .addClass('label'); + }); - - var xScale = tableData.xScale = (o.width -2*o.lineMargin) / totalXRange; - var marginDiffX = 0; - if(o.lineMargin) { - var marginDiffX = -2*xScale-o.lineMargin; - } - zeroLocX = tableData.zeroLocX = xBottomValue + o.lineMargin; - - tableData.xBottomValue = xBottomValue; - tableData.xTopValue = xTopValue; - tableData.totalXRange = totalXRange; - } - - var yScale = tableData.yScale = (o.height - 2*o.lineMargin) / totalYRange; - var zeroLocY = tableData.zeroLocY = (o.height-2*o.lineMargin) * (tableData.topValue/tableData.totalYRange) + o.lineMargin; - - var yLabels = tableData.yLabels = []; + //write Y labels + var yScale = canvas.height() / totalYRange; + var liBottom = canvas.height() / (yLabels.length-1); + var ylabelsUL = $('
        ') + .width(canvas.width()) + .height(canvas.height()) + .insertBefore(canvas); + + $.each(yLabels, function(i){ + var thisLi = $('
      • '+this+'
      • ') + .prepend('') + .css('bottom',liBottom*i) + .prependTo(ylabelsUL); + var label = thisLi.find('span:not(.line)'); + var topOffset = label.height()/-2; + if(i == 0){ topOffset = -label.height(); } + else if(i== yLabels.length-1){ topOffset = 0; } + label + .css('margin-top', topOffset) + .addClass('label'); + }); - var numLabels = Math.floor((o.height - 2*o.lineMargin) / 30); - var loopInterval = tableData.totalYRange / numLabels; //fix provided from lab - loopInterval = Math.round(parseFloat(loopInterval)/5)*5; - loopInterval = Math.max(loopInterval, 1); - // var start = - for(var j=Math.round(parseInt(tableData.bottomValue)/5)*5; j<=tableData.topValue+loopInterval; j+=loopInterval){ - yLabels.push(j); - } - if(yLabels[yLabels.length-1] > tableData.topValue+loopInterval) { - yLabels.pop(); - } else if (yLabels[yLabels.length-1] <= tableData.topValue-10) { - yLabels.push(tableData.topValue); - } - - // populate some data - $.each(dataGroups,function(i,row){ - row.yLabels = tableData.yAllLabels[i]; - $.each(row.points, function(j,point){ - point.zeroLocY = tableData.zeroLocY; - point.zeroLocX = tableData.zeroLocX; - point.xLabels = tableData.xAllLabels[j]; - point.yLabels = tableData.yAllLabels[i]; - point.color = row.color; - }); - }); - - try{console.log(tableData);}catch(e){} - - var charts = {}; - - charts.pie = { - interactionPoints: dataGroups, - - setup: function() { - charts.pie.draw(true); - }, - draw: function(drawHtml){ + //start from the bottom left + ctx.translate(0,zeroLoc); + //iterate and draw + $.each(dataGroups,function(h){ + ctx.beginPath(); + ctx.lineWidth = o.lineWeight; + ctx.lineJoin = 'round'; + var points = this.points; + var integer = 0; + ctx.moveTo(0,-(points[0]*yScale)); + $.each(points, function(){ + ctx.lineTo(integer,-(this*yScale)); + integer+=xInterval; + }); + ctx.strokeStyle = this.color; + ctx.stroke(); + if(area){ + ctx.lineTo(integer,0); + ctx.lineTo(0,0); + ctx.closePath(); + ctx.fillStyle = this.color; + ctx.globalAlpha = .3; + ctx.fill(); + ctx.globalAlpha = 1.0; + } + else {ctx.closePath();} + }); + }, + + area: function(){ + createChart.line(true); + }, + + bar: function(){ + + canvasContain.addClass('visualize-bar'); + + //write X labels + var xInterval = canvas.width() / (xLabels.length); + var xlabelsUL = $('
          ') + .width(canvas.width()) + .height(canvas.height()) + .insertBefore(canvas); + $.each(xLabels, function(i){ + var thisLi = $('
        • '+this+'
        • ') + .prepend('') + .css('left', xInterval * i) + .width(xInterval) + .appendTo(xlabelsUL); + var label = thisLi.find('span.label'); + label.addClass('label'); + }); - var centerx = Math.round(canvas.width()/2); - var centery = Math.round(canvas.height()/2); - var radius = centery - o.pieMargin; - var counter = 0.0; + //write Y labels + var yScale = canvas.height() / totalYRange; + var liBottom = canvas.height() / (yLabels.length-1); + var ylabelsUL = $('
            ') + .width(canvas.width()) + .height(canvas.height()) + .insertBefore(canvas); + $.each(yLabels, function(i){ + var thisLi = $('
          • '+this+'
          • ') + .prepend('') + .css('bottom',liBottom*i) + .prependTo(ylabelsUL); + var label = thisLi.find('span:not(.line)'); + var topOffset = label.height()/-2; + if(i == 0){ topOffset = -label.height(); } + else if(i== yLabels.length-1){ topOffset = 0; } + label + .css('margin-top', topOffset) + .addClass('label'); + }); - if(drawHtml) { - canvasContain.addClass('visualize-pie'); + //start from the bottom left + ctx.translate(0,zeroLoc); + //iterate and draw + for(var h=0; h')) + .height(o.height) + .width(o.width) + .append(canvas); - if(o.pieLabelPos == 'outside'){ canvasContain.addClass('visualize-pie-outside'); } - - var toRad = function(integer){ return (Math.PI/180)*integer; }; - var labels = $('
              ') - .insertAfter(canvas); - } - - - //draw the pie pieces - $.each(dataGroups, function(i,row){ - var fraction = row.groupTotal / dataSum; - if (fraction <= 0 || isNaN(fraction)) - return; - ctx.beginPath(); - ctx.moveTo(centerx, centery); - ctx.arc(centerx, centery, radius, - counter * Math.PI * 2 - Math.PI * 0.5, - (counter + fraction) * Math.PI * 2 - Math.PI * 0.5, - false); - ctx.lineTo(centerx, centery); - ctx.closePath(); - ctx.fillStyle = dataGroups[i].color; - ctx.fill(); - // draw labels - if(drawHtml) { - var sliceMiddle = (counter + fraction/2); - var distance = o.pieLabelPos == 'inside' ? radius/1.5 : radius + radius / 5; - var labelx = Math.round(centerx + Math.sin(sliceMiddle * Math.PI * 2) * (distance)); - var labely = Math.round(centery - Math.cos(sliceMiddle * Math.PI * 2) * (distance)); - var leftRight = (labelx > centerx) ? 'right' : 'left'; - var topBottom = (labely > centery) ? 'bottom' : 'top'; - var percentage = parseFloat((fraction*100).toFixed(2)); - - // interaction variables - row.canvasCords = [labelx,labely]; - row.zeroLocY = tableData.zeroLocY = 0; // related to zeroLocY and plugin API - row.zeroLocX = tableData.zeroLocX = 0; // related to zeroLocX and plugin API - row.value = row.groupTotal; - - - if(percentage){ - var labelval = (o.pieLabelsAsPercent) ? percentage + '%' : row.groupTotal; - var labeltext = $('' + labelval +'') - .css(leftRight, 0) - .css(topBottom, 0); - if(labeltext) - var label = $('
            • ') - .appendTo(labels) - .css({left: labelx, top: labely}) - .append(labeltext); - labeltext - .css('font-size', radius / 8) - .css('margin-'+leftRight, -labeltext.width()/2) - .css('margin-'+topBottom, -labeltext.outerHeight()/2); - - if(dataGroups[i].textColor){ labeltext.css('color', dataGroups[i].textColor); } - - } - } - counter+=fraction; - }); - } - }; - - (function(){ - - var xInterval; - - var drawPoint = function (ctx,x,y,color,size) { - ctx.moveTo(x,y); - ctx.beginPath(); - ctx.arc(x,y,size/2,0,2*Math.PI,false); - ctx.closePath(); - ctx.fillStyle = color; - ctx.fill(); - }; - - charts.line = { - - interactionPoints: allItems, - - setup: function(area){ - - if(area){ canvasContain.addClass('visualize-area'); } - else{ canvasContain.addClass('visualize-line'); } - - //write X labels - var xlabelsUL = $('
                ') - .width(canvas.width()) - .height(canvas.height()) - .insertBefore(canvas); - - if(!o.customXLabels) { - xInterval = (canvas.width() - 2*o.lineMargin) / (xLabels.length -1); - $.each(xLabels, function(i){ - var thisLi = $('
              • '+this+'
              • ') - .prepend('') - .css('left', o.lineMargin + xInterval * i) - .appendTo(xlabelsUL); - var label = thisLi.find('span:not(.line)'); - var leftOffset = label.width()/-2; - if(i == 0){ leftOffset = 0; } - else if(i== xLabels.length-1){ leftOffset = -label.width(); } - label - .css('margin-left', leftOffset) - .addClass('label'); - }); - } else { - o.customXLabels(tableData,xlabelsUL); - } - - //write Y labels - var liBottom = (canvas.height() - 2*o.lineMargin) / (yLabels.length-1); - var ylabelsUL = $('
                  ') - .width(canvas.width()) - .height(canvas.height()) - // .css('margin-top',-o.lineMargin) - .insertBefore(scroller); - - $.each(yLabels, function(i){ - var value = Math.floor(this); - var posB = (value-bottomValue)*yScale + o.lineMargin; - if(posB >= o.height-1 || posB < 0) { - return; - } - var thisLi = $('
                • '+value+'
                • ') - .css('bottom', posB); - if(Math.abs(posB) < o.height-1) { - thisLi.prepend(''); - } - thisLi.prependTo(ylabelsUL); - - var label = thisLi.find('span:not(.line)'); - var topOffset = label.height()/-2; - if(!o.lineMargin) { - if(i == 0){ topOffset = -label.height(); } - else if(i== yLabels.length-1){ topOffset = 0; } - } - label - .css('margin-top', topOffset) - .addClass('label'); - }); - - //start from the bottom left - ctx.translate(zeroLocX,zeroLocY); - - charts.line.draw(area); - - }, - - draw: function(area) { - // prevent drawing on top of previous draw - ctx.clearRect(-zeroLocX,-zeroLocY,o.width,o.height); - // Calculate each point properties before hand - var integer; - $.each(dataGroups,function(i,row){ - integer = o.lineMargin; // the current offset - $.each(row.points, function(j,point){ - if(o.xLabelParser) { - point.canvasCords = [(xLabels[j]-zeroLocX)*xScale - xBottomValue,-(point.value*yScale)]; - } else { - point.canvasCords = [integer,-(point.value*yScale)]; - } - - if(o.lineDots) { - point.dotSize = o.dotSize||o.lineWeight*Math.PI; - point.dotInnerSize = o.dotInnerSize||o.lineWeight*Math.PI/2; - if(o.lineDots == 'double') { - point.innerColor = o.dotInnerColor; - } - } - integer+=xInterval; - }); - }); - // fire custom event so we can enable rich interaction - self.trigger('vizualizeBeforeDraw',{options:o,table:self,canvasContain:canvasContain,tableData:tableData}); - // draw lines and areas - $.each(dataGroups,function(h){ - // Draw lines - ctx.beginPath(); - ctx.lineWidth = o.lineWeight; - ctx.lineJoin = 'round'; - $.each(this.points, function(g){ - var loc = this.canvasCords; - if(g == 0) { - ctx.moveTo(loc[0],loc[1]); - } - ctx.lineTo(loc[0],loc[1]); - }); - ctx.strokeStyle = this.color; - ctx.stroke(); - // Draw fills - if(area){ - var integer = this.points[this.points.length-1].canvasCords[0]; - if (isFinite(integer)) - ctx.lineTo(integer,0); - ctx.lineTo(o.lineMargin,0); - ctx.closePath(); - ctx.fillStyle = this.color; - ctx.globalAlpha = .3; - ctx.fill(); - ctx.globalAlpha = 1.0; - } - else {ctx.closePath();} - }); - // draw points - if(o.lineDots) { - $.each(dataGroups,function(h){ - $.each(this.points, function(g){ - drawPoint(ctx,this.canvasCords[0],this.canvasCords[1],this.color,this.dotSize); - if(o.lineDots === 'double') { - drawPoint(ctx,this.canvasCords[0],this.canvasCords[1],this.innerColor,this.dotInnerSize); - } - }); - }); - } - - } - }; - - })(); - - charts.area = { - setup: function() { - charts.line.setup(true); - }, - draw: charts.line.draw - }; - - (function(){ - - var horizontal,bottomLabels; - - charts.bar = { - setup:function(){ - /** - * We can draw horizontal or vertical bars depending on the - * value of the 'barDirection' option (which may be 'vertical' or - * 'horizontal'). - */ - - horizontal = (o.barDirection == 'horizontal'); - - canvasContain.addClass('visualize-bar'); - - /** - * Write labels along the bottom of the chart. If we're drawing - * horizontal bars, these will be the yLabels, otherwise they - * will be the xLabels. The positioning also varies slightly: - * yLabels are values, hence they will span the whole width of - * the canvas, whereas xLabels are supposed to line up with the - * bars. - */ - bottomLabels = horizontal ? yLabels : xLabels; - - var xInterval = canvas.width() / (bottomLabels.length - (horizontal ? 1 : 0)); - - var xlabelsUL = $('
                    ') - .width(canvas.width()) - .height(canvas.height()) - .insertBefore(canvas); - - $.each(bottomLabels, function(i){ - var thisLi = $('
                  • '+this+'
                  • ') - .prepend('') - .css('left', xInterval * i) - .width(xInterval) - .appendTo(xlabelsUL); - - if (horizontal) { - var label = thisLi.find('span.label'); - label.css("margin-left", -label.width() / 2); - } - }); - - /** - * Write labels along the left of the chart. Follows the same idea - * as the bottom labels. - */ - var leftLabels = horizontal ? xLabels : yLabels; - var liBottom = canvas.height() / (leftLabels.length - (horizontal ? 0 : 1)); - - var ylabelsUL = $('
                      ') - .width(canvas.width()) - .height(canvas.height()) - .insertBefore(canvas); - - $.each(leftLabels, function(i){ - var thisLi = $('
                    • '+this+'
                    • ').prependTo(ylabelsUL); - - var label = thisLi.find('span:not(.line)').addClass('label'); - - if (horizontal) { - /** - * For left labels, we want to vertically align the text - * to the middle of its container, but we don't know how - * many lines of text we will have, since the labels could - * be very long. - * - * So we set a min-height of liBottom, and a max-height - * of liBottom + 1, so we can then check the label's actual - * height to determine if it spans one line or more lines. - */ - label.css({ - 'min-height': liBottom, - 'max-height': liBottom + 1, - 'vertical-align': 'middle' - }); - thisLi.css({'top': liBottom * i, 'min-height': liBottom}); - - var r = label[0].getClientRects()[0]; - if (r.bottom - r.top == liBottom) { - /* This means we have only one line of text; hence - * we can centre the text vertically by setting the line-height, - * as described at: - * http://www.ampsoft.net/webdesign-l/vertical-aligned-nav-list.html - * - * (Although firefox has .height on the rectangle, IE doesn't, - * so we use r.bottom - r.top rather than r.height.) - */ - label.css('line-height', parseInt(liBottom) + 'px'); - } - else { - /* - * If there is more than one line of text, then we shouldn't - * touch the line height, but we should make sure the text - * doesn't overflow the container. - */ - label.css("overflow", "hidden"); - } - } - else { - thisLi.css('bottom', liBottom * i).prepend(''); - label.css('margin-top', -label.height() / 2) - } - }); - - charts.bar.draw(); - - }, - - draw: function() { - // Draw bars - - if (horizontal) { - // for horizontal, keep the same code, but rotate everything 90 degrees - // clockwise. - ctx.rotate(Math.PI / 2); - } - else { - // for vertical, translate to the top left corner. - ctx.translate(0, zeroLocY); - } - - // Don't attempt to draw anything if all the values are zero, - // otherwise we will get weird exceptions from the canvas methods. - if (totalYRange <= 0) - return; - - var yScale = (horizontal ? canvas.width() : canvas.height()) / totalYRange; - var barWidth = horizontal ? (canvas.height() / xLabels.length) : (canvas.width() / (bottomLabels.length)); - var linewidth = (barWidth - o.barGroupMargin*2) / dataGroups.length; - - for(var h=0; h')) - .height(o.height) - .width(o.width); - - var scroller = $('
                      ') - .appendTo(canvasContain) - .append(canvas); - - //title/key container - if(o.appendTitle || o.appendKey){ - var infoContain = $('
                      ') - .appendTo(canvasContain); - } - - //append title - if(o.appendTitle){ - $('
                      '+ title +'
                      ').appendTo(infoContain); - } - - - //append key - if(o.appendKey){ - var newKey = $('
                        '); - $.each(yAllLabels, function(i,label){ - $('
                      • '+ label +'
                      • ') - .appendTo(newKey); - }); - newKey.appendTo(infoContain); - }; - - // init interaction - if(o.interaction) { - // sets the canvas to track interaction - // IE needs one div on top of the canvas since the VML shapes prevent mousemove from triggering correctly. - // Pie charts needs tracker because labels goes on top of the canvas and also messes up with mousemove - var tracker = $('
                        ') - .css({ - 'height': o.height + 'px', - 'width': o.width + 'px', - 'position':'relative', - 'z-index': 200 - }) - .insertAfter(canvas); - - var triggerInteraction = function(overOut,data) { - var data = $.extend({ - canvasContain:canvasContain, - tableData:tableData - },data); - self.trigger('vizualize'+overOut,data); - }; - - var over=false, last=false, started=false; - tracker.mousemove(function(e){ - var x,y,x1,y1,data,dist,i,current,selector,zLabel,elem,color,minDist,found,ev=e.originalEvent; - - // get mouse position relative to the tracker/canvas - x = ev.layerX || ev.offsetX || 0; - y = ev.layerY || ev.offsetY || 0; - - found = false; - minDist = started?30000:(o.type=='pie'?(Math.round(canvas.height()/2)-o.pieMargin)/3:o.lineWeight*4); - // iterate datagroups to find points with matching - $.each(charts[o.type].interactionPoints,function(i,current){ - x1 = current.canvasCords[0] + zeroLocX; - y1 = current.canvasCords[1] + (o.type=="pie"?0:zeroLocY); - dist = Math.sqrt( (x1 - x)*(x1 - x) + (y1 - y)*(y1 - y) ); - if(dist < minDist) { - found = current; - minDist = dist; - } - }); - - if(o.multiHover && found) { - x = found.canvasCords[0] + zeroLocX; - y = found.canvasCords[1] + (o.type=="pie"?0:zeroLocY); - found = [found]; - $.each(charts[o.type].interactionPoints,function(i,current){ - if(current == found[0]) {return;} - x1 = current.canvasCords[0] + zeroLocX; - y1 = current.canvasCords[1] + zeroLocY; - dist = Math.sqrt( (x1 - x)*(x1 - x) + (y1 - y)*(y1 - y) ); - if(dist <= o.multiHover) { - found.push(current); - } - }); - } - // trigger over and out only when state changes, instead of on every mousemove - over = found; - if(over != last) { - if(over) { - if(last) { - triggerInteraction('Out',{point:last}); - } - triggerInteraction('Over',{point:over}); - last = over; - } - if(last && !over) { - triggerInteraction('Out',{point:last}); - last=false; - } - started=true; - } - }); - tracker.mouseleave(function(){ - triggerInteraction('Out',{ - point:last, - mouseOutGraph:true - }); - over = (last = false); - }); - } - - //append new canvas to page - if(!container){canvasContain.insertAfter(this); } - if( typeof(G_vmlCanvasManager) != 'undefined' ){ G_vmlCanvasManager.init(); G_vmlCanvasManager.initElement(canvas[0]); } - - //set up the drawing board - var ctx = canvas[0].getContext('2d'); - - // Scroll graphs - scroller.scrollLeft(o.width-scroller.width()); - - // init plugins - $.each($.visualizePlugins,function(i,plugin){ - plugin.call(self,o,tableData); - }); - - //create chart - charts[o.type].setup(); - - if(!container){ - //add event for updating - self.bind('visualizeRefresh', function(){ - self.visualize(o, $(this).empty()); - }); - //add event for redraw - self.bind('visualizeRedraw', function(){ - charts[o.type].draw(); - }); - } - }).next(); //returns canvas(es) + //scrape table (this should be cleaned up into an obj) + var tableData = scrapeTable(); + var dataGroups = tableData.dataGroups(); + var allData = tableData.allData(); + var dataSum = tableData.dataSum(); + var topValue = tableData.topValue(); + var bottomValue = tableData.bottomValue(); + var memberTotals = tableData.memberTotals(); + var totalYRange = tableData.totalYRange(); + var zeroLoc = o.height * (topValue/totalYRange); + var xLabels = tableData.xLabels(); + var yLabels = tableData.yLabels(); + + //title/key container + if(o.appendTitle || o.appendKey){ + var infoContain = $('
                        ') + .appendTo(canvasContain); + } + + //append title + if(o.appendTitle){ + $('
                        '+ title +'
                        ').appendTo(infoContain); + } + + + //append key + if(o.appendKey){ + var newKey = $('
                          '); + var selector; + if(o.parseDirection == 'x'){ + selector = self.find('tr:gt(0) th').filter(o.rowFilter); + } + else{ + selector = self.find('tr:eq(0) th').filter(o.colFilter); + } + + selector.each(function(i){ + $('
                        • '+ $(this).text() +'
                        • ') + .appendTo(newKey); + }); + newKey.appendTo(infoContain); + }; + + //append new canvas to page + + if(!container){canvasContain.insertAfter(this); } + if( typeof(G_vmlCanvasManager) != 'undefined' ){ G_vmlCanvasManager.init(); G_vmlCanvasManager.initElement(canvas[0]); } + + //set up the drawing board + var ctx = canvas[0].getContext('2d'); + + //create chart + createChart[o.type](); + + //clean up some doubled lines that sit on top of canvas borders (done via JS due to IE) + $('.visualize-line li:first-child span.line, .visualize-line li:last-child span.line, .visualize-area li:first-child span.line, .visualize-area li:last-child span.line, .visualize-bar li:first-child span.line,.visualize-bar .visualize-labels-y li:last-child span.line').css('border','none'); + if(!container){ + //add event for updating + canvasContain.bind('visualizeRefresh', function(){ + self.visualize(o, $(this).empty()); + }); + } + }).next(); //returns canvas(es) }; -// create array for plugins. if you wish to make a plugin, -// just push your init funcion into this array -$.visualizePlugins = []; - })(jQuery); - - From f636e4fc99fd7d9144b5030f5e1a717f4aa0f829 Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Wed, 16 Oct 2013 09:35:12 +0200 Subject: [PATCH 15/19] [FIX] Return 0 data not NULL if cache-key missing --- public/include/classes/statistics.class.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/public/include/classes/statistics.class.php b/public/include/classes/statistics.class.php index 0950b632..cf797da3 100644 --- a/public/include/classes/statistics.class.php +++ b/public/include/classes/statistics.class.php @@ -242,7 +242,12 @@ class Statistics { public function getUserShares($account_id) { $this->debug->append("STA " . __METHOD__, 4); // Dual-caching, try statistics cron first, then fallback to local, then fallbock to SQL - if ($data = $this->memcache->get(STATISTICS_ALL_USER_SHARES)) return @$data['data'][$account_id]; + if ($data = $this->memcache->get(STATISTICS_ALL_USER_SHARES)) { + if (array_key_exists($account_id, $data['data'])) + return $data['data'][$account_id]; + // We have no cached value, we return defaults + return array('valid' => 0, 'invalid' => 0, 'donate_percent' => 0, 'is_anonymous' => 0); + } if ($data = $this->memcache->get(__FUNCTION__ . $account_id)) return $data; $stmt = $this->mysqli->prepare(" SELECT From aedb37aa3d9e6c0908e1ba50f89207f7b09a0acf Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Wed, 16 Oct 2013 11:34:59 +0200 Subject: [PATCH 16/19] [IMPROVED] Added getUserEstimates method --- public/include/classes/statistics.class.php | 25 +++++++++++++++++++ public/include/classes/user.class.php | 3 +++ .../pages/api/getdashboarddata.inc.php | 3 ++- public/include/smarty_globals.inc.php | 16 ++++-------- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/public/include/classes/statistics.class.php b/public/include/classes/statistics.class.php index cf797da3..b607ec0c 100644 --- a/public/include/classes/statistics.class.php +++ b/public/include/classes/statistics.class.php @@ -534,6 +534,31 @@ class Statistics { $this->debug->append("Failed to fetch hourly hashrate: " . $this->mysqli->error); return false; } + + /** + * get user estimated payouts based on share counts + * @param aRoundShares array Round shares + * @param aUserShares array User shares + * @param dDonate double User donation setting + * @param bNoFees bool User no-fees option setting + * @return aEstimates array User estimations + **/ + public function getUserEstimates($aRoundShares, $aUserShares, $dDonate, $bNoFees) { + $this->debug->append("STA " . __METHOD__, 4); + // Fetch some user information that we need + if (@$aRoundShares['valid'] > 0 && @$aUserShares['valid'] > 0) { + $aEstimates['block'] = round(( (int)$aUserShares['valid'] / (int)$aRoundShares['valid'] ) * (float)$this->config['reward'], 8); + $bNoFees == 0 ? $aEstimates['fee'] = round(((float)$this->config['fees'] / 100) * (float)$aEstimates['block'], 8) : $aEstimates['fee'] = 0; + $aEstimates['donation'] = round((( (float)$dDonate / 100) * ((float)$aEstimates['block'] - (float)$aEstimates['fee'])), 8); + $aEstimates['payout'] = round((float)$aEstimates['block'] - (float)$aEstimates['donation'] - (float)$aEstimates['fee'], 8); + } else { + $aEstimates['block'] = 0; + $aEstimates['fee'] = 0; + $aEstimates['donation'] = 0; + $aEstimates['payout'] = 0; + } + return $aEstimates; + } } $statistics = new Statistics($debug, $mysqli, $config, $share, $user, $block, $memcache); diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index d811c708..31ecd0f1 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -55,6 +55,9 @@ class User { public function getUserNoFee($id) { return $this->getSingle($id, 'no_fees', 'id'); } + public function getUserDonatePercent($id) { + return $this->getSingle($id, 'donate_percent', 'id'); + } public function getUserAdmin($id) { return $this->getSingle($id, 'is_admin', 'id'); } diff --git a/public/include/pages/api/getdashboarddata.inc.php b/public/include/pages/api/getdashboarddata.inc.php index 9eac5d46..e16d89cd 100644 --- a/public/include/pages/api/getdashboarddata.inc.php +++ b/public/include/pages/api/getdashboarddata.inc.php @@ -37,6 +37,7 @@ $statistics->setGetCache(true); // Use caches for this one $aUserRoundShares = $statistics->getUserShares($user_id); $aRoundShares = $statistics->getRoundShares(); +$aEstimates = $statistics->getUserEstimates($aRoundShares, $aUserRoundShares, $user->getUserDonatePercent($user_id), $user->getUserNoFee($user_id)); // Apply pool modifiers $dPersonalHashrateAdjusted = $dPersonalHashrate * $dPersonalHashrateModifier; @@ -46,7 +47,7 @@ $dNetworkHashrateAdjusted = $dNetworkHashrate / 1000 * $dNetworkHashrateModifier // Output JSON format $data = array( 'raw' => array( 'personal' => array( 'hashrate' => $dPersonalHashrate ), 'pool' => array( 'hashrate' => $dPoolHashrate ), 'network' => array( 'hashrate' => $dNetworkHashrate / 1000 ) ), - 'personal' => array ( 'hashrate' => $dPersonalHashrateAdjusted, 'sharerate' => $dPersonalSharerate, 'shares' => $aUserRoundShares, 'balance' => $transaction->getBalance($user_id)), + 'personal' => array ( 'hashrate' => $dPersonalHashrateAdjusted, 'sharerate' => $dPersonalSharerate, 'shares' => $aUserRoundShares, 'balance' => $transaction->getBalance($user_id), 'estimates' => $aEstimates), 'pool' => array( 'hashrate' => $dPoolHashrateAdjusted, 'shares' => $aRoundShares ), 'network' => array( 'hashrate' => $dNetworkHashrateAdjusted, 'difficulty' => $dDifficulty, 'block' => $iBlock ), ); diff --git a/public/include/smarty_globals.inc.php b/public/include/smarty_globals.inc.php index 1cb7e21d..8323a2fa 100644 --- a/public/include/smarty_globals.inc.php +++ b/public/include/smarty_globals.inc.php @@ -128,17 +128,11 @@ if (@$_SESSION['USERDATA']['id']) { switch ($config['payout_system']) { 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); - $aGlobal['userdata']['no_fees'] == 0 ? $aGlobal['userdata']['est_fee'] = round(((float)$config['fees'] / 100) * (float)$aGlobal['userdata']['est_block'], 8) : $aGlobal['userdata']['est_fee'] = 0; - $aGlobal['userdata']['est_donation'] = round((( (float)$aGlobal['userdata']['donate_percent'] / 100) * ((float)$aGlobal['userdata']['est_block'] - (float)$aGlobal['userdata']['est_fee'])), 8); - $aGlobal['userdata']['est_payout'] = round((float)$aGlobal['userdata']['est_block'] - (float)$aGlobal['userdata']['est_donation'] - (float)$aGlobal['userdata']['est_fee'], 8); - } else { - $aGlobal['userdata']['est_block'] = 0; - $aGlobal['userdata']['est_fee'] = 0; - $aGlobal['userdata']['est_donation'] = 0; - $aGlobal['userdata']['est_payout'] = 0; - } + $aEstimates = $statistics->getUserEstimates($aRoundShares, $aGlobal['userdata']['shares'], $aGlobal['userdata']['no_fees'], $aGlobal['userdata']['donate_percent']); + $aGlobal['userdata']['est_block'] = $aEstimates['block']; + $aGlobal['userdata']['est_fee'] = $aEstimates['fee']; + $aGlobal['userdata']['est_donation'] = $aEstimates['donation']; + $aGlobal['userdata']['est_payout'] = $aEstimates['payout']; case 'pplns': $aGlobal['pplns']['target'] = $config['pplns']['shares']['default']; if ($aLastBlock = $block->getLast()) { From 50fb5218b1cdfd36b2c17e10231652912ba1e2a7 Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Wed, 16 Oct 2013 11:54:59 +0200 Subject: [PATCH 17/19] [FIX] Use existing getDonatePercent --- public/include/classes/user.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index 31ecd0f1..50955f35 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -56,7 +56,7 @@ class User { return $this->getSingle($id, 'no_fees', 'id'); } public function getUserDonatePercent($id) { - return $this->getSingle($id, 'donate_percent', 'id'); + return $this->getDonatePercent($id); } public function getUserAdmin($id) { return $this->getSingle($id, 'is_admin', 'id'); From 668f6abf780533ee2472f51fd98249cdd3e3ba29 Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Wed, 16 Oct 2013 12:00:29 +0200 Subject: [PATCH 18/19] [FIX] Method argument order fixed --- public/include/smarty_globals.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/include/smarty_globals.inc.php b/public/include/smarty_globals.inc.php index 8323a2fa..8beb33b0 100644 --- a/public/include/smarty_globals.inc.php +++ b/public/include/smarty_globals.inc.php @@ -128,7 +128,7 @@ if (@$_SESSION['USERDATA']['id']) { switch ($config['payout_system']) { case 'prop' || 'pplns': // Some estimations - $aEstimates = $statistics->getUserEstimates($aRoundShares, $aGlobal['userdata']['shares'], $aGlobal['userdata']['no_fees'], $aGlobal['userdata']['donate_percent']); + $aEstimates = $statistics->getUserEstimates($aRoundShares, $aGlobal['userdata']['shares'], $aGlobal['userdata']['donate_percent'], $aGlobal['userdata']['no_fees']); $aGlobal['userdata']['est_block'] = $aEstimates['block']; $aGlobal['userdata']['est_fee'] = $aEstimates['fee']; $aGlobal['userdata']['est_donation'] = $aEstimates['donation']; From 8786a99382c38e6bbfdcadc270ad2f6ea450cfa2 Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Fri, 18 Oct 2013 07:21:51 +0200 Subject: [PATCH 19/19] [FIX] Fix sending mails even though diabled Fixes #732 --- public/include/classes/user.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index 50955f35..df8969c1 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -565,7 +565,7 @@ class User { $username_clean = strip_tags($username); if ($this->checkStmt($stmt) && $stmt->bind_param('sssssi', $username_clean, $password_hash, $email1, $pin_hash, $apikey_hash, $is_locked) && $stmt->execute()) { - if (! $this->setting->getValue('accounts_confirm_email_enabled') && $is_admin != 1) { + if (! $this->setting->getValue('accounts_confirm_email_disabled') && $is_admin != 1) { if ($token = $this->token->createToken('confirm_email', $stmt->insert_id)) { $aData['username'] = $username_clean; $aData['token'] = $token;