commit 1b50f76f728515de54b0a93e6408b17bbb38a14a Author: Sebastian Grewe Date: Mon May 6 14:11:38 2013 +0200 initial import of file base of my WIP diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d67a566e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/public/include/config/global.inc.php +/public/templates/compile/*.php diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..2261feba --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +0.0.1 (Mar 7th 2013) +-------------------- + +* First release on GitHub, basic working version diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 00000000..69b8f501 --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +Description +=========== + +This is a simple PHP framework that I used in a few projects of mine. I +was getting annoyed how long it always took to get the basic setup of a +PHP website done (classes, pages, actions) so I wrote this framework to +help speed up my development process without using one of the big +frameworks out there. + +Requirements +============ + +*No other software required* + +Overview +======== + +Here a quick overview on how the framework works. + +Classes +------- + +* [[include/classes/]] + +For classes used in your projects, a debug class is already included. + +Pages +----- + +* [include/pages](include/pages)/`pagename`.inc.php + +For pages that are called by `index.php?page=`. *pagename.inc.php* +must exist and will be included by the index.php file when required. + +Actions +------- + +* [include/pages](include/pages)/`pagename`/`action`.inc.php + +For each page you can create a subdirectory that will be searched for callable +actions for a page: `index.php?page=&action=`. *action.inc.php* +must exist in the directory and will be included by the `index.php` file. + +Default Page +------------ + +Currently the framework always defaults to `home`. This still needs to be made +configurable at some point but technical difficulties made this impossible as +a short term solution. + +Default Action +-------------- + +The default action for all pages is unset which means by default the page +include file is loaded. Once an action is defined the page include file +is skipped and the action loaded instead. + +Smarty Defaults +--------------- + +Smarty defaults are configured in the [include/smarty.inc.php](include/smarty.inc.php) file. Change +if need be but usually the defaults should work for you. + +Themes +------ + +As it is the framework has simple theming support. If you wish to add a new +theme create a subdirectory in templates and define that new theme in the +global.inc.php configuration file. You will also need to place any images +and the CSS in the site_assets directory (which is always public accessible). + +index.php +--------- + +The index file is taking care of including all your coded pages and actions. +Please prefix each class, function, action and page with a + + if (!defined('SECURITY')) + die('Hacking attempt'); + +This check ensures all files are called through the `index.php` and not directly. + +Setup +===== + +To use this framework simply upload everything to your webserver and change +the permissions on the + +`templates/compile` +`templates/cache` + +to `777` (read/write by everyone) so the Webserver can create the compiled +templates and cache files (if configured, disabled by default). + +Once installed just call the `index.php` and you should see the demo page. + +Debugger +======== + +This framework comes with a debugger. Examples on how to use them are given +inside the example pages. You can enable the debugger by setting the defined +DEBUG variable to your debugging verbosity level. Locate the file in: + +[include/config/global.inc.php](include/config/global.inc.php) + +You can also add any other defines and configuration variables you use +throughout your scripts in this file. It will be included by `index.php`. + +License and Author +================== + +Author:: Sebastian Grewe () + +Copyright:: 2013, Sebastian Grewe + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/cronjobs/README b/cronjobs/README new file mode 100644 index 00000000..f8f87124 --- /dev/null +++ b/cronjobs/README @@ -0,0 +1,22 @@ + +Install: +-------- + +These scripts should not really be placed inside of the webroot. They should be run +from the shell via a cronjob. Set something up like this: + +# m h dom mon dow command + * * * * * /full/path/to/pool_update.sh 1>/dev/null 2>/dev/null + +Also be sure to define in each of these scripts the full path to the include directory. +See the top of each script in the cronjob directory for details. + + + +Notes: +------ +These scripts (from version 2.x.x forward) are intended to run every 60 secs. Best results will be achieved +if you configure your server to run them at the same interval. You risk missing new block information being +inserted into the database the longer you set the interval between these scripts running. Database load has +been significantly improved from version 2.x.x forward and it should now be perfectly safe to run these +scripts every 60 secs. diff --git a/cronjobs/archive.php b/cronjobs/archive.php new file mode 100644 index 00000000..df9b0bc8 --- /dev/null +++ b/cronjobs/archive.php @@ -0,0 +1,58 @@ +getblocknumber(); +$num_blocks_old = ($currentBlockNumber - 10); + +if (!$num_blocks_old) { die($num_blocks_old); } + +// get all shares by user id from shares_history and move to shares_uncounted + + $sql = "SELECT DISTINCT p.associatedUserId, blockNumber, sum(s.valid) as valid, IFNULL(sum(si.invalid),0) as invalid, max(maxId) as maxId FROM ". + "(SELECT DISTINCT username, max(blockNumber) as blockNumber, count(id) as valid, max(id) as maxId FROM shares_history ". + "WHERE counted='0' AND our_result='Y' AND blockNumber <= '" .$num_blocks_old. "' GROUP BY username) s ". + "LEFT JOIN ". + "(SELECT DISTINCT username, count(id) as invalid FROM shares_history ". + "WHERE counted='0' AND our_result='N' AND blockNumber <= '" .$num_blocks_old. "' GROUP BY username) si ". + "ON s.username=si.username ". + "INNER JOIN pool_worker p ON p.username = s.username ". + "GROUP BY associatedUserId"; + + +$sharesQ = mysql_query($sql); +$i = 0; +$maxId = 0; +$shareInputSql = ""; + +while ($sharesR = mysql_fetch_object($sharesQ)) { + if ($sharesR->maxId > $maxId) + $maxId = $sharesR->maxId; + if ($i == 0) { + $shareInputSql = "INSERT INTO shares_uncounted (blockNumber, userId, count, invalid, counted, score) VALUES "; + } + if ($i > 0) { + $shareInputSql .= ","; + } + $i++; + $shareInputSql .= "($sharesR->blockNumber,$sharesR->associatedUserId,$sharesR->valid,$sharesR->invalid,0,0)"; + if ($i > 20) + { + mysql_query($shareInputSql); + $shareInputSql = ""; + $i = 0; + } +} +if (strlen($shareInputSql) > 0) + mysql_query($shareInputSql); + +//Remove counted shares from shares_history + mysql_query("DELETE FROM shares_history WHERE counted = '0' AND id <= $maxId AND blockNumber <= '" .$num_blocks_old. "'"); + +?> diff --git a/cronjobs/cronjob.php b/cronjobs/cronjob.php new file mode 100644 index 00000000..ab7e4a99 --- /dev/null +++ b/cronjobs/cronjob.php @@ -0,0 +1,364 @@ +getblocknumber(); +$difficulty = $bitcoinController->query("getdifficulty"); + +//Get site percentage +$sitePercent = 0; +$sitePercentQ = mysql_query("SELECT value FROM settings WHERE setting='sitepercent'"); +if ($sitePercentR = mysql_fetch_object($sitePercentQ)) $sitePercent = $sitePercentR->value; + +//Setup score variables +$c = .00001; +$f=1; +$f = $sitePercent / 100; +$p = 1.0/$difficulty; +$r = log(1.0-$p+$p/$c); +$B = 50; +$los = log(1/(exp($r)-1)); + +//Query bitcoind for list of transactions +$transactions = $bitcoinController->query('listtransactions', '', '240'); +$numAccounts = count($transactions); + +for($i = 0; $i < $numAccounts; $i++){ + // Check for 50BTC in each transaction (even when immature so we can start tracking confirms) + if($transactions[$i]["amount"] >= 50 && ($transactions[$i]["category"] == "immature" || $transactions[$i]["category"] == "immature")) { + + // At this point we may have found a block, Check to see if this accountAddres is already added to `networkBlocks` + $accountExistsQ = mysql_query("SELECT id FROM networkBlocks WHERE accountAddress = '".$transactions[$i]["txid"]."' ORDER BY blockNumber DESC LIMIT 0,1")or die(mysql_error()); + $accountExists = mysql_num_rows($accountExistsQ); + + // We have a new immature transaction for 50 BTC or more - make an entry in `networkBlocks` so we can start tracking the confirms + if(!$accountExists){ + $assoc_block = ($currentBlockNumber + 1) - $transactions[$i]["confirmations"]; + $assoc_timestamp = $transactions[$i]["time"]; + $finder = mysql_fetch_object(mysql_query("SELECT DISTINCT id, username FROM shares where upstream_result = 'Y'")); + + // save the winning share and username (if we know it) + if ($finder) { + $last_winning_share = $finder->id; + $username = $finder->username; + mysql_query("INSERT INTO winning_shares (blockNumber, username) VALUES ('" .$assoc_block. "', '" .$username. "')"); + } else { + mysql_query("INSERT INTO winning_shares (blockNumber, username) VALUES ('" .$assoc_block. "', 'unknown')"); + } + + // save the block info so we can track confirms + mysql_query("INSERT INTO `networkBlocks` (`blockNumber`, `timestamp`, `accountAddress`, `confirms`, `difficulty`) ". + "VALUES ('$assoc_block', '$assoc_timestamp', '" .$transactions[$i]["txid"]. "', '" .$transactions[$i]["confirmations"]. "', '$difficulty')"); + + // score and move shares from this block to shares_history + $shareInputQ = ""; + $i=0; + $lastId = 0; + $lastScore = 0; + + if ($finder) { + $getAllShares = mysql_query("SELECT `id`, `rem_host`, `username`, `our_result`, `upstream_result`, `reason`, `solution`, time FROM `shares` WHERE id <='" .$last_winning_share. "' ORDER BY `id` ASC"); + } else { + $getAllShares = mysql_query("SELECT `id`, `rem_host`, `username`, `our_result`, `upstream_result`, `reason`, `solution`, time FROM `shares` ORDER BY `id` ASC"); + } + + while($share = mysql_fetch_array($getAllShares)){ + if ($i==0) + $shareInputQ = "INSERT INTO `shares_history` (`blockNumber`, `rem_host`, `username`, `our_result`, `upstream_result`, `reason`, `solution`, time, score) VALUES "; + $i++; + if($i > 1){ + $shareInputQ .= ","; + } + $score = $lastScore + $r; + $shareInputQ .="('".$assoc_block."', + '".$share["rem_host"]."', + '".$share["username"]."', + '".$share["our_result"]."', + '".$share["upstream_result"]."', + '".$share["reason"]."', + '".$share["solution"]."', + '".$share["time"]."', + ".$score.")"; + $lastId = $share["id"]; + $lastScore = $score; + if ($i > 5) { + //Add to `shares_history` + $shareHistoryQ = mysql_query($shareInputQ); + + //If the add to shares_history was successful, lets clean up `shares` table + if($shareHistoryQ){ + //Delete all from shares whoms "id" is less then $lastId (keep everything that didnt get moved. Its probably from the new round. + mysql_query("DELETE FROM shares WHERE id <= ".$lastId); + } + $i = 0; + } + } + // less than five share entries? still do the same as above. + $shareHistoryQ = mysql_query($shareInputQ); + if($shareHistoryQ){ + //Delete all from shares whoms "id" is less then $lastId to prevent new "hard-earned" shares to be deleted + mysql_query("DELETE FROM shares WHERE id <= ".$lastId); + } + // Count number of shares we needed to solve this block + + // get last block number we found + $last_winning_blockQ = mysql_query("SELECT DISTINCT blockNumber FROM winning_shares ORDER BY blockNumber DESC LIMIT 1,1"); + $last_winning_blockObj = mysql_fetch_object($last_winning_blockQ); + $last_winning_block = $last_winning_blockObj->blockNumber; + + $block_share_countQ = mysql_query("SELECT sum(su_count) as total FROM (". + "SELECT sum(count) as su_count FROM shares_uncounted where blockNumber > " .$last_winning_block. " ". + "and blockNumber <= " .$assoc_block. " ". + "UNION SELECT count(id) as sh_count from shares_history where blockNumber <= " .$assoc_block. " AND blockNumber > " .$last_winning_block. " AND our_result != 'N' ". + ") a"); + $block_share_countObj = mysql_fetch_object($block_share_countQ); + + if($block_share_countObj) { + mysql_query("UPDATE `winning_shares` SET `shareCount` = " .$block_share_countObj->total. " WHERE blockNumber = " .$assoc_block); + } + } + } +} + + + +///// Update confirms ///// + +// run thru list of transactions we got from bitcoind and update their confirms (when immature) +for($i = 0; $i < $numAccounts; $i++){ + //if ($transactions[$i]["category"] = "receive") + if (($transactions[$i]["category"] = "immature") || ($transactions[$i]["category"] = "immature")){ + //Check to see if this account was one of the winning accounts from `networkBlocks` + $arrayAddress = $transactions[$i]["txid"]; + $winningAccountQ = mysql_query("SELECT id FROM networkBlocks WHERE accountAddress = '".$arrayAddress."' LIMIT 0,1"); + $winningAccount = mysql_num_rows($winningAccountQ); + + if($winningAccount > 0){ + //This is a winning account + $winningAccountObj = mysql_fetch_object($winningAccountQ); + $winningId = $winningAccountObj->id; + $confirms = $transactions[$i]["confirmations"]; + + //Update X amount of confirms + mysql_query("UPDATE networkBlocks SET confirms = '".$confirms."' WHERE id = ".$winningId); + } + } +} + + + + +///// Check for new network block and score and move shares to shares_history if true /// + +// refresh the current block number data +$currentBlockNumber = $bitcoinController->getblocknumber(); + +// check if we have it in the database (if so we exit because we already did this and we were the block finder) +$inDatabaseQ = mysql_query("SELECT `id` FROM `networkBlocks` WHERE `blockNumber` = '$currentBlockNumber' LIMIT 0,1"); +$inDatabase = mysql_num_rows($inDatabaseQ); +$finder = mysql_fetch_object(mysql_query("SELECT DISTINCT id, username FROM shares where upstream_result = 'Y'")); + +if(!$inDatabase){ + // make an entry in the DB for this new block + $currentTime = time(); + mysql_query("INSERT INTO `networkBlocks` (`blockNumber`, `timestamp`, `difficulty`) VALUES ('$currentBlockNumber', '$currentTime', '$difficulty')"); + + // score and move shares from this block to shares_history + $shareInputQ = ""; + $i=0; + $lastId = 0; + $lastScore = 0; + + $getAllShares = mysql_query("SELECT `id`, `rem_host`, `username`, `our_result`, `upstream_result`, `reason`, `solution`, time FROM `shares` ORDER BY `id` ASC"); + + while($share = mysql_fetch_array($getAllShares)){ + if ($i==0) + $shareInputQ = "INSERT INTO `shares_history` (`blockNumber`, `rem_host`, `username`, `our_result`, `upstream_result`, `reason`, `solution`, time, score) VALUES "; + $i++; + if($i > 1){ + $shareInputQ .= ","; + } + $score = $lastScore + $r; + $shareInputQ .="('".$currentBlockNumber."', + '".$share["rem_host"]."', + '".$share["username"]."', + '".$share["our_result"]."', + '".$share["upstream_result"]."', + '".$share["reason"]."', + '".$share["solution"]."', + '".$share["time"]."', + ".$score.")"; + $lastId = $share["id"]; + $lastScore = $score; + if ($i > 5) { + //Add to `shares_history` + $shareHistoryQ = mysql_query($shareInputQ); + + //If the add to shares_history was successful, lets clean up `shares` table + if($shareHistoryQ){ + //Delete all from shares whoms "id" is less then $lastId (keep everything that didnt get moved. Its probably from the new round. + mysql_query("DELETE FROM shares WHERE id <= ".$lastId); + } + $i = 0; + } + } + // less than five share entries? still do the same as above. + $shareHistoryQ = mysql_query($shareInputQ); + if($shareHistoryQ) { + //Delete all from shares whoms "id" is less then $lastId to prevent new "hard-earned" shares to be deleted + mysql_query("DELETE FROM shares WHERE id <= ".$lastId); + //exec("cd /sites/mmc/cronjobs/; /usr/bin/php archive.php"); + } +} + + + + +///// Proportional Payout Method ///// + +// Get uncounted share total +$overallReward = 0; +$blocksQ = mysql_query("SELECT DISTINCT s.blockNumber FROM shares_uncounted s, networkBlocks n WHERE s.blockNumber = n.blocknumber AND s.counted=0 AND n.confirms > 119 ORDER BY s.blockNumber ASC"); + +while ($blocks = mysql_fetch_object($blocksQ)) { + $block = $blocks->blockNumber; + + // LastNshares - mark all shares below the $lastNshares threshold counted + $l_bound = 0; + $total = 0; + $lastNshares = 1000000; + + $sql = mysql_query("SELECT blockNumber, count FROM ( ". + "SELECT blockNumber, count FROM `shares_uncounted` WHERE blockNumber <= " .$block. " ". + "UNION SELECT blockNumber, count FROM `shares_counted` WHERE blockNumber <= " .$block. " AND blockNumber > ".($block - 1000)." ". + ")a ORDER BY blockNumber DESC"); + + while ($result = mysql_fetch_object($sql)) { + + // increment $total with each row returned + $total = $total + $result->count; + + // if $lastNshares criteria is met, and $l_bound is not our whole count, set everything below $l_bound as counted = 1 + if ($total >= $lastNshares) { + $l_bound = $result->blockNumber; + + if ($l_bound < $block) { + mysql_query("UPDATE shares_uncounted SET counted = 1 WHERE blockNumber < ".$l_bound); + } + break; + } + } + + $totalRoundSharesQ = mysql_query("SELECT sum(id) as id FROM ( ". + "SELECT sum(count) as id FROM shares_uncounted WHERE blockNumber <= ".$block." AND blockNumber >= ".$l_bound." ". + "UNION SELECT sum(count) as id FROM shares_counted WHERE blockNumber <= " .$block. " AND blockNumber >= ".$l_bound."". + " )a"); + + if ($totalRoundSharesR = mysql_fetch_object($totalRoundSharesQ)) { + $totalRoundShares = $totalRoundSharesR->id; + + $userListCountQ = mysql_query("SELECT userId, sum(id) as id FROM ( ". + "SELECT DISTINCT userId, sum(count) as id FROM shares_uncounted WHERE blockNumber <= ".$block." AND blockNumber >= ".$l_bound." GROUP BY userId ". + "UNION DISTINCT SELECT userId, sum(count) as id FROM shares_counted WHERE blockNumber <= " .$block. " AND blockNumber >= ".$l_bound." GROUP BY userId ". + " )a GROUP BY userId"); + + while ($userListCountR = mysql_fetch_object($userListCountQ)) { + $userInfoR = mysql_fetch_object(mysql_query("SELECT DISTINCT username, donate_percent FROM webUsers WHERE id = '" .$userListCountR->userId. "'")); + + $username = $userInfoR->username; + $uncountedShares = $userListCountR->id; + $shareRatio = $uncountedShares/$totalRoundShares; + $ownerId = $userListCountR->userId; + $donatePercent = $userInfoR->donate_percent; + + //Take out site percent unless user is of early adopter account type + $account_type = account_type($ownerId); + if ($account_type == 0) { + // is normal account + $predonateAmount = (1-$f)*(50*$shareRatio); + $predonateAmount = rtrim(sprintf("%f",$predonateAmount ),"0"); + $totalReward = $predonateAmount - ($predonateAmount * ($sitePercent/100)); + } else { + // is early adopter round 1 0% lifetime fees + $predonateAmount = 0.9999*(50*$shareRatio); + $predonateAmount = rtrim(sprintf("%f",$predonateAmount ),"0"); + $totalReward = $predonateAmount; + } + + if ($predonateAmount > 0.00000001) { + + //Take out donation + $totalReward = $totalReward - ($totalReward * ($donatePercent/100)); + + //Round Down to 8 digits + $totalReward = $totalReward * 100000000; + $totalReward = floor($totalReward); + $totalReward = $totalReward/100000000; + + //Get total site reward + $donateAmount = round(($predonateAmount - $totalReward), 8); + + $overallReward += $totalReward; + + //Update account balance & site ledger + mysql_query("UPDATE accountBalance SET balance = balance + ".$totalReward." WHERE userId = ".$ownerId); + + mysql_query("INSERT INTO ledger (userId, transType, amount, feeAmount, assocBlock) ". + " VALUES ". + "('$ownerId', 'Credit', '$totalReward', '$donateAmount', '$block')"); + } + mysql_query("UPDATE shares_uncounted SET counted = 1 WHERE userId='".$ownerId."' AND blockNumber <= ".$block); + } + // update site wallet with our reward from this block + if (isset($B)) { + $poolReward = $B -$overallReward; + } + //mysql_query("UPDATE settings SET value = value +".$poolReward." WHERE setting='sitebalance'"); + mv_uncountedToCounted(); + } +} + +function mv_uncountedToCounted() { + // clean counted shares_uncounted and move to shares_counted + $sql = "SELECT DISTINCT * FROM shares_uncounted WHERE counted=1"; + + $sharesQ = mysql_query($sql); + $i = 0; + //$maxId = 0; + $shareInputSql = ""; + + while ($sharesR = mysql_fetch_object($sharesQ)) { + //if ($sharesR->maxId > $maxId) + // $maxId = $sharesR->maxId; + if ($i == 0) { + $shareInputSql = "INSERT INTO shares_counted (blockNumber, userId, count, invalid, counted, score) VALUES "; + } + if ($i > 0) { + $shareInputSql .= ","; + } + $i++; + $shareInputSql .= "($sharesR->blockNumber,$sharesR->userId,$sharesR->count,$sharesR->invalid,$sharesR->counted,$sharesR->score)"; + if ($i > 20) + { + mysql_query($shareInputSql); + $shareInputSql = ""; + $i = 0; + } + } + + // if not empty, Insert + if (strlen($shareInputSql) > 0) + mysql_query($shareInputSql); + + //Remove counted shares from shares_uncounted (this should empty it completely or something went wrong. + mysql_query("DELETE FROM shares_uncounted WHERE counted = '1'"); +} +?> diff --git a/cronjobs/hashrate.php b/cronjobs/hashrate.php new file mode 100644 index 00000000..6bd54987 --- /dev/null +++ b/cronjobs/hashrate.php @@ -0,0 +1,63 @@ + DATE_SUB(now(), INTERVAL 10 MINUTE) ". + "GROUP BY username) ". + "UNION ". + "(SELECT count(id) as id, username ". + "FROM shares_history ". + "WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE) ". + "GROUP BY username)) a ". + "ON p.username=a.username ". + "GROUP BY username"; +$result = mysql_query($sql); +while ($resultrow = mysql_fetch_object($result)) { + $hashrate = $resultrow->id; + $hashrate = round((($hashrate*4294967296)/600)/1000000, 0); + mysql_query("UPDATE pool_worker SET hashrate = $hashrate WHERE username = '$resultrow->username'"); +} + +//Total Hashrate (more exact than adding) +$sql = "SELECT sum(a.id) as id FROM ". + "((SELECT count(id) as id FROM shares WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE)) ". + "UNION ". + "(SELECT count(id) as id FROM shares_history WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE)) ". + ") a "; +$result = mysql_query($sql); +if ($resultrow = mysql_fetch_object($result)) { + $hashrate = $resultrow->id; + $hashrate = round((($hashrate*4294967296)/600)/1000000, 0); + mysql_query("UPDATE settings SET value = '$hashrate' WHERE setting='currenthashrate'"); +} + +//Hashrate by user +$sql = "SELECT u.id, IFNULL(sum(p.hashrate),0) as hashrate ". + "FROM webUsers u LEFT JOIN pool_worker p ". + "ON p.associatedUserId = u.id ". + "GROUP BY id"; +$result = mysql_query($sql); +while ($resultrow = mysql_fetch_object($result)) { + mysql_query("UPDATE webUsers SET hashrate = $resultrow->hashrate WHERE id = $resultrow->id"); + + // Enable this for lots of stats for graphing + if ($resultrow->hashrate > 0) { + mysql_query("INSERT INTO userHashrates (userId, hashrate) VALUES ($resultrow->id, $resultrow->hashrate)"); // active users hashrate + } +} + +mysql_query("INSERT INTO userHashrates (userId, hashrate) VALUES (0, $hashrate)"); // the pool total hashrate + +$currentTime = time(); +mysql_query("update settings set value='$currentTime' where setting='statstime'"); + +// Clean up the userHashrate table (anything older than 4 days) +mysql_query("DELETE FROM userHashrates WHERE timestamp < DATE_SUB(now(), INTERVAL 96 HOUR)"); + +?> diff --git a/cronjobs/payout.php b/cronjobs/payout.php new file mode 100644 index 00000000..4bf5c851 --- /dev/null +++ b/cronjobs/payout.php @@ -0,0 +1,40 @@ += 0.10 AND balance > threshold"); +while ($resultR = mysql_fetch_object($resultQ)) { + $currentBalance = $resultR->balance; + $paid = $resultR->paid; + $paymentAddress = $resultR->sendAddress; + $userId = $resultR->userId; + + if ($paymentAddress != '') + { + $isValidAddress = $bitcoinController->validateaddress($paymentAddress); + if($isValidAddress){ + // Subtract TX fee & calculate total amount the pool will pay + $currentBalance = $currentBalance - 0.0005; + $tot_paid = $resultR->paid + $currentBalance; + + // Send the BTC! + // debug + // echo "sending: ". $currentBalance . " to ". $paymentAddress; + + if($bitcoinController->sendtoaddress($paymentAddress, $currentBalance)) { + // Reduce balance amount to zero, update total paid amount, and make a ledger entry + mysql_query("UPDATE `accountBalance` SET balance = '0', paid = '".$tot_paid."' WHERE `userId` = '".$userId."'"); + + mysql_query("INSERT INTO ledger (userId, transType, amount, sendAddress) ". + " VALUES ". + "('$userId', 'Debit_ATP', '$currentBalance', '$paymentAddress')"); + + } + } + } +} diff --git a/cronjobs/pool_update.sh b/cronjobs/pool_update.sh new file mode 100644 index 00000000..e34ba857 --- /dev/null +++ b/cronjobs/pool_update.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# +# This is where all the magic happens. These scripts count shares, calculate payouts, +# calculate user hashrates, decide when we have found a block and what to do about it, +# and determine if workers are alive or dead. They are the the meat of this package. +# + +## Make sure this matches the location of your +## of your php binary. +PHP_BIN="/usr/bin/php"; + +#################################################################################### +PID=$$; +PIDFILE=/var/run/pool_update.pid + +if [ -e $PIDFILE ]; then + echo "Already running. I cannot be twice invoked."; + exit +else + echo $PID > $PIDFILE + cd /sites/mmc/cronjobs/ + echo -e "\ncronjob.php\n-------------"; time $PHP_BIN cronjob.php; sleep 1; + echo -e "\nshares.php\n-------------"; time $PHP_BIN shares.php; sleep 1; + echo -e "\npayout.php\n-------------"; time $PHP_BIN payout.php; sleep 1; + echo -e "\nworkers.php\n-------------"; time $PHP_BIN workers.php; sleep 1; + echo -e "\nhashrate.php\n-------------"; time $PHP_BIN hashrate.php; sleep 1; + echo -e "\narchive.php\n-------------"; time $PHP_BIN archive.php; sleep 1; + $PHP_BIN tickers.php & + rm -rf $PIDFILE +fi diff --git a/cronjobs/shares.php b/cronjobs/shares.php new file mode 100644 index 00000000..cf89ce59 --- /dev/null +++ b/cronjobs/shares.php @@ -0,0 +1,56 @@ +valid, stale_share_count = $pastSharesR->invalid WHERE id = $pastSharesR->userId"); + } +} catch (Exception $ex) {} + +///// Update current round shares + +// reset counters +mysql_query("UPDATE webUsers SET shares_this_round=0"); + +try { + $sql = "SELECT SUM( id ) AS id, a.associatedUserId ". + "FROM ( ". + "SELECT COUNT( s.id ) AS id, p.associatedUserId ". + "FROM shares s, pool_worker p ". + "WHERE p.username = s.username ". + "AND s.our_result = 'Y' ". + "GROUP BY p.associatedUserId ". + "UNION SELECT COUNT( s.id ) AS id, p.associatedUserId ". + "FROM shares_history s, pool_worker p ". + "WHERE p.username = s.username ". + "AND s.our_result = 'Y' ". + "AND s.counted = '0' ". + "GROUP BY p.associatedUserId ". + ")a ". + "GROUP BY associatedUserId"; + + $result = mysql_query($sql); + $totalsharesthisround = 0; + while ($row = mysql_fetch_object($result)) { + mysql_query("UPDATE webUsers SET shares_this_round = $row->id WHERE id = $row->associatedUserId"); + $totalsharesthisround += $row->id; + } + + $currentSharesQ = mysql_query("SELECT DISTINCT userId, sum(count) AS valid, sum(invalid) AS invalid, id FROM shares_uncounted GROUP BY userId"); + while ($currentSharesR = mysql_fetch_object($currentSharesQ)) { + mysql_query("UPDATE webUsers SET shares_this_round = (shares_this_round + $currentSharesR->valid) ". + "WHERE id = $currentSharesR->userId"); + $totalsharesthisround += $currentSharesR->valid; + } + + mysql_query("UPDATE settings SET value = '$totalsharesthisround' WHERE setting='currentroundshares'"); +} catch (Exception $ex) {} + +?> diff --git a/cronjobs/tickers.php b/cronjobs/tickers.php new file mode 100644 index 00000000..abc8f63d --- /dev/null +++ b/cronjobs/tickers.php @@ -0,0 +1,33 @@ +. +// + +//Set page starter variables// +$includeDirectory = "/sites/mmc/www/includes/"; + +//Include site functions +include($includeDirectory."requiredFunctions.php"); + + + // Update MtGox last price via curl, 3 second timeout on connection + $mtgox_ticker = exec("/usr/bin/curl -q -s --connect-timeout 3 'https://mtgox.com/code/data/ticker.php'"); + if (!is_null($mtgox_ticker)) { + $ticker_obj = json_decode($mtgox_ticker); + if (intval($ticker_obj->ticker->last) > 0) { + $settings->setsetting('mtgoxlast', round($ticker_obj->ticker->last, 4)); + } + } + +?> diff --git a/cronjobs/workers.php b/cronjobs/workers.php new file mode 100644 index 00000000..5ff264e6 --- /dev/null +++ b/cronjobs/workers.php @@ -0,0 +1,56 @@ +query("getdifficulty"); + //$difficulty = '1'; + +//Get site percentage fee +$sitePercent = 0; +$sitePercentQ = mysql_query("SELECT value FROM settings WHERE setting='sitepercent'"); +if ($sitePercentR = mysql_fetch_object($sitePercentQ)) $sitePercent = $sitePercentR->value; + +// set up some scoring variables +$c = .00000001; +$f = $sitePercent / 100; +$p = 1.0/$difficulty; +$r = log(1.0-$p+$p/$c); +$B = 50; +$los = log(1/(exp($r)-1)); + +// Check for if worker is active (submitted shares in the last 10 mins) +$currentWorkers = 0; +try { + $sql ="SELECT sum(a.id) IS NOT NULL AS active, p.username FROM pool_worker p LEFT JOIN ". + "(SELECT count(id) AS id, username FROM shares WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE) group by username ". + "UNION ". + "SELECT count(id) AS id, username FROM shares_history WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE) group by username) a ON p.username=a.username group by username"; + $result = mysql_query($sql); + while ($resultObj = mysql_fetch_object($result)) { + if ($resultObj->active == 1) + $currentWorkers += 1; + mysql_query("UPDATE pool_worker p SET active=".$resultObj->active." WHERE username='".$resultObj->username."'"); + } + + // Update number of workers in our pool status + $settings->setsetting('currentworkers', $currentWorkers); + +} catch (Exception $e) {} + + + // Calculate estimated round earnings for each user + + //Proportional estimate + $totalRoundShares = $settings->getsetting("currentroundshares"); + + //if ($totalRoundShares < $difficulty) $totalRoundShares = $difficulty; + mysql_query("UPDATE webUsers SET round_estimate = round((1-".$f.")*50*(shares_this_round/".$totalRoundShares.")*(1-(donate_percent/100)), 8)"); + + // comment the one line below out if you want to disable 0% fees for first 35 users + mysql_query("UPDATE webUsers SET round_estimate = round(0.9999*50*(shares_this_round/".$totalRoundShares.")*(1-(donate_percent/100)), 8) WHERE account_type = '9'"); + diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 00000000..ae652ed2 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,10 @@ +RedirectMatch 404 /templates(/|$) +RedirectMatch 404 /include(/|$) +RedirectMatch 404 /.git(/|$) + +php_value error_reporting 30711 +php_flag display_startup_errors on +php_flag display_errors on +php_flag html_errors on +php_value docref_root 1 +php_value docref_ext 1 diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/public/include/classes/bitcoin.class.php b/public/include/classes/bitcoin.class.php new file mode 100644 index 00000000..c73bf04e --- /dev/null +++ b/public/include/classes/bitcoin.class.php @@ -0,0 +1,903 @@ + hexdec($addressversion)) { + return false; + } + $check = substr($addr, 0, strlen($addr) - 8); + $check = pack("H*", $check); + $check = strtoupper(hash("sha256", hash("sha256", $check, true))); + $check = substr($check, 0, 8); + return $check == substr($addr, strlen($addr) - 8); + } + + /** + * Convert the input to its 160-bit Bitcoin hash + * + * @param string $data + * @return string + * @access private + */ + private function hash160($data) { + $data = pack("H*", $data); + return strtoupper(hash("ripemd160", hash("sha256", $data, true))); + } + + /** + * Convert a Bitcoin public key to a 160-bit Bitcoin hash + * + * @param string $pubkey + * @return string + * @access public + */ + public static function pubKeyToAddress($pubkey) { + return self::hash160ToAddress($this->hash160($pubkey)); + } + + /** + * Remove leading "0x" from a hex value if present. + * + * @param string $string + * @return string + * @access public + */ + public static function remove0x($string) { + if (substr($string, 0, 2) == "0x" || substr($string, 0, 2) == "0X") { + $string = substr($string, 2); + } + return $string; + } +} + +/** + * Exception class for BitcoinClient + * + * @author Mike Gogulski + * http://www.gogulski.com/ http://www.nostate.com/ + */ +class BitcoinClientException extends ErrorException { + // Redefine the exception so message isn't optional + public function __construct($message, $code = 0, $severity = E_USER_NOTICE, Exception $previous = null) { + parent::__construct($message, $code, $severity, $previous); + } + + public function __toString() { + return __CLASS__ . ": [{$this->code}]: {$this->message}\n"; + } +} + +require_once(INCLUDE_DIR . "/xmlrpc.inc.php"); +require_once(INCLUDE_DIR . "/jsonrpc.inc.php"); + +/** + * Bitcoin client class for access to a Bitcoin server via JSON-RPC-HTTP[S] + * + * Implements the methods documented at https://www.bitcoin.org/wiki/doku.php?id=api + * + * @version 0.3.19 + * @author Mike Gogulski + * http://www.gogulski.com/ http://www.nostate.com/ + */ +class BitcoinClient extends jsonrpc_client { + + /** + * Create a jsonrpc_client object to talk to the bitcoin server and return it, + * or false on failure. + * + * @param string $scheme + * "http" or "https" + * @param string $username + * User name to use in connection the Bitcoin server's JSON-RPC interface + * @param string $password + * Server password + * @param string $address + * Server hostname or IP address + * @param mixed $port + * Server port (string or integer) + * @param string $certificate_path + * Path on the local filesystem to server's PEM certificate (ignored if $scheme != "https") + * @param integer $debug_level + * 0 (default) = no debugging; + * 1 = echo JSON-RPC messages received to stdout; + * 2 = log transmitted messages also + * @return jsonrpc_client + * @access public + * @throws BitcoinClientException + */ + public function __construct($scheme, $username, $password, $address = "localhost", $port = 8332, $certificate_path = '', $debug_level = 0) { + $scheme = strtolower($scheme); + if ($scheme != "http" && $scheme != "https") + throw new BitcoinClientException("Scheme must be http or https"); + if (empty($username)) + throw new BitcoinClientException("Username must be non-blank"); + if (empty($password)) + throw new BitcoinClientException("Password must be non-blank"); + $port = (string) $port; + if (!$port || empty($port) || !is_numeric($port) || $port < 1 || $port > 65535 || floatval($port) != intval($port)) + throw new BitcoinClientException("Port must be an integer and between 1 and 65535"); + if (!empty($certificate_path) && !is_readable($certificate_path)) + throw new BitcoinClientException("Certificate file " . $certificate_path . " is not readable"); + $uri = $scheme . "://" . $username . ":" . $password . "@" . $address . ":" . $port . "/"; + parent::__construct($uri); + $this->setDebug($debug_level); + $this->setSSLVerifyHost(0); + if ($scheme == "https") + if (!empty($certificate_path)) + $this->setCaCertificate($certificate_path); + else + $this->setSSLVerifyPeer(false); + } + + /** + * Test if the connection to the Bitcoin JSON-RPC server is working + * + * The check is done by calling the server's getinfo() method and checking + * for a fault. + * + * @return mixed boolean TRUE if successful, or a fault string otherwise + * @access public + * @throws none + */ + public function can_connect() { + try { + $r = $this->getinfo(); + } catch (BitcoinClientException $e) { + return $e->getMessage(); + } + return true; + } + + /** + * Convert a Bitcoin server query argument to a jsonrpcval + * + * @param mixed $argument + * @return jsonrpcval + * @throws none + * @todo Make this method private. + */ + public function query_arg_to_parameter($argument) { + $type = "";// "string" is encoded as this default type value in xmlrpc.inc + if (is_numeric($argument)) { + if (intval($argument) != floatval($argument)) { + $argument = floatval($argument); + $type = "double"; + } else { + $argument = intval($argument); + $type = "int"; + } + } + if (is_bool($argument)) + $type = "boolean"; + if (is_int($argument)) + $type = "int"; + if (is_float($argument)) + $type = "double"; + if (is_array($argument)) + $type = "array"; + return new jsonrpcval($argument, $type); + } + + /** + * Send a JSON-RPC message and optional parameter arguments to the server. + * + * Use the API functions if possible. This method remains public to support + * changes being made to the API before this libarary can be updated. + * + * @param string $message + * @param mixed $args, ... + * @return mixed + * @throws BitcoinClientException + * @see xmlrpc.inc:php_xmlrpc_decode() + */ + public function query($message) { + if (!$message || empty($message)) + throw new BitcoinClientException("Bitcoin client query requires a message"); + $msg = new jsonrpcmsg($message); + if (func_num_args() > 1) { + for ($i = 1; $i < func_num_args(); $i++) { + $msg->addParam(self::query_arg_to_parameter(func_get_arg($i))); + } + } + $response = $this->send($msg); + if ($response->faultCode()) { + throw new BitcoinClientException($response->faultString()); + } + return php_xmlrpc_decode($response->value()); + } + + /* + * The following functions implement the Bitcoin RPC API as documented at https://www.bitcoin.org/wiki/doku.php?id=api + */ + + /** + * Safely copies wallet.dat to destination, which can be a directory or + * a path with filename. + * + * @param string $destination + * @return mixed Nothing, or an error array + * @throws BitcoinClientException + */ + public function backupwallet($destination) { + if (!$destination || empty($destination)) + throw new BitcoinClientException("backupwallet requires a destination"); + return $this->query("backupwallet", $destination); + } + + /** + * Returns the server's available balance, or the balance for $account with + * at least $minconf confirmations. + * + * @param string $account Account to check. If not provided, the server's + * total available balance is returned. + * @param integer $minconf If specified, only transactions with at least + * $minconf confirmations will be included in the returned total. + * @return float Bitcoin balance + * @throws BitcoinClientException + */ + public function getbalance($account = NULL, $minconf = 1) { + if (!is_numeric($minconf) || $minconf < 0) + throw new BitcoinClientException('getbalance requires a numeric minconf >= 0'); + if ($account == NULL) + return $this->query("getbalance"); + return $this->query("getbalance", $account, $minconf); + } + + /** + * Returns the number of blocks in the longest block chain. + * + * @return integer Current block count + * @throws BitcoinClientException + */ + public function getblockcount() { + return $this->query("getblockcount"); + } + + /** + * Returns the block number of the latest block in the longest block chain. + * + * @return integer Block number + * @throws BitcoinClientException + */ + public function getblocknumber() { + return $this->query("getblocknumber"); + } + + /** + * Returns the number of connections to other nodes. + * + * @return integer Connection count + * @throws BitcoinClientException + */ + public function getconnectioncount() { + return $this->query("getconnectioncount"); + } + + /** + * Returns the proof-of-work difficulty as a multiple of the minimum difficulty. + * + * @return float Difficulty + * @throws BitcoinClientException + */ + public function getdifficulty() { + return $this->query("getdifficulty"); + } + + /** + * Returns boolean true if server is trying to generate bitcoins, false otherwise. + * + * @return boolean Generation status + * @throws BitcoinClientException + */ + public function getgenerate() { + return $this->query("getgenerate"); + } + + /** + * Tell Bitcoin server to generate Bitcoins or not, and how many processors + * to use. + * + * @param boolean $generate + * @param integer $maxproc + * Limit generation to $maxproc processors, unlimited if -1 + * @return mixed Nothing if successful, error array if not + * @throws BitcoinClientException + */ + public function setgenerate($generate = TRUE, $maxproc = -1) { + if (!is_numeric($maxproc) || $maxproc < -1) + throw new BitcoinClientException('setgenerate: $maxproc must be numeric and >= -1'); + return $this->query("setgenerate", $generate, $maxproc); + } + + /** + * Returns an array containing server information. + * + * @return array Server information + * @throws BitcoinClientException + */ + public function getinfo() { + return $this->query("getinfo"); + } + + /** + * Returns the account associated with the given address. + * + * @param string $address + * @return string Account + * @throws BitcoinClientException + * @since 0.3.17 + */ + public function getaccount($address) { + if (!$address || empty($address)) + throw new BitcoinClientException("getaccount requires an address"); + return $this->query("getaccount", $address); + } + + /** + * Returns the label associated with the given address. + * + * @param string $address + * @return string Label + * @throws BitcoinClientException + * @deprecated Since 0.3.17 + */ + public function getlabel($address) { + if (!$address || empty($address)) + throw new BitcoinClientException("getlabel requires an address"); + return $this->query("getlabel", $address); + } + + /** + * Sets the account associated with the given address. + * $account may be omitted to remove an account from an address. + * + * @param string $address + * @param string $account + * @return NULL + * @throws BitcoinClientException + * @since 0.3.17 + */ + public function setaccount($address, $account = "") { + if (!$address || empty($address)) + throw new BitcoinClientException("setaccount requires an address"); + return $this->query("setaccount", $address, $account); + } + + /** + * Sets the label associated with the given address. + * $label may be omitted to remove a label from an address. + * + * @param string $address + * @param string $label + * @return NULL + * @throws BitcoinClientException + * @deprecated Since 0.3.17 + */ + public function setlabel($address, $label = "") { + if (!$address || empty($address)) + throw new BitcoinClientException("setlabel requires an address"); + return $this->query("setlabel", $address, $label); + } + + /** + * Returns a new bitcoin address for receiving payments. + * + * If $account is specified (recommended), it is added to the address book so + * payments received with the address will be credited to $account. + * + * @param string $account Label to apply to the new address + * @return string Bitcoin address + * @throws BitcoinClientException + */ + public function getnewaddress($account = NULL) { + if (!$account || empty($account)) + return $this->query("getnewaddress"); + return $this->query("getnewaddress", $account); + } + + /** + * Returns the total amount received by $address in transactions with at least + * $minconf confirmations. + * + * @param string $address + * Bitcoin address + * @param integer $minconf + * Minimum number of confirmations for transactions to be counted + * @return float Bitcoin total + * @throws BitcoinClientException + */ + public function getreceivedbyaddress($address, $minconf = 1) { + if (!is_numeric($minconf) || $minconf < 0) + throw new BitcoinClientException('getreceivedbyaddress requires a numeric minconf >= 0'); + if (!$address || empty($address)) + throw new BitcoinClientException("getreceivedbyaddress requires an address"); + return $this->query("getreceivedbyaddress", $address, $minconf); + } + + /** + * Returns the total amount received by addresses associated with $account + * in transactions with at least $minconf confirmations. + * + * @param string $account + * @param integer $minconf + * Minimum number of confirmations for transactions to be counted + * @return float Bitcoin total + * @throws BitcoinClientException + * @since 0.3.17 + */ + public function getreceivedbyaccount($account, $minconf = 1) { + if (!is_numeric($minconf) || $minconf < 0) + throw new BitcoinClientException('getreceivedbyaccount requires a numeric minconf >= 0'); + if (!$account || empty($account)) + throw new BitcoinClientException("getreceivedbyaccount requires an account"); + return $this->query("getreceivedbyaccount", $account, $minconf); + } + + /** + * Returns the total amount received by addresses with $label in + * transactions with at least $minconf confirmations. + * + * @param string $label + * @param integer $minconf + * Minimum number of confirmations for transactions to be counted + * @return float Bitcoin total + * @throws BitcoinClientException + * @deprecated Since 0.3.17 + */ + public function getreceivedbylabel($label, $minconf = 1) { + if (!is_numeric($minconf) || $minconf < 0) + throw new BitcoinClientException('getreceivedbylabel requires a numeric minconf >= 0'); + if (!$label || empty($label)) + throw new BitcoinClientException("getreceivedbylabel requires a label"); + return $this->query("getreceivedbylabel", $label, $minconf); + } + + /** + * Return a list of server RPC commands or help for $command, if specified. + * + * @param string $command + * @return string Help text + * @throws BitcoinClientException + */ + public function help($command = NULL) { + if (!$command || empty($command)) + return $this->query("help"); + return $this->query("help", $command); + } + + /** + * Return an array of arrays showing how many Bitcoins have been received by + * each address in the server's wallet. + * + * @param integer $minconf Minimum number of confirmations before payments are included. + * @param boolean $includeempty Whether to include addresses that haven't received any payments. + * @return array An array of arrays. The elements are: + * "address" => receiving address + * "account" => the account of the receiving address + * "amount" => total amount received by the address + * "confirmations" => number of confirmations of the most recent transaction included + * @throws BitcoinClientException + */ + public function listreceivedbyaddress($minconf = 1, $includeempty = FALSE) { + if (!is_numeric($minconf) || $minconf < 0) + throw new BitcoinClientException('listreceivedbyaddress requires a numeric minconf >= 0'); + return $this->query("listreceivedbyaddress", $minconf, $includeempty); + } + + /** + * Return an array of arrays showing how many Bitcoins have been received by + * each account in the server's wallet. + * + * @param integer $minconf + * Minimum number of confirmations before payments are included. + * @param boolean $includeempty + * Whether to include addresses that haven't received any payments. + * @return array An array of arrays. The elements are: + * "account" => the label of the receiving address + * "amount" => total amount received by the address + * "confirmations" => number of confirmations of the most recent transaction included + * @throws BitcoinClientException + * @since 0.3.17 + */ + public function listreceivedbyaccount($minconf = 1, $includeempty = FALSE) { + if (!is_numeric($minconf) || $minconf < 0) + throw new BitcoinClientException('listreceivedbyaccount requires a numeric minconf >= 0'); + return $this->query("listreceivedbyaccount", $minconf, $includeempty); + } + + /** + * Return an array of arrays showing how many Bitcoins have been received by + * each label in the server's wallet. + * + * @param integer $minconf Minimum number of confirmations before payments are included. + * @param boolean $includeempty Whether to include addresses that haven't received any payments. + * @return array An array of arrays. The elements are: + * "label" => the label of the receiving address + * "amount" => total amount received by the address + * "confirmations" => number of confirmations of the most recent transaction included + * @throws BitcoinClientException + * @deprecated Since 0.3.17 + */ + public function listreceivedbylabel($minconf = 1, $includeempty = FALSE) { + if (!is_numeric($minconf) || $minconf < 0) + throw new BitcoinClientException('listreceivedbylabel requires a numeric minconf >= 0'); + return $this->query("listreceivedbylabel", $minconf, $includeempty); + } + + /** + * Send amount from the server's available balance. + * + * $amount is a real and is rounded to the nearest 0.01. Returns string "sent" on success. + * + * @param string $address Destination Bitcoin address or IP address + * @param float $amount Amount to send. Will be rounded to the nearest 0.01. + * @param string $comment + * @param string $comment_to + * @return string Hexadecimal transaction ID on success. + * @throws BitcoinClientException + * @todo Document the comment arguments better. + */ + public function sendtoaddress($address, $amount, $comment = NULL, $comment_to = NULL) { + if (!$address || empty($address)) + throw new BitcoinClientException("sendtoaddress requires a destination address"); + if (!$amount || empty($amount)) + throw new BitcoinClientException("sendtoaddress requires an amount to send"); + if (!is_numeric($amount) || $amount <= 0) + throw new BitcoinClientException("sendtoaddress requires the amount sent to be a number > 0"); + $amount = floatval($amount); + if (!$comment && !$comment_to) + return $this->query("sendtoaddress", $address, $amount); + if (!$comment_to) + return $this->query("sendtoaddress", $address, $amount, $comment); + return $this->query("sendtoaddress", $address, $amount, $comment, $comment_to); + } + + /** + * Stop the Bitcoin server. + * + * @throws BitcoinClientException + */ + public function stop() { + return $this->query("stop"); + } + + /** + * Check that $address looks like a proper Bitcoin address. + * + * @param string $address String to test for validity as a Bitcoin address + * @return array An array containing: + * "isvalid" => true or false + * "ismine" => true if the address is in the server's wallet + * "address" => bitcoinaddress + * Note: ismine and address are only returned if the address is valid. + * @throws BitcoinClientException + */ + public function validateaddress($address) { + if (!$address || empty($address)) + throw new BitcoinClientException("validateaddress requires a Bitcoin address"); + return $this->query("validateaddress", $address); + } + + /** + * Return information about a specific transaction. + * + * @param string $txid 64-digit hexadecimal transaction ID + * @return array An error array, or an array containing: + * "amount" => float Transaction amount + * "fee" => float Transaction fee + * "confirmations" => integer Network confirmations of this transaction + * "txid" => string The transaction ID + * "message" => string Transaction "comment" message + * "to" => string Transaction "to" message + * @throws BitcoinClientException + * @since 0.3.18 + */ + public function gettransaction($txid) { + if (!$txid || empty($txid) || strlen($txid) != 64 || !preg_match('/^[0-9a-fA-F]+$/', $txid)) + throw new BitcoinClientException("gettransaction requires a valid hexadecimal transaction ID"); + return $this->query("getttransaction", $txid); + } + + /** + * Move bitcoins between accounts. + * + * @param string $fromaccount + * Account to move from. If given as an empty string ("") or NULL, bitcoins will + * be moved from the wallet balance to the target account. + * @param string $toaccount + * Account to move to + * @param float $amount + * Amount to move + * @param integer $minconf + * Minimum number of confirmations on bitcoins being moved + * @param string $comment + * Transaction comment + * @throws BitcoinClientException + * @since 0.3.18 + */ + public function move($fromaccount = "", $toaccount, $amount, $minconf = 1, $comment = NULL) { + if (!$fromaccount) + $fromaccount = ""; + if (!$toaccount || empty($toaccount) || !$amount || !is_numeric($amount) || $amount <= 0) + throw new BitcoinClientException("move requires a from account, to account and numeric amount > 0"); + if (!is_numeric($minconf) || $minconf < 0) + throw new BitcoinClientException('move requires a numeric $minconf >= 0'); + if (!$comment || empty($comment)) + return $this->query("move", $fromaccount, $toaccount, $amount, $minconf); + return $this->query("move", $fromaccount, $toaccount, $amount, $minconf, $comment); + } + + /** + * Send $amount from $account's balance to $toaddress. This method will fail + * if there is less than $amount bitcoins with $minconf confirmations in the + * account's balance (unless $account is the empty-string-named default + * account; it behaves like the sendtoaddress method). Returns transaction + * ID on success. + * + * @param string $account Account to send from + * @param string $toaddress Bitcoin address to send to + * @param float $amount Amount to send + * @param integer $minconf Minimum number of confirmations on bitcoins being sent + * @param string $comment + * @param string $comment_to + * @return string Hexadecimal transaction ID + * @throws BitcoinClientException + * @since 0.3.18 + */ + public function sendfrom($account, $toaddress, $amount, $minconf = 1, $comment = NULL, $comment_to = NULL) { + if (!$account || !$toaddress || empty($toaddress) || !$amount || !is_numeric($amount) || $amount <= 0) + throw new BitcoinClientException("sendfrom requires a from account, to account and numeric amount > 0"); + if (!is_numeric($minconf) || $minconf < 0) + throw new BitcoinClientException('sendfrom requires a numeric $minconf >= 0'); + if (!$comment && !$comment_to) + return $this->query("sendfrom", $account, $toaddress, $amount, $minconf); + if (!$comment_to) + return $this->query("sendfrom", $account, $toaddress, $amount, $minconf, $comment); + $this->query("sendfrom", $account, $toaddress, $amount, $minconf, $comment, $comment_to); + } + + /** + * Return formatted hash data to work on, or try to solve specified block. + * + * If $data is provided, tries to solve the block and returns true if successful. + * If $data is not provided, returns formatted hash data to work on. + * + * @param string $data Block data + * @return mixed + * boolean TRUE if $data provided and block solving successful + * array otherwise, containing: + * "midstate" => string, precomputed hash state after hashing the first half of the data + * "data" => string, block data + * "hash1" => string, formatted hash buffer for second hash + * "target" => string, little endian hash target + * @throws BitcoinClientException + * @since 0.3.18 + */ + public function getwork($data = NULL) { + if (!$data) + return $this->query("getwork"); + return $this->query("getwork", $data); + } + + /** + * Return the current bitcoin address for receiving payments to $account. + * The account and address will be created if $account doesn't exist. + * + * @param string $account Account name + * @return string Bitcoin address for $account + * @throws BitcoinClientException + * @since 0.3.18 + */ + public function getaccountaddress($account) { + if (!$account || empty($account)) + throw new BitcoinClientException("getaccountaddress requires an account"); + return $this->query("getaccountaddress", $account); + } + + /** + * Return a recent hashes per second performance measurement. + * + * @return integer Hashes per second + * @throws BitcoinClientException + */ + public function gethashespersec() { + return $this->query("gethashespersec"); + } + + /** + * Returns the list of addresses associated with the given account. + * + * @param string $account + * @return array + * A simple array of Bitcoin addresses associated with $account, empty + * if the account doesn't exist. + * @throws BitcoinClientException + */ + public function getaddressesbyaccount($account) { + if (!$account || empty($account)) + throw new BitcoinClientException("getaddressesbyaccount requires an account"); + return $this->query("getaddressesbyaccount", $account); + } +} + +// Auto-load this class +$bitcoin = new BitcoinClient($config['wallet']['type'], $config['wallet']['username'], $config['wallet']['password'], $config['wallet']['host']); diff --git a/public/include/classes/debug.class.php b/public/include/classes/debug.class.php new file mode 100644 index 00000000..fa7b13b8 --- /dev/null +++ b/public/include/classes/debug.class.php @@ -0,0 +1,115 @@ +=1) or disable (0) debugging + * @return none + */ + function __construct($DEBUG=0) { + $this->DEBUG = $DEBUG; + if ($DEBUG >= 1) { + $this->floatStartTime = microtime(true); + $this->append("Debugging enabled", 1); + } + } + + /** + * If we want to set our debugging level in a child class this allows us to do so + * @param integer $DEBUG our debugging level + */ + function setDebug($DEBUG) { + $this->DEBUG = $DEBUG; + } + + /** + * Return a backtrace strin + * @return string Full backtrace : but no refereces to debug class + */ + public function getBacktrace() { + $bth = debug_backtrace(); + while (strpos($bth[0]['file'], "classes/debug") != false) array_shift($bth); + foreach ($bth as $x) { + $backtrace[] = array( + 'file' => substr($x['file'], strrpos($x['file'], '/') + 1), + 'line' => $x['line'], + 'function' => $x['function'], + ); + } + return $backtrace; + } + + /** + * We fill our data array here + * @param string $msg Debug Message + * @param string $file [optional] File name + * @param integer $line [optional] Line inside the $file + * @param integer $debug [optional] Debugging level, default 1 + * @param string $class [optional] Class this is called from + * @param string $method [optional] Method this is called from + * @return none + */ + function append($msg, $debug=1) { + if ($this->DEBUG >= $debug) { + $this->arrDebugInfo[] = array( + 'level' => $debug, + 'time' => round((microtime(true) - $this->floatStartTime) * 1000, 2), + 'backtrace' => $this->getBacktrace(), + 'message' => $msg, + ); + } + } + + /** + * Return the created strDebugInfo array + * @return none + */ + public function getDebugInfo() { + return $this->arrDebugInfo; + } + + /** + * Directly print the debugging information, table formatted + * @return none + */ + function printDebugInfo() { + echo ""; + foreach ($this->getDebugInfo() as $content) { + echo ""; + } + echo "
TimestampFileLineClassMethodMessage
" . $content['time'] . "" . $content['backtrace'] . "" . $content['message'] . "
"; + } + +} + +// Instantiate this class +$debug = new Debug(DEBUG); +?> diff --git a/public/include/classes/settings.class.php b/public/include/classes/settings.class.php new file mode 100644 index 00000000..602e92bc --- /dev/null +++ b/public/include/classes/settings.class.php @@ -0,0 +1,26 @@ +debug = $debug; + $this->mysqli = $mysqli; + $this->salt = $salt; + $this->table = 'settings'; + } + + public function getValue($name) { + $query = $this->mysqli->prepare("SELECT value FROM $this->table WHERE setting=? LIMIT 1"); + $query->bind_param('s', $name); + $query->execute(); + $query->bind_result($value); + $query->fetch(); + $query->close(); + return $value; + } +} + +$settings = new Settings($debug, $mysqli, SALT); diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php new file mode 100644 index 00000000..dff33b63 --- /dev/null +++ b/public/include/classes/user.class.php @@ -0,0 +1,311 @@ +error = ''; + $this->userID = false; + $this->debug = $debug; + $this->mysqli = $mysqli; + $this->salt = $salt; + $this->table = 'webUsers'; + $this->user = array(); + $this->tableAccountBalance = 'accountBalance'; + $this->tablePoolWorker = 'pool_worker'; + $this->tableLedger = 'ledger'; + } + + public function checkLogin($username, $password) { + if ( $this->checkUserPassword($username, $password) ) { + $this->createSession($username); + return true; + } + return false; + } + + public function checkPin($userId, $pin=false) { + $stmt = $this->mysqli->prepare("SELECT pin FROM $this->table WHERE id=? AND pin=? LIMIT 1"); + $pin_hash = hash('sha256', $pin.$this->salt); + $stmt->bind_param('is', $userId, $pin_hash); + $stmt->execute(); + $stmt->bind_result($row_pin); + $stmt->fetch(); + $stmt->close(); + return $pin_hash === $row_pin; + } + + private function getSingle($userID, $search='id', $field='id', $table='') { + if ( empty($table) ) { + $table = $this->table; + } + // Hack for inconsistent field names + $stmt = $this->mysqli->prepare("SELECT $field FROM $table WHERE $search=? LIMIT 1"); + if ($this->checkStmt($stmt)) { + $stmt->bind_param('i', $userID); + $stmt->execute(); + $stmt->bind_result($value); + $stmt->fetch(); + $stmt->close(); + return $value; + } + return false; + } + private function updateSingle($userID, $field, $table) { + $stmt = $this->mysqli->prepare("UPDATE $table SET " . $field['name'] . " = ? WHERE userId = ? LIMIT 1"); + $stmt->bind_param($field['type'].'i', $field['value'], $userID); + $stmt->execute(); + $stmt->close(); + return true; + } + + public function addLedger($userID, $balance, $address, $fee=0.1) { + $stmt = $this->mysqli->prepare("INSERT INTO $this->tableLedger (userId, transType, amount, sendAddress, feeAmount) VALUES (?, 'Debit_MP', ?, ?, ?)"); + $stmt->bind_param('idsd', $userID, $balance, $address, $fee); + $stmt->execute(); + $stmt->close(); + return true; + } + + private function checkStmt($bState) { + if ($bState ===! true) { + $this->debug->append("Failed to prepare statement: " . $this->mysqli->error); + $this->error = 'Unable to prepare database statement'; + return false; + } + return true; + } + + public function updatePassword($userID, $current, $new1, $new2) { + if ($new1 !== $new2) { + $this->error = 'New passwords do not match'; + return false; + } + if ( strlen($new1) < 8 ) { + $this->error = 'New password is too short, please use more than 8 chars'; + return false; + } + $current = hash('sha256', $current.$this->salt); + $new = hash('sha256', $new1.$this->salt); + $stmt = $this->mysqli->prepare("UPDATE $this->table SET pass = ? WHERE ( id = ? AND pass = ? )"); + if ($this->checkStmt($stmt)) { + $stmt->bind_param('sis', $new, $userID, $current); + $stmt->execute(); + if ($stmt->errno == 0 && $stmt->affected_rows === 1) { + return true; + } + $stmt->close(); + } + $this->error = 'Unable to update password, current password wrong?'; + return false; + } + + public function updateAccount($userID, $address, $threshold, $donate) { + $bUser = false; + $bAccount = false; + $threshold = min(250, max(0, floatval($threshold))); + if ($threshold < 1) $threshold = 0.0; + $donate = min(100, max(0, floatval($donate))); + $stmt = $this->mysqli->prepare("UPDATE $this->tableAccountBalance SET sendAddress = ?, threshold = ? WHERE userId = ?"); + $stmt->bind_param('ssi', $address, $threshold, $userID); + $stmt->execute(); + if ( $stmt->errno == 0 ) { + $bAccount = true; + } + $stmt->close(); + + $stmt = $this->mysqli->prepare("UPDATE $this->table SET donate_percent = ? WHERE id = ?"); + $stmt->bind_param('di', $donate, $userID); + $stmt->execute(); + if ( $stmt->errno == 0 ) { + $bUser = true; + } + $stmt->close(); + if ($bAccount && $bUser) return true; + return false; + } + // set/get methods + public function getPaid($userID) { + return $this->getSingle($userID, 'userId', 'paid', $this->tableAccountBalance); + } + public function getBalance($userID) { + return $this->getSingle($userID, 'userId', 'balance', $this->tableAccountBalance); + } + public function getLtcAddress($userID) { + return $this->getSingle($userID, 'userId', 'sendAddress', $this->tableAccountBalance); + } + public function getUserName($userID) { + return $this->getSingle($userID, 'id', 'username', $this->table); + } + + public function setPaid($userID, $paid) { + $field = array('name' => 'paid', 'type' => 'd', 'value' => $paid); + return $this->updateSingle($userID, $field, $this->tableAccountBalance); + } + public function setBalance($userID, $balance) { + $field = array('name' => 'balance', 'type' => 'd', 'value' => $balance); + return $this->updateSingle($userID, $field, $this->tableAccountBalance); + } + + private function checkUserPassword($username, $password) { + $user = array(); + $stmt = $this->mysqli->prepare("SELECT username, id FROM $this->table WHERE username=? AND pass=? LIMIT 1"); + $stmt->bind_param('ss', $username, hash('sha256', $password.$this->salt)); + $stmt->execute(); + $stmt->bind_result($row_username, $row_id); + $stmt->fetch(); + $stmt->close(); + // Store the basic login information + $this->user = array('username' => $row_username, 'id' => $row_id); + return $username === $row_username; + } + + private function createSession($username) { + $this->debug->append("Log in user to _SESSION", 2); + session_regenerate_id(true); + $_SESSION['AUTHENTICATED'] = '1'; + // $this->user from checkUserPassword + $_SESSION['USERDATA'] = $this->user; + } + + public function logoutUser() { + session_destroy(); + session_regenerate_id(true); + return true; + } + + public function getUserData($userID) { + $this->debug->append("Fetching user information for user id: $userID"); + $stmt = $this->mysqli->prepare(" + SELECT + u.id, u.username, u.pin, u.pass, u.admin, u.share_count, u.stale_share_count, u.shares_this_round, u.hashrate, u.api_key, + IFNULL(u.donate_percent, '0') as donate_percent, IFNULL(u.round_estimate, '0') as round_estimate, a.sendAddress, a.threshold, + a.balance + FROM $this->table as u LEFT JOIN $this->tableAccountBalance as a + ON u.id = a.userId + WHERE u.id=? LIMIT 0,1"); + if ($this->checkStmt($stmt)) { + $stmt->bind_param('i', $userID); + $stmt->execute(); + $result = $stmt->get_result(); + $stmt->close(); + return $result->fetch_array(); + } else { + echo $this->mysqli->error; + echo "FAIL"; + } + } + + // Get 15 most recent transactions + public function getTransactions($userID, $start=0) { + $stmt = $this->mysqli->prepare("SELECT * FROM $this->tableLedger where userId = ? ORDER BY timestamp DESC LIMIT ?,15"); + if ($this->checkStmt($stmt)) { + if(!$stmt->bind_param('ii', $userID, $start)) return false; + $stmt->execute(); + $result = $stmt->get_result(); + return $result->fetch_all(MYSQLI_ASSOC); + } + return false; + } + + // Worker code, could possibly be moved to it's own class someday + public function updateWorkers($userID, $data) { + $username = $this->getUserName($userID); + foreach ($data as $key => $value) { + // Prefix the WebUser to Worker name + $value['username'] = "$username." . $value['username']; + $stmt = $this->mysqli->prepare("UPDATE $this->tablePoolWorker SET password = ?, username = ? WHERE associatedUserId = ? AND id = ?"); + if ($this->checkStmt($stmt)) { + if (!$stmt->bind_param('ssii', $value['password'], $value['username'], $userID, $key)) return false; + if (!$stmt->execute()) return false; + $stmt->close(); + } + } + return true; + } + public function getWorkers($userID) { + $stmt = $this->mysqli->prepare("SELECT id, username, password, active, hashrate FROM $this->tablePoolWorker WHERE associatedUserId = ? ORDER BY username ASC"); + if ($this->checkStmt($stmt)) { + if (!$stmt->bind_param('i', $userID)) return false; + if (!$stmt->execute()) return false; + $result = $stmt->get_result(); + $stmt->close(); + return $result->fetch_all(MYSQLI_ASSOC); + } + return false; + } + public function addWorker($userID, $workerName, $workerPassword) { + $username = $this->getUserName($userID); + $workerName = "$username.$workerName"; + $stmt = $this->mysqli->prepare("INSERT INTO pool_worker (associatedUserId, username, password) VALUES(?, ?, ?)"); + if ($this->checkStmt($stmt)) { + $stmt->bind_param('iss', $userID, $workerName, $workerPassword); + if (!$stmt->execute()) { + $this->error = 'Failed to add worker'; + if ($stmt->sqlstate == '23000') $this->error = 'Worker already exists'; + return false; + } + return true; + } + return false; + } + public function deleteWorker($userID, $workerID) { + $stmt = $this->mysqli->prepare("DELETE FROM $this->tablePoolWorker WHERE associatedUserId = ? AND id = ?"); + if ($this->checkStmt($stmt)) { + $stmt->bind_param('ii', $userID, $workerID); + if ($stmt->execute() && $stmt->affected_rows == 1) { + $stmt->close; + return true; + } else { + $this->error = 'Unable to delete worker'; + } + } + return false; + } + + public function register($username, $password1, $password2, $pin, $email1='', $email2='') { + if (strlen($password1) < 8) { + $this->error = 'Password is too short, minimum of 8 characters required'; + return false; + } + if ($password1 !== $password2) { + $this->error = 'Password do not match'; + return false; + } + if (!empty($email1) && !filter_var($email1, FILTER_VALIDATE_EMAIL)) { + $this->error = 'Invalid e-mail address'; + return false; + } + if ($email1 !== $email2) { + $this->error = 'E-mail do not match'; + return false; + } + if (!is_numeric($pin) || strlen($pin) > 4) { + $this->error = 'Invalid PIN'; + return false; + } + $apikey = hash("sha256",$username.$salt); + $stmt = $this->mysqli->prepare(" + INSERT INTO $this->table ( + admin, username, pass, email, + loggedIp, sessionTimeoutStamp, accountLocked, accountFailedAttempts, + pin, share_count, stale_share_count, shares_this_round, api_key) + VALUES ( + 0, ?, ?, ?, + '0', '0', '0', '0', + ?, '0', '0', '0', ?) + "); + $stmt->bind_param('sssis', $username, hash("sha256", $password1.$this->salt), $email1, $pin, $apikey); + if (!$stmt->execute()) { + $this->error = 'Unable to register'; + if ($stmt->sqlstate == '23000') $this->error = 'Username already exists'; + return false; + } + $stmt->close(); + return true; + } +} + +$user = new User($debug, $mysqli, SALT); diff --git a/public/include/config/global.inc.dist.php b/public/include/config/global.inc.dist.php new file mode 100644 index 00000000..2fa14457 --- /dev/null +++ b/public/include/config/global.inc.dist.php @@ -0,0 +1,47 @@ + array( + 'type' => 'http', // http or https are supported + 'host' => 'localhost:9332', + 'username' => 'litecoinrpc', + 'password' => 'somepass' + ), + 'cashout' => array( + 'min_balance' => 0.0 // Minimal balance to cash out + ), + 'cookie' => array( + 'path' => '/', + 'name' => 'POOLERCOOKIE', + 'domain' => '' + ), + 'cache' => 0, // 1 to enable smarty cache in templates/cache + 'db' => array( + 'host' => 'localhost', + 'user' => 'someuser', + 'pass' => 'somepass', + 'port' => '3306', + 'name' => 'litecoin', + ), +); +?> diff --git a/public/include/database.inc.php b/public/include/database.inc.php new file mode 100644 index 00000000..a1d70530 --- /dev/null +++ b/public/include/database.inc.php @@ -0,0 +1,26 @@ +append("Failed to connect to database as non fatal error", 1); +} + +/* Example for a query + $query = $mysqli->prepare("SELECT CountryCode, Percentage FROM Language WHERE Language=?"); + $lang = "English"; + $query->bind_param("s", $lang); + $query->execute(); + $query->bind_result($countrycode, $percentage); + while ($query->fetch()) { + printf("%s lang is in CountryCode %s with Percentage %s\n", $lang, $countrycode, $percentage); + } + $query->close(); + */ +?> diff --git a/public/include/jsonrpc.inc.php b/public/include/jsonrpc.inc.php new file mode 100644 index 00000000..7be254da --- /dev/null +++ b/public/include/jsonrpc.inc.php @@ -0,0 +1,1583 @@ + chr(8), + 'f' => chr(12), + 'n' => chr(10), + 'r' => chr(13), + 't' => chr(9), + 'v' => chr(11) + ); + + // tables used for transcoding different charsets into us-ascii javascript + + $GLOBALS['ecma262_iso88591_Entities']=array(); + $GLOBALS['ecma262_iso88591_Entities']['in'] = array(); + $GLOBALS['ecma262_iso88591_Entities']['out'] = array(); + for ($i = 0; $i < 32; $i++) + { + $GLOBALS['ecma262_iso88591_Entities']['in'][] = chr($i); + $GLOBALS['ecma262_iso88591_Entities']['out'][] = sprintf('\u%\'04x', $i); + } + for ($i = 160; $i < 256; $i++) + { + $GLOBALS['ecma262_iso88591_Entities']['in'][] = chr($i); + $GLOBALS['ecma262_iso88591_Entities']['out'][] = sprintf('\u%\'04x', $i); + } + + /** + * Encode php strings to valid JSON unicode representation. + * All chars outside ASCII range are converted to \uXXXX for maximum portability. + * @param string $data (in iso-8859-1 charset by default) + * @param string charset of source string, defaults to $GLOBALS['xmlrpc_internalencoding'] + * @param string charset of the encoded string, defaults to ASCII for maximum interoperabilty + * @return string + * @access private + * @todo add support for UTF-16 as destination charset instead of ASCII + * @todo add support for UTF-16 as source charset + */ + function json_encode_entities($data, $src_encoding='', $dest_encoding='') + { + if ($src_encoding == '') + { + // lame, but we know no better... + $src_encoding = $GLOBALS['xmlrpc_internalencoding']; + } + + switch(strtoupper($src_encoding.'_'.$dest_encoding)) + { + case 'ISO-8859-1_': + case 'ISO-8859-1_US-ASCII': + $escaped_data = str_replace(array('\\', '"', '/', "\t", "\n", "\r", chr(8), chr(11), chr(12)), array('\\\\', '\"', '\/', '\t', '\n', '\r', '\b', '\v', '\f'), $data); + $escaped_data = str_replace($GLOBALS['ecma262_iso88591_Entities']['in'], $GLOBALS['ecma262_iso88591_Entities']['out'], $escaped_data); + break; + case 'ISO-8859-1_UTF-8': + $escaped_data = str_replace(array('\\', '"', '/', "\t", "\n", "\r", chr(8), chr(11), chr(12)), array('\\\\', '\"', '\/', '\t', '\n', '\r', '\b', '\v', '\f'), $data); + $escaped_data = utf8_encode($escaped_data); + break; + case 'ISO-8859-1_ISO-8859-1': + case 'US-ASCII_US-ASCII': + case 'US-ASCII_UTF-8': + case 'US-ASCII_': + case 'US-ASCII_ISO-8859-1': + case 'UTF-8_UTF-8': + $escaped_data = str_replace(array('\\', '"', '/', "\t", "\n", "\r", chr(8), chr(11), chr(12)), array('\\\\', '\"', '\/', '\t', '\n', '\r', '\b', '\v', '\f'), $data); + break; + case 'UTF-8_': + case 'UTF-8_US-ASCII': + case 'UTF-8_ISO-8859-1': + // NB: this will choke on invalid UTF-8, going most likely beyond EOF + $escaped_data = ""; + // be kind to users creating string jsonrpcvals out of different php types + $data = (string) $data; + $ns = strlen ($data); + for ($nn = 0; $nn < $ns; $nn++) + { + $ch = $data[$nn]; + $ii = ord($ch); + //1 7 0bbbbbbb (127) + if ($ii < 128) + { + /// @todo shall we replace this with a (supposedly) faster str_replace? + switch($ii){ + case 8: + $escaped_data .= '\b'; + break; + case 9: + $escaped_data .= '\t'; + break; + case 10: + $escaped_data .= '\n'; + break; + case 11: + $escaped_data .= '\v'; + break; + case 12: + $escaped_data .= '\f'; + break; + case 13: + $escaped_data .= '\r'; + break; + case 34: + $escaped_data .= '\"'; + break; + case 47: + $escaped_data .= '\/'; + break; + case 92: + $escaped_data .= '\\\\'; + break; + default: + $escaped_data .= $ch; + } // switch + } + //2 11 110bbbbb 10bbbbbb (2047) + else if ($ii>>5 == 6) + { + $b1 = ($ii & 31); + $ii = ord($data[$nn+1]); + $b2 = ($ii & 63); + $ii = ($b1 * 64) + $b2; + $ent = sprintf ('\u%\'04x', $ii); + $escaped_data .= $ent; + $nn += 1; + } + //3 16 1110bbbb 10bbbbbb 10bbbbbb + else if ($ii>>4 == 14) + { + $b1 = ($ii & 15); + $ii = ord($data[$nn+1]); + $b2 = ($ii & 63); + $ii = ord($data[$nn+2]); + $b3 = ($ii & 63); + $ii = ((($b1 * 64) + $b2) * 64) + $b3; + $ent = sprintf ('\u%\'04x', $ii); + $escaped_data .= $ent; + $nn += 2; + } + //4 21 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb + else if ($ii>>3 == 30) + { + $b1 = ($ii & 7); + $ii = ord($data[$nn+1]); + $b2 = ($ii & 63); + $ii = ord($data[$nn+2]); + $b3 = ($ii & 63); + $ii = ord($data[$nn+3]); + $b4 = ($ii & 63); + $ii = ((((($b1 * 64) + $b2) * 64) + $b3) * 64) + $b4; + $ent = sprintf ('\u%\'04x', $ii); + $escaped_data .= $ent; + $nn += 3; + } + } + break; + default: + $escaped_data = ''; + error_log("Converting from $src_encoding to $dest_encoding: not supported..."); + } // switch + return $escaped_data; + + /* + $length = strlen($data); + $escapeddata = ""; + for($position = 0; $position < $length; $position++) + { + $character = substr($data, $position, 1); + $code = ord($character); + switch($code) + { + case 8: + $character = '\b'; + break; + case 9: + $character = '\t'; + break; + case 10: + $character = '\n'; + break; + case 12: + $character = '\f'; + break; + case 13: + $character = '\r'; + break; + case 34: + $character = '\"'; + break; + case 47: + $character = '\/'; + break; + case 92: + $character = '\\\\'; + break; + default: + if($code < 32 || $code > 159) + { + $character = "\u".str_pad(dechex($code), 4, '0', STR_PAD_LEFT); + } + break; + } + $escapeddata .= $character; + } + return $escapeddata; + */ + } + + /** + * Parse a JSON string. + * NB: try to accept any valid string according to ECMA, even though the JSON + * spec is much more strict. + * Assumes input is UTF-8... + * @param string $data a json string + * @param bool $return_phpvals if true, do not rebuild jsonrpcval objects, but plain php values + * @param string $src_encoding + * @param string $dest_encoding + * @return bool + * @access private + * @todo support for other source encodings than UTF-8 + * @todo optimization creep: build elements of arrays/objects asap instead of counting chars many times + * @todo we should move to xmlrpc_defencoding and xmlrpc_internalencoding as predefined values, but it would make this even slower... + * Maybe just move those two parameters outside of here into callers? + * + * @bug parsing of "[1]// comment here" works in ie/ff, but not here + * @bug parsing of "[.1]" works in ie/ff, but not here + * @bug parsing of "[01]" works in ie/ff, but not here + * @bug parsing of "{true:1}" works here, but not in ie/ff + * @bug parsing of "{a b:1}" works here, but not in ie/ff + */ + function json_parse($data, $return_phpvals=false, $src_encoding='UTF-8', $dest_encoding='ISO-8859-1') + { + // optimization creep: this is quite costly. Is there any better way to achieve it? + // also note that json does not really allow comments... + $data = preg_replace(array( + // eliminate single line comments in '// ...' form + // REMOVED BECAUSE OF BUGS: 1-does not match at end of non-empty line, 2-eats inside strings, too + //'#^\s*//(.*)$#m', + // eliminate multi-line comments in '/* ... */' form, at start of string + '#^\s*/\*(.*)\*/#Us', + // eliminate multi-line comments in '/* ... */' form, at end of string + '#/\*(.*)\*/\s*$#Us' + ), '', $data); + + $data = trim($data); // remove excess whitespace + + if ($data == '') + { + $GLOBALS['_xh']['isf_reason'] = 'Invalid data (empty string?)'; + return false; + } + +//echo "Parsing string (".$data.")\n"; + switch($data[0]) + { + case '"': + case "'": + $len = strlen($data); + // quoted string: check for closing char first + if ($data[$len-1] == $data[0] && $len > 1) + { + // UTF8-decode (or encode) string + // NB: we MUST do this BEFORE looking for \xNN, \uMMMM or other escape sequences + if ($src_encoding == 'UTF-8' && ($dest_encoding == 'ISO-8859-1' || $dest_encoding == 'US-ASCII')) + { + $data = utf8_decode($data); + $len = strlen($data); + } + else + { + if ($dest_encoding == 'UTF-8' && ($src_encoding == 'ISO-8859-1' || $src_encoding == 'US-ASCII')) + { + $data = utf8_encode($data); + $len = strlen($data); + } + //else + //{ + // $GLOBALS['_xh']['value'] = $GLOBALS['_xh']['ac']; + //} + } + + $outdata = ''; + $delim = $data[0]; + for ($i = 1; $i < $len-1; $i++) + { + switch($data[$i]) + { + case '\\': + if ($i == $len-2) + { + break; + } + switch($data[$i+1]) + { + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + case 'v': + $outdata .= $GLOBALS['ecma262_entities'][$data[$i+1]]; + $i++; + break; + case 'u': + // most likely unicode code point + if ($dest_encoding == 'UTF-8') + { + /// @todo see if this is faster / works in all cases + //$outdata .= utf8_encode(chr(hexdec(substr($data, $i+4, 2)))); + + // encode the UTF code point into utf-8... + $ii = hexdec(substr($data, $i+2, 4)); + if ($ii < 0x80) + { + $outdata .= chr($ii); + } + else if ($ii <= 0x800) + { + $outdata .= chr(0xc0 | $ii >> 6) . chr(0x80 | ($ii & 0x3f)); + } + else if ($ii <= 0x10000) + { + $outdata .= chr(0xe0 | $ii >> 12) . chr(0x80 | ($ii >> 6 & 0x3f)) . chr(0x80 | ($ii & 0x3f)); + } + else + { + $outdata .= chr(0xf0 | $ii >> 20) . chr(0x80 | ($ii >> 12 & 0x3f)) . chr(0x80 | ($ii >> 6 & 0x3f)) . chr(0x80 | ($ii & 0x3f)); + } + $i += 5; + } + else + { + // Note: we only decode code points below 256, so we take the last 2 chars of the unicode representation + $outdata .= chr(hexdec(substr($data, $i+4, 2))); + $i += 5; + } + break; + case 'x': + // most likely unicode code point in hexadecimal + // Note: the json spec omits this case, but ECMA-262 does not... + if ($dest_encoding == 'UTF-8') + { + // encode the UTF code point into utf-8... + $ii = hexdec(substr($data, $i+2, 2)); + if ($ii < 0x80) + { + $outdata .= chr($ii); + } + else if ($ii <= 0x800) + { + $outdata .= chr(0xc0 | $ii >> 6) . chr(0x80 | ($ii & 0x3f)); + } + else if ($ii <= 0x10000) + { + $outdata .= chr(0xe0 | $ii >> 12) . chr(0x80 | ($ii >> 6 & 0x3f)) . chr(0x80 | ($ii & 0x3f)); + } + else + { + $outdata .= chr(0xf0 | $ii >> 20) . chr(0x80 | ($ii >> 12 & 0x3f)) . chr(0x80 | ($ii >> 6 & 0x3f)) . chr(0x80 | ($ii & 0x3f)); + } + $i += 3; + } + else + { + $outdata .= chr(hexdec(substr($data, $i+2, 2))); + $i += 3; + } + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + // Note: ECMA-262 forbids these escapes, we just skip it... + break; + default: + // Note: Javascript 1.5 on http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Guide + // mentions syntax /XXX with X octal number, but ECMA262 + // explicitly forbids it... + $outdata .= $data[$i+1]; + $i++; + } // end of switch on slash char found + break; + case $delim: + // found unquoted end of string in middle of string + $GLOBALS['_xh']['isf_reason'] = 'Invalid data (unescaped quote char inside string?)'; + return false; + case "\n": + case "\r": + $GLOBALS['_xh']['isf_reason'] = 'Invalid data (line terminator char inside string?)'; + return false; + default: + $outdata .= $data[$i]; + } + } // end of loop on string chars +//echo "Found a string\n"; + $GLOBALS['_xh']['vt'] = 'string'; + $GLOBALS['_xh']['value'] = $outdata; + } + else + { + // string without a terminating quote + $GLOBALS['_xh']['isf_reason'] = 'Invalid data (string missing closing quote?)'; + return false; + } + break; + case '[': + case '{': + $len = strlen($data); + // object and array notation: use the same parsing code + if ($data[0] == '[') + { + if ($data[$len-1] != ']') + { + // invalid array + $GLOBALS['_xh']['isf_reason'] = 'Invalid data (array missing closing bracket?)'; + return false; + } + $GLOBALS['_xh']['vt'] = 'array'; + } + else + { + if ($data[$len-1] != '}') + { + // invalid object + $GLOBALS['_xh']['isf_reason'] = 'Invalid data (object missing closing bracket?)'; + return false; + } + $GLOBALS['_xh']['vt'] = 'struct'; + } + + $data = trim(substr($data, 1, -1)); +//echo "Parsing array/obj (".$data.")\n"; + if ($data == '') + { + // empty array/object + $GLOBALS['_xh']['value'] = array(); + } + else + { + $valuestack = array(); + $last = array('type' => 'sl', 'start' => 0); + $len = strlen($data); + $value = array(); + $keypos = null; + //$ac = ''; + $vt = ''; + //$start = 0; + for ($i = 0; $i <= $len; $i++) + { + if ($i == $len || ($data[$i] == ',' && $last['type'] == 'sl')) + { + + // end of element: push it onto array + $slice = substr($data, $last['start'], ($i - $last['start'])); + //$slice = trim($slice); useless here, sincewe trim it on sub-elementparsing +//echo "Found slice (".$slice.")\n"; + + //$valuestack[] = $last; // necessario ??? + //$last = array('type' => 'sl', 'start' => ($i + 1)); + if ($GLOBALS['_xh']['vt'] == 'array') + { + if ($slice == '') + { + // 'elided' element: ecma supports it, so do we + // what should happen here in fact is that + // "array index is augmented and element is undefined" + + // NOTE: Firefox's js engine does not create + // trailing undefined elements, while IE does... + //if ($i < $len) + //{ + if ($return_phpvals) + { + $value[] = null; + } + else + { + $value[] = new jsonrpcval(null, 'null'); + } + //} + } + else + { + if (!json_parse($slice, $return_phpvals, $src_encoding, $dest_encoding)) + { + return false; + } + else + { + $value[] = $GLOBALS['_xh']['value']; + $GLOBALS['_xh']['vt'] = 'array'; + } + } + } + else + { + if (!$keypos) + { + $GLOBALS['_xh']['isf_reason'] = 'Invalid data (missing object member name?)'; + return false; + } + else + { + if (!json_parse(substr($data, $last['start'], $keypos-$last['start']), true, $src_encoding, $dest_encoding) || + $GLOBALS['_xh']['vt'] != 'string') + { + // object member name received unquoted: what to do??? + // be tolerant as much as we can. ecma tolerates numbers as identifiers, too... + $key = trim(substr($data, $last['start'], $keypos-$last['start'])); + } + else + { + $key = $GLOBALS['_xh']['value']; + } + +//echo "Use extension: $use_extension\n"; + if (!json_parse(substr($data, $keypos+1, $i-$keypos-1), $return_phpvals, $src_encoding, $dest_encoding)) + { + return false; + } + $value[$key] = $GLOBALS['_xh']['value']; + $GLOBALS['_xh']['vt'] = 'struct'; + $keypos = null; + } + } + $last['start'] = $i + 1; + $vt = ''; // reset type of val found + } + else if ($data[$i] == '"' || $data[$i] == "'") + { + // found beginning of string: run till end + $ok = false; + for ($j = $i+1; $j < $len; $j++) + { + if ($data[$j] == $data[$i]) + { + $ok = true; + break; + } + else if($data[$j] == '\\') + { + $j++; + } + } + if ($ok) + { + $i = $j; // advance pointer to end of string + $vt = 'st'; + } + else + { + $GLOBALS['_xh']['isf_reason'] = 'Invalid data (string missing closing quote?)'; + return false; + } + } + else if ($data[$i] == "[") + { + $valuestack[] = $last; + $last = array('type' => 'ar', 'start' => $i); + } + else if ($data[$i] == '{') + { + $valuestack[] = $last; + $last = array('type' => 'ob', 'start' => $i); + } + else if ($data[$i] == "]") + { + if ($last['type'] == 'ar') + { + $last = array_pop($valuestack); + $vt = 'ar'; + } + else + { + $GLOBALS['_xh']['isf_reason'] = 'Invalid data (unmatched array closing bracket?)'; + return false; + } + } + else if ($data[$i] == '}') + { + if ($last['type'] == 'ob') + { + $last = array_pop($valuestack); + $vt = 'ob'; + } + else + { + $GLOBALS['_xh']['isf_reason'] = 'Invalid data (unmatched object closing bracket?)'; + return false; + } + } + else if ($data[$i] == ':' && $last['type'] == 'sl' && !$keypos) + { +//echo "Found key stop at pos. $i\n"; + $keypos = $i; + } + else if ($data[$i] == '/' && $i < $len-1 && $data[$i+1] == "*") + { + // found beginning of comment: run till end + $ok = false; + for ($j = $i+2; $j < $len-1; $j++) + { + if ($data[$j] == '*' && $data[$j+1] == '/') + { + $ok = true; + break; + } + } + if ($ok) + { + $i = $j+1; // advance pointer to end of string + } + else + { + $GLOBALS['_xh']['isf_reason'] = 'Invalid data (comment missing closing tag?)'; + return false; + } + } + + } + $GLOBALS['_xh']['value'] = $value; + } + //return true; + break; + default: +//echo "Found a scalar val (not string): '$data'\n"; + // be tolerant of uppercase chars in numbers/booleans/null + $data = strtolower($data); + if ($data == "true") + { +//echo "Found a true\n"; + $GLOBALS['_xh']['value'] = true; + $GLOBALS['_xh']['vt'] = 'boolean'; + } + else if ($data == "false") + { +//echo "Found a false\n"; + $GLOBALS['_xh']['value'] = false; + $GLOBALS['_xh']['vt'] = 'boolean'; + } + else if ($data == "null") + { +//echo "Found a null\n"; + $GLOBALS['_xh']['value'] = null; + $GLOBALS['_xh']['vt'] = 'null'; + } + // we could use is_numeric here, but rules are slightly different, + // e.g. 012 is NOT valid according to JSON or ECMA, but browsers inetrpret it as octal + /// @todo add support for .5 + /// @todo add support for numbers in octal notation, eg. 010 + else if (preg_match("#^-?(0|[1-9][0-9]*)(\.[0-9]*)?([e][+-]?[0-9]+)?$#" ,$data)) + { + if (preg_match('#[.e]#', $data)) + { +//echo "Found a double\n"; + // floating point + $GLOBALS['_xh']['value'] = (double)$data; + $GLOBALS['_xh']['vt'] = 'double'; + } + else + { +//echo "Found an int\n"; + //integer + $GLOBALS['_xh']['value'] = (int)$data; + $GLOBALS['_xh']['vt'] = 'int'; + } + //return true; + } + else if (preg_match("#^0x[0-9a-f]+$#", $data)) + { + // int in hex notation: not in JSON, but in ECMA... + $GLOBALS['_xh']['vt'] = 'int'; + $GLOBALS['_xh']['value'] = hexdec(substr($data, 2)); + } + else + { + $GLOBALS['_xh']['isf_reason'] = 'Invalid data'; + return false; + } + } // switch $data[0] + + if (!$return_phpvals) + { + $GLOBALS['_xh']['value'] = new jsonrpcval($GLOBALS['_xh']['value'], $GLOBALS['_xh']['vt']); + } + + return true; + + } + + /** + * Used in place of json_parse to take advantage of native json decoding when available: + * it parses either a jsonrpc request or a response. + * NB: php native decoding of json balks anyway at anything but array / struct as top level element + * @access private + * @bug unicode chars are handled differently from this and json_parse... + * @todo add support for src and dest encoding!!! + */ + function json_parse_native($data) + { +//echo "Parsing string - internal way (".$data.")\n"; + $out = json_decode($data, true); + if (!is_array($out)) + { + //$GLOBALS['_xh']['isf'] = 2; + $GLOBALS['_xh']['isf_reason'] = 'JSON parsing failed'; + return false; + } + // decoding will be fine for a jsonrpc error response, so we have to + // check for it by hand here... + //else if (array_key_exists('error', $out) && $out['error'] != null) + //{ + // $GLOBALS['_xh']['isf'] = 1; + //$GLOBALS['_xh']['value'] = $out['error']; + //} + else + { + $GLOBALS['_xh']['value'] = $out; + return true; + } + } + + /** + * Parse a json string, expected to be jsonrpc request format + * @access private + */ + function jsonrpc_parse_req($data, $return_phpvals=false, $use_extension=false, $src_encoding='') + { + $GLOBALS['_xh']['isf']=0; + $GLOBALS['_xh']['isf_reason']=''; + $GLOBALS['_xh']['pt'] = array(); + if ($return_phpvals && $use_extension) + { + $ok = json_parse_native($data); + } + else + { + $ok = json_parse($data, $return_phpvals, $src_encoding); + } + if ($ok) + { + if (!$return_phpvals) + $GLOBALS['_xh']['value'] = @$GLOBALS['_xh']['value']->me['struct']; + + if (!is_array($GLOBALS['_xh']['value']) || !array_key_exists('method', $GLOBALS['_xh']['value']) + || !array_key_exists('params', $GLOBALS['_xh']['value']) || !array_key_exists('id', $GLOBALS['_xh']['value'])) + { + $GLOBALS['_xh']['isf_reason'] = 'JSON parsing did not return correct jsonrpc request object'; + return false; + } + else + { + $GLOBALS['_xh']['method'] = $GLOBALS['_xh']['value']['method']; + $GLOBALS['_xh']['params'] = $GLOBALS['_xh']['value']['params']; + $GLOBALS['_xh']['id'] = $GLOBALS['_xh']['value']['id']; + if (!$return_phpvals) + { + /// @todo we should check for appropriate type for method name and params array... + $GLOBALS['_xh']['method'] = $GLOBALS['_xh']['method']->scalarval(); + $GLOBALS['_xh']['params'] = $GLOBALS['_xh']['params']->me['array']; + $GLOBALS['_xh']['id'] = php_jsonrpc_decode($GLOBALS['_xh']['id']); + } + else + { + // to allow 'phpvals' type servers to work, we need to rebuild $GLOBALS['_xh']['pt'] too + foreach($GLOBALS['_xh']['params'] as $val) + { + // since we rebuild this after converting json values to php, + // we've lost the info about array/struct, and we try to rebuild it + /// @bug empty objects will be recognized as empty arrays + /// @bug an object with keys '0', '1', ... 'n' will be recognized as an array + $typ = gettype($val); + if ($typ == 'array' && count($val) && count(array_diff_key($val, array_fill(0, count($val), null))) !== 0) + { + $typ = 'object'; + } + $GLOBALS['_xh']['pt'][] = php_2_jsonrpc_type($typ); + } + } + return true; + } + } + else + { + return false; + } + } + + /** + * Parse a json string, expected to be in json-rpc response format. + * @access private + * @todo checks missing: + * - no extra members in response + * - no extra members in error struct + * - resp. ID validation + */ + function jsonrpc_parse_resp($data, $return_phpvals=false, $use_extension=false, $src_encoding='') + { + $GLOBALS['_xh']['isf']=0; + $GLOBALS['_xh']['isf_reason']=''; + if ($return_phpvals && $use_extension) + { + $ok = json_parse_native($data); + } + else + { + $ok = json_parse($data, $return_phpvals, $src_encoding); + } + if ($ok) + { + if (!$return_phpvals) + { + $GLOBALS['_xh']['value'] = @$GLOBALS['_xh']['value']->me['struct']; + } + if (!is_array($GLOBALS['_xh']['value']) || !array_key_exists('result', $GLOBALS['_xh']['value']) + || !array_key_exists('error', $GLOBALS['_xh']['value']) || !array_key_exists('id', $GLOBALS['_xh']['value'])) + { + //$GLOBALS['_xh']['isf'] = 2; + $GLOBALS['_xh']['isf_reason'] = 'JSON parsing did not return correct jsonrpc response object'; + return false; + } + if (!$return_phpvals) + { + $d_error = php_jsonrpc_decode($GLOBALS['_xh']['value']['error']); + $GLOBALS['_xh']['value']['id'] = php_jsonrpc_decode($GLOBALS['_xh']['value']['id']); + } + else + { + $d_error = $GLOBALS['_xh']['value']['error']; + } + $GLOBALS['_xh']['id'] = $GLOBALS['_xh']['value']['id']; + if ($d_error != null) + { + $GLOBALS['_xh']['isf'] = 1; + + //$GLOBALS['_xh']['value'] = $d_error; + if (is_array($d_error) && array_key_exists('faultCode', $d_error) + && array_key_exists('faultString', $d_error)) + { + if($d_error['faultCode'] == 0) + { + // FAULT returned, errno needs to reflect that + $d_error['faultCode'] = -1; + } + $GLOBALS['_xh']['value'] = $d_error; + } + // NB: what about jsonrpc servers that do NOT respect + // the faultCode/faultString convention??? + // we force the error into a string. regardless of type... + else //if (is_string($GLOBALS['_xh']['value'])) + { + if ($return_phpvals) + { + $GLOBALS['_xh']['value'] = array('faultCode' => -1, 'faultString' => var_export($GLOBALS['_xh']['value']['error'], true)); + } + else + { + $GLOBALS['_xh']['value'] = array('faultCode' => -1, 'faultString' => serialize_jsonrpcval($GLOBALS['_xh']['value']['error'])); + } + } + + } + else + { + $GLOBALS['_xh']['value'] = $GLOBALS['_xh']['value']['result']; + } + return true; + + } + else + { + return false; + } + } + + class jsonrpc_client extends xmlrpc_client + { + // by default, no multicall exists for JSON-RPC, so do not try it + var $no_multicall = true; + // default return type of calls to json-rpc servers: jsonrpcvals + var $return_type = 'jsonrpcvals'; + + /* + function jsonrpc_client($path, $server='', $port='', $method='') + { + $this->xmlrpc_client($path, $server, $port, $method); + // we need to override the list of std supported encodings, since + // according to ECMA-262, the standard charset is UTF-16 + $this->accepted_charset_encodings = array('UTF-16', 'UTF-8', 'ISO-8859-1', 'US-ASCII'); + } + */ + } + + + class jsonrpcmsg extends xmlrpcmsg + { + var $id = null; // used to store request ID internally + var $content_type = 'application/json'; + + /** + * @param string $meth the name of the method to invoke + * @param array $pars array of parameters to be paased to the method (xmlrpcval objects) + * @param mixed $id the id of the jsonrpc request + */ + function jsonrpcmsg($meth, $pars=0, $id=null) + { + // NB: a NULL id is allowed and has a very definite meaning! + $this->id = $id; + $this->xmlrpcmsg($meth, $pars); + } + + /** + * @access private + */ + function createPayload($charset_encoding='') + { + if ($charset_encoding != '') + $this->content_type = 'application/json; charset=' . $charset_encoding; + else + $this->content_type = 'application/json'; + // @ todo: verify if all chars are allowed for method names or can + // we just skip the js encoding on it? + $this->payload = "{\n\"method\": \"" . json_encode_entities($this->methodname, '', $charset_encoding) . "\",\n\"params\": [ "; + for($i = 0; $i < sizeof($this->params); $i++) + { + $p = $this->params[$i]; + // MB: we try to force serialization as json even though the object + // param might be a plain xmlrpcval object. + // This way we do not need to override addParam, aren't we lazy? + $this->payload .= "\n " . serialize_jsonrpcval($p, $charset_encoding) . + ","; + } + $this->payload = substr($this->payload, 0, -1) . "\n],\n\"id\": "; + switch (true) + { + case $this->id === null: + $this->payload .= 'null'; + break; + case is_string($this->id): + $this->payload .= '"'.json_encode_entities($this->id, '', $charset_encoding).'"'; + break; + case is_bool($this->id): + $this->payload .= ($this->id ? 'true' : 'false'); + break; + default: + $this->payload .= $this->id; + } + $this->payload .= "\n}\n"; + } + + /** + * Parse the jsonrpc response contained in the string $data and return a jsonrpcresp object. + * @param string $data the xmlrpc response, eventually including http headers + * @param bool $headers_processed when true prevents parsing HTTP headers for interpretation of content-encoding and conseuqent decoding + * @param string $return_type decides return type, i.e. content of response->value(). Either 'xmlrpcvals', 'xml' or 'phpvals' + * @return jsonrpcresp + * @access private + */ + function &parseResponse($data='', $headers_processed=false, $return_type='jsonrpcvals') + { + if($this->debug) + { + print "
---GOT---\n" . htmlentities($data) . "\n---END---\n
"; + } + + if($data == '') + { + error_log('XML-RPC: '.__METHOD__.': no response received from server.'); + $r = new jsonrpcresp(0, $GLOBALS['xmlrpcerr']['no_data'], $GLOBALS['xmlrpcstr']['no_data']); + return $r; + } + + $GLOBALS['_xh']=array(); + + $raw_data = $data; + // parse the HTTP headers of the response, if present, and separate them from data + if(substr($data, 0, 4) == 'HTTP') + { + $r =& $this->parseResponseHeaders($data, $headers_processed); + if ($r) + { + // parent class implementation of parseResponseHeaders returns in case + // of error an object of the wrong type: recode it into correct object + $rj = new jsonrpcresp(0, $r->faultCode(), $r->faultString()); + $rj->raw_data = $data; + return $rj; + } + } + else + { + $GLOBALS['_xh']['headers'] = array(); + $GLOBALS['_xh']['cookies'] = array(); + } + + if($this->debug) + { + $start = strpos($data, '/* SERVER DEBUG INFO (BASE64 ENCODED):'); + if ($start !== false) + { + $start += strlen('/* SERVER DEBUG INFO (BASE64 ENCODED):'); + $end = strpos($data, '*/', $start); + $comments = substr($data, $start, $end-$start); + print "
---SERVER DEBUG INFO (DECODED) ---\n\t".htmlentities(str_replace("\n", "\n\t", base64_decode($comments)))."\n---END---\n
"; + } + } + + // be tolerant of extra whitespace in response body + $data = trim($data); + + // be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts) + $end = strrpos($data, '}'); + if ($end) + { + $data = substr($data, 0, $end+1); + } + // if user wants back raw json, give it to him + if ($return_type == 'json') + { + $r = new jsonrpcresp($data, 0, '', 'json'); + $r->hdrs = $GLOBALS['_xh']['headers']; + $r->_cookies = $GLOBALS['_xh']['cookies']; + $r->raw_data = $raw_data; + return $r; + } + + // @todo shall we try to check for non-unicode json received ??? + + if (!jsonrpc_parse_resp($data, $return_type=='phpvals')) + { + if ($this->debug) + { + /// @todo echo something for user? + } + + $r = new jsonrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'], + $GLOBALS['xmlrpcstr']['invalid_return'] . ' ' . $GLOBALS['_xh']['isf_reason']); + } + //elseif ($return_type == 'jsonrpcvals' && !is_object($GLOBALS['_xh']['value'])) + //{ + // then something odd has happened + // and it's time to generate a client side error + // indicating something odd went on + // $r = new jsonrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'], + // $GLOBALS['xmlrpcstr']['invalid_return']); + //} + else + { + $v = $GLOBALS['_xh']['value']; + + if ($this->debug) + { + print "
---PARSED---\n" ;
+					var_export($v);
+					print "\n---END---
"; + } + + if($GLOBALS['_xh']['isf']) + { + $r = new jsonrpcresp(0, $v['faultCode'], $v['faultString']); + } + else + { + $r = new jsonrpcresp($v, 0, '', $return_type); + } + $r->id = $GLOBALS['_xh']['id']; + } + + $r->hdrs = $GLOBALS['_xh']['headers']; + $r->_cookies = $GLOBALS['_xh']['cookies']; + $r->raw_data = $raw_data; + return $r; + } + } + + class jsonrpcresp extends xmlrpcresp + { + var $content_type = 'application/json'; // NB: forces us to send US-ASCII over http + var $id = null; + + /// @todo override creator, to set proper valtyp and id! + + /** + * Returns json representation of the response. + * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed + * @return string the json representation of the response + * @access public + */ + function serialize($charset_encoding='') + { + if ($charset_encoding != '') + $this->content_type = 'application/json; charset=' . $charset_encoding; + else + $this->content_type = 'application/json'; + $this->payload = serialize_jsonrpcresp($this, $this->id, $charset_encoding); + return $this->payload; + } + + } + + class jsonrpcval extends xmlrpcval + { + /** + * Returns json representation of the value. + * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed + * @return string + * @access public + */ + function serialize($charset_encoding='') + { + return serialize_jsonrpcval($this, $charset_encoding); + } + } + + /** + * Takes a json value in PHP jsonrpcval object format + * and translates it into native PHP types. + * + * @param jsonrpcval $jsonrpc_val + * @param array $options if 'decode_php_objs' is set in the options array, jsonrpc objects can be decoded into php objects + * @return mixed + * @access public + */ + function php_jsonrpc_decode($jsonrpc_val, $options=array()) + { + $kind = $jsonrpc_val->kindOf(); + + if($kind == 'scalar') + { + return $jsonrpc_val->scalarval(); + } + elseif($kind == 'array') + { + $size = $jsonrpc_val->arraysize(); + $arr = array(); + + for($i = 0; $i < $size; $i++) + { + $arr[] = php_jsonrpc_decode($jsonrpc_val->arraymem($i), $options); + } + return $arr; + } + elseif($kind == 'struct') + { + $jsonrpc_val->structreset(); + // If user said so, try to rebuild php objects for specific struct vals. + /// @todo should we raise a warning for class not found? + // shall we check for proper subclass of xmlrpcval instead of + // presence of _php_class to detect what we can do? + if (in_array('decode_php_objs', $options)) + { + if( $jsonrpc_val->_php_class != '' + && class_exists($jsonrpc_val->_php_class)) + { + $obj = @new $jsonrpc_val->_php_class; + } + else + { + $obj = new stdClass(); + } + while(list($key,$value) = $jsonrpc_val->structeach()) + { + $obj->$key = php_jsonrpc_decode($value, $options); + } + return $obj; + } + else + { + $arr = array(); + while(list($key,$value) = $jsonrpc_val->structeach()) + { + $arr[$key] = php_jsonrpc_decode($value, $options); + } + return $arr; + } + } + } + + /** + * Takes native php types and encodes them into jsonrpc PHP object format. + * It will not re-encode jsonrpcval objects. + * + * @param mixed $php_val the value to be converted into a jsonrpcval object + * @param array $options can include 'encode_php_objs' + * @return jsonrpcval + * @access public + */ + function php_jsonrpc_encode($php_val, $options='') + { + $type = gettype($php_val); + + switch($type) + { + case 'string': + $jsonrpc_val = new jsonrpcval($php_val, $GLOBALS['xmlrpcString']); + break; + case 'integer': + $jsonrpc_val = new jsonrpcval($php_val, $GLOBALS['xmlrpcInt']); + break; + case 'double': + $jsonrpc_val = new jsonrpcval($php_val, $GLOBALS['xmlrpcDouble']); + break; + case 'boolean': + $jsonrpc_val = new jsonrpcval($php_val, $GLOBALS['xmlrpcBoolean']); + break; + case 'resource': // for compat with php json extension... + case 'NULL': + $jsonrpc_val = new jsonrpcval($php_val, $GLOBALS['xmlrpcNull']); + break; + case 'array': + // PHP arrays can be encoded to either objects or arrays, + // depending on wheter they are hashes or plain 0..n integer indexed + // A shorter one-liner would be + // $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1)); + // but execution time skyrockets! + $j = 0; + $arr = array(); + $ko = false; + foreach($php_val as $key => $val) + { + $arr[$key] = php_jsonrpc_encode($val, $options); + if(!$ko && $key !== $j) + { + $ko = true; + } + $j++; + } + if($ko) + { + $jsonrpc_val = new jsonrpcval($arr, $GLOBALS['xmlrpcStruct']); + } + else + { + $jsonrpc_val = new jsonrpcval($arr, $GLOBALS['xmlrpcArray']); + } + break; + case 'object': + if(is_a($php_val, 'jsonrpcval')) + { + $jsonrpc_val = $php_val; + } + else + { + $arr = array(); + reset($php_val); + while(list($k,$v) = each($php_val)) + { + $arr[$k] = php_jsonrpc_encode($v, $options); + } + $jsonrpc_val = new jsonrpcval($arr, $GLOBALS['xmlrpcStruct']); + if (in_array('encode_php_objs', $options)) + { + // let's save original class name into xmlrpcval: + // might be useful later on... + $jsonrpc_val->_php_class = get_class($php_val); + } + } + break; + // catch "user function", "unknown type" + default: + $jsonrpc_val = new jsonrpcval(); + break; + } + return $jsonrpc_val; + } + + /** + * Convert the json representation of a jsonrpc method call, jsonrpc method response + * or single json value into the appropriate object (a.k.a. deserialize). + * Please note that there is no way to distinguish the serialized representation + * of a single json val of type object which has the 3 appropriate members from + * the serialization of a method call or method response. + * In such a case, the function will return a jsonrpcresp or jsonrpcmsg + * @param string $json_val + * @param array $options + * @return mixed false on error, or an instance of jsonrpcval, jsonrpcresp or jsonrpcmsg + * @access public + * @todo add options controlling character set encodings + */ + function php_jsonrpc_decode_json($json_val, $options=array()) + { + $src_encoding = array_key_exists('src_encoding', $options) ? $options['src_encoding'] : $GLOBALS['xmlrpc_defencoding']; + $dest_encoding = array_key_exists('dest_encoding', $options) ? $options['dest_encoding'] : $GLOBALS['xmlrpc_internalencoding']; + + //$GLOBALS['_xh'] = array(); + $GLOBALS['_xh']['isf'] = 0; + if (!json_parse($json_val, false, $src_encoding, $dest_encoding)) + { + error_log($GLOBALS['_xh']['isf_reason']); + return false; + } + else + { + $val = $GLOBALS['_xh']['value']; // shortcut + if ($GLOBALS['_xh']['value']->kindOf() == 'struct') + { + if ($GLOBALS['_xh']['value']->structSize() == 3) + { + if ($GLOBALS['_xh']['value']->structMemExists('method') && + $GLOBALS['_xh']['value']->structMemExists('params') && + $GLOBALS['_xh']['value']->structMemExists('id')) + { + /// @todo we do not check for correct type of 'method', 'params' struct members... + $method = $GLOBALS['_xh']['value']->structMem('method'); + $msg = new jsonrpcmsg($method->scalarval(), null, php_jsonrpc_decode($GLOBALS['_xh']['value']->structMem('id'))); + $params = $GLOBALS['_xh']['value']->structMem('params'); + for($i = 0; $i < $params->arraySize(); ++$i) + { + $msg->addparam($params->arrayMem($i)); + } + return $msg; + } + else + if ($GLOBALS['_xh']['value']->structMemExists('result') && + $GLOBALS['_xh']['value']->structMemExists('error') && + $GLOBALS['_xh']['value']->structMemExists('id')) + { + $id = php_jsonrpc_decode($GLOBALS['_xh']['value']->structMem('id')); + $err = php_jsonrpc_decode($GLOBALS['_xh']['value']->structMem('error')); + if ($err == null) + { + $resp = new jsonrpcresp($GLOBALS['_xh']['value']->structMem('result')); + } + else + { + if (is_array($err) && array_key_exists('faultCode', $err) + && array_key_exists('faultString', $err)) + { + if($err['faultCode'] == 0) + { + // FAULT returned, errno needs to reflect that + $err['faultCode'] = -1; + } + } + // NB: what about jsonrpc servers that do NOT respect + // the faultCode/faultString convention??? + // we force the error into a string. regardless of type... + else //if (is_string($GLOBALS['_xh']['value'])) + { + $err = array('faultCode' => -1, 'faultString' => serialize_jsonrpcval($GLOBALS['_xh']['value']->structMem('error'))); + } + $resp = new jsonrpcresp(0, $err['faultCode'], $err['faultString']); + } + $resp->id = $id; + return $resp; + } + } + } + // not a request msg nor a response: a plain jsonrpcval obj + return $GLOBALS['_xh']['value']; + } + } + + /** + * Serialize a jsonrpcresp (or xmlrpcresp) as json. + * Moved outside of the corresponding class to ease multi-serialization of + * xmlrpcresp objects + * @param xmlrpcresp or jsonrpcresp $resp + * @param mixed $id + * @return string + * @access private + */ + function serialize_jsonrpcresp($resp, $id=null, $charset_encoding='') + { + $result = "{\n\"id\": "; + switch (true) + { + case $id === null: + $result .= 'null'; + break; + case is_string($id): + $result .= '"'.json_encode_entities($id, '', $charset_encoding).'"'; + break; + case is_bool($id): + $result .= ($id ? 'true' : 'false'); + break; + default: + $result .= $id; + } + $result .= ", "; + if($resp->errno) + { + // let non-ASCII response messages be tolerated by clients + // by encoding non ascii chars + $result .= "\"error\": { \"faultCode\": " . $resp->errno . ", \"faultString\": \"" . json_encode_entities($resp->errstr, null, $charset_encoding) . "\" }, \"result\": null"; + } + else + { + if(!is_object($resp->val) || !is_a($resp->val, 'xmlrpcval')) + { + if (is_string($resp->val) && $resp->valtyp == 'json') + { + $result .= "\"error\": null, \"result\": " . $resp->val; + } + else + { + /// @todo try to build something serializable? + die('cannot serialize jsonrpcresp objects whose content is native php values'); + } + } + else + { + $result .= "\"error\": null, \"result\": " . + serialize_jsonrpcval($resp->val, $charset_encoding); + } + } + $result .= "\n}"; + return $result; + } + + /** + * Serialize a jsonrpcval (or xmlrpcval) as json. + * Moved outside of the corresponding class to ease multi-serialization of + * xmlrpcval objects + * @param xmlrpcval or jsonrpcval $value + * @string $charset_encoding + * @access private + */ + function serialize_jsonrpcval($value, $charset_encoding='') + { + reset($value->me); + list($typ, $val) = each($value->me); + + $rs = ''; + switch(@$GLOBALS['xmlrpcTypes'][$typ]) + { + case 1: + switch($typ) + { + case $GLOBALS['xmlrpcString']: + $rs .= '"' . json_encode_entities($val, null, $charset_encoding). '"'; + break; + case $GLOBALS['xmlrpcI4']: + case $GLOBALS['xmlrpcInt']: + $rs .= (int)$val; + break; + case $GLOBALS['xmlrpcDateTime']: + // quote date as a json string. + // assumes date format is valid and will not break js... + $rs .= '"' . $val . '"'; + break; + case $GLOBALS['xmlrpcDouble']: + // add a .0 in case value is integer. + // This helps us carrying around floats in js, and keep them separated from ints + $sval = strval((double)$val); // convert to string + // fix usage of comma, in case of eg. german locale + $sval = str_replace(',', '.', $sval); + if (strpos($sval, '.') !== false || strpos($sval, 'e') !== false) + { + $rs .= $sval; + } + else + { + $rs .= $val.'.0'; + } + break; + case $GLOBALS['xmlrpcBoolean']: + $rs .= ($val ? 'true' : 'false'); + break; + case $GLOBALS['xmlrpcBase64']: + // treat base 64 values as strings ??? + $rs .= '"' . base64_encode($val) . '"'; + break; + default: + $rs .= "null"; + } + break; + case 2: + // array + $rs .= "["; + $len = sizeof($val); + if ($len) + { + for($i = 0; $i < $len; $i++) + { + $rs .= serialize_jsonrpcval($val[$i], $charset_encoding); + $rs .= ","; + } + $rs = substr($rs, 0, -1) . "]"; + } + else + { + $rs .= "]"; + } + break; + case 3: + // struct + //if ($value->_php_class) + //{ + /// @todo implement json-rpc extension for object serialization + //$rs.='\n"; + //} + //else + //{ + //} + foreach($val as $key2 => $val2) + { + $rs .= ',"'.json_encode_entities($key2, null, $charset_encoding).'":'; + $rs .= serialize_jsonrpcval($val2, $charset_encoding); + } + $rs = '{' . substr($rs, 1) . '}'; + break; + case 0: + // let uninitialized jsonrpcval objects serialize to an empty string, as they do in xmlrpc land + $rs = '""'; + break; + default: + break; + } + return $rs; + } + + /** + * Given a string defining a php type or phpxmlrpc type (loosely defined: strings + * accepted come from javadoc blocks), return corresponding phpxmlrpc type. + * NB: for php 'resource' types returns empty string, since resources cannot be serialized; + * for php class names returns 'struct', since php objects can be serialized as json structs; + * for php arrays always retutn 'array', even though arrays sometiles serialize as json structs + * @param string $phptype + * @return string + */ + function php_2_jsonrpc_type($phptype) + { + switch(strtolower($phptype)) + { + case 'string': + return $GLOBALS['xmlrpcString']; + case 'integer': + case $GLOBALS['xmlrpcInt']: // 'int' + case $GLOBALS['xmlrpcI4']: + return $GLOBALS['xmlrpcInt']; + case 'double': + return $GLOBALS['xmlrpcDouble']; + case 'boolean': + return $GLOBALS['xmlrpcBoolean']; + case 'array': + return $GLOBALS['xmlrpcArray']; + case 'object': + return $GLOBALS['xmlrpcStruct']; + //case $GLOBALS['xmlrpcBase64']: + case $GLOBALS['xmlrpcStruct']: + return strtolower($phptype); + case 'resource': + return ''; + default: + if(class_exists($phptype)) + { + return $GLOBALS['xmlrpcStruct']; + } + else + { + // unknown: might be any 'extended' jsonrpc type + return $GLOBALS['xmlrpcValue']; + } + } + } +?> diff --git a/public/include/pages/about.inc.php b/public/include/pages/about.inc.php new file mode 100644 index 00000000..aecab054 --- /dev/null +++ b/public/include/pages/about.inc.php @@ -0,0 +1,9 @@ +assign("CONTENT", "default.tpl"); +?> diff --git a/public/include/pages/about/pool.inc.php b/public/include/pages/about/pool.inc.php new file mode 100644 index 00000000..aecab054 --- /dev/null +++ b/public/include/pages/about/pool.inc.php @@ -0,0 +1,9 @@ +assign("CONTENT", "default.tpl"); +?> diff --git a/public/include/pages/about/pplns.inc.php b/public/include/pages/about/pplns.inc.php new file mode 100644 index 00000000..aecab054 --- /dev/null +++ b/public/include/pages/about/pplns.inc.php @@ -0,0 +1,9 @@ +assign("CONTENT", "default.tpl"); +?> diff --git a/public/include/pages/account.inc.php b/public/include/pages/account.inc.php new file mode 100644 index 00000000..859575d9 --- /dev/null +++ b/public/include/pages/account.inc.php @@ -0,0 +1,13 @@ +assign("CONTENT", "default.tpl"); +?> diff --git a/public/include/pages/account/edit.inc.php b/public/include/pages/account/edit.inc.php new file mode 100644 index 00000000..7f96c219 --- /dev/null +++ b/public/include/pages/account/edit.inc.php @@ -0,0 +1,66 @@ +checkPin($_SESSION['USERDATA']['id'], $_POST['authPin']) && $_POST['do']) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Invalid PIN','TYPE' => 'errormsg'); +} else { + switch ($_POST['do']) { + case 'cashOut': + $dUserBalance = $user->getBalance($_SESSION['USERDATA']['id']); + $sUserSendAddress = $user->getLtcAddress($_SESSION['USERDATA']['id']); + $dUserPaid = $user->getPaid($_SESSION['USERDATA']['id']); + if ($dUserBalance > 0.1) { + if ($bitcoin->can_connect() === true) { + try { + $bitcoin->validateaddress($sUserSendAddress); + } catch (BitcoinClientException $e) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Invalid payment address: ' . $sUserSendAddress, 'TYPE' => 'errormsg'); + } + // Remove the transfer fee + $dUserBalance = $dUserBalance - 0.1; + try { + $bitcoin->sendtoaddress($sUserSendAddress, $dUserBalance); + } catch (BitcoinClientException $e) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Failed to send LTC, please contact site support immidiately', 'TYPE' => 'errormsg'); + } + // Set balance to 0, add to paid out, insert to ledger + if ( $user->setBalance($_SESSION['USERDATA']['id'], 0) && + $user->setPaid($_SESSION['USERDATA']['id'], $dUserPaid + $dUserBalance) && + $user->addLedger($_SESSION['USERDATA']['id'], $dUserBalance, $sUserSendAddress) ) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Transaction completed', 'TYPE' => 'success'); + } + } else { + $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to pushpool service', 'TYPE' => 'errormsg'); + } + } else { + $_SESSION['POPUP'][] = array('CONTENT' => 'Insufficient funds, you need more than 0.1 LTC to cover transaction fees', 'TYPE' => 'errormsg'); + } + break; + + case 'updateAccount': + if ($user->updateAccount($_SESSION['USERDATA']['id'], $_POST['paymentAddress'], $_POST['payoutThreshold'], $_POST['donatePercent'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Account details updated', 'TYPE' => 'success'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => 'Failed to update your account', 'TYPE' => 'errormsg'); + } + break; + + case 'updatePassword': + if ($user->updatePassword($_SESSION['USERDATA']['id'], $_POST['currentPassword'], $_POST['newPassword'], $_POST['newPassword2'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Password updated', 'TYPE' => 'success'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => $user->error, 'TYPE' => 'errormsg'); + } + break; + } +} +// Tempalte specifics +$smarty->assign("CONTENT", "default.tpl"); +?> diff --git a/public/include/pages/account/transactions.inc.php b/public/include/pages/account/transactions.inc.php new file mode 100644 index 00000000..e291b1dc --- /dev/null +++ b/public/include/pages/account/transactions.inc.php @@ -0,0 +1,12 @@ +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'); +?> diff --git a/public/include/pages/account/workers.inc.php b/public/include/pages/account/workers.inc.php new file mode 100644 index 00000000..fb2db840 --- /dev/null +++ b/public/include/pages/account/workers.inc.php @@ -0,0 +1,36 @@ +deleteWorker($_SESSION['USERDATA']['id'], $_GET['id'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Worker removed'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => $user->error, 'TYPE' => 'errormsg'); + } + break; +case 'add': + if ($user->addWorker($_SESSION['USERDATA']['id'], $_POST['username'], $_POST['password'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Worker added'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => $user->error, 'TYPE' => 'errormsg'); + } + break; +case 'update': + if ($user->updateWorkers($_SESSION['USERDATA']['id'], $_POST['data'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Worker updated'); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => $user->error, 'TYPE' => 'errormsg'); + } + break; +} + +$aWorkers = $user->getWorkers($_SESSION['USERDATA']['id']); +if (!$aWorkers) $_SESSION['POPUP'][] = array('CONTENT' => 'You have no workers configured', 'TYPE' => 'errormsg'); + +$smarty->assign('CONTENT', 'default.tpl'); +$smarty->assign('WORKERS', $aWorkers); +?> diff --git a/public/include/pages/gettingstarted.inc.php b/public/include/pages/gettingstarted.inc.php new file mode 100644 index 00000000..aecab054 --- /dev/null +++ b/public/include/pages/gettingstarted.inc.php @@ -0,0 +1,9 @@ +assign("CONTENT", "default.tpl"); +?> diff --git a/public/include/pages/home.inc.php b/public/include/pages/home.inc.php new file mode 100644 index 00000000..aecab054 --- /dev/null +++ b/public/include/pages/home.inc.php @@ -0,0 +1,9 @@ +assign("CONTENT", "default.tpl"); +?> diff --git a/public/include/pages/login.inc.php b/public/include/pages/login.inc.php new file mode 100644 index 00000000..6600a872 --- /dev/null +++ b/public/include/pages/login.inc.php @@ -0,0 +1,13 @@ +checkLogin($_POST['username'],$_POST['password']) ) { + header('Location: index.php?page=home'); +} else { + $_SESSION['POPUP'][] = array('CONTENT' => 'Invalid username or password', 'TYPE' => 'errormsg'); +} +$smarty->assign('CONTENT', 'default.tpl'); +?> diff --git a/public/include/pages/logout.inc.php b/public/include/pages/logout.inc.php new file mode 100644 index 00000000..b10415f4 --- /dev/null +++ b/public/include/pages/logout.inc.php @@ -0,0 +1,10 @@ +logoutUser(); +header('Location: index.php?page=home'); +?> diff --git a/public/include/pages/news.inc.php b/public/include/pages/news.inc.php new file mode 100644 index 00000000..aecab054 --- /dev/null +++ b/public/include/pages/news.inc.php @@ -0,0 +1,9 @@ +assign("CONTENT", "default.tpl"); +?> diff --git a/public/include/pages/register.inc.php b/public/include/pages/register.inc.php new file mode 100644 index 00000000..aecab054 --- /dev/null +++ b/public/include/pages/register.inc.php @@ -0,0 +1,9 @@ +assign("CONTENT", "default.tpl"); +?> diff --git a/public/include/pages/register/register.inc.php b/public/include/pages/register/register.inc.php new file mode 100644 index 00000000..37a3ac1d --- /dev/null +++ b/public/include/pages/register/register.inc.php @@ -0,0 +1,16 @@ +register($_POST['username'], $_POST['password1'], $_POST['password2'], $_POST['pin'], $_POST['email1'], $_POST['email2'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Account created, please login'); +} else { + $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to create account: ' . $user->error, 'TYPE' => 'errormsg'); +} + +// We load the default registration template instead of an action specific one +$smarty->assign("CONTENT", "../default.tpl"); +?> diff --git a/public/include/pages/statistics.inc.php b/public/include/pages/statistics.inc.php new file mode 100644 index 00000000..c3f1e298 --- /dev/null +++ b/public/include/pages/statistics.inc.php @@ -0,0 +1,18 @@ +can_connect() === true){ + $iDifficulty = $bitcoin->query('getdifficulty'); + $iBlock = $bitcoin->query('getblockcount'); +} else { + $iDifficulty = 1; + $iBlock = 0; + $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to pushpool service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg'); +} + +$smarty->assign("CURRENTBLOCK", $iBlock); +$smarty->assign("CURRENTDIFFICULTY", $iDifficulty); +$smarty->assign("CONTENT", "pool/default.tpl"); diff --git a/public/include/pages/statistics/pool.inc.php b/public/include/pages/statistics/pool.inc.php new file mode 100644 index 00000000..acfdf9c2 --- /dev/null +++ b/public/include/pages/statistics/pool.inc.php @@ -0,0 +1,24 @@ +can_connect() === true){ + $iDifficulty = $bitcoin->query('getdifficulty'); + $iBlock = $bitcoin->query('getblockcount'); +} else { + $iDifficulty = 1; + $iBlock = 0; + $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to pushpool service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg'); +} + +$smarty->assign("CURRENTBLOCK", $iBlock); +$smarty->assign("DIFFICULTY", $iDifficulty); + +if ($_SESSION['AUTHENTICATED']) { + $smarty->assign("CONTENT", "authenticated.tpl"); +} else { + $smarty->assign("CONTENT", "default.tpl"); +} +?> diff --git a/public/include/pages/support.inc.php b/public/include/pages/support.inc.php new file mode 100644 index 00000000..aecab054 --- /dev/null +++ b/public/include/pages/support.inc.php @@ -0,0 +1,9 @@ +assign("CONTENT", "default.tpl"); +?> diff --git a/public/include/smarty.inc.php b/public/include/smarty.inc.php new file mode 100644 index 00000000..465edba5 --- /dev/null +++ b/public/include/smarty.inc.php @@ -0,0 +1,25 @@ +append('Loading Smarty libraries', 2); +define('SMARTY_DIR', INCLUDE_DIR . '/smarty/libs/'); + +// Include the actual smarty class file +include(INCLUDE_DIR . '/smarty/libs/Smarty.class.php'); + +// We initialize smarty here +$debug->append('Instantiating Smarty Object', 3); +$smarty = new Smarty; + +// Assign our local paths +$debug->append('Define Smarty Paths', 3); +$smarty->template_dir = 'templates/' . THEME . '/'; +$smarty->compile_dir = 'templates/compile/'; + +// Optional smarty caching, check Smarty documentation for details +$smarty->caching = $config['cache']; +$smarty->cache_dir = "templates/cache"; +?> diff --git a/public/include/smarty/libs/Smarty.class.php b/public/include/smarty/libs/Smarty.class.php new file mode 100644 index 00000000..40532fc2 --- /dev/null +++ b/public/include/smarty/libs/Smarty.class.php @@ -0,0 +1,1528 @@ + + * @author Uwe Tews + * @author Rodney Rehm + * @package Smarty + * @version 3.1.13 + */ + +/** + * define shorthand directory separator constant + */ +if (!defined('DS')) { + define('DS', DIRECTORY_SEPARATOR); +} + +/** + * set SMARTY_DIR to absolute path to Smarty library files. + * Sets SMARTY_DIR only if user application has not already defined it. + */ +if (!defined('SMARTY_DIR')) { + define('SMARTY_DIR', dirname(__FILE__) . DS); +} + +/** + * set SMARTY_SYSPLUGINS_DIR to absolute path to Smarty internal plugins. + * Sets SMARTY_SYSPLUGINS_DIR only if user application has not already defined it. + */ +if (!defined('SMARTY_SYSPLUGINS_DIR')) { + define('SMARTY_SYSPLUGINS_DIR', SMARTY_DIR . 'sysplugins' . DS); +} +if (!defined('SMARTY_PLUGINS_DIR')) { + define('SMARTY_PLUGINS_DIR', SMARTY_DIR . 'plugins' . DS); +} +if (!defined('SMARTY_MBSTRING')) { + define('SMARTY_MBSTRING', function_exists('mb_split')); +} +if (!defined('SMARTY_RESOURCE_CHAR_SET')) { + // UTF-8 can only be done properly when mbstring is available! + /** + * @deprecated in favor of Smarty::$_CHARSET + */ + define('SMARTY_RESOURCE_CHAR_SET', SMARTY_MBSTRING ? 'UTF-8' : 'ISO-8859-1'); +} +if (!defined('SMARTY_RESOURCE_DATE_FORMAT')) { + /** + * @deprecated in favor of Smarty::$_DATE_FORMAT + */ + define('SMARTY_RESOURCE_DATE_FORMAT', '%b %e, %Y'); +} + +/** + * register the class autoloader + */ +if (!defined('SMARTY_SPL_AUTOLOAD')) { + define('SMARTY_SPL_AUTOLOAD', 0); +} + +if (SMARTY_SPL_AUTOLOAD && set_include_path(get_include_path() . PATH_SEPARATOR . SMARTY_SYSPLUGINS_DIR) !== false) { + $registeredAutoLoadFunctions = spl_autoload_functions(); + if (!isset($registeredAutoLoadFunctions['spl_autoload'])) { + spl_autoload_register(); + } +} else { + spl_autoload_register('smartyAutoload'); +} + +/** + * Load always needed external class files + */ +include_once SMARTY_SYSPLUGINS_DIR.'smarty_internal_data.php'; +include_once SMARTY_SYSPLUGINS_DIR.'smarty_internal_templatebase.php'; +include_once SMARTY_SYSPLUGINS_DIR.'smarty_internal_template.php'; +include_once SMARTY_SYSPLUGINS_DIR.'smarty_resource.php'; +include_once SMARTY_SYSPLUGINS_DIR.'smarty_internal_resource_file.php'; +include_once SMARTY_SYSPLUGINS_DIR.'smarty_cacheresource.php'; +include_once SMARTY_SYSPLUGINS_DIR.'smarty_internal_cacheresource_file.php'; + +/** + * This is the main Smarty class + * @package Smarty + */ +class Smarty extends Smarty_Internal_TemplateBase { + + /**#@+ + * constant definitions + */ + + /** + * smarty version + */ + const SMARTY_VERSION = 'Smarty-3.1.13'; + + /** + * define variable scopes + */ + const SCOPE_LOCAL = 0; + const SCOPE_PARENT = 1; + const SCOPE_ROOT = 2; + const SCOPE_GLOBAL = 3; + /** + * define caching modes + */ + const CACHING_OFF = 0; + const CACHING_LIFETIME_CURRENT = 1; + const CACHING_LIFETIME_SAVED = 2; + /** + * define compile check modes + */ + const COMPILECHECK_OFF = 0; + const COMPILECHECK_ON = 1; + const COMPILECHECK_CACHEMISS = 2; + /** + * modes for handling of "" tags in templates. + */ + const PHP_PASSTHRU = 0; //-> print tags as plain text + const PHP_QUOTE = 1; //-> escape tags as entities + const PHP_REMOVE = 2; //-> escape tags as entities + const PHP_ALLOW = 3; //-> escape tags as entities + /** + * filter types + */ + const FILTER_POST = 'post'; + const FILTER_PRE = 'pre'; + const FILTER_OUTPUT = 'output'; + const FILTER_VARIABLE = 'variable'; + /** + * plugin types + */ + const PLUGIN_FUNCTION = 'function'; + const PLUGIN_BLOCK = 'block'; + const PLUGIN_COMPILER = 'compiler'; + const PLUGIN_MODIFIER = 'modifier'; + const PLUGIN_MODIFIERCOMPILER = 'modifiercompiler'; + + /**#@-*/ + + /** + * assigned global tpl vars + */ + public static $global_tpl_vars = array(); + + /** + * error handler returned by set_error_hanlder() in Smarty::muteExpectedErrors() + */ + public static $_previous_error_handler = null; + /** + * contains directories outside of SMARTY_DIR that are to be muted by muteExpectedErrors() + */ + public static $_muted_directories = array(); + /** + * Flag denoting if Multibyte String functions are available + */ + public static $_MBSTRING = SMARTY_MBSTRING; + /** + * The character set to adhere to (e.g. "UTF-8") + */ + public static $_CHARSET = SMARTY_RESOURCE_CHAR_SET; + /** + * The date format to be used internally + * (accepts date() and strftime()) + */ + public static $_DATE_FORMAT = SMARTY_RESOURCE_DATE_FORMAT; + /** + * Flag denoting if PCRE should run in UTF-8 mode + */ + public static $_UTF8_MODIFIER = 'u'; + + /** + * Flag denoting if operating system is windows + */ + public static $_IS_WINDOWS = false; + + /**#@+ + * variables + */ + + /** + * auto literal on delimiters with whitspace + * @var boolean + */ + public $auto_literal = true; + /** + * display error on not assigned variables + * @var boolean + */ + public $error_unassigned = false; + /** + * look up relative filepaths in include_path + * @var boolean + */ + public $use_include_path = false; + /** + * template directory + * @var array + */ + private $template_dir = array(); + /** + * joined template directory string used in cache keys + * @var string + */ + public $joined_template_dir = null; + /** + * joined config directory string used in cache keys + * @var string + */ + public $joined_config_dir = null; + /** + * default template handler + * @var callable + */ + public $default_template_handler_func = null; + /** + * default config handler + * @var callable + */ + public $default_config_handler_func = null; + /** + * default plugin handler + * @var callable + */ + public $default_plugin_handler_func = null; + /** + * compile directory + * @var string + */ + private $compile_dir = null; + /** + * plugins directory + * @var array + */ + private $plugins_dir = array(); + /** + * cache directory + * @var string + */ + private $cache_dir = null; + /** + * config directory + * @var array + */ + private $config_dir = array(); + /** + * force template compiling? + * @var boolean + */ + public $force_compile = false; + /** + * check template for modifications? + * @var boolean + */ + public $compile_check = true; + /** + * use sub dirs for compiled/cached files? + * @var boolean + */ + public $use_sub_dirs = false; + /** + * allow ambiguous resources (that are made unique by the resource handler) + * @var boolean + */ + public $allow_ambiguous_resources = false; + /** + * caching enabled + * @var boolean + */ + public $caching = false; + /** + * merge compiled includes + * @var boolean + */ + public $merge_compiled_includes = false; + /** + * cache lifetime in seconds + * @var integer + */ + public $cache_lifetime = 3600; + /** + * force cache file creation + * @var boolean + */ + public $force_cache = false; + /** + * Set this if you want different sets of cache files for the same + * templates. + * + * @var string + */ + public $cache_id = null; + /** + * Set this if you want different sets of compiled files for the same + * templates. + * + * @var string + */ + public $compile_id = null; + /** + * template left-delimiter + * @var string + */ + public $left_delimiter = "{"; + /** + * template right-delimiter + * @var string + */ + public $right_delimiter = "}"; + /**#@+ + * security + */ + /** + * class name + * + * This should be instance of Smarty_Security. + * + * @var string + * @see Smarty_Security + */ + public $security_class = 'Smarty_Security'; + /** + * implementation of security class + * + * @var Smarty_Security + */ + public $security_policy = null; + /** + * controls handling of PHP-blocks + * + * @var integer + */ + public $php_handling = self::PHP_PASSTHRU; + /** + * controls if the php template file resource is allowed + * + * @var bool + */ + public $allow_php_templates = false; + /** + * Should compiled-templates be prevented from being called directly? + * + * {@internal + * Currently used by Smarty_Internal_Template only. + * }} + * + * @var boolean + */ + public $direct_access_security = true; + /**#@-*/ + /** + * debug mode + * + * Setting this to true enables the debug-console. + * + * @var boolean + */ + public $debugging = false; + /** + * This determines if debugging is enable-able from the browser. + *
    + *
  • NONE => no debugging control allowed
  • + *
  • URL => enable debugging when SMARTY_DEBUG is found in the URL.
  • + *
+ * @var string + */ + public $debugging_ctrl = 'NONE'; + /** + * Name of debugging URL-param. + * + * Only used when $debugging_ctrl is set to 'URL'. + * The name of the URL-parameter that activates debugging. + * + * @var type + */ + public $smarty_debug_id = 'SMARTY_DEBUG'; + /** + * Path of debug template. + * @var string + */ + public $debug_tpl = null; + /** + * When set, smarty uses this value as error_reporting-level. + * @var int + */ + public $error_reporting = null; + /** + * Internal flag for getTags() + * @var boolean + */ + public $get_used_tags = false; + + /**#@+ + * config var settings + */ + + /** + * Controls whether variables with the same name overwrite each other. + * @var boolean + */ + public $config_overwrite = true; + /** + * Controls whether config values of on/true/yes and off/false/no get converted to boolean. + * @var boolean + */ + public $config_booleanize = true; + /** + * Controls whether hidden config sections/vars are read from the file. + * @var boolean + */ + public $config_read_hidden = false; + + /**#@-*/ + + /**#@+ + * resource locking + */ + + /** + * locking concurrent compiles + * @var boolean + */ + public $compile_locking = true; + /** + * Controls whether cache resources should emply locking mechanism + * @var boolean + */ + public $cache_locking = false; + /** + * seconds to wait for acquiring a lock before ignoring the write lock + * @var float + */ + public $locking_timeout = 10; + + /**#@-*/ + + /** + * global template functions + * @var array + */ + public $template_functions = array(); + /** + * resource type used if none given + * + * Must be an valid key of $registered_resources. + * @var string + */ + public $default_resource_type = 'file'; + /** + * caching type + * + * Must be an element of $cache_resource_types. + * + * @var string + */ + public $caching_type = 'file'; + /** + * internal config properties + * @var array + */ + public $properties = array(); + /** + * config type + * @var string + */ + public $default_config_type = 'file'; + /** + * cached template objects + * @var array + */ + public $template_objects = array(); + /** + * check If-Modified-Since headers + * @var boolean + */ + public $cache_modified_check = false; + /** + * registered plugins + * @var array + */ + public $registered_plugins = array(); + /** + * plugin search order + * @var array + */ + public $plugin_search_order = array('function', 'block', 'compiler', 'class'); + /** + * registered objects + * @var array + */ + public $registered_objects = array(); + /** + * registered classes + * @var array + */ + public $registered_classes = array(); + /** + * registered filters + * @var array + */ + public $registered_filters = array(); + /** + * registered resources + * @var array + */ + public $registered_resources = array(); + /** + * resource handler cache + * @var array + */ + public $_resource_handlers = array(); + /** + * registered cache resources + * @var array + */ + public $registered_cache_resources = array(); + /** + * cache resource handler cache + * @var array + */ + public $_cacheresource_handlers = array(); + /** + * autoload filter + * @var array + */ + public $autoload_filters = array(); + /** + * default modifier + * @var array + */ + public $default_modifiers = array(); + /** + * autoescape variable output + * @var boolean + */ + public $escape_html = false; + /** + * global internal smarty vars + * @var array + */ + public static $_smarty_vars = array(); + /** + * start time for execution time calculation + * @var int + */ + public $start_time = 0; + /** + * default file permissions + * @var int + */ + public $_file_perms = 0644; + /** + * default dir permissions + * @var int + */ + public $_dir_perms = 0771; + /** + * block tag hierarchy + * @var array + */ + public $_tag_stack = array(); + /** + * self pointer to Smarty object + * @var Smarty + */ + public $smarty; + /** + * required by the compiler for BC + * @var string + */ + public $_current_file = null; + /** + * internal flag to enable parser debugging + * @var bool + */ + public $_parserdebug = false; + /** + * Saved parameter of merged templates during compilation + * + * @var array + */ + public $merged_templates_func = array(); + /**#@-*/ + + /** + * Initialize new Smarty object + * + */ + public function __construct() + { + // selfpointer needed by some other class methods + $this->smarty = $this; + if (is_callable('mb_internal_encoding')) { + mb_internal_encoding(Smarty::$_CHARSET); + } + $this->start_time = microtime(true); + // set default dirs + $this->setTemplateDir('.' . DS . 'templates' . DS) + ->setCompileDir('.' . DS . 'templates_c' . DS) + ->setPluginsDir(SMARTY_PLUGINS_DIR) + ->setCacheDir('.' . DS . 'cache' . DS) + ->setConfigDir('.' . DS . 'configs' . DS); + + $this->debug_tpl = 'file:' . dirname(__FILE__) . '/debug.tpl'; + if (isset($_SERVER['SCRIPT_NAME'])) { + $this->assignGlobal('SCRIPT_NAME', $_SERVER['SCRIPT_NAME']); + } + } + + + /** + * Class destructor + */ + public function __destruct() + { + // intentionally left blank + } + + /** + * <> set selfpointer on cloned object + */ + public function __clone() + { + $this->smarty = $this; + } + + + /** + * <> Generic getter. + * + * Calls the appropriate getter function. + * Issues an E_USER_NOTICE if no valid getter is found. + * + * @param string $name property name + * @return mixed + */ + public function __get($name) + { + $allowed = array( + 'template_dir' => 'getTemplateDir', + 'config_dir' => 'getConfigDir', + 'plugins_dir' => 'getPluginsDir', + 'compile_dir' => 'getCompileDir', + 'cache_dir' => 'getCacheDir', + ); + + if (isset($allowed[$name])) { + return $this->{$allowed[$name]}(); + } else { + trigger_error('Undefined property: '. get_class($this) .'::$'. $name, E_USER_NOTICE); + } + } + + /** + * <> Generic setter. + * + * Calls the appropriate setter function. + * Issues an E_USER_NOTICE if no valid setter is found. + * + * @param string $name property name + * @param mixed $value parameter passed to setter + */ + public function __set($name, $value) + { + $allowed = array( + 'template_dir' => 'setTemplateDir', + 'config_dir' => 'setConfigDir', + 'plugins_dir' => 'setPluginsDir', + 'compile_dir' => 'setCompileDir', + 'cache_dir' => 'setCacheDir', + ); + + if (isset($allowed[$name])) { + $this->{$allowed[$name]}($value); + } else { + trigger_error('Undefined property: ' . get_class($this) . '::$' . $name, E_USER_NOTICE); + } + } + + /** + * Check if a template resource exists + * + * @param string $resource_name template name + * @return boolean status + */ + public function templateExists($resource_name) + { + // create template object + $save = $this->template_objects; + $tpl = new $this->template_class($resource_name, $this); + // check if it does exists + $result = $tpl->source->exists; + $this->template_objects = $save; + return $result; + } + + /** + * Returns a single or all global variables + * + * @param object $smarty + * @param string $varname variable name or null + * @return string variable value or or array of variables + */ + public function getGlobal($varname = null) + { + if (isset($varname)) { + if (isset(self::$global_tpl_vars[$varname])) { + return self::$global_tpl_vars[$varname]->value; + } else { + return ''; + } + } else { + $_result = array(); + foreach (self::$global_tpl_vars AS $key => $var) { + $_result[$key] = $var->value; + } + return $_result; + } + } + + /** + * Empty cache folder + * + * @param integer $exp_time expiration time + * @param string $type resource type + * @return integer number of cache files deleted + */ + function clearAllCache($exp_time = null, $type = null) + { + // load cache resource and call clearAll + $_cache_resource = Smarty_CacheResource::load($this, $type); + Smarty_CacheResource::invalidLoadedCache($this); + return $_cache_resource->clearAll($this, $exp_time); + } + + /** + * Empty cache for a specific template + * + * @param string $template_name template name + * @param string $cache_id cache id + * @param string $compile_id compile id + * @param integer $exp_time expiration time + * @param string $type resource type + * @return integer number of cache files deleted + */ + public function clearCache($template_name, $cache_id = null, $compile_id = null, $exp_time = null, $type = null) + { + // load cache resource and call clear + $_cache_resource = Smarty_CacheResource::load($this, $type); + Smarty_CacheResource::invalidLoadedCache($this); + return $_cache_resource->clear($this, $template_name, $cache_id, $compile_id, $exp_time); + } + + /** + * Loads security class and enables security + * + * @param string|Smarty_Security $security_class if a string is used, it must be class-name + * @return Smarty current Smarty instance for chaining + * @throws SmartyException when an invalid class name is provided + */ + public function enableSecurity($security_class = null) + { + if ($security_class instanceof Smarty_Security) { + $this->security_policy = $security_class; + return $this; + } elseif (is_object($security_class)) { + throw new SmartyException("Class '" . get_class($security_class) . "' must extend Smarty_Security."); + } + if ($security_class == null) { + $security_class = $this->security_class; + } + if (!class_exists($security_class)) { + throw new SmartyException("Security class '$security_class' is not defined"); + } elseif ($security_class !== 'Smarty_Security' && !is_subclass_of($security_class, 'Smarty_Security')) { + throw new SmartyException("Class '$security_class' must extend Smarty_Security."); + } else { + $this->security_policy = new $security_class($this); + } + + return $this; + } + + /** + * Disable security + * @return Smarty current Smarty instance for chaining + */ + public function disableSecurity() + { + $this->security_policy = null; + + return $this; + } + + /** + * Set template directory + * + * @param string|array $template_dir directory(s) of template sources + * @return Smarty current Smarty instance for chaining + */ + public function setTemplateDir($template_dir) + { + $this->template_dir = array(); + foreach ((array) $template_dir as $k => $v) { + $this->template_dir[$k] = rtrim($v, '/\\') . DS; + } + + $this->joined_template_dir = join(DIRECTORY_SEPARATOR, $this->template_dir); + return $this; + } + + /** + * Add template directory(s) + * + * @param string|array $template_dir directory(s) of template sources + * @param string $key of the array element to assign the template dir to + * @return Smarty current Smarty instance for chaining + * @throws SmartyException when the given template directory is not valid + */ + public function addTemplateDir($template_dir, $key=null) + { + // make sure we're dealing with an array + $this->template_dir = (array) $this->template_dir; + + if (is_array($template_dir)) { + foreach ($template_dir as $k => $v) { + if (is_int($k)) { + // indexes are not merged but appended + $this->template_dir[] = rtrim($v, '/\\') . DS; + } else { + // string indexes are overridden + $this->template_dir[$k] = rtrim($v, '/\\') . DS; + } + } + } elseif ($key !== null) { + // override directory at specified index + $this->template_dir[$key] = rtrim($template_dir, '/\\') . DS; + } else { + // append new directory + $this->template_dir[] = rtrim($template_dir, '/\\') . DS; + } + $this->joined_template_dir = join(DIRECTORY_SEPARATOR, $this->template_dir); + return $this; + } + + /** + * Get template directories + * + * @param mixed index of directory to get, null to get all + * @return array|string list of template directories, or directory of $index + */ + public function getTemplateDir($index=null) + { + if ($index !== null) { + return isset($this->template_dir[$index]) ? $this->template_dir[$index] : null; + } + + return (array)$this->template_dir; + } + + /** + * Set config directory + * + * @param string|array $template_dir directory(s) of configuration sources + * @return Smarty current Smarty instance for chaining + */ + public function setConfigDir($config_dir) + { + $this->config_dir = array(); + foreach ((array) $config_dir as $k => $v) { + $this->config_dir[$k] = rtrim($v, '/\\') . DS; + } + + $this->joined_config_dir = join(DIRECTORY_SEPARATOR, $this->config_dir); + return $this; + } + + /** + * Add config directory(s) + * + * @param string|array $config_dir directory(s) of config sources + * @param string key of the array element to assign the config dir to + * @return Smarty current Smarty instance for chaining + */ + public function addConfigDir($config_dir, $key=null) + { + // make sure we're dealing with an array + $this->config_dir = (array) $this->config_dir; + + if (is_array($config_dir)) { + foreach ($config_dir as $k => $v) { + if (is_int($k)) { + // indexes are not merged but appended + $this->config_dir[] = rtrim($v, '/\\') . DS; + } else { + // string indexes are overridden + $this->config_dir[$k] = rtrim($v, '/\\') . DS; + } + } + } elseif( $key !== null ) { + // override directory at specified index + $this->config_dir[$key] = rtrim($config_dir, '/\\') . DS; + } else { + // append new directory + $this->config_dir[] = rtrim($config_dir, '/\\') . DS; + } + + $this->joined_config_dir = join(DIRECTORY_SEPARATOR, $this->config_dir); + return $this; + } + + /** + * Get config directory + * + * @param mixed index of directory to get, null to get all + * @return array|string configuration directory + */ + public function getConfigDir($index=null) + { + if ($index !== null) { + return isset($this->config_dir[$index]) ? $this->config_dir[$index] : null; + } + + return (array)$this->config_dir; + } + + /** + * Set plugins directory + * + * @param string|array $plugins_dir directory(s) of plugins + * @return Smarty current Smarty instance for chaining + */ + public function setPluginsDir($plugins_dir) + { + $this->plugins_dir = array(); + foreach ((array)$plugins_dir as $k => $v) { + $this->plugins_dir[$k] = rtrim($v, '/\\') . DS; + } + + return $this; + } + + /** + * Adds directory of plugin files + * + * @param object $smarty + * @param string $ |array $ plugins folder + * @return Smarty current Smarty instance for chaining + */ + public function addPluginsDir($plugins_dir) + { + // make sure we're dealing with an array + $this->plugins_dir = (array) $this->plugins_dir; + + if (is_array($plugins_dir)) { + foreach ($plugins_dir as $k => $v) { + if (is_int($k)) { + // indexes are not merged but appended + $this->plugins_dir[] = rtrim($v, '/\\') . DS; + } else { + // string indexes are overridden + $this->plugins_dir[$k] = rtrim($v, '/\\') . DS; + } + } + } else { + // append new directory + $this->plugins_dir[] = rtrim($plugins_dir, '/\\') . DS; + } + + $this->plugins_dir = array_unique($this->plugins_dir); + return $this; + } + + /** + * Get plugin directories + * + * @return array list of plugin directories + */ + public function getPluginsDir() + { + return (array)$this->plugins_dir; + } + + /** + * Set compile directory + * + * @param string $compile_dir directory to store compiled templates in + * @return Smarty current Smarty instance for chaining + */ + public function setCompileDir($compile_dir) + { + $this->compile_dir = rtrim($compile_dir, '/\\') . DS; + if (!isset(Smarty::$_muted_directories[$this->compile_dir])) { + Smarty::$_muted_directories[$this->compile_dir] = null; + } + return $this; + } + + /** + * Get compiled directory + * + * @return string path to compiled templates + */ + public function getCompileDir() + { + return $this->compile_dir; + } + + /** + * Set cache directory + * + * @param string $cache_dir directory to store cached templates in + * @return Smarty current Smarty instance for chaining + */ + public function setCacheDir($cache_dir) + { + $this->cache_dir = rtrim($cache_dir, '/\\') . DS; + if (!isset(Smarty::$_muted_directories[$this->cache_dir])) { + Smarty::$_muted_directories[$this->cache_dir] = null; + } + return $this; + } + + /** + * Get cache directory + * + * @return string path of cache directory + */ + public function getCacheDir() + { + return $this->cache_dir; + } + + /** + * Set default modifiers + * + * @param array|string $modifiers modifier or list of modifiers to set + * @return Smarty current Smarty instance for chaining + */ + public function setDefaultModifiers($modifiers) + { + $this->default_modifiers = (array) $modifiers; + return $this; + } + + /** + * Add default modifiers + * + * @param array|string $modifiers modifier or list of modifiers to add + * @return Smarty current Smarty instance for chaining + */ + public function addDefaultModifiers($modifiers) + { + if (is_array($modifiers)) { + $this->default_modifiers = array_merge($this->default_modifiers, $modifiers); + } else { + $this->default_modifiers[] = $modifiers; + } + + return $this; + } + + /** + * Get default modifiers + * + * @return array list of default modifiers + */ + public function getDefaultModifiers() + { + return $this->default_modifiers; + } + + + /** + * Set autoload filters + * + * @param array $filters filters to load automatically + * @param string $type "pre", "output", … specify the filter type to set. Defaults to none treating $filters' keys as the appropriate types + * @return Smarty current Smarty instance for chaining + */ + public function setAutoloadFilters($filters, $type=null) + { + if ($type !== null) { + $this->autoload_filters[$type] = (array) $filters; + } else { + $this->autoload_filters = (array) $filters; + } + + return $this; + } + + /** + * Add autoload filters + * + * @param array $filters filters to load automatically + * @param string $type "pre", "output", … specify the filter type to set. Defaults to none treating $filters' keys as the appropriate types + * @return Smarty current Smarty instance for chaining + */ + public function addAutoloadFilters($filters, $type=null) + { + if ($type !== null) { + if (!empty($this->autoload_filters[$type])) { + $this->autoload_filters[$type] = array_merge($this->autoload_filters[$type], (array) $filters); + } else { + $this->autoload_filters[$type] = (array) $filters; + } + } else { + foreach ((array) $filters as $key => $value) { + if (!empty($this->autoload_filters[$key])) { + $this->autoload_filters[$key] = array_merge($this->autoload_filters[$key], (array) $value); + } else { + $this->autoload_filters[$key] = (array) $value; + } + } + } + + return $this; + } + + /** + * Get autoload filters + * + * @param string $type type of filter to get autoloads for. Defaults to all autoload filters + * @return array array( 'type1' => array( 'filter1', 'filter2', … ) ) or array( 'filter1', 'filter2', …) if $type was specified + */ + public function getAutoloadFilters($type=null) + { + if ($type !== null) { + return isset($this->autoload_filters[$type]) ? $this->autoload_filters[$type] : array(); + } + + return $this->autoload_filters; + } + + /** + * return name of debugging template + * + * @return string + */ + public function getDebugTemplate() + { + return $this->debug_tpl; + } + + /** + * set the debug template + * + * @param string $tpl_name + * @return Smarty current Smarty instance for chaining + * @throws SmartyException if file is not readable + */ + public function setDebugTemplate($tpl_name) + { + if (!is_readable($tpl_name)) { + throw new SmartyException("Unknown file '{$tpl_name}'"); + } + $this->debug_tpl = $tpl_name; + + return $this; + } + + /** + * creates a template object + * + * @param string $template the resource handle of the template file + * @param mixed $cache_id cache id to be used with this template + * @param mixed $compile_id compile id to be used with this template + * @param object $parent next higher level of Smarty variables + * @param boolean $do_clone flag is Smarty object shall be cloned + * @return object template object + */ + public function createTemplate($template, $cache_id = null, $compile_id = null, $parent = null, $do_clone = true) + { + if (!empty($cache_id) && (is_object($cache_id) || is_array($cache_id))) { + $parent = $cache_id; + $cache_id = null; + } + if (!empty($parent) && is_array($parent)) { + $data = $parent; + $parent = null; + } else { + $data = null; + } + // default to cache_id and compile_id of Smarty object + $cache_id = $cache_id === null ? $this->cache_id : $cache_id; + $compile_id = $compile_id === null ? $this->compile_id : $compile_id; + // already in template cache? + if ($this->allow_ambiguous_resources) { + $_templateId = Smarty_Resource::getUniqueTemplateName($this, $template) . $cache_id . $compile_id; + } else { + $_templateId = $this->joined_template_dir . '#' . $template . $cache_id . $compile_id; + } + if (isset($_templateId[150])) { + $_templateId = sha1($_templateId); + } + if ($do_clone) { + if (isset($this->template_objects[$_templateId])) { + // return cached template object + $tpl = clone $this->template_objects[$_templateId]; + $tpl->smarty = clone $tpl->smarty; + $tpl->parent = $parent; + $tpl->tpl_vars = array(); + $tpl->config_vars = array(); + } else { + $tpl = new $this->template_class($template, clone $this, $parent, $cache_id, $compile_id); + } + } else { + if (isset($this->template_objects[$_templateId])) { + // return cached template object + $tpl = $this->template_objects[$_templateId]; + $tpl->parent = $parent; + $tpl->tpl_vars = array(); + $tpl->config_vars = array(); + } else { + $tpl = new $this->template_class($template, $this, $parent, $cache_id, $compile_id); + } + } + // fill data if present + if (!empty($data) && is_array($data)) { + // set up variable values + foreach ($data as $_key => $_val) { + $tpl->tpl_vars[$_key] = new Smarty_variable($_val); + } + } + return $tpl; + } + + + /** + * Takes unknown classes and loads plugin files for them + * class name format: Smarty_PluginType_PluginName + * plugin filename format: plugintype.pluginname.php + * + * @param string $plugin_name class plugin name to load + * @param bool $check check if already loaded + * @return string |boolean filepath of loaded file or false + */ + public function loadPlugin($plugin_name, $check = true) + { + // if function or class exists, exit silently (already loaded) + if ($check && (is_callable($plugin_name) || class_exists($plugin_name, false))) { + return true; + } + // Plugin name is expected to be: Smarty_[Type]_[Name] + $_name_parts = explode('_', $plugin_name, 3); + // class name must have three parts to be valid plugin + // count($_name_parts) < 3 === !isset($_name_parts[2]) + if (!isset($_name_parts[2]) || strtolower($_name_parts[0]) !== 'smarty') { + throw new SmartyException("plugin {$plugin_name} is not a valid name format"); + return false; + } + // if type is "internal", get plugin from sysplugins + if (strtolower($_name_parts[1]) == 'internal') { + $file = SMARTY_SYSPLUGINS_DIR . strtolower($plugin_name) . '.php'; + if (file_exists($file)) { + require_once($file); + return $file; + } else { + return false; + } + } + // plugin filename is expected to be: [type].[name].php + $_plugin_filename = "{$_name_parts[1]}.{$_name_parts[2]}.php"; + + $_stream_resolve_include_path = function_exists('stream_resolve_include_path'); + + // loop through plugin dirs and find the plugin + foreach($this->getPluginsDir() as $_plugin_dir) { + $names = array( + $_plugin_dir . $_plugin_filename, + $_plugin_dir . strtolower($_plugin_filename), + ); + foreach ($names as $file) { + if (file_exists($file)) { + require_once($file); + return $file; + } + if ($this->use_include_path && !preg_match('/^([\/\\\\]|[a-zA-Z]:[\/\\\\])/', $_plugin_dir)) { + // try PHP include_path + if ($_stream_resolve_include_path) { + $file = stream_resolve_include_path($file); + } else { + $file = Smarty_Internal_Get_Include_Path::getIncludePath($file); + } + + if ($file !== false) { + require_once($file); + return $file; + } + } + } + } + // no plugin loaded + return false; + } + + /** + * Compile all template files + * + * @param string $extension file extension + * @param bool $force_compile force all to recompile + * @param int $time_limit + * @param int $max_errors + * @return integer number of template files recompiled + */ + public function compileAllTemplates($extention = '.tpl', $force_compile = false, $time_limit = 0, $max_errors = null) + { + return Smarty_Internal_Utility::compileAllTemplates($extention, $force_compile, $time_limit, $max_errors, $this); + } + + /** + * Compile all config files + * + * @param string $extension file extension + * @param bool $force_compile force all to recompile + * @param int $time_limit + * @param int $max_errors + * @return integer number of template files recompiled + */ + public function compileAllConfig($extention = '.conf', $force_compile = false, $time_limit = 0, $max_errors = null) + { + return Smarty_Internal_Utility::compileAllConfig($extention, $force_compile, $time_limit, $max_errors, $this); + } + + /** + * Delete compiled template file + * + * @param string $resource_name template name + * @param string $compile_id compile id + * @param integer $exp_time expiration time + * @return integer number of template files deleted + */ + public function clearCompiledTemplate($resource_name = null, $compile_id = null, $exp_time = null) + { + return Smarty_Internal_Utility::clearCompiledTemplate($resource_name, $compile_id, $exp_time, $this); + } + + + /** + * Return array of tag/attributes of all tags used by an template + * + * @param object $templae template object + * @return array of tag/attributes + */ + public function getTags(Smarty_Internal_Template $template) + { + return Smarty_Internal_Utility::getTags($template); + } + + /** + * Run installation test + * + * @param array $errors Array to write errors into, rather than outputting them + * @return boolean true if setup is fine, false if something is wrong + */ + public function testInstall(&$errors=null) + { + return Smarty_Internal_Utility::testInstall($this, $errors); + } + + /** + * Error Handler to mute expected messages + * + * @link http://php.net/set_error_handler + * @param integer $errno Error level + * @return boolean + */ + public static function mutingErrorHandler($errno, $errstr, $errfile, $errline, $errcontext) + { + $_is_muted_directory = false; + + // add the SMARTY_DIR to the list of muted directories + if (!isset(Smarty::$_muted_directories[SMARTY_DIR])) { + $smarty_dir = realpath(SMARTY_DIR); + if ($smarty_dir !== false) { + Smarty::$_muted_directories[SMARTY_DIR] = array( + 'file' => $smarty_dir, + 'length' => strlen($smarty_dir), + ); + } + } + + // walk the muted directories and test against $errfile + foreach (Smarty::$_muted_directories as $key => &$dir) { + if (!$dir) { + // resolve directory and length for speedy comparisons + $file = realpath($key); + if ($file === false) { + // this directory does not exist, remove and skip it + unset(Smarty::$_muted_directories[$key]); + continue; + } + $dir = array( + 'file' => $file, + 'length' => strlen($file), + ); + } + if (!strncmp($errfile, $dir['file'], $dir['length'])) { + $_is_muted_directory = true; + break; + } + } + + // pass to next error handler if this error did not occur inside SMARTY_DIR + // or the error was within smarty but masked to be ignored + if (!$_is_muted_directory || ($errno && $errno & error_reporting())) { + if (Smarty::$_previous_error_handler) { + return call_user_func(Smarty::$_previous_error_handler, $errno, $errstr, $errfile, $errline, $errcontext); + } else { + return false; + } + } + } + + /** + * Enable error handler to mute expected messages + * + * @return void + */ + public static function muteExpectedErrors() + { + /* + error muting is done because some people implemented custom error_handlers using + http://php.net/set_error_handler and for some reason did not understand the following paragraph: + + It is important to remember that the standard PHP error handler is completely bypassed for the + error types specified by error_types unless the callback function returns FALSE. + error_reporting() settings will have no effect and your error handler will be called regardless - + however you are still able to read the current value of error_reporting and act appropriately. + Of particular note is that this value will be 0 if the statement that caused the error was + prepended by the @ error-control operator. + + Smarty deliberately uses @filemtime() over file_exists() and filemtime() in some places. Reasons include + - @filemtime() is almost twice as fast as using an additional file_exists() + - between file_exists() and filemtime() a possible race condition is opened, + which does not exist using the simple @filemtime() approach. + */ + $error_handler = array('Smarty', 'mutingErrorHandler'); + $previous = set_error_handler($error_handler); + + // avoid dead loops + if ($previous !== $error_handler) { + Smarty::$_previous_error_handler = $previous; + } + } + + /** + * Disable error handler muting expected messages + * + * @return void + */ + public static function unmuteExpectedErrors() + { + restore_error_handler(); + } +} + +// Check if we're running on windows +Smarty::$_IS_WINDOWS = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; + +// let PCRE (preg_*) treat strings as ISO-8859-1 if we're not dealing with UTF-8 +if (Smarty::$_CHARSET !== 'UTF-8') { + Smarty::$_UTF8_MODIFIER = ''; +} + +/** + * Smarty exception class + * @package Smarty + */ +class SmartyException extends Exception { + public static $escape = true; + public function __construct($message) { + $this->message = self::$escape ? htmlentities($message) : $message; + } +} + +/** + * Smarty compiler exception class + * @package Smarty + */ +class SmartyCompilerException extends SmartyException { +} + +/** + * Autoloader + */ +function smartyAutoload($class) +{ + $_class = strtolower($class); + $_classes = array( + 'smarty_config_source' => true, + 'smarty_config_compiled' => true, + 'smarty_security' => true, + 'smarty_cacheresource' => true, + 'smarty_cacheresource_custom' => true, + 'smarty_cacheresource_keyvaluestore' => true, + 'smarty_resource' => true, + 'smarty_resource_custom' => true, + 'smarty_resource_uncompiled' => true, + 'smarty_resource_recompiled' => true, + ); + + if (!strncmp($_class, 'smarty_internal_', 16) || isset($_classes[$_class])) { + include SMARTY_SYSPLUGINS_DIR . $_class . '.php'; + } +} + +?> diff --git a/public/include/smarty/libs/SmartyBC.class.php b/public/include/smarty/libs/SmartyBC.class.php new file mode 100644 index 00000000..f8f0a138 --- /dev/null +++ b/public/include/smarty/libs/SmartyBC.class.php @@ -0,0 +1,460 @@ + + * @author Uwe Tews + * @author Rodney Rehm + * @package Smarty + */ +/** + * @ignore + */ +require(dirname(__FILE__) . '/Smarty.class.php'); + +/** + * Smarty Backward Compatability Wrapper Class + * + * @package Smarty + */ +class SmartyBC extends Smarty { + + /** + * Smarty 2 BC + * @var string + */ + public $_version = self::SMARTY_VERSION; + + /** + * Initialize new SmartyBC object + * + * @param array $options options to set during initialization, e.g. array( 'forceCompile' => false ) + */ + public function __construct(array $options=array()) + { + parent::__construct($options); + // register {php} tag + $this->registerPlugin('block', 'php', 'smarty_php_tag'); + } + + /** + * wrapper for assign_by_ref + * + * @param string $tpl_var the template variable name + * @param mixed &$value the referenced value to assign + */ + public function assign_by_ref($tpl_var, &$value) + { + $this->assignByRef($tpl_var, $value); + } + + /** + * wrapper for append_by_ref + * + * @param string $tpl_var the template variable name + * @param mixed &$value the referenced value to append + * @param boolean $merge flag if array elements shall be merged + */ + public function append_by_ref($tpl_var, &$value, $merge = false) + { + $this->appendByRef($tpl_var, $value, $merge); + } + + /** + * clear the given assigned template variable. + * + * @param string $tpl_var the template variable to clear + */ + public function clear_assign($tpl_var) + { + $this->clearAssign($tpl_var); + } + + /** + * Registers custom function to be used in templates + * + * @param string $function the name of the template function + * @param string $function_impl the name of the PHP function to register + * @param bool $cacheable + * @param mixed $cache_attrs + */ + public function register_function($function, $function_impl, $cacheable=true, $cache_attrs=null) + { + $this->registerPlugin('function', $function, $function_impl, $cacheable, $cache_attrs); + } + + /** + * Unregisters custom function + * + * @param string $function name of template function + */ + public function unregister_function($function) + { + $this->unregisterPlugin('function', $function); + } + + /** + * Registers object to be used in templates + * + * @param string $object name of template object + * @param object $object_impl the referenced PHP object to register + * @param array $allowed list of allowed methods (empty = all) + * @param boolean $smarty_args smarty argument format, else traditional + * @param array $block_functs list of methods that are block format + */ + public function register_object($object, $object_impl, $allowed = array(), $smarty_args = true, $block_methods = array()) + { + settype($allowed, 'array'); + settype($smarty_args, 'boolean'); + $this->registerObject($object, $object_impl, $allowed, $smarty_args, $block_methods); + } + + /** + * Unregisters object + * + * @param string $object name of template object + */ + public function unregister_object($object) + { + $this->unregisterObject($object); + } + + /** + * Registers block function to be used in templates + * + * @param string $block name of template block + * @param string $block_impl PHP function to register + * @param bool $cacheable + * @param mixed $cache_attrs + */ + public function register_block($block, $block_impl, $cacheable=true, $cache_attrs=null) + { + $this->registerPlugin('block', $block, $block_impl, $cacheable, $cache_attrs); + } + + /** + * Unregisters block function + * + * @param string $block name of template function + */ + public function unregister_block($block) + { + $this->unregisterPlugin('block', $block); + } + + /** + * Registers compiler function + * + * @param string $function name of template function + * @param string $function_impl name of PHP function to register + * @param bool $cacheable + */ + public function register_compiler_function($function, $function_impl, $cacheable=true) + { + $this->registerPlugin('compiler', $function, $function_impl, $cacheable); + } + + /** + * Unregisters compiler function + * + * @param string $function name of template function + */ + public function unregister_compiler_function($function) + { + $this->unregisterPlugin('compiler', $function); + } + + /** + * Registers modifier to be used in templates + * + * @param string $modifier name of template modifier + * @param string $modifier_impl name of PHP function to register + */ + public function register_modifier($modifier, $modifier_impl) + { + $this->registerPlugin('modifier', $modifier, $modifier_impl); + } + + /** + * Unregisters modifier + * + * @param string $modifier name of template modifier + */ + public function unregister_modifier($modifier) + { + $this->unregisterPlugin('modifier', $modifier); + } + + /** + * Registers a resource to fetch a template + * + * @param string $type name of resource + * @param array $functions array of functions to handle resource + */ + public function register_resource($type, $functions) + { + $this->registerResource($type, $functions); + } + + /** + * Unregisters a resource + * + * @param string $type name of resource + */ + public function unregister_resource($type) + { + $this->unregisterResource($type); + } + + /** + * Registers a prefilter function to apply + * to a template before compiling + * + * @param callable $function + */ + public function register_prefilter($function) + { + $this->registerFilter('pre', $function); + } + + /** + * Unregisters a prefilter function + * + * @param callable $function + */ + public function unregister_prefilter($function) + { + $this->unregisterFilter('pre', $function); + } + + /** + * Registers a postfilter function to apply + * to a compiled template after compilation + * + * @param callable $function + */ + public function register_postfilter($function) + { + $this->registerFilter('post', $function); + } + + /** + * Unregisters a postfilter function + * + * @param callable $function + */ + public function unregister_postfilter($function) + { + $this->unregisterFilter('post', $function); + } + + /** + * Registers an output filter function to apply + * to a template output + * + * @param callable $function + */ + public function register_outputfilter($function) + { + $this->registerFilter('output', $function); + } + + /** + * Unregisters an outputfilter function + * + * @param callable $function + */ + public function unregister_outputfilter($function) + { + $this->unregisterFilter('output', $function); + } + + /** + * load a filter of specified type and name + * + * @param string $type filter type + * @param string $name filter name + */ + public function load_filter($type, $name) + { + $this->loadFilter($type, $name); + } + + /** + * clear cached content for the given template and cache id + * + * @param string $tpl_file name of template file + * @param string $cache_id name of cache_id + * @param string $compile_id name of compile_id + * @param string $exp_time expiration time + * @return boolean + */ + public function clear_cache($tpl_file = null, $cache_id = null, $compile_id = null, $exp_time = null) + { + return $this->clearCache($tpl_file, $cache_id, $compile_id, $exp_time); + } + + /** + * clear the entire contents of cache (all templates) + * + * @param string $exp_time expire time + * @return boolean + */ + public function clear_all_cache($exp_time = null) + { + return $this->clearCache(null, null, null, $exp_time); + } + + /** + * test to see if valid cache exists for this template + * + * @param string $tpl_file name of template file + * @param string $cache_id + * @param string $compile_id + * @return boolean + */ + public function is_cached($tpl_file, $cache_id = null, $compile_id = null) + { + return $this->isCached($tpl_file, $cache_id, $compile_id); + } + + /** + * clear all the assigned template variables. + */ + public function clear_all_assign() + { + $this->clearAllAssign(); + } + + /** + * clears compiled version of specified template resource, + * or all compiled template files if one is not specified. + * This function is for advanced use only, not normally needed. + * + * @param string $tpl_file + * @param string $compile_id + * @param string $exp_time + * @return boolean results of {@link smarty_core_rm_auto()} + */ + public function clear_compiled_tpl($tpl_file = null, $compile_id = null, $exp_time = null) + { + return $this->clearCompiledTemplate($tpl_file, $compile_id, $exp_time); + } + + /** + * Checks whether requested template exists. + * + * @param string $tpl_file + * @return boolean + */ + public function template_exists($tpl_file) + { + return $this->templateExists($tpl_file); + } + + /** + * Returns an array containing template variables + * + * @param string $name + * @return array + */ + public function get_template_vars($name=null) + { + return $this->getTemplateVars($name); + } + + /** + * Returns an array containing config variables + * + * @param string $name + * @return array + */ + public function get_config_vars($name=null) + { + return $this->getConfigVars($name); + } + + /** + * load configuration values + * + * @param string $file + * @param string $section + * @param string $scope + */ + public function config_load($file, $section = null, $scope = 'global') + { + $this->ConfigLoad($file, $section, $scope); + } + + /** + * return a reference to a registered object + * + * @param string $name + * @return object + */ + public function get_registered_object($name) + { + return $this->getRegisteredObject($name); + } + + /** + * clear configuration values + * + * @param string $var + */ + public function clear_config($var = null) + { + $this->clearConfig($var); + } + + /** + * trigger Smarty error + * + * @param string $error_msg + * @param integer $error_type + */ + public function trigger_error($error_msg, $error_type = E_USER_WARNING) + { + trigger_error("Smarty error: $error_msg", $error_type); + } + +} + +/** + * Smarty {php}{/php} block function + * + * @param array $params parameter list + * @param string $content contents of the block + * @param object $template template object + * @param boolean &$repeat repeat flag + * @return string content re-formatted + */ +function smarty_php_tag($params, $content, $template, &$repeat) +{ + eval($content); + return ''; +} + +?> \ No newline at end of file diff --git a/public/include/smarty/libs/debug.tpl b/public/include/smarty/libs/debug.tpl new file mode 100644 index 00000000..12eef0ff --- /dev/null +++ b/public/include/smarty/libs/debug.tpl @@ -0,0 +1,133 @@ +{capture name='_smarty_debug' assign=debug_output} + + + + Smarty Debug Console + + + + +

Smarty Debug Console - {if isset($template_name)}{$template_name|debug_print_var nofilter}{else}Total Time {$execution_time|string_format:"%.5f"}{/if}

+ +{if !empty($template_data)} +

included templates & config files (load time in seconds)

+ +
+{foreach $template_data as $template} + {$template.name} + + (compile {$template['compile_time']|string_format:"%.5f"}) (render {$template['render_time']|string_format:"%.5f"}) (cache {$template['cache_time']|string_format:"%.5f"}) + +
+{/foreach} +
+{/if} + +

assigned template variables

+ + + {foreach $assigned_vars as $vars} + + + + {/foreach} +
${$vars@key|escape:'html'}{$vars|debug_print_var nofilter}
+ +

assigned config file variables (outer template scope)

+ + + {foreach $config_vars as $vars} + + + + {/foreach} + +
{$vars@key|escape:'html'}{$vars|debug_print_var nofilter}
+ + +{/capture} + diff --git a/public/include/smarty/libs/plugins/block.textformat.php b/public/include/smarty/libs/plugins/block.textformat.php new file mode 100644 index 00000000..b22b104a --- /dev/null +++ b/public/include/smarty/libs/plugins/block.textformat.php @@ -0,0 +1,113 @@ + + * Name: textformat
+ * Purpose: format text a certain way with preset styles + * or custom wrap/indent settings
+ * Params: + *
+ * - style         - string (email)
+ * - indent        - integer (0)
+ * - wrap          - integer (80)
+ * - wrap_char     - string ("\n")
+ * - indent_char   - string (" ")
+ * - wrap_boundary - boolean (true)
+ * 
+ * + * @link http://www.smarty.net/manual/en/language.function.textformat.php {textformat} + * (Smarty online manual) + * @param array $params parameters + * @param string $content contents of the block + * @param Smarty_Internal_Template $template template object + * @param boolean &$repeat repeat flag + * @return string content re-formatted + * @author Monte Ohrt + */ +function smarty_block_textformat($params, $content, $template, &$repeat) +{ + if (is_null($content)) { + return; + } + + $style = null; + $indent = 0; + $indent_first = 0; + $indent_char = ' '; + $wrap = 80; + $wrap_char = "\n"; + $wrap_cut = false; + $assign = null; + + foreach ($params as $_key => $_val) { + switch ($_key) { + case 'style': + case 'indent_char': + case 'wrap_char': + case 'assign': + $$_key = (string)$_val; + break; + + case 'indent': + case 'indent_first': + case 'wrap': + $$_key = (int)$_val; + break; + + case 'wrap_cut': + $$_key = (bool)$_val; + break; + + default: + trigger_error("textformat: unknown attribute '$_key'"); + } + } + + if ($style == 'email') { + $wrap = 72; + } + // split into paragraphs + $_paragraphs = preg_split('![\r\n]{2}!', $content); + $_output = ''; + + + foreach ($_paragraphs as &$_paragraph) { + if (!$_paragraph) { + continue; + } + // convert mult. spaces & special chars to single space + $_paragraph = preg_replace(array('!\s+!' . Smarty::$_UTF8_MODIFIER, '!(^\s+)|(\s+$)!' . Smarty::$_UTF8_MODIFIER), array(' ', ''), $_paragraph); + // indent first line + if ($indent_first > 0) { + $_paragraph = str_repeat($indent_char, $indent_first) . $_paragraph; + } + // wordwrap sentences + if (Smarty::$_MBSTRING) { + require_once(SMARTY_PLUGINS_DIR . 'shared.mb_wordwrap.php'); + $_paragraph = smarty_mb_wordwrap($_paragraph, $wrap - $indent, $wrap_char, $wrap_cut); + } else { + $_paragraph = wordwrap($_paragraph, $wrap - $indent, $wrap_char, $wrap_cut); + } + // indent lines + if ($indent > 0) { + $_paragraph = preg_replace('!^!m', str_repeat($indent_char, $indent), $_paragraph); + } + } + $_output = implode($wrap_char . $wrap_char, $_paragraphs); + + if ($assign) { + $template->assign($assign, $_output); + } else { + return $_output; + } +} + +?> \ No newline at end of file diff --git a/public/include/smarty/libs/plugins/function.counter.php b/public/include/smarty/libs/plugins/function.counter.php new file mode 100644 index 00000000..3906badf --- /dev/null +++ b/public/include/smarty/libs/plugins/function.counter.php @@ -0,0 +1,78 @@ + + * Name: counter
+ * Purpose: print out a counter value + * + * @author Monte Ohrt + * @link http://www.smarty.net/manual/en/language.function.counter.php {counter} + * (Smarty online manual) + * @param array $params parameters + * @param Smarty_Internal_Template $template template object + * @return string|null + */ +function smarty_function_counter($params, $template) +{ + static $counters = array(); + + $name = (isset($params['name'])) ? $params['name'] : 'default'; + if (!isset($counters[$name])) { + $counters[$name] = array( + 'start'=>1, + 'skip'=>1, + 'direction'=>'up', + 'count'=>1 + ); + } + $counter =& $counters[$name]; + + if (isset($params['start'])) { + $counter['start'] = $counter['count'] = (int)$params['start']; + } + + if (!empty($params['assign'])) { + $counter['assign'] = $params['assign']; + } + + if (isset($counter['assign'])) { + $template->assign($counter['assign'], $counter['count']); + } + + if (isset($params['print'])) { + $print = (bool)$params['print']; + } else { + $print = empty($counter['assign']); + } + + if ($print) { + $retval = $counter['count']; + } else { + $retval = null; + } + + if (isset($params['skip'])) { + $counter['skip'] = $params['skip']; + } + + if (isset($params['direction'])) { + $counter['direction'] = $params['direction']; + } + + if ($counter['direction'] == "down") + $counter['count'] -= $counter['skip']; + else + $counter['count'] += $counter['skip']; + + return $retval; + +} + +?> \ No newline at end of file diff --git a/public/include/smarty/libs/plugins/function.cycle.php b/public/include/smarty/libs/plugins/function.cycle.php new file mode 100644 index 00000000..1778ffb5 --- /dev/null +++ b/public/include/smarty/libs/plugins/function.cycle.php @@ -0,0 +1,106 @@ + + * Name: cycle
+ * Date: May 3, 2002
+ * Purpose: cycle through given values
+ * Params: + *
+ * - name      - name of cycle (optional)
+ * - values    - comma separated list of values to cycle, or an array of values to cycle
+ *               (this can be left out for subsequent calls)
+ * - reset     - boolean - resets given var to true
+ * - print     - boolean - print var or not. default is true
+ * - advance   - boolean - whether or not to advance the cycle
+ * - delimiter - the value delimiter, default is ","
+ * - assign    - boolean, assigns to template var instead of printed.
+ * 
+ * Examples:
+ *
+ * {cycle values="#eeeeee,#d0d0d0d"}
+ * {cycle name=row values="one,two,three" reset=true}
+ * {cycle name=row}
+ * 
+ * + * @link http://www.smarty.net/manual/en/language.function.cycle.php {cycle} + * (Smarty online manual) + * @author Monte Ohrt + * @author credit to Mark Priatel + * @author credit to Gerard + * @author credit to Jason Sweat + * @version 1.3 + * @param array $params parameters + * @param Smarty_Internal_Template $template template object + * @return string|null + */ + +function smarty_function_cycle($params, $template) +{ + static $cycle_vars; + + $name = (empty($params['name'])) ? 'default' : $params['name']; + $print = (isset($params['print'])) ? (bool)$params['print'] : true; + $advance = (isset($params['advance'])) ? (bool)$params['advance'] : true; + $reset = (isset($params['reset'])) ? (bool)$params['reset'] : false; + + if (!isset($params['values'])) { + if(!isset($cycle_vars[$name]['values'])) { + trigger_error("cycle: missing 'values' parameter"); + return; + } + } else { + if(isset($cycle_vars[$name]['values']) + && $cycle_vars[$name]['values'] != $params['values'] ) { + $cycle_vars[$name]['index'] = 0; + } + $cycle_vars[$name]['values'] = $params['values']; + } + + if (isset($params['delimiter'])) { + $cycle_vars[$name]['delimiter'] = $params['delimiter']; + } elseif (!isset($cycle_vars[$name]['delimiter'])) { + $cycle_vars[$name]['delimiter'] = ','; + } + + if(is_array($cycle_vars[$name]['values'])) { + $cycle_array = $cycle_vars[$name]['values']; + } else { + $cycle_array = explode($cycle_vars[$name]['delimiter'],$cycle_vars[$name]['values']); + } + + if(!isset($cycle_vars[$name]['index']) || $reset ) { + $cycle_vars[$name]['index'] = 0; + } + + if (isset($params['assign'])) { + $print = false; + $template->assign($params['assign'], $cycle_array[$cycle_vars[$name]['index']]); + } + + if($print) { + $retval = $cycle_array[$cycle_vars[$name]['index']]; + } else { + $retval = null; + } + + if($advance) { + if ( $cycle_vars[$name]['index'] >= count($cycle_array) -1 ) { + $cycle_vars[$name]['index'] = 0; + } else { + $cycle_vars[$name]['index']++; + } + } + + return $retval; +} + +?> \ No newline at end of file diff --git a/public/include/smarty/libs/plugins/function.fetch.php b/public/include/smarty/libs/plugins/function.fetch.php new file mode 100644 index 00000000..eca1182d --- /dev/null +++ b/public/include/smarty/libs/plugins/function.fetch.php @@ -0,0 +1,214 @@ + + * Name: fetch
+ * Purpose: fetch file, web or ftp data and display results + * + * @link http://www.smarty.net/manual/en/language.function.fetch.php {fetch} + * (Smarty online manual) + * @author Monte Ohrt + * @param array $params parameters + * @param Smarty_Internal_Template $template template object + * @return string|null if the assign parameter is passed, Smarty assigns the result to a template variable + */ +function smarty_function_fetch($params, $template) +{ + if (empty($params['file'])) { + trigger_error("[plugin] fetch parameter 'file' cannot be empty",E_USER_NOTICE); + return; + } + + // strip file protocol + if (stripos($params['file'], 'file://') === 0) { + $params['file'] = substr($params['file'], 7); + } + + $protocol = strpos($params['file'], '://'); + if ($protocol !== false) { + $protocol = strtolower(substr($params['file'], 0, $protocol)); + } + + if (isset($template->smarty->security_policy)) { + if ($protocol) { + // remote resource (or php stream, …) + if(!$template->smarty->security_policy->isTrustedUri($params['file'])) { + return; + } + } else { + // local file + if(!$template->smarty->security_policy->isTrustedResourceDir($params['file'])) { + return; + } + } + } + + $content = ''; + if ($protocol == 'http') { + // http fetch + if($uri_parts = parse_url($params['file'])) { + // set defaults + $host = $server_name = $uri_parts['host']; + $timeout = 30; + $accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*"; + $agent = "Smarty Template Engine ". Smarty::SMARTY_VERSION; + $referer = ""; + $uri = !empty($uri_parts['path']) ? $uri_parts['path'] : '/'; + $uri .= !empty($uri_parts['query']) ? '?' . $uri_parts['query'] : ''; + $_is_proxy = false; + if(empty($uri_parts['port'])) { + $port = 80; + } else { + $port = $uri_parts['port']; + } + if(!empty($uri_parts['user'])) { + $user = $uri_parts['user']; + } + if(!empty($uri_parts['pass'])) { + $pass = $uri_parts['pass']; + } + // loop through parameters, setup headers + foreach($params as $param_key => $param_value) { + switch($param_key) { + case "file": + case "assign": + case "assign_headers": + break; + case "user": + if(!empty($param_value)) { + $user = $param_value; + } + break; + case "pass": + if(!empty($param_value)) { + $pass = $param_value; + } + break; + case "accept": + if(!empty($param_value)) { + $accept = $param_value; + } + break; + case "header": + if(!empty($param_value)) { + if(!preg_match('![\w\d-]+: .+!',$param_value)) { + trigger_error("[plugin] invalid header format '".$param_value."'",E_USER_NOTICE); + return; + } else { + $extra_headers[] = $param_value; + } + } + break; + case "proxy_host": + if(!empty($param_value)) { + $proxy_host = $param_value; + } + break; + case "proxy_port": + if(!preg_match('!\D!', $param_value)) { + $proxy_port = (int) $param_value; + } else { + trigger_error("[plugin] invalid value for attribute '".$param_key."'",E_USER_NOTICE); + return; + } + break; + case "agent": + if(!empty($param_value)) { + $agent = $param_value; + } + break; + case "referer": + if(!empty($param_value)) { + $referer = $param_value; + } + break; + case "timeout": + if(!preg_match('!\D!', $param_value)) { + $timeout = (int) $param_value; + } else { + trigger_error("[plugin] invalid value for attribute '".$param_key."'",E_USER_NOTICE); + return; + } + break; + default: + trigger_error("[plugin] unrecognized attribute '".$param_key."'",E_USER_NOTICE); + return; + } + } + if(!empty($proxy_host) && !empty($proxy_port)) { + $_is_proxy = true; + $fp = fsockopen($proxy_host,$proxy_port,$errno,$errstr,$timeout); + } else { + $fp = fsockopen($server_name,$port,$errno,$errstr,$timeout); + } + + if(!$fp) { + trigger_error("[plugin] unable to fetch: $errstr ($errno)",E_USER_NOTICE); + return; + } else { + if($_is_proxy) { + fputs($fp, 'GET ' . $params['file'] . " HTTP/1.0\r\n"); + } else { + fputs($fp, "GET $uri HTTP/1.0\r\n"); + } + if(!empty($host)) { + fputs($fp, "Host: $host\r\n"); + } + if(!empty($accept)) { + fputs($fp, "Accept: $accept\r\n"); + } + if(!empty($agent)) { + fputs($fp, "User-Agent: $agent\r\n"); + } + if(!empty($referer)) { + fputs($fp, "Referer: $referer\r\n"); + } + if(isset($extra_headers) && is_array($extra_headers)) { + foreach($extra_headers as $curr_header) { + fputs($fp, $curr_header."\r\n"); + } + } + if(!empty($user) && !empty($pass)) { + fputs($fp, "Authorization: BASIC ".base64_encode("$user:$pass")."\r\n"); + } + + fputs($fp, "\r\n"); + while(!feof($fp)) { + $content .= fgets($fp,4096); + } + fclose($fp); + $csplit = preg_split("!\r\n\r\n!",$content,2); + + $content = $csplit[1]; + + if(!empty($params['assign_headers'])) { + $template->assign($params['assign_headers'],preg_split("!\r\n!",$csplit[0])); + } + } + } else { + trigger_error("[plugin fetch] unable to parse URL, check syntax",E_USER_NOTICE); + return; + } + } else { + $content = @file_get_contents($params['file']); + if ($content === false) { + throw new SmartyException("{fetch} cannot read resource '" . $params['file'] ."'"); + } + } + + if (!empty($params['assign'])) { + $template->assign($params['assign'], $content); + } else { + return $content; + } +} + +?> \ No newline at end of file diff --git a/public/include/smarty/libs/plugins/function.html_checkboxes.php b/public/include/smarty/libs/plugins/function.html_checkboxes.php new file mode 100644 index 00000000..1866bc2f --- /dev/null +++ b/public/include/smarty/libs/plugins/function.html_checkboxes.php @@ -0,0 +1,233 @@ + + * Type: function
+ * Name: html_checkboxes
+ * Date: 24.Feb.2003
+ * Purpose: Prints out a list of checkbox input types
+ * Examples: + *
+ * {html_checkboxes values=$ids output=$names}
+ * {html_checkboxes values=$ids name='box' separator='
' output=$names} + * {html_checkboxes values=$ids checked=$checked separator='
' output=$names} + *
+ * Params: + *
+ * - name       (optional) - string default "checkbox"
+ * - values     (required) - array
+ * - options    (optional) - associative array
+ * - checked    (optional) - array default not set
+ * - separator  (optional) - ie 
or   + * - output (optional) - the output next to each checkbox + * - assign (optional) - assign the output as an array to this variable + * - escape (optional) - escape the content (not value), defaults to true + *
+ * + * @link http://www.smarty.net/manual/en/language.function.html.checkboxes.php {html_checkboxes} + * (Smarty online manual) + * @author Christopher Kvarme + * @author credits to Monte Ohrt + * @version 1.0 + * @param array $params parameters + * @param object $template template object + * @return string + * @uses smarty_function_escape_special_chars() + */ +function smarty_function_html_checkboxes($params, $template) +{ + require_once(SMARTY_PLUGINS_DIR . 'shared.escape_special_chars.php'); + + $name = 'checkbox'; + $values = null; + $options = null; + $selected = array(); + $separator = ''; + $escape = true; + $labels = true; + $label_ids = false; + $output = null; + + $extra = ''; + + foreach($params as $_key => $_val) { + switch($_key) { + case 'name': + case 'separator': + $$_key = (string) $_val; + break; + + case 'escape': + case 'labels': + case 'label_ids': + $$_key = (bool) $_val; + break; + + case 'options': + $$_key = (array) $_val; + break; + + case 'values': + case 'output': + $$_key = array_values((array) $_val); + break; + + case 'checked': + case 'selected': + if (is_array($_val)) { + $selected = array(); + foreach ($_val as $_sel) { + if (is_object($_sel)) { + if (method_exists($_sel, "__toString")) { + $_sel = smarty_function_escape_special_chars((string) $_sel->__toString()); + } else { + trigger_error("html_checkboxes: selected attribute contains an object of class '". get_class($_sel) ."' without __toString() method", E_USER_NOTICE); + continue; + } + } else { + $_sel = smarty_function_escape_special_chars((string) $_sel); + } + $selected[$_sel] = true; + } + } elseif (is_object($_val)) { + if (method_exists($_val, "__toString")) { + $selected = smarty_function_escape_special_chars((string) $_val->__toString()); + } else { + trigger_error("html_checkboxes: selected attribute is an object of class '". get_class($_val) ."' without __toString() method", E_USER_NOTICE); + } + } else { + $selected = smarty_function_escape_special_chars((string) $_val); + } + break; + + case 'checkboxes': + trigger_error('html_checkboxes: the use of the "checkboxes" attribute is deprecated, use "options" instead', E_USER_WARNING); + $options = (array) $_val; + break; + + case 'assign': + break; + + case 'strict': break; + + case 'disabled': + case 'readonly': + if (!empty($params['strict'])) { + if (!is_scalar($_val)) { + trigger_error("html_options: $_key attribute must be a scalar, only boolean true or string '$_key' will actually add the attribute", E_USER_NOTICE); + } + + if ($_val === true || $_val === $_key) { + $extra .= ' ' . $_key . '="' . smarty_function_escape_special_chars($_key) . '"'; + } + + break; + } + // omit break; to fall through! + + default: + if(!is_array($_val)) { + $extra .= ' '.$_key.'="'.smarty_function_escape_special_chars($_val).'"'; + } else { + trigger_error("html_checkboxes: extra attribute '$_key' cannot be an array", E_USER_NOTICE); + } + break; + } + } + + if (!isset($options) && !isset($values)) + return ''; /* raise error here? */ + + $_html_result = array(); + + if (isset($options)) { + foreach ($options as $_key=>$_val) { + $_html_result[] = smarty_function_html_checkboxes_output($name, $_key, $_val, $selected, $extra, $separator, $labels, $label_ids, $escape); + } + } else { + foreach ($values as $_i=>$_key) { + $_val = isset($output[$_i]) ? $output[$_i] : ''; + $_html_result[] = smarty_function_html_checkboxes_output($name, $_key, $_val, $selected, $extra, $separator, $labels, $label_ids, $escape); + } + } + + if(!empty($params['assign'])) { + $template->assign($params['assign'], $_html_result); + } else { + return implode("\n", $_html_result); + } + +} + +function smarty_function_html_checkboxes_output($name, $value, $output, $selected, $extra, $separator, $labels, $label_ids, $escape=true) { + $_output = ''; + + if (is_object($value)) { + if (method_exists($value, "__toString")) { + $value = (string) $value->__toString(); + } else { + trigger_error("html_options: value is an object of class '". get_class($value) ."' without __toString() method", E_USER_NOTICE); + return ''; + } + } else { + $value = (string) $value; + } + + if (is_object($output)) { + if (method_exists($output, "__toString")) { + $output = (string) $output->__toString(); + } else { + trigger_error("html_options: output is an object of class '". get_class($output) ."' without __toString() method", E_USER_NOTICE); + return ''; + } + } else { + $output = (string) $output; + } + + if ($labels) { + if ($label_ids) { + $_id = smarty_function_escape_special_chars(preg_replace('![^\w\-\.]!' . Smarty::$_UTF8_MODIFIER, '_', $name . '_' . $value)); + $_output .= '