commit fe5bfd71f934ae6d7fd1e05e5ad78ab58ed7581d Author: abhishek_almighty Date: Tue May 8 08:58:01 2018 +0530 Initial commit. Basic and market orders functionality complete diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c231a4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +vendor/ +.idea/ +includes/config.php \ No newline at end of file diff --git a/ajax/OrderMatchingAlgorithmAjax.php b/ajax/OrderMatchingAlgorithmAjax.php new file mode 100644 index 0000000..a841174 --- /dev/null +++ b/ajax/OrderMatchingAlgorithmAjax.php @@ -0,0 +1,49 @@ +OrderMatchingService($_POST['sel1'], $_POST['sel2']); + + /*If user is logged in user send him messages, if any*/ + if (checkLoginStatus()) { + + $std = new stdClass(); + $std->user = null; + $std->order = null; + $std->error = false; + $std->msg = null; + + if (isset($user_id)) { + + $validate_user = $UserClass->check_user($user_id); + + if($validate_user == "" || empty($validate_user)) { + $std->error = true; + $std->msg = "No such user exist. Please login again."; + echo json_encode($std); + return false; + } + + $std->user = $validate_user; + $std->order = $refresh_orders; + $std->error = false; + $std->msg = "userLoggedIn"; + + echo json_encode($std); + + } else { + return false; + } + } + } + } else { + return false; +} \ No newline at end of file diff --git a/ajax/pending_orders.php b/ajax/pending_orders.php new file mode 100644 index 0000000..c884ff3 --- /dev/null +++ b/ajax/pending_orders.php @@ -0,0 +1,102 @@ +user = null; + $std->order = null; + $std->error = false; + $std->msg = null; + + if (isset($_POST['sel1'], $_POST['qty'], $_POST['price'], $_POST['sel2'], $_POST['bs_rad'], $_POST['is_mkt'])) { + $WantAssetTypeId = trim($_POST['sel1']); + $OfferAssetTypeId = trim($_POST['sel2']); + $qty = (float) trim($_POST['qty']); + $price = (float) trim($_POST['price']); + $buy_sell = trim($_POST['bs_rad']); + $is_mkt = (bool) trim($_POST['is_mkt']); + + $orderStatusId = 2; // 0 -> cancelled; 1 -> complete; 2 -> pending + + if($WantAssetTypeId == '') { + $std->error = true; + $std->msg = "Please select first Blockchain contract."; + echo json_encode($std); + return false; + } + if($OfferAssetTypeId == '') { + $std->error = true; + $std->msg = "Please select second Blockchain contract."; + echo json_encode($std); + return false; + } + if($qty == '' || $qty < 0) { + $std->error = true; + $std->msg = "Please provide a valid quantity to be traded."; + echo json_encode($std); + return false; + } + if (!$is_mkt) { + if($price == '' || $price < 1) { + $std->error = true; + $std->msg = "Please provide a valid price. Price cannot be less than $1."; + echo json_encode($std); + return false; + } + } + + if ($buy_sell=='ex-buy') { + $orderTypeId = 0; // It is a buy + $order_type = 'm-buy'; // for market req + $total_trade_val = $qty * $price; + } elseif($buy_sell=='ex-sell') { + $orderTypeId = 1; // It is a sell + $order_type = 'm-sell'; // for market req + $total_trade_val = $qty; + } else { + $std->error = true; + $std->msg = "Invalid Buy or Sell order. Please choose Buy or Sell to proceed."; + echo json_encode($std); + return false; + } + + $place_order = ""; + $validate_user = ""; + if (isset($UserClass, $OrderClass)) { + + $validate_user = $UserClass->check_user($user_id); + + if($validate_user == "" || empty($validate_user)) { + $std->error = true; + $std->msg = "No such user exist. Please login again."; + echo json_encode($std); + return false; + } + + if ($is_mkt) { + $place_order = $OrderClass->market_order($order_type, $qty, $OfferAssetTypeId, $WantAssetTypeId); + } else { + $place_order = $OrderClass->insert_pending_order($orderTypeId, $qty, $price, $orderStatusId, $OfferAssetTypeId, $WantAssetTypeId); + } + + } + + $std->user = $validate_user; + $std->order = $place_order; + $std->error = false; + $std->msg = "Order placed successfully."; + echo json_encode($std); + return false; + } + $std->error = true; + $std->msg = "Please fill all the fields."; + echo json_encode($std); + return false; +} + + diff --git a/ajax/refresh_table.php b/ajax/refresh_table.php new file mode 100644 index 0000000..64154f4 --- /dev/null +++ b/ajax/refresh_table.php @@ -0,0 +1,29 @@ +buys = null; + $std->sells = null; + $std->message = array(); + $std->error = true; + + if (isset($OrderClass, $UserClass)) { + + $buy_list = $OrderClass->get_top_buy_sell_list(TOP_BUYS_TABLE, $bc1, $bc2, $asc_desc='DESC'); // buy + $sell_list = $OrderClass->get_top_buy_sell_list(TOP_SELLS_TABLE, $bc1, $bc2, $asc_desc='ASC'); // sell + + $std->buys = $buy_list; + $std->sells = $sell_list; + $std->error = false; + } + echo json_encode($std); + +} else { + return false; +} \ No newline at end of file diff --git a/classes/Orders.php b/classes/Orders.php new file mode 100644 index 0000000..72c39dc --- /dev/null +++ b/classes/Orders.php @@ -0,0 +1,1250 @@ +databaseConnection()) { + + $n = new DateTime("now", new DateTimeZone("Asia/Kolkata")); + $now = $n->format('Y-m-d H:i:s'); + + $query = $this->db_connection->prepare("INSERT INTO $top_table(`id`, `bc1`, `bc2`, `price`, `quantity`, `order_id`, `uid`, `insert_dt`) + VALUES ('', :bc1, :bc2, :price, :quantity, :orderId, :user_id, '$now')"); + $query->bindParam("bc1", $bc1); + $query->bindParam("bc2", $bc2); + $query->bindParam("price", $price); + $query->bindParam("quantity", $quantity); + $query->bindParam("orderId", $orderId); + $query->bindParam("user_id", $_SESSION['user_id']); + + if ($query->execute()) { + $this->updateOrderStatus($orderId, 3); + return true; + } + return false; + } + return false; + } + + public function insert_pending_order($orderTypeId, $qty, $price, $orderStatusId, $OfferAssetTypeId=null, $WantAssetTypeId=null) { + + if ($this->databaseConnection()) { + $now = $this->time_now(); + $messages = null; + + $this->customerId = isset($_SESSION['user_id']) ? $_SESSION['user_id'] : 0; + $this->orderTypeId = $orderTypeId; // 0-> buy; 1 -> sell; + $this->quantity = $qty; + $this->price = $price; + $this->orderStatusId = $orderStatusId; // 0 -> cancelled; 1 -> complete; 2 -> pending; 3 ->order active + + $std = new stdClass(); + $std->insertedrowid = null; + $std->orderTypeId = null; // 0 -> buy; 1 -> sell + $std->item_qty = null; + $std->item_price = null; + $std->orderStatusId = null; + $std->insert_date = null; + $std->error = true; + $std->message = null; + + // check user balance + $assetType = null; + $total_trade_val = null; + if ($this->orderTypeId == 0) { + $assetType = $WantAssetTypeId; + $total_trade_val = $this->quantity * $this->price; + } else if ($this->orderTypeId == 1) { + $assetType = $OfferAssetTypeId; + $total_trade_val = $this->quantity; + } + + $customer_bal = (float)$this->check_customer_balance($assetType, $this->customerId)->balance; + + $this->customer_balance = $customer_bal; + + if ($this->customer_balance == '' || $this->customer_balance == null || !is_float($this->customer_balance)) { + $messages = "0 balance: Your account balance is nill."; + $std->message = $messages; + //$this->storeMessages($order_id=null, $this->customerId, $messages); + return $std; + } + + if ($total_trade_val > $this->customer_balance) { + $messages = "Insufficient balance: You have insufficient balance to continue this trade. Please recharge your wallet or lower the quantity."; + $std->message = $messages; + //$this->storeMessages($order_id=null, $this->customerId, $messages); + return $std; + } + + $query = $this->db_connection->prepare("INSERT INTO ".ORDERS_TABLE." (`id`, `uid`, `order_type`, `offer_asset`, `want_asset`, `qty`, `price`, `status`, `market_order`, `insert_dt`, `update_dt`) + VALUES ('', " . $this->customerId . ", :a, :e, :f, :b, :c, :d, NULL, '$now', NULL)"); + + $query->bindParam(':a', $this->orderTypeId, PDO::PARAM_STR); + $query->bindParam(':e', $OfferAssetTypeId, PDO::PARAM_STR); + $query->bindParam(':f', $WantAssetTypeId, PDO::PARAM_STR); + $query->bindParam(':b', $this->quantity, PDO::PARAM_STR); + $query->bindParam(':c', $this->price, PDO::PARAM_STR); + $query->bindParam(':d', $this->orderStatusId); + + if ($query->execute()) { + + $insertedrowid = $this->db_connection->lastInsertId(); + + // Check if $price is eligible to be inserted into top_buy or top_sell table + $top_table = null; + $asc_desc = null; + $new_balance = null; + $new_frozenbalance = null; + + if ($orderTypeId == '0') { + $top_table = TOP_BUYS_TABLE; + } else if ($orderTypeId == '1') { + $top_table = TOP_SELLS_TABLE; + } + + $trade_type = ($this->orderTypeId==1) ? "sell" : "buy"; + $messages = "You entered a $trade_type order for $qty token at $ $price per token for $ ".$qty*$price; + + //$this->storeMessages($insertedrowid, $this->customerId, $messages); + + // Change the order status to active and insert in active table in DB + $insert_in_active_table = $this->insert_order_in_active_table($top_table, $WantAssetTypeId, $OfferAssetTypeId, $insertedrowid, $this->price, $this->quantity); + + if ($insert_in_active_table) { + $std->message = "Order moved to active table."; + } + + $this->orderStatusId = 3; // order activated + + $std = new stdClass(); + $std->insertedrowid = $insertedrowid; + $std->made_to_active_list = $insert_in_active_table; + $std->orderTypeId = $this->orderTypeId; // 0 -> buy; 1 -> sell + $std->item_qty = $qty; + $std->item_price = $price; + $std->orderStatusId = $this->orderStatusId; + $std->insert_date = date('Y-m-d H:i:s'); + $std->insert_in_active_table = $insert_in_active_table; + $std->error = false; + + return $std; + } + return null; + } + return false; + } + + private function updateOrderStatus($orderId=null, $status=null) { + if ($this->databaseConnection()) { + $query = $this->db_connection->prepare("UPDATE ".ORDERS_TABLE." SET `status`= '$status' WHERE `id` = :id LIMIT 1"); + $query->bindParam("id", $orderId); + if ($query->execute()) { + return true; + } + } + return false; + } + + public function get_top_buy_sell_list($top_table, $WantAssetTypeId, $OfferAssetTypeId, $asc_desc) { + + if ($this->databaseConnection()) { + + $top_list = array(); + + $query = $this->db_connection->query("SELECT $top_table.order_id, $top_table.uid, $top_table.quantity, $top_table.price, ".USERS_TABLE.".name + FROM $top_table, ".USERS_TABLE." + WHERE $top_table.uid = ".USERS_TABLE.".id + AND $top_table.bc1 = '".$WantAssetTypeId."' + AND $top_table.bc2 = '".$OfferAssetTypeId."' + ORDER BY price $asc_desc + LIMIT $this->max_top_bids + "); + + if ($query) { + + $rowCount = $query->rowCount(); + + if ($rowCount > 0) { + + while ($orders = $query->fetchObject()) { + + $top_list[] = $orders; + + } + } + + } else { + return false; + } + return $top_list; + } + return false; + } + + private function get_highest_demand($bc1, $bc2) { + if ($this->databaseConnection()) { + + $query = $this->db_connection->query("SELECT ".TOP_BUYS_TABLE.".order_id, ".TOP_BUYS_TABLE.".price, ".TOP_BUYS_TABLE.".quantity FROM ".ORDERS_TABLE.", ".TOP_BUYS_TABLE." WHERE ".ORDERS_TABLE.".id = ".TOP_BUYS_TABLE.".order_id AND ".TOP_BUYS_TABLE.".bc1 = '".$bc1."' + AND ".TOP_BUYS_TABLE.".bc2 = '".$bc2."' ORDER BY ".TOP_BUYS_TABLE.".price DESC LIMIT 1"); + $rowCount_Qty = $query->rowCount(); + if (!$rowCount_Qty) { + return false; + } + return $highest_demanded = $query->fetchObject(); + } + return false; + } + + public function OrderMatchingQuery($bc1, $bc2) { + + if ($this->databaseConnection()) { + + $query = $this->db_connection->query(" + SELECT ".TOP_SELLS_TABLE.".order_id, ".TOP_SELLS_TABLE.".price, ".TOP_SELLS_TABLE.".quantity, ".TOP_SELLS_TABLE.".order_id, (SELECT `uid` FROM ".TOP_BUYS_TABLE." ORDER BY price DESC LIMIT 1) AS BUYER_ID, ".TOP_SELLS_TABLE.".uid AS SELLER_ID + FROM ".TOP_SELLS_TABLE.", ".ORDERS_TABLE." + WHERE ( + (".TOP_SELLS_TABLE.".price <= (SELECT `price` FROM ".TOP_BUYS_TABLE." ORDER BY price DESC LIMIT 1)) + AND (".ORDERS_TABLE.".id = ".TOP_SELLS_TABLE.".order_id) + AND (".ORDERS_TABLE.".status = '3') + AND (".ORDERS_TABLE.".order_type= '1') + AND (".TOP_SELLS_TABLE.".bc1 = '".$bc1."') + AND (".TOP_SELLS_TABLE.".bc2 = '".$bc2."') + ) + ORDER BY ".TOP_SELLS_TABLE.".price ASC + "); + + if($rowCount = $query->rowCount() > 0) { // Transaction is possible + $matched_orders = array(); + while ($obj = $query->fetchObject()) { + $matched_orders[] = $obj; + } + return $matched_orders; + } + return false; + } + return false; + } + + public function OrderMatchingService($bc1, $bc2) { + + if ($this->databaseConnection()) { + + $message = array(); + $trade_qty = 0; + + $get_highest_demand = $this->get_highest_demand($bc1, $bc2); + if ($get_highest_demand == false) { + return false; + } + + $this->demanded_qty = (float)$get_highest_demand->quantity; + $buy_order_id = $get_highest_demand->order_id; + $buy_amount = $get_highest_demand->price; + + $supply_available = $this->OrderMatchingQuery($bc1, $bc2); + + if (trim($bc1) == '' || trim($bc2) == '' || $this->demanded_qty == false || $this->demanded_qty == '' || $this->demanded_qty == '0' || $supply_available == false || $supply_available == '' || !is_array($supply_available) || empty($supply_available)) { + return false; + } + + if(is_array($supply_available) && !empty($supply_available) ) { + + foreach ($supply_available as $available) { + + if ($this->demanded_qty > 0) { + $supply_available = $this->OrderMatchingQuery($bc1, $bc2); + $seller_order_id = (int)$available->order_id; + $available->quantity = (float)$available->quantity; + $seller_id = $available->SELLER_ID; + $seller_balance_bc1 = $this->check_customer_balance($assetType = $bc1, $seller_id)->balance; + $seller_balance_bc2 = $this->check_customer_balance($assetType = $bc2, $seller_id)->balance; + + $buyer_id = $available->BUYER_ID; + $buyer_balance_bc1 = $this->check_customer_balance($assetType = $bc1, $buyer_id)->balance; + $buyer_balance_bc2 = $this->check_customer_balance($assetType = $bc2, $buyer_id)->balance; + + if ($this->demanded_qty > $available->quantity) { + + $trade_qty = (float) $available->quantity; + + $cost_of_total_supply = $available->quantity * $available->price; + $cost_of_total_supply = (float) $cost_of_total_supply; + + if ($buyer_balance_bc2 < $cost_of_total_supply) { + /*Record the message*/ + $this->storeMessages($buy_order_id, $buyer_id, $msg="Transaction failed: You have insufficient ".$bc2." balance."); + if ($_SESSION['user_id'] == $buyer_id) { + $message[] = "Transaction failed: You have insufficient ".$bc2." balance."; + } + // Delete the culprit order + $this->del_order($buy_order_id, $buyer_id); + break; + } + if (($seller_balance_bc1 == 0) || ($seller_balance_bc1 < $available->quantity)) { + /*Record the message*/ + $this->storeMessages($seller_order_id, $seller_id, $msg="Transaction failed: You had insufficient ".$bc1." balance"); + if ($_SESSION['user_id'] == $seller_id) { + $message[] = "Transaction failed: You had insufficient ".$bc1." balance"; + } + $this->del_order($seller_order_id, $seller_id); + break; + } + + if ($buyer_id != $seller_id) { + $new_seller_balance_bc2 = $seller_balance_bc2 + $cost_of_total_supply; // traditional or cash + $new_seller_balance_bc1 = $seller_balance_bc1 - $available->quantity; // deduct the btc sold + + $new_buyer_balance_bc1 = $buyer_balance_bc1 + $available->quantity; // btc + $new_buyer_balance_bc2 = $buyer_balance_bc2 - $cost_of_total_supply; // traditional or cash + + // subtract the debit access (customers balance of $ or BTC) + $this->update_user_balance($assetType = $bc1, $balance = $new_buyer_balance_bc1, $buyer_id); + + // increment the credit asset (customers balance of $ or BTC) + $this->update_user_balance($assetType = $bc2, $balance = $new_seller_balance_bc2, $seller_id); + + // record the commission in the commission account + // decrease respective balances + $this->update_user_balance($assetType = $bc1, $balance = $new_seller_balance_bc1, $seller_id); + $this->update_user_balance($assetType = $bc2, $balance = $new_buyer_balance_bc2, $buyer_id); + } + + $this->demanded_qty = $this->demanded_qty - $available->quantity; + + // update the quantity field for demand + $this->update_quantity($top_table = TOP_BUYS_TABLE, $this->demanded_qty, $buy_order_id); + + // Delete the row from Sell list + $this->delete_order(TOP_SELLS_TABLE, $available->order_id); + + // Mark this order status 1 i.e transaction successful + $this->updateOrderStatus($available->order_id, $status='1'); + + // Record messages + $buyer_msg = "Transaction successful: You bought $available->quantity ".$bc1." for ".$bc2." $cost_of_total_supply at the rate of ".$bc2." $available->price per unit."; + $seller_msg = "Transaction successful: You sold $available->quantity ".$bc1." for ".$bc2." $cost_of_total_supply at the rate of ".$bc2." $available->price per unit."; + $this->storeMessages($buy_order_id, $buyer_id, $msg=$buyer_msg); + $this->storeMessages($seller_order_id, $seller_id, $msg=$seller_msg); + + if(isset($_SESSION['user_id'])) { + $logged_in_user = (int) $_SESSION['user_id']; + if ($this->check_user($logged_in_user) != false) { + if ($logged_in_user == $buyer_id) { + $message[] = $buyer_msg; + } else if($logged_in_user == $seller_id) { + $message[] = $seller_msg; + } + } + } + + $available->quantity = 0; + + } elseif ($this->demanded_qty == $available->quantity) { + + $trade_qty = (float) $available->quantity; + + $cost_of_total_supply = $available->quantity * $available->price; + $cost_of_total_supply = (float) $cost_of_total_supply; + + if ($buyer_balance_bc2 < $cost_of_total_supply) { + /*Record the message*/ + $this->storeMessages($buy_order_id, $buyer_id, $msg="Transaction failed: You had insufficient ".$bc2." balance."); + if ($_SESSION['user_id'] == $buyer_id) { + $message[] = "Transaction failed: You have insufficient ".$bc2." balance."; + } + $this->del_order($buy_order_id, $buyer_id); + break; + } + if (($seller_balance_bc1 == 0) || ($seller_balance_bc1 < $available->quantity)) { + /*Record the message*/ + $this->storeMessages($seller_order_id, $seller_id, $msg="Transaction failed: You had insufficient ".$bc1." balance."); + if ($_SESSION['user_id'] == $seller_id) { + $message[] = "Transaction failed: You had insufficient ".$bc1." balance"; + } + $this->del_order($seller_order_id, $seller_id); + break; + } + + if ($buyer_id != $seller_id) { + $new_seller_balance_bc2 = $seller_balance_bc2 + $cost_of_total_supply; // traditional or cash + $new_seller_balance_bc1 = $seller_balance_bc1 - $available->quantity; // deduct the btc sold + + $new_buyer_balance_bc1 = $buyer_balance_bc1 + $available->quantity; // btc + $new_buyer_balance_bc2 = $buyer_balance_bc2 - $cost_of_total_supply; // traditional or cash + + // subtract the debit access (customers balance of $ or BTC) + $this->update_user_balance($assetType = $bc1, $balance = $new_buyer_balance_bc1, $user_id = $buyer_id); + + // increment the credit asset (customers balance of $ or BTC) + $this->update_user_balance($assetType = $bc2, $balance = $new_seller_balance_bc2, $user_id = $seller_id); + + // decrease respective balances + $this->update_user_balance($assetType = $bc1, $balance = $new_seller_balance_bc1, $user_id = $seller_id); + $this->update_user_balance($assetType = $bc2, $balance = $new_buyer_balance_bc2, $user_id = $buyer_id); + } + + // Delete the row from Sell list And Buy list + $this->delete_order(TOP_SELLS_TABLE, $available->order_id); + $this->delete_order(TOP_BUYS_TABLE, $buy_order_id); + + // Mark trades of buyer & seller 1 i.e 'successful' + $this->updateOrderStatus($available->order_id, $status='1'); + $this->updateOrderStatus($buy_order_id, $status='1'); + + // Record messages + $buyer_msg = "Transaction successful: You bought $available->quantity ".$bc1." for ".$bc2." $cost_of_total_supply at the rate of ".$bc2." $available->price per unit."; + $seller_msg = "Transaction successful: You sold $available->quantity ".$bc1." for ".$bc2." $cost_of_total_supply at the rate of ".$bc2." $available->price per unit."; + $this->storeMessages($buy_order_id, $buyer_id, $msg=$buyer_msg); + $this->storeMessages($seller_order_id, $seller_id, $msg=$seller_msg); + + if(isset($_SESSION['user_id'])) { + $logged_in_user = (int) $_SESSION['user_id']; + if ($this->check_user($logged_in_user) != false) { + if ($logged_in_user == $buyer_id) { + $message[] = $buyer_msg; + } else if($logged_in_user == $seller_id) { + $message[] = $seller_msg; + } + } + } + + // save changes + $this->demanded_qty = 0; + $available->quantity = 0; + + } elseif ($this->demanded_qty < $available->quantity) { + + $trade_qty = (float) $this->demanded_qty; + + $cost_of_total_supply = $this->demanded_qty * $available->price; // traditional or cash + $cost_of_total_supply = (float) $cost_of_total_supply; + + if ($buyer_balance_bc2 < $cost_of_total_supply) { + /*Record the message*/ + $this->storeMessages($buy_order_id, $buyer_id, $msg="Transaction failed: You had insufficient ".$bc2." balance."); + if ($_SESSION['user_id'] == $buyer_id) { + $message[] = "Transaction failed: You have insufficient ".$bc2." balance."; + } + $this->del_order($buy_order_id, $buyer_id); + break; + } + if (($seller_balance_bc1 == 0) || ($seller_balance_bc1 < $this->demanded_qty)) { + /*Record the message*/ + $this->storeMessages($seller_order_id, $seller_id, $msg="Transaction failed: You had insufficient ".$bc1." balance."); + if ($_SESSION['user_id'] == $seller_id) { + $message[] = "Transaction failed: You had insufficient ".$bc1." balance"; + } + $this->del_order($seller_order_id, $seller_id); + break; + } + + if ($buyer_id != $seller_id) { + $new_seller_balance_bc2 = $seller_balance_bc2 + $cost_of_total_supply; // traditional or cash + $new_seller_balance_bc1 = $seller_balance_bc1 - $this->demanded_qty; // deduct the btc sold + + $new_buyer_balance_bc1 = $buyer_balance_bc1 + $this->demanded_qty; // btc + $new_buyer_balance_bc2 = $buyer_balance_bc2 - $cost_of_total_supply; // traditional or cash + + // subtract the debit access (customers balance of $ or BTC) + $this->update_user_balance($assetType = $bc1, $balance = $new_buyer_balance_bc1, $user_id = $buyer_id); + + // increment the credit asset (customers balance of $ or BTC) + $this->update_user_balance($assetType = $bc2, $balance = $new_seller_balance_bc2, $user_id = $seller_id); + + // record the commission in the commission account + // decrease respective balances + $this->update_user_balance($assetType = $bc1, $balance = $new_seller_balance_bc1, $user_id = $seller_id); + + $this->update_user_balance($assetType = $bc2, $balance = $new_buyer_balance_bc2, $user_id = $buyer_id); + + } + + // update the quantity field for $availableQuantity + $availableQuantity = $available->quantity - $this->demanded_qty; + $this->update_quantity($top_table = TOP_SELLS_TABLE, $availableQuantity, $available->order_id); + + // Delete the row from Buy list + $this->delete_order(TOP_BUYS_TABLE, $buy_order_id); + + // Mark this order status 1 i.e transaction successful + $this->updateOrderStatus($buy_order_id, $status='1'); + + // Record messages + $buyer_msg = "Transaction successful: You bought $this->demanded_qty ".$bc1." for ".$bc2." $cost_of_total_supply at the rate of ".$bc2." $available->price per unit."; + $seller_msg = "Transaction successful: You sold $this->demanded_qty ".$bc1." for ".$bc2." $cost_of_total_supply at the rate of ".$bc2." $available->price per unit."; + $this->storeMessages($buy_order_id, $buyer_id, $msg=$buyer_msg); + $this->storeMessages($seller_order_id, $seller_id, $msg=$seller_msg); + + if(isset($_SESSION['user_id'])) { + $logged_in_user = (int) $_SESSION['user_id']; + if ($this->check_user($logged_in_user) != false) { + if ($logged_in_user == $buyer_id) { + $message[] = $buyer_msg; + } else if($logged_in_user == $seller_id) { + $message[] = $seller_msg; + } + } + } + + // save changes + $this->demanded_qty = 0; + } + + // Record the transaction + $this->record_transaction($buyer_id,$buy_order_id, $buy_amount, $bc1, $seller_id, $available->order_id, $available->price, $bc2, $trade_qty); + } else { + return false; + } + $this->OrderMatchingQuery($bc1, $bc2); + } + return $message; + } + return false; + } + return false; + } + + private function insert_market_order($customerId, $orderTypeId, $OfferAssetTypeId=null, $WantAssetTypeId=null, $qty, $price) { + if ($this->databaseConnection()) { + $now = $this->time_now(); + $query = $this->db_connection->prepare("INSERT INTO ".ORDERS_TABLE." (`id`, `uid`, `order_type`, `offer_asset`, `want_asset`, `qty`, `price`, `status`, `market_order`, `insert_dt`, `update_dt`) + VALUES ('', :u, :a, :d, :e, :b, :c, 1, 1, '$now', NULL)"); + $query->bindParam(':u', $customerId, PDO::PARAM_INT); + $query->bindParam(':a', $orderTypeId, PDO::PARAM_INT); + $query->bindParam(':d', $OfferAssetTypeId, PDO::PARAM_STR); + $query->bindParam(':e', $WantAssetTypeId, PDO::PARAM_STR); + $query->bindParam(':b', $qty, PDO::PARAM_STR); + $query->bindParam(':c', $price, PDO::PARAM_STR); + + if ($query->execute()) { + $insertedrowid = $this->db_connection->lastInsertId(); + + $trade_type = ($orderTypeId=='1') ? "sell" : "buy"; + $messages = "You entered an instant $trade_type order for $qty units at $OfferAssetTypeId $price per unit for $OfferAssetTypeId ".$qty*$price; + $this->storeMessages($insertedrowid, $customerId, $messages); + + return (int) $insertedrowid; + } + return false; + } + return false; + } + + public function market_order($order_type, $qty, $OfferAssetTypeId, $WantAssetTypeId) { + + if ($this->databaseConnection()) { + + $message = array(); + + // Check if it is a buy or sell + if ($order_type == 'm-buy') { + + if (is_float($qty) || is_int($qty)) { + if ($qty > 0) { + + $sell_list = $this->get_top_buy_sell_list($top_table = TOP_SELLS_TABLE, $WantAssetTypeId, $OfferAssetTypeId, $asc_desc = 'ASC'); + + if (!empty($sell_list)) { + + /*Code to find the last iteration of loop. Required to print shortage of token supply.*/ + end($sell_list); + $last_iter = key($sell_list); + + foreach ($sell_list as $key => $available) { + + $trade_qty = 0; + $sell_order_id = (int)$available->order_id; + $available->quantity = (float)$available->quantity; + + if ($available->quantity <= 0 || $qty <= 0) { + if (isset($message)) { + if ($available->quantity <= 0) { + $message[] = "Oops! There's shortage of the availability of Blockchain Contracts."; + } else if ($qty <= 0) { + //$message[] = "The demanded asset is nill."; + } + } else { + exit; + } + return $message; + } + + $seller_id = $available->uid; + $seller_balance_bc2 = (float)$this->check_customer_balance($assetType = $OfferAssetTypeId, $seller_id)->balance; + $seller_balance_bc1 = (float)$this->check_customer_balance($assetType = $WantAssetTypeId, $seller_id)->balance; + $buyer_id = $_SESSION['user_id']; + $buyer_balance_bc2 = (float)$this->check_customer_balance($assetType = $OfferAssetTypeId, $buyer_id)->balance; + $buyer_balance_bc1 = (float)$this->check_customer_balance($assetType = $WantAssetTypeId, $buyer_id)->balance; + + switch ($qty) { + case ($qty > $available->quantity): + + $trade_qty = (float)$available->quantity; + $cost_of_total_supply = $available->quantity * $available->price; // traditional or cash + + if ($buyer_balance_bc2 < $cost_of_total_supply) { + $message[] = "Transaction failed: You have insufficient ".$OfferAssetTypeId." balance."; + $this->storeMessages($buy_order_id = null, $buyer_id, $msg = "Transaction failed: You had insufficient ".$OfferAssetTypeId." balance."); + return $message; + } + if (($seller_balance_bc1 == 0) || ($seller_balance_bc1 < $available->quantity)) { + $message[] = "Transaction failed: The seller has insufficient ".$WantAssetTypeId." balance."; + $this->storeMessages($sell_order_id, $seller_id, $msg = "Transaction failed: The seller has insufficient ".$WantAssetTypeId." balance."); + return $message; + } + + $new_seller_cash_balance = $seller_balance_bc2 + $cost_of_total_supply; // traditional or cash + $new_seller_bit_balance = $seller_balance_bc1 - $available->quantity; + $new_buyer_bit_balance = $buyer_balance_bc1 + $available->quantity; // traditional or cash + $new_buyer_cash_balance = $buyer_balance_bc2 - $cost_of_total_supply; // traditional + + $insert_market_order = $this->insert_market_order($_SESSION['user_id'], $orderTypeId = '0', $OfferAssetTypeId, $WantAssetTypeId, $available->quantity, $available->price); + + $buy_order_id = 0; + if ($insert_market_order == false) { + return false; + } else if (is_int($insert_market_order)) { + $buy_order_id = (int)$insert_market_order; + } else { + return false; + } + + if ($buyer_id != $seller_id) { +// increment the bits of buyer + $this->update_user_balance($assetType = $WantAssetTypeId, $balance = $new_buyer_bit_balance, $user_id = $buyer_id); + +// deduct cash of buyer + $this->update_user_balance($assetType = $OfferAssetTypeId, $balance = $new_buyer_cash_balance, $user_id = $buyer_id); + +// increase the cash of seller + $this->update_user_balance($assetType = $OfferAssetTypeId, $balance = $new_seller_cash_balance, $user_id = $seller_id); + +// deduct bits of seller + $this->update_user_balance($assetType = $WantAssetTypeId, $balance = $new_seller_bit_balance, $user_id = $seller_id); + } + +// Record the transaction + $this->record_transaction($buyer_id, $buy_order_id, $available->price, $WantAssetTypeId, $seller_id, $available->order_id, $available->price, $OfferAssetTypeId, $trade_qty); + +// Delete the row from Sell list + $this->delete_order(TOP_SELLS_TABLE, $available->order_id); + +// Update Order Status in Order table + $this->UpdateOrderStatus($available->order_id, '1'); + + $message[] = "Instant Transaction Successful: You bought $available->quantity ".$WantAssetTypeId." at ".$OfferAssetTypeId." $available->price per unit for ".$OfferAssetTypeId." $cost_of_total_supply."; + +// Record message in DB + $this->storeMessages($buy_order_id, $buyer_id, $msg = "Instant Transaction Successful: You bought $available->quantity $WantAssetTypeId at $OfferAssetTypeId $available->price per unit for $OfferAssetTypeId $cost_of_total_supply."); + $this->storeMessages($sell_order_id, $seller_id, $msg = "Transaction Successful: You sold $available->quantity $WantAssetTypeId at $OfferAssetTypeId $available->price per unit for $OfferAssetTypeId $cost_of_total_supply."); + + $qty = $qty - $available->quantity; + $available->quantity = 0; +// save changes + break; + case ($qty == $available->quantity): + + $trade_qty = (float)$available->quantity; + $cost_of_total_supply = $available->quantity * $available->price; // traditional or cash + + if ($buyer_balance_bc2 < $cost_of_total_supply) { + $message[] = "Instant Transaction failed: You have insufficient $OfferAssetTypeId balance."; + $this->storeMessages($buy_order_id = null, $buyer_id, $msg = "Transaction failed: You had insufficient $OfferAssetTypeId balance."); + return $message; + } + if (($seller_balance_bc1 == 0) || ($seller_balance_bc1 < $available->quantity)) { + $message[] = "Instant Transaction failed: The seller has insufficient $WantAssetTypeId balance."; + $this->storeMessages($sell_order_id, $seller_id, $msg = "Transaction failed: The seller has insufficient $WantAssetTypeId balance."); + return $message; + } + + $new_seller_cash_balance = $seller_balance_bc2 + $cost_of_total_supply; // traditional or cash + $new_seller_bit_balance = $seller_balance_bc1 - $qty; + $new_buyer_cash_balance = $buyer_balance_bc2 - $cost_of_total_supply; // traditional + $new_buyer_bit_balance = $buyer_balance_bc1 + $available->quantity; // traditional or cash + + $insert_market_order = $this->insert_market_order($_SESSION['user_id'], $orderTypeId = '0', $OfferAssetTypeId, $WantAssetTypeId, $available->quantity, $available->price); + + $buy_order_id = 0; + if ($insert_market_order == false) { + return false; + } else if (is_int($insert_market_order)) { + $buy_order_id = (int)$insert_market_order; + } else { + return false; + } + + if ($buyer_id != $seller_id) { +// increment the bits of buyer + $this->update_user_balance($assetType = $WantAssetTypeId, $balance = $new_buyer_bit_balance, $user_id = $buyer_id); + +// deduct cash of buyer + $this->update_user_balance($assetType = $OfferAssetTypeId, $balance = $new_buyer_cash_balance, $user_id = $buyer_id); + +// increase the cash of seller + $this->update_user_balance($assetType = $OfferAssetTypeId, $balance = $new_seller_cash_balance, $user_id = $seller_id); + +// deduct bits of seller + $this->update_user_balance($assetType = $WantAssetTypeId, $balance = $new_seller_bit_balance, $user_id = $seller_id); + } + + $message[] = "Instant Transaction Successful: You bought $qty $WantAssetTypeId at $OfferAssetTypeId $available->price per unit for $OfferAssetTypeId $cost_of_total_supply."; + +// Record message in DB + $this->storeMessages($buy_order_id, $buyer_id, $msg = "Instant Transaction Successful: You bought $available->quantity $WantAssetTypeId at $OfferAssetTypeId $available->price per unit for $OfferAssetTypeId $cost_of_total_supply."); + $this->storeMessages($sell_order_id, $seller_id, $msg = "Transaction Successful: You sold $available->quantity $WantAssetTypeId at $OfferAssetTypeId $available->price per unit for $OfferAssetTypeId $cost_of_total_supply."); + + $qty = $qty - $available->quantity; // should be equal to 0 + $available->quantity = 0; + +// Record the transaction + $this->record_transaction($buyer_id, $buy_order_id, $available->price, $WantAssetTypeId, $seller_id, $available->order_id, $available->price, $OfferAssetTypeId, $trade_qty); + +// Delete the row from Sell list + $this->delete_order(TOP_SELLS_TABLE, $available->order_id); + +// Update Order Status in Order table + $this->UpdateOrderStatus($buy_order_id, '1'); + $this->UpdateOrderStatus($available->order_id, '1'); + + break; + case ($qty < $available->quantity): + + $trade_qty = (float)$qty; + $cost_of_total_supply = $qty * $available->price; // traditional or cash + + if ($buyer_balance_bc2 < $cost_of_total_supply) { + $message[] = "Instant Transaction failed: You have insufficient $OfferAssetTypeId balance."; + $this->storeMessages($buy_order_id = null, $buyer_id, $msg = "Transaction failed: You had insufficient $OfferAssetTypeId balance."); + return $message; + } + if (($seller_balance_bc1 == 0) || ($seller_balance_bc1 < $qty)) { + $message[] = "Instant Transaction failed: The seller has insufficient $WantAssetTypeId balance."; + $this->storeMessages($sell_order_id, $seller_id, $msg = "Transaction failed: The seller has insufficient $WantAssetTypeId balance."); + return $message; + } + + $new_seller_cash_balance = $seller_balance_bc2 + $cost_of_total_supply; // traditional or cash + $new_seller_bit_balance = $seller_balance_bc1 - $qty; + $new_buyer_cash_balance = $buyer_balance_bc2 - $cost_of_total_supply; // traditional + $new_buyer_bit_balance = $buyer_balance_bc1 + $qty; // traditional or cash + + $insert_market_order = $this->insert_market_order($_SESSION['user_id'], $orderTypeId = '0', $OfferAssetTypeId, $WantAssetTypeId, $qty, $available->price); + + $buy_order_id = 0; + if ($insert_market_order == false) { + return false; + } else if (is_int($insert_market_order)) { + $buy_order_id = (int)$insert_market_order; + } else { + return false; + } + + if ($buyer_id != $seller_id) { +// increment the bits of buyer + $this->update_user_balance($assetType = $WantAssetTypeId, $balance = $new_buyer_bit_balance, $user_id = $buyer_id); + +// deduct cash of buyer + $this->update_user_balance($assetType = $OfferAssetTypeId, $balance = $new_buyer_cash_balance, $user_id = $buyer_id); + +// increase the cash of seller + $this->update_user_balance($assetType = $OfferAssetTypeId, $balance = $new_seller_cash_balance, $user_id = $seller_id); + +// deduct bits of seller + $this->update_user_balance($assetType = $WantAssetTypeId, $balance = $new_seller_bit_balance, $user_id = $seller_id); + } + +// Record the transaction + $this->record_transaction($buyer_id, $buy_order_id, $available->price, $WantAssetTypeId, $seller_id, $available->order_id, $available->price, $OfferAssetTypeId, $trade_qty); + + $available->quantity = $available->quantity - $qty; + +// update the quantity field for supply + $this->update_quantity($top_table = TOP_SELLS_TABLE, $available->quantity, $available->order_id); + +// Update Order Status in Order table + $this->UpdateOrderStatus($buy_order_id, '1'); + + $message[] = "Instant Transaction Successful: You bought $qty $WantAssetTypeId at $OfferAssetTypeId $available->price per unit for $OfferAssetTypeId $cost_of_total_supply."; +// Record message in DB + $this->storeMessages($buy_order_id, $buyer_id, $msg = "Instant Transaction Successful: You bought $qty $WantAssetTypeId at $OfferAssetTypeId $available->price per unit for $OfferAssetTypeId $cost_of_total_supply."); + $this->storeMessages($sell_order_id, $seller_id, $msg = "Transaction Successful: You sold $qty $WantAssetTypeId at $OfferAssetTypeId $available->price per unit for $OfferAssetTypeId $cost_of_total_supply."); + +// update the quantity field for demand + $qty = 0; + + break; + } + if (($available->quantity <= 0) && ($qty > 0) && ($key === $last_iter)) { + /*The supply of token is 0. Stop further transaction. */ + $message[] = "Instant Transaction failure: There's no token left to be sold any more. $qty tokens could not be bought."; + $this->storeMessages($buy_order_id = null, $buyer_id, $msg = "There's no token left to be sold any more. $qty tokens could not be bought."); + } + } + return $message; + } else { + $message[] = "empty_sell_list"; + return $message; + } + } + } + + } elseif ($order_type == 'm-sell') { + if (is_float($qty) || is_int($qty)) { + if ($qty > 0) { + + $buy_list = $this->get_top_buy_sell_list($top_table = TOP_BUYS_TABLE, $WantAssetTypeId, $OfferAssetTypeId, $asc_desc = 'DESC'); + + if (!empty($buy_list)) { + foreach ($buy_list as $available) { + + $trade_qty = 0; + $buy_order_id = (int)$available->order_id; + $available->quantity = (float)$available->quantity; + + if ($available->quantity <= 0 || $qty <= 0) { + if (isset($message)) { + if ($available->quantity <= 0) { + $message[] = "Instant Transaction Failure: The available asset is nill."; + } else if ($qty <= 0) { +//$message[] = "The demanded asset is nill."; + } + return $message; + } else { + exit; + } + } + + $seller_id = $_SESSION['user_id']; + $seller_balance_bc2 = $this->check_customer_balance($assetType = $OfferAssetTypeId, $seller_id)->balance; + $seller_balance_bc1 = $this->check_customer_balance($assetType = $WantAssetTypeId, $seller_id)->balance; + $buyer_id = $available->uid; + $buyer_balance_bc2 = $this->check_customer_balance($assetType = $OfferAssetTypeId, $buyer_id)->balance; + $buyer_balance_bc1 = $this->check_customer_balance($assetType = $WantAssetTypeId, $buyer_id)->balance; + + switch ($qty) { + case ($qty > $available->quantity): + + $trade_qty = (float)$available->quantity; + $cost_of_total_supply = $available->quantity * $available->price; // traditional or cash + + if ($buyer_balance_bc2 < $cost_of_total_supply) { + $message[] = "Instant Transaction failed: The buyer has insufficient $OfferAssetTypeId balance."; + $this->storeMessages($buy_order_id, $buyer_id, $msg = "Your order no $buy_order_id was unprocessed due to insufficient $OfferAssetTypeId balance."); + $this->storeMessages($sell_order_id = null, $seller_id, $msg = "A transaction was unprocessed due to insufficient $OfferAssetTypeId balance of buyer with id $buyer_id."); + return $message; + } + if (($seller_balance_bc1 == 0) || ($seller_balance_bc1 < $available->quantity)) { + $message[] = "Instant Transaction failed: You have insufficient $WantAssetTypeId balance."; + $this->storeMessages($buy_order_id, $buyer_id, $msg = "Your order no $buy_order_id was unprocessed due to insufficient $WantAssetTypeId balance of seller with id $seller_id."); + $this->storeMessages($sell_order_id = null, $seller_id, $msg = "Transaction failed: You have insufficient $WantAssetTypeId balance."); + return $message; + } + + $new_seller_cash_balance = $seller_balance_bc2 + $cost_of_total_supply; // traditional or cash + $new_seller_bit_balance = $seller_balance_bc1 - $available->quantity; // deduct the btc sold + $new_buyer_cash_balance = $buyer_balance_bc2 - $cost_of_total_supply; // traditional + $new_buyer_bit_balance = $buyer_balance_bc1 + $available->quantity; // btc + + $insert_market_order = $this->insert_market_order($_SESSION['user_id'], $orderTypeId = '1', $OfferAssetTypeId, $WantAssetTypeId, $available->quantity, $available->price); + + $sell_order_id = 0; + if ($insert_market_order == false) { + return false; + } else if (is_int($insert_market_order)) { + $sell_order_id = (int)$insert_market_order; + } else { + return false; + } + + if ($buyer_id != $seller_id) { +// increment the bits of buyer + $this->update_user_balance($assetType = $WantAssetTypeId, $balance = $new_buyer_bit_balance, $user_id = $buyer_id); + +// deduct cash of buyer + $this->update_user_balance($assetType = $OfferAssetTypeId, $balance = $new_buyer_cash_balance, $user_id = $buyer_id); + +// increase the cash of seller + $this->update_user_balance($assetType = $OfferAssetTypeId, $balance = $new_seller_cash_balance, $user_id = $seller_id); + +// deduct bits of seller + $this->update_user_balance($assetType = $WantAssetTypeId, $balance = $new_seller_bit_balance, $user_id = $seller_id); + } + +// Delete the row from buy list + $this->delete_order(TOP_BUYS_TABLE, $available->order_id); + +// Update Order Status in Order table + $this->UpdateOrderStatus($available->order_id, '1'); + +// Record the transaction + $this->record_transaction($buyer_id, $available->order_id, $available->price, $WantAssetTypeId, $seller_id, $sell_order_id, $available->price, $OfferAssetTypeId, $trade_qty); + + $message[] = "Instant Transaction Successful: You sold $available->quantity $WantAssetTypeId for $OfferAssetTypeId $cost_of_total_supply."; + $this->storeMessages($sell_order_id, $seller_id, $msg = "Instant Transaction Successful: You sold $available->quantity $WantAssetTypeId for $OfferAssetTypeId $cost_of_total_supply."); + $this->storeMessages($buy_order_id, $buyer_id, $msg = "Transaction Successful: You bought $available->quantity $WantAssetTypeId for $OfferAssetTypeId $cost_of_total_supply."); + + $qty = $qty - $available->quantity; + + break; + case ($qty == $available->quantity): + + $trade_qty = (float)$available->quantity; + $cost_of_total_supply = $available->quantity * $available->price; // traditional or cash + + if ($buyer_balance_bc2 < $cost_of_total_supply) { + $message[] = "Instant Transaction failed: The buyer has insufficient $OfferAssetTypeId balance."; + $this->storeMessages($buy_order_id, $buyer_id, $msg = "Your order no $buy_order_id was unprocessed due to insufficient $OfferAssetTypeId balance."); + $this->storeMessages($sell_order_id = null, $seller_id, $msg = "A transaction was unprocessed due to insufficient $OfferAssetTypeId balance of buyer with id $buyer_id."); + return $message; + } + if (($seller_balance_bc1 == 0) || ($seller_balance_bc1 < $available->quantity)) { + $message[] = "Instant Transaction failed: You have insufficient $WantAssetTypeId balance."; + $this->storeMessages($buy_order_id, $buyer_id, $msg = "Your order no $buy_order_id was unprocessed due to insufficient $WantAssetTypeId balance of seller with id $seller_id."); + $this->storeMessages($sell_order_id = null, $seller_id, $msg = "Transaction failed: You have insufficient $WantAssetTypeId balance."); + return $message; + } + + $new_seller_cash_balance = $seller_balance_bc2 + $cost_of_total_supply; // traditional or cash + $new_seller_bit_balance = $seller_balance_bc1 - $available->quantity; // deduct the btc sold + $new_buyer_cash_balance = $buyer_balance_bc2 - $cost_of_total_supply; // traditional + $new_buyer_bit_balance = $buyer_balance_bc1 + $available->quantity; // traditional or cash + + $insert_market_order = $this->insert_market_order($_SESSION['user_id'], $orderTypeId = '1', $OfferAssetTypeId, $WantAssetTypeId, $available->quantity, $available->price); + + $sell_order_id = 0; + if ($insert_market_order == false) { + return false; + } else if (is_int($insert_market_order)) { + $sell_order_id = (int)$insert_market_order; + } else { + return false; + } + + if ($buyer_id != $seller_id) { +// increment the bits of buyer + $this->update_user_balance($assetType = $WantAssetTypeId, $balance = $new_buyer_bit_balance, $user_id = $buyer_id); + +// deduct cash of buyer + $this->update_user_balance($assetType = $OfferAssetTypeId, $balance = $new_buyer_cash_balance, $user_id = $buyer_id); + +// subtract the cash of seller + $this->update_user_balance($assetType = $OfferAssetTypeId, $balance = $new_seller_cash_balance, $user_id = $seller_id); + +// deduct bits of seller + $this->update_user_balance($assetType = $WantAssetTypeId, $balance = $new_seller_bit_balance, $user_id = $seller_id); + } + + $message[] = "Instant Transaction Successful: You sold $qty $WantAssetTypeId for $OfferAssetTypeId $cost_of_total_supply."; + $this->storeMessages($sell_order_id, $seller_id, $msg = "Instant Transaction Successful: You sold $available->quantity $WantAssetTypeId for $OfferAssetTypeId $cost_of_total_supply."); + $this->storeMessages($buy_order_id, $buyer_id, $msg = "Transaction Successful: You bought $available->quantity $WantAssetTypeId for $OfferAssetTypeId $cost_of_total_supply."); + + $qty = $qty - $available->quantity; + +// Update Order Status in Order table + $this->UpdateOrderStatus($sell_order_id, '1'); + $this->UpdateOrderStatus($available->order_id, '1'); + +// Record the transaction + $this->record_transaction($buyer_id, $available->order_id, $available->price, $WantAssetTypeId, $seller_id, $sell_order_id, $available->price, $OfferAssetTypeId, $trade_qty); + +// Delete the row from buy list + $this->delete_order(TOP_BUYS_TABLE, $available->order_id); + + break; + case ($qty < $available->quantity): + + $trade_qty = (float)$qty; + $available->quantity = $available->quantity - $qty; + $cost_of_total_supply = $qty * $available->price; // traditional or cash + + if ($buyer_balance_bc2 < $cost_of_total_supply) { + $message[] = "Instant Transaction failed: The buyer has insufficient $OfferAssetTypeId balance."; + $this->storeMessages($buy_order_id, $buyer_id, $msg = "Your order no $buy_order_id was unprocessed due to insufficient $OfferAssetTypeId balance."); + $this->storeMessages($sell_order_id = null, $seller_id, $msg = "A transaction was unprocessed due to insufficient $OfferAssetTypeId balance of buyer with id $buyer_id."); + return $message; + } + if (($seller_balance_bc1 == 0) || ($seller_balance_bc1 < $qty)) { + $message[] = "Instant Transaction failed: You have insufficient $WantAssetTypeId balance."; + $this->storeMessages($buy_order_id, $buyer_id, $msg = "Your order no $buy_order_id was unprocessed due to insufficient $WantAssetTypeId balance of seller with id $seller_id."); + $this->storeMessages($sell_order_id = null, $seller_id, $msg = "Transaction failed: You have insufficient $WantAssetTypeId balance."); + return $message; + } + + $new_seller_cash_balance = $seller_balance_bc2 + $cost_of_total_supply; // traditional or cash + $new_seller_bit_balance = $seller_balance_bc1 - $qty; // deduct the btc sold + $new_buyer_cash_balance = $buyer_balance_bc2 - $cost_of_total_supply; // traditional + $new_buyer_bit_balance = $buyer_balance_bc1 + $qty; // traditional or cash + + $insert_market_order = $this->insert_market_order($_SESSION['user_id'], $orderTypeId = '1', $OfferAssetTypeId, $WantAssetTypeId, $qty, $available->price); + + $sell_order_id = 0; + if ($insert_market_order == false) { + return false; + } else if (is_int($insert_market_order)) { + $sell_order_id = (int)$insert_market_order; + } else { + return false; + } + + if ($buyer_id != $seller_id) { +// increment the bits of buyer + $this->update_user_balance($assetType = $WantAssetTypeId, $balance = $new_buyer_bit_balance, $user_id = $buyer_id); + +// deduct cash of buyer + $this->update_user_balance($assetType = $OfferAssetTypeId, $balance = $new_buyer_cash_balance, $user_id = $buyer_id); + +// subtract the cash of seller + $this->update_user_balance($assetType = $OfferAssetTypeId, $balance = $new_seller_cash_balance, $user_id = $seller_id); + +// deduct bits of seller + $this->update_user_balance($assetType = $WantAssetTypeId, $balance = $new_seller_bit_balance, $user_id = $seller_id); + } + +// Record the transaction + $this->record_transaction($buyer_id, $available->order_id, $available->price, $buy_commission = '0', $seller_id, $sell_order_id, $available->price, $sell_commission = '0', $trade_qty); + +// update the quantity field for supply + $this->update_quantity($top_table = TOP_BUYS_TABLE, $available->quantity, $available->order_id); + +// Update Order Status in Order table + $this->UpdateOrderStatus($sell_order_id, '1'); + + $message[] = "Instant Transaction Successful: You sold $qty $WantAssetTypeId for $OfferAssetTypeId $cost_of_total_supply."; + $this->storeMessages($sell_order_id, $seller_id, $msg = "Instant Transaction Successful: You sold $qty $WantAssetTypeId for $OfferAssetTypeId $cost_of_total_supply."); + $this->storeMessages($buy_order_id, $buyer_id, $msg = "Transaction Successful: You bought $qty $WantAssetTypeId for $OfferAssetTypeId $cost_of_total_supply."); + +// update the quantity field for demand + $qty = 0; + + break; + } + } + return $message; + } else { + $message[] = "empty_buy_list"; + return $message; + } + } + } + } + } + return false; +} + + public function update_user_balance($assetType, $balance=null, $user_id) { + + if ($this->databaseConnection()) { + $now = $this->time_now(); + $sql = ""; + + if ($balance >= 0) { + $sql .= "UPDATE ".CREDITS_TABLE." "; + $sql .= " SET `balance`= :balance, "; + $sql .= " `update_date`= '$now' "; + $sql .= " WHERE `uid`= :user_id "; + $sql .= " AND `bc`= :asset_type "; + $sql .= "LIMIT 1"; + + $query = $this->db_connection->prepare($sql); + + if ($balance >= 0) { + $query->bindParam("balance", $balance); + } + $query->bindParam("user_id", $user_id); + $query->bindParam("asset_type", $assetType); + if ($query->execute()) { + //$this->record_bal_history($user_id, $balance, $assetType); + return true; + } + } + return false; + } + return false; + } + + private function record_transaction($buyer, $buy_order_id, $buy_amount, $buy_bc, $seller, $sell_order_id, $sell_amount, $sell_bc, $trade_qty) { + if ($this->databaseConnection()) { + $now = $this->time_now(); + $query = $this->db_connection->prepare(" + INSERT INTO ".TX_TABLE."(`txid`, `a_buyer`, `a_order_id`, `a_amount`, `a_bc`, `b_seller`, `b_order_id`, `b_amount`, `b_bc`, `qty_traded`, `insert_dt`, `update_dt`) + VALUES ('', :buyer,:buy_order_id, :buy_amount, :buy_bc, :seller, :sell_order_id, :sell_amount, :sell_bc, :trade_qty, '$now', NULL) + "); + $query->bindParam("buyer", $buyer); + $query->bindParam("buy_order_id", $buy_order_id); + $query->bindParam("buy_amount", $buy_amount); + $query->bindParam("buy_bc", $buy_bc); + $query->bindParam("seller", $seller); + $query->bindParam("sell_order_id", $sell_order_id); + $query->bindParam("sell_amount", $sell_amount); + $query->bindParam("sell_bc", $sell_bc); + $query->bindParam("trade_qty", $trade_qty); + if($query->execute()) { + return true; + } + } + return false; + } + + private function delete_order($top_table, $orderId) { + if ($this->databaseConnection()) { + $query = $this->db_connection->prepare("DELETE FROM `$top_table` WHERE `order_id`=:id LIMIT 1"); + $query->bindParam('id', $orderId); + if($query->execute()) { + return true; + } + return false; + } + return false; + } + + private function update_quantity($top_table, $qty, $orderId) { + + if ($this->databaseConnection()) { + $now = $this->time_now(); + $query = $this->db_connection->prepare(" + UPDATE $top_table + SET `quantity`= :qty, `insert_dt`='$now' + WHERE order_id = :orderId + LIMIT 1 + "); + $query->bindParam('qty', $qty); + $query->bindParam('orderId', $orderId); + if($query->execute()) { + return true; + } + return false; + } + return false; + } + + public function del_order($order_id, $usid=null) { + if ($this->databaseConnection()) { + + $user_id = 0; + if (!isset($_SESSION['user_id'])) { + return false; + } + $user_id = (int) $_SESSION['user_id']; + // Allow Admin to delete order, if its not admin check owner of order + if ($usid == null) { + $is_owner = $this->isUserOrderOwner($order_id, $user_id); + + if (!$is_owner) { + return false; + } + } else if(($usid != null) && ($user_id == ADMIN_ID)) { // This else part to be used by admin in delete_orders_of_user() + $user_id = $usid; + } else { + return false; + } + + // Finally cancel the order + return $this->cancel_order($order_id, $user_id); + } + return false; + } + + private function isUserOrderOwner($order_id=0, $user_id=0) { + if ($this->databaseConnection()) { + $query = $this->db_connection->prepare(" + SELECT `id` FROM ".ORDERS_TABLE." + WHERE `id`=:o_id + AND `uid`=:c_id + LIMIT 1 + "); + $query->bindParam('o_id', $order_id); + $query->bindParam('c_id', $user_id); + if ($query->execute()) { + if ($query->rowCount()==1) { + return true; + } + } + } + return false; + } + + protected function cancel_order($order_id=null, $user_id=null) { + if ($this->databaseConnection()) { + $query = $this->db_connection->prepare(" + DELETE FROM ".TOP_BUYS_TABLE." WHERE `order_id`=:id AND uid = :cus_id; + DELETE FROM ".TOP_SELLS_TABLE." WHERE `order_id`=:id AND uid = :cus_id + "); + + $query->bindParam('id', $order_id); + $query->bindParam('cus_id', $user_id); + + $query->execute(); + unset($query); // Unset the query + + $q = $this->db_connection->prepare(" + UPDATE ".ORDERS_TABLE." SET `status`= 0 + WHERE `id` = :ord + AND uid = :cust_id + "); + $q->bindParam('ord', $order_id); + $q->bindParam('cust_id', $user_id); + + $q->execute(); + unset($q); + + $query2 = $this->db_connection->prepare(" + SELECT * FROM ".TOP_BUYS_TABLE." WHERE `order_id`=:o_id; + SELECT * FROM ".TOP_SELLS_TABLE." WHERE `order_id`=:o_id + "); + $query2->bindParam('o_id', $order_id); + + if ($query2->execute()) { + if ($query2->rowCount() == 0) { + if ($_SESSION['user_id']==ADMIN_ID) { + $this->storeMessages($order_id, ADMIN_ID, $msg="Order number $order_id was deleted by user id ".ADMIN_ID); + $this->storeMessages($order_id, $user_id, $msg="Order number $order_id was deleted by Admin."); + } else { + $this->storeMessages($order_id, $user_id, $msg="Order number $order_id was deleted by you."); + } + return true; // This means row was actually deleted + } + } + } + return false; + } + + public function storeMessages($order_id=null, $user_id=null, $msg=null) { + if($this->databaseConnection()) { + return; + $now = $this->time_now(); + if ($user_id == false) { + return false; + } + $username = $this->get_username($user_id); + + $query = $this->db_connection->prepare(" + INSERT INTO ".MSG_TABLE."(`id`, `order_id`, `username_key`, `username`, `messages`, `datetime`) + VALUES ('', :order_id, :user_id, :username, :msg, '$now') + "); + $query->bindParam("order_id", $order_id); + $query->bindParam("user_id", $user_id); + $query->bindParam("username", $username); + $query->bindParam("msg", $msg); + + if ($query->execute()) { + return true; + } + } + return false; + } + +} \ No newline at end of file diff --git a/classes/PHPMailer.php b/classes/PHPMailer.php new file mode 100644 index 0000000..bfb8eca --- /dev/null +++ b/classes/PHPMailer.php @@ -0,0 +1,2949 @@ +UseSendmailOptions) ) { + $rt = @mail($to, $this->EncodeHeader($this->SecureHeader($subject)), $body, $header); + } else { + $rt = @mail($to, $this->EncodeHeader($this->SecureHeader($subject)), $body, $header, $params); + } + return $rt; + } + + /** + * Outputs debugging info via user-defined method + * @param string $str + */ + protected function edebug($str) { + switch ($this->Debugoutput) { + case 'error_log': + error_log($str); + break; + case 'html': + //Cleans up output a bit for a better looking display that's HTML-safe + echo htmlentities(preg_replace('/[\r\n]+/', '', $str), ENT_QUOTES, $this->CharSet)."
\n"; + break; + case 'echo': + default: + //Just echoes exactly what was received + echo $str; + } + } + + /** + * Constructor + * @param boolean $exceptions Should we throw external exceptions? + */ + public function __construct($exceptions = false) { + $this->exceptions = ($exceptions == true); + } + + /** + * Destructor + */ + public function __destruct() { + if ($this->Mailer == 'smtp') { //Close any open SMTP connection nicely + $this->SmtpClose(); + } + } + + /** + * Sets message type to HTML. + * @param bool $ishtml + * @return void + */ + public function IsHTML($ishtml = true) { + if ($ishtml) { + $this->ContentType = 'text/html'; + } else { + $this->ContentType = 'text/plain'; + } + } + + /** + * Sets Mailer to send message using SMTP. + * @return void + */ + public function IsSMTP() { + $this->Mailer = 'smtp'; + } + + /** + * Sets Mailer to send message using PHP mail() function. + * @return void + */ + public function IsMail() { + $this->Mailer = 'mail'; + } + + /** + * Sets Mailer to send message using the $Sendmail program. + * @return void + */ + public function IsSendmail() { + if (!stristr(ini_get('sendmail_path'), 'sendmail')) { + $this->Sendmail = '/var/qmail/bin/sendmail'; + } + $this->Mailer = 'sendmail'; + } + + /** + * Sets Mailer to send message using the qmail MTA. + * @return void + */ + public function IsQmail() { + if (stristr(ini_get('sendmail_path'), 'qmail')) { + $this->Sendmail = '/var/qmail/bin/sendmail'; + } + $this->Mailer = 'sendmail'; + } + + ///////////////////////////////////////////////// + // METHODS, RECIPIENTS + ///////////////////////////////////////////////// + + /** + * Adds a "To" address. + * @param string $address + * @param string $name + * @return boolean true on success, false if address already used + */ + public function AddAddress($address, $name = '') { + return $this->AddAnAddress('to', $address, $name); + } + + /** + * Adds a "Cc" address. + * Note: this function works with the SMTP mailer on win32, not with the "mail" mailer. + * @param string $address + * @param string $name + * @return boolean true on success, false if address already used + */ + public function AddCC($address, $name = '') { + return $this->AddAnAddress('cc', $address, $name); + } + + /** + * Adds a "Bcc" address. + * Note: this function works with the SMTP mailer on win32, not with the "mail" mailer. + * @param string $address + * @param string $name + * @return boolean true on success, false if address already used + */ + public function AddBCC($address, $name = '') { + return $this->AddAnAddress('bcc', $address, $name); + } + + /** + * Adds a "Reply-to" address. + * @param string $address + * @param string $name + * @return boolean + */ + public function AddReplyTo($address, $name = '') { + return $this->AddAnAddress('Reply-To', $address, $name); + } + + /** + * Adds an address to one of the recipient arrays + * Addresses that have been added already return false, but do not throw exceptions + * @param string $kind One of 'to', 'cc', 'bcc', 'ReplyTo' + * @param string $address The email address to send to + * @param string $name + * @throws phpmailerException + * @return boolean true on success, false if address already used or invalid in some way + * @access protected + */ + protected function AddAnAddress($kind, $address, $name = '') { + if (!preg_match('/^(to|cc|bcc|Reply-To)$/', $kind)) { + $this->SetError($this->Lang('Invalid recipient array').': '.$kind); + if ($this->exceptions) { + throw new phpmailerException('Invalid recipient array: ' . $kind); + } + if ($this->SMTPDebug) { + $this->edebug($this->Lang('Invalid recipient array').': '.$kind); + } + return false; + } + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + if (!$this->ValidateAddress($address)) { + $this->SetError($this->Lang('invalid_address').': '. $address); + if ($this->exceptions) { + throw new phpmailerException($this->Lang('invalid_address').': '.$address); + } + if ($this->SMTPDebug) { + $this->edebug($this->Lang('invalid_address').': '.$address); + } + return false; + } + if ($kind != 'Reply-To') { + if (!isset($this->all_recipients[strtolower($address)])) { + array_push($this->$kind, array($address, $name)); + $this->all_recipients[strtolower($address)] = true; + return true; + } + } else { + if (!array_key_exists(strtolower($address), $this->ReplyTo)) { + $this->ReplyTo[strtolower($address)] = array($address, $name); + return true; + } + } + return false; +} + + /** + * Set the From and FromName properties + * @param string $address + * @param string $name + * @param boolean $auto Whether to also set the Sender address, defaults to true + * @throws phpmailerException + * @return boolean + */ + public function SetFrom($address, $name = '', $auto = true) { + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + if (!$this->ValidateAddress($address)) { + $this->SetError($this->Lang('invalid_address').': '. $address); + if ($this->exceptions) { + throw new phpmailerException($this->Lang('invalid_address').': '.$address); + } + if ($this->SMTPDebug) { + $this->edebug($this->Lang('invalid_address').': '.$address); + } + return false; + } + $this->From = $address; + $this->FromName = $name; + if ($auto) { + if (empty($this->Sender)) { + $this->Sender = $address; + } + } + return true; + } + + /** + * Check that a string looks roughly like an email address should + * Static so it can be used without instantiation, public so people can overload + * Conforms to RFC5322: Uses *correct* regex on which FILTER_VALIDATE_EMAIL is + * based; So why not use FILTER_VALIDATE_EMAIL? Because it was broken to + * not allow a@b type valid addresses :( + * @link http://squiloople.com/2009/12/20/email-address-validation/ + * @copyright regex Copyright Michael Rushton 2009-10 | http://squiloople.com/ | Feel free to use and redistribute this code. But please keep this copyright notice. + * @param string $address The email address to check + * @return boolean + * @static + * @access public + */ + public static function ValidateAddress($address) { + if (defined('PCRE_VERSION')) { //Check this instead of extension_loaded so it works when that function is disabled + if (version_compare(PCRE_VERSION, '8.0') >= 0) { + return (boolean)preg_match('/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', $address); + } else { + //Fall back to an older regex that doesn't need a recent PCRE + return (boolean)preg_match('/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD', $address); + } + } else { + //No PCRE! Do something _very_ approximate! + //Check the address is 3 chars or longer and contains an @ that's not the first or last char + return (strlen($address) >= 3 and strpos($address, '@') >= 1 and strpos($address, '@') != strlen($address) - 1); + } + } + + ///////////////////////////////////////////////// + // METHODS, MAIL SENDING + ///////////////////////////////////////////////// + + /** + * Creates message and assigns Mailer. If the message is + * not sent successfully then it returns false. Use the ErrorInfo + * variable to view description of the error. + * @throws phpmailerException + * @return bool + */ + public function Send() { + try { + if(!$this->PreSend()) return false; + return $this->PostSend(); + } catch (phpmailerException $e) { + $this->mailHeader = ''; + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + return false; + } + } + + /** + * Prep mail by constructing all message entities + * @throws phpmailerException + * @return bool + */ + public function PreSend() { + try { + $this->mailHeader = ""; + if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) { + throw new phpmailerException($this->Lang('provide_address'), self::STOP_CRITICAL); + } + + // Set whether the message is multipart/alternative + if(!empty($this->AltBody)) { + $this->ContentType = 'multipart/alternative'; + } + + $this->error_count = 0; // reset errors + $this->SetMessageType(); + //Refuse to send an empty message unless we are specifically allowing it + if (!$this->AllowEmpty and empty($this->Body)) { + throw new phpmailerException($this->Lang('empty_message'), self::STOP_CRITICAL); + } + + $this->MIMEHeader = $this->CreateHeader(); + $this->MIMEBody = $this->CreateBody(); + + // To capture the complete message when using mail(), create + // an extra header list which CreateHeader() doesn't fold in + if ($this->Mailer == 'mail') { + if (count($this->to) > 0) { + $this->mailHeader .= $this->AddrAppend("To", $this->to); + } else { + $this->mailHeader .= $this->HeaderLine("To", "undisclosed-recipients:;"); + } + $this->mailHeader .= $this->HeaderLine('Subject', $this->EncodeHeader($this->SecureHeader(trim($this->Subject)))); + } + + // digitally sign with DKIM if enabled + if (!empty($this->DKIM_domain) && !empty($this->DKIM_private) && !empty($this->DKIM_selector) && !empty($this->DKIM_domain) && file_exists($this->DKIM_private)) { + $header_dkim = $this->DKIM_Add($this->MIMEHeader . $this->mailHeader, $this->EncodeHeader($this->SecureHeader($this->Subject)), $this->MIMEBody); + $this->MIMEHeader = str_replace("\r\n", "\n", $header_dkim) . $this->MIMEHeader; + } + + return true; + + } catch (phpmailerException $e) { + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + return false; + } + } + + /** + * Actual Email transport function + * Send the email via the selected mechanism + * @throws phpmailerException + * @return bool + */ + public function PostSend() { + try { + // Choose the mailer and send through it + switch($this->Mailer) { + case 'sendmail': + return $this->SendmailSend($this->MIMEHeader, $this->MIMEBody); + case 'smtp': + return $this->SmtpSend($this->MIMEHeader, $this->MIMEBody); + case 'mail': + return $this->MailSend($this->MIMEHeader, $this->MIMEBody); + default: + return $this->MailSend($this->MIMEHeader, $this->MIMEBody); + } + } catch (phpmailerException $e) { + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + if ($this->SMTPDebug) { + $this->edebug($e->getMessage()."\n"); + } + } + return false; + } + + /** + * Sends mail using the $Sendmail program. + * @param string $header The message headers + * @param string $body The message body + * @throws phpmailerException + * @access protected + * @return bool + */ + protected function SendmailSend($header, $body) { + if ($this->Sender != '') { + $sendmail = sprintf("%s -oi -f%s -t", escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender)); + } else { + $sendmail = sprintf("%s -oi -t", escapeshellcmd($this->Sendmail)); + } + if ($this->SingleTo === true) { + foreach ($this->SingleToArray as $val) { + if(!@$mail = popen($sendmail, 'w')) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fputs($mail, "To: " . $val . "\n"); + fputs($mail, $header); + fputs($mail, $body); + $result = pclose($mail); + // implement call back function if it exists + $isSent = ($result == 0) ? 1 : 0; + $this->doCallback($isSent, $val, $this->cc, $this->bcc, $this->Subject, $body); + if($result != 0) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + } else { + if(!@$mail = popen($sendmail, 'w')) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fputs($mail, $header); + fputs($mail, $body); + $result = pclose($mail); + // implement call back function if it exists + $isSent = ($result == 0) ? 1 : 0; + $this->doCallback($isSent, $this->to, $this->cc, $this->bcc, $this->Subject, $body); + if($result != 0) { + throw new phpmailerException($this->Lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + return true; + } + + /** + * Sends mail using the PHP mail() function. + * @param string $header The message headers + * @param string $body The message body + * @throws phpmailerException + * @access protected + * @return bool + */ + protected function MailSend($header, $body) { + $toArr = array(); + foreach($this->to as $t) { + $toArr[] = $this->AddrFormat($t); + } + $to = implode(', ', $toArr); + + if (empty($this->Sender)) { + $params = " "; + } else { + $params = sprintf("-f%s", $this->Sender); + } + if ($this->Sender != '' and !ini_get('safe_mode')) { + $old_from = ini_get('sendmail_from'); + ini_set('sendmail_from', $this->Sender); + } + $rt = false; + if ($this->SingleTo === true && count($toArr) > 1) { + foreach ($toArr as $val) { + $rt = $this->mail_passthru($val, $this->Subject, $body, $header, $params); + // implement call back function if it exists + $isSent = ($rt == 1) ? 1 : 0; + $this->doCallback($isSent, $val, $this->cc, $this->bcc, $this->Subject, $body); + } + } else { + $rt = $this->mail_passthru($to, $this->Subject, $body, $header, $params); + // implement call back function if it exists + $isSent = ($rt == 1) ? 1 : 0; + $this->doCallback($isSent, $to, $this->cc, $this->bcc, $this->Subject, $body); + } + if (isset($old_from)) { + ini_set('sendmail_from', $old_from); + } + if(!$rt) { + throw new phpmailerException($this->Lang('instantiate'), self::STOP_CRITICAL); + } + return true; + } + + /** + * Sends mail via SMTP using PhpSMTP + * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. + * @param string $header The message headers + * @param string $body The message body + * @throws phpmailerException + * @uses SMTP + * @access protected + * @return bool + */ + protected function SmtpSend($header, $body) { + require_once $this->PluginDir . 'class.smtp.php'; + $bad_rcpt = array(); + + if(!$this->SmtpConnect()) { + throw new phpmailerException($this->Lang('smtp_connect_failed'), self::STOP_CRITICAL); + } + $smtp_from = ($this->Sender == '') ? $this->From : $this->Sender; + if(!$this->smtp->Mail($smtp_from)) { + $this->SetError($this->Lang('from_failed') . $smtp_from . ' : ' .implode(',', $this->smtp->getError())); + throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL); + } + + // Attempt to send attach all recipients + foreach($this->to as $to) { + if (!$this->smtp->Recipient($to[0])) { + $bad_rcpt[] = $to[0]; + // implement call back function if it exists + $isSent = 0; + $this->doCallback($isSent, $to[0], '', '', $this->Subject, $body); + } else { + // implement call back function if it exists + $isSent = 1; + $this->doCallback($isSent, $to[0], '', '', $this->Subject, $body); + } + } + foreach($this->cc as $cc) { + if (!$this->smtp->Recipient($cc[0])) { + $bad_rcpt[] = $cc[0]; + // implement call back function if it exists + $isSent = 0; + $this->doCallback($isSent, '', $cc[0], '', $this->Subject, $body); + } else { + // implement call back function if it exists + $isSent = 1; + $this->doCallback($isSent, '', $cc[0], '', $this->Subject, $body); + } + } + foreach($this->bcc as $bcc) { + if (!$this->smtp->Recipient($bcc[0])) { + $bad_rcpt[] = $bcc[0]; + // implement call back function if it exists + $isSent = 0; + $this->doCallback($isSent, '', '', $bcc[0], $this->Subject, $body); + } else { + // implement call back function if it exists + $isSent = 1; + $this->doCallback($isSent, '', '', $bcc[0], $this->Subject, $body); + } + } + + + if (count($bad_rcpt) > 0 ) { //Create error message for any bad addresses + $badaddresses = implode(', ', $bad_rcpt); + throw new phpmailerException($this->Lang('recipients_failed') . $badaddresses); + } + if(!$this->smtp->Data($header . $body)) { + throw new phpmailerException($this->Lang('data_not_accepted'), self::STOP_CRITICAL); + } + if($this->SMTPKeepAlive == true) { + $this->smtp->Reset(); + } else { + $this->smtp->Quit(); + $this->smtp->Close(); + } + return true; + } + + /** + * Initiates a connection to an SMTP server. + * Returns false if the operation failed. + * @param array $options An array of options compatible with stream_context_create() + * @uses SMTP + * @access public + * @throws phpmailerException + * @return bool + */ + public function SmtpConnect($options = array()) { + if(is_null($this->smtp)) { + $this->smtp = new SMTP; + } + + //Already connected? + if ($this->smtp->Connected()) { + return true; + } + + $this->smtp->Timeout = $this->Timeout; + $this->smtp->do_debug = $this->SMTPDebug; + $this->smtp->Debugoutput = $this->Debugoutput; + $this->smtp->do_verp = $this->do_verp; + $index = 0; + $tls = ($this->SMTPSecure == 'tls'); + $ssl = ($this->SMTPSecure == 'ssl'); + $hosts = explode(';', $this->Host); + $lastexception = null; + + foreach ($hosts as $hostentry) { + $hostinfo = array(); + $host = $hostentry; + $port = $this->Port; + if (preg_match('/^(.+):([0-9]+)$/', $hostentry, $hostinfo)) { //If $hostentry contains 'address:port', override default + $host = $hostinfo[1]; + $port = $hostinfo[2]; + } + if ($this->smtp->Connect(($ssl ? 'ssl://':'').$host, $port, $this->Timeout, $options)) { + try { + if ($this->Helo) { + $hello = $this->Helo; + } else { + $hello = $this->ServerHostname(); + } + $this->smtp->Hello($hello); + + if ($tls) { + if (!$this->smtp->StartTLS()) { + throw new phpmailerException($this->Lang('connect_host')); + } + //We must resend HELO after tls negotiation + $this->smtp->Hello($hello); + } + if ($this->SMTPAuth) { + if (!$this->smtp->Authenticate($this->Username, $this->Password, $this->AuthType, $this->Realm, $this->Workstation)) { + throw new phpmailerException($this->Lang('authenticate')); + } + } + return true; + } catch (phpmailerException $e) { + $lastexception = $e; + //We must have connected, but then failed TLS or Auth, so close connection nicely + $this->smtp->Quit(); + } + } + } + //If we get here, all connection attempts have failed, so close connection hard + $this->smtp->Close(); + //As we've caught all exceptions, just report whatever the last one was + if ($this->exceptions and !is_null($lastexception)) { + throw $lastexception; + } + return false; + } + + /** + * Closes the active SMTP session if one exists. + * @return void + */ + public function SmtpClose() { + if ($this->smtp !== null) { + if($this->smtp->Connected()) { + $this->smtp->Quit(); + $this->smtp->Close(); + } + } + } + + /** + * Sets the language for all class error messages. + * Returns false if it cannot load the language file. The default language is English. + * @param string $langcode ISO 639-1 2-character language code (e.g. Portuguese: "br") + * @param string $lang_path Path to the language file directory + * @return bool + * @access public + */ + function SetLanguage($langcode = 'en', $lang_path = 'language/') { + //Define full set of translatable strings + $PHPMAILER_LANG = array( + 'authenticate' => 'SMTP Error: Could not authenticate.', + 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', + 'data_not_accepted' => 'SMTP Error: Data not accepted.', + 'empty_message' => 'Message body empty', + 'encoding' => 'Unknown encoding: ', + 'execute' => 'Could not execute: ', + 'file_access' => 'Could not access file: ', + 'file_open' => 'File Error: Could not open file: ', + 'from_failed' => 'The following From address failed: ', + 'instantiate' => 'Could not instantiate mail function.', + 'invalid_address' => 'Invalid address', + 'mailer_not_supported' => ' mailer is not supported.', + 'provide_address' => 'You must provide at least one recipient email address.', + 'recipients_failed' => 'SMTP Error: The following recipients failed: ', + 'signing' => 'Signing Error: ', + 'smtp_connect_failed' => 'SMTP Connect() failed.', + 'smtp_error' => 'SMTP server error: ', + 'variable_set' => 'Cannot set or reset variable: ' + ); + //Overwrite language-specific strings. This way we'll never have missing translations - no more "language string failed to load"! + $l = true; + if ($langcode != 'en') { //There is no English translation file + $l = @include $lang_path.'phpmailer.lang-'.$langcode.'.php'; + } + $this->language = $PHPMAILER_LANG; + return ($l == true); //Returns false if language not found + } + + /** + * Return the current array of language strings + * @return array + */ + public function GetTranslations() { + return $this->language; + } + + ///////////////////////////////////////////////// + // METHODS, MESSAGE CREATION + ///////////////////////////////////////////////// + + /** + * Creates recipient headers. + * @access public + * @param string $type + * @param array $addr + * @return string + */ + public function AddrAppend($type, $addr) { + $addr_str = $type . ': '; + $addresses = array(); + foreach ($addr as $a) { + $addresses[] = $this->AddrFormat($a); + } + $addr_str .= implode(', ', $addresses); + $addr_str .= $this->LE; + + return $addr_str; + } + + /** + * Formats an address correctly. + * @access public + * @param string $addr + * @return string + */ + public function AddrFormat($addr) { + if (empty($addr[1])) { + return $this->SecureHeader($addr[0]); + } else { + return $this->EncodeHeader($this->SecureHeader($addr[1]), 'phrase') . " <" . $this->SecureHeader($addr[0]) . ">"; + } + } + + /** + * Wraps message for use with mailers that do not + * automatically perform wrapping and for quoted-printable. + * Original written by philippe. + * @param string $message The message to wrap + * @param integer $length The line length to wrap to + * @param boolean $qp_mode Whether to run in Quoted-Printable mode + * @access public + * @return string + */ + public function WrapText($message, $length, $qp_mode = false) { + $soft_break = ($qp_mode) ? sprintf(" =%s", $this->LE) : $this->LE; + // If utf-8 encoding is used, we will need to make sure we don't + // split multibyte characters when we wrap + $is_utf8 = (strtolower($this->CharSet) == "utf-8"); + $lelen = strlen($this->LE); + $crlflen = strlen(self::CRLF); + + $message = $this->FixEOL($message); + if (substr($message, -$lelen) == $this->LE) { + $message = substr($message, 0, -$lelen); + } + + $line = explode($this->LE, $message); // Magic. We know FixEOL uses $LE + $message = ''; + for ($i = 0 ;$i < count($line); $i++) { + $line_part = explode(' ', $line[$i]); + $buf = ''; + for ($e = 0; $e $length)) { + $space_left = $length - strlen($buf) - $crlflen; + if ($e != 0) { + if ($space_left > 20) { + $len = $space_left; + if ($is_utf8) { + $len = $this->UTF8CharBoundary($word, $len); + } elseif (substr($word, $len - 1, 1) == "=") { + $len--; + } elseif (substr($word, $len - 2, 1) == "=") { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = substr($word, $len); + $buf .= ' ' . $part; + $message .= $buf . sprintf("=%s", self::CRLF); + } else { + $message .= $buf . $soft_break; + } + $buf = ''; + } + while (strlen($word) > 0) { + if ($length <= 0) { + break; + } + $len = $length; + if ($is_utf8) { + $len = $this->UTF8CharBoundary($word, $len); + } elseif (substr($word, $len - 1, 1) == "=") { + $len--; + } elseif (substr($word, $len - 2, 1) == "=") { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = substr($word, $len); + + if (strlen($word) > 0) { + $message .= $part . sprintf("=%s", self::CRLF); + } else { + $buf = $part; + } + } + } else { + $buf_o = $buf; + $buf .= ($e == 0) ? $word : (' ' . $word); + + if (strlen($buf) > $length and $buf_o != '') { + $message .= $buf_o . $soft_break; + $buf = $word; + } + } + } + $message .= $buf . self::CRLF; + } + + return $message; + } + + /** + * Finds last character boundary prior to maxLength in a utf-8 + * quoted (printable) encoded string. + * Original written by Colin Brown. + * @access public + * @param string $encodedText utf-8 QP text + * @param int $maxLength find last character boundary prior to this length + * @return int + */ + public function UTF8CharBoundary($encodedText, $maxLength) { + $foundSplitPos = false; + $lookBack = 3; + while (!$foundSplitPos) { + $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack); + $encodedCharPos = strpos($lastChunk, "="); + if ($encodedCharPos !== false) { + // Found start of encoded character byte within $lookBack block. + // Check the encoded byte value (the 2 chars after the '=') + $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2); + $dec = hexdec($hex); + if ($dec < 128) { // Single byte character. + // If the encoded char was found at pos 0, it will fit + // otherwise reduce maxLength to start of the encoded char + $maxLength = ($encodedCharPos == 0) ? $maxLength : + $maxLength - ($lookBack - $encodedCharPos); + $foundSplitPos = true; + } elseif ($dec >= 192) { // First byte of a multi byte character + // Reduce maxLength to split at start of character + $maxLength = $maxLength - ($lookBack - $encodedCharPos); + $foundSplitPos = true; + } elseif ($dec < 192) { // Middle byte of a multi byte character, look further back + $lookBack += 3; + } + } else { + // No encoded character found + $foundSplitPos = true; + } + } + return $maxLength; + } + + + /** + * Set the body wrapping. + * @access public + * @return void + */ + public function SetWordWrap() { + if($this->WordWrap < 1) { + return; + } + + switch($this->message_type) { + case 'alt': + case 'alt_inline': + case 'alt_attach': + case 'alt_inline_attach': + $this->AltBody = $this->WrapText($this->AltBody, $this->WordWrap); + break; + default: + $this->Body = $this->WrapText($this->Body, $this->WordWrap); + break; + } + } + + /** + * Assembles message header. + * @access public + * @return string The assembled header + */ + public function CreateHeader() { + $result = ''; + + // Set the boundaries + $uniq_id = md5(uniqid(time())); + $this->boundary[1] = 'b1_' . $uniq_id; + $this->boundary[2] = 'b2_' . $uniq_id; + $this->boundary[3] = 'b3_' . $uniq_id; + + if ($this->MessageDate == '') { + $result .= $this->HeaderLine('Date', self::RFCDate()); + } else { + $result .= $this->HeaderLine('Date', $this->MessageDate); + } + + if ($this->ReturnPath) { + $result .= $this->HeaderLine('Return-Path', '<'.trim($this->ReturnPath).'>'); + } elseif ($this->Sender == '') { + $result .= $this->HeaderLine('Return-Path', '<'.trim($this->From).'>'); + } else { + $result .= $this->HeaderLine('Return-Path', '<'.trim($this->Sender).'>'); + } + + // To be created automatically by mail() + if($this->Mailer != 'mail') { + if ($this->SingleTo === true) { + foreach($this->to as $t) { + $this->SingleToArray[] = $this->AddrFormat($t); + } + } else { + if(count($this->to) > 0) { + $result .= $this->AddrAppend('To', $this->to); + } elseif (count($this->cc) == 0) { + $result .= $this->HeaderLine('To', 'undisclosed-recipients:;'); + } + } + } + + $from = array(); + $from[0][0] = trim($this->From); + $from[0][1] = $this->FromName; + $result .= $this->AddrAppend('From', $from); + + // sendmail and mail() extract Cc from the header before sending + if(count($this->cc) > 0) { + $result .= $this->AddrAppend('Cc', $this->cc); + } + + // sendmail and mail() extract Bcc from the header before sending + if((($this->Mailer == 'sendmail') || ($this->Mailer == 'mail')) && (count($this->bcc) > 0)) { + $result .= $this->AddrAppend('Bcc', $this->bcc); + } + + if(count($this->ReplyTo) > 0) { + $result .= $this->AddrAppend('Reply-To', $this->ReplyTo); + } + + // mail() sets the subject itself + if($this->Mailer != 'mail') { + $result .= $this->HeaderLine('Subject', $this->EncodeHeader($this->SecureHeader($this->Subject))); + } + + if($this->MessageID != '') { + $result .= $this->HeaderLine('Message-ID', $this->MessageID); + } else { + $result .= sprintf("Message-ID: <%s@%s>%s", $uniq_id, $this->ServerHostname(), $this->LE); + } + $result .= $this->HeaderLine('X-Priority', $this->Priority); + if ($this->XMailer == '') { + $result .= $this->HeaderLine('X-Mailer', 'PHPMailer '.$this->Version.' (https://github.com/PHPMailer/PHPMailer/)'); + } else { + $myXmailer = trim($this->XMailer); + if ($myXmailer) { + $result .= $this->HeaderLine('X-Mailer', $myXmailer); + } + } + + if($this->ConfirmReadingTo != '') { + $result .= $this->HeaderLine('Disposition-Notification-To', '<' . trim($this->ConfirmReadingTo) . '>'); + } + + // Add custom headers + for($index = 0; $index < count($this->CustomHeader); $index++) { + $result .= $this->HeaderLine(trim($this->CustomHeader[$index][0]), $this->EncodeHeader(trim($this->CustomHeader[$index][1]))); + } + if (!$this->sign_key_file) { + $result .= $this->HeaderLine('MIME-Version', '1.0'); + $result .= $this->GetMailMIME(); + } + + return $result; + } + + /** + * Returns the message MIME. + * @access public + * @return string + */ + public function GetMailMIME() { + $result = ''; + switch($this->message_type) { + case 'inline': + $result .= $this->HeaderLine('Content-Type', 'multipart/related;'); + $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1].'"'); + break; + case 'attach': + case 'inline_attach': + case 'alt_attach': + case 'alt_inline_attach': + $result .= $this->HeaderLine('Content-Type', 'multipart/mixed;'); + $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1].'"'); + break; + case 'alt': + case 'alt_inline': + $result .= $this->HeaderLine('Content-Type', 'multipart/alternative;'); + $result .= $this->TextLine("\tboundary=\"" . $this->boundary[1].'"'); + break; + default: + // Catches case 'plain': and case '': + $result .= $this->TextLine('Content-Type: '.$this->ContentType.'; charset='.$this->CharSet); + break; + } + //RFC1341 part 5 says 7bit is assumed if not specified + if ($this->Encoding != '7bit') { + $result .= $this->HeaderLine('Content-Transfer-Encoding', $this->Encoding); + } + + if($this->Mailer != 'mail') { + $result .= $this->LE; + } + + return $result; + } + + /** + * Returns the MIME message (headers and body). Only really valid post PreSend(). + * @access public + * @return string + */ + public function GetSentMIMEMessage() { + return $this->MIMEHeader . $this->mailHeader . self::CRLF . $this->MIMEBody; + } + + + /** + * Assembles the message body. Returns an empty string on failure. + * @access public + * @throws phpmailerException + * @return string The assembled message body + */ + public function CreateBody() { + $body = ''; + + if ($this->sign_key_file) { + $body .= $this->GetMailMIME().$this->LE; + } + + $this->SetWordWrap(); + + switch($this->message_type) { + case 'inline': + $body .= $this->GetBoundary($this->boundary[1], '', '', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll('inline', $this->boundary[1]); + break; + case 'attach': + $body .= $this->GetBoundary($this->boundary[1], '', '', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll('attachment', $this->boundary[1]); + break; + case 'inline_attach': + $body .= $this->TextLine('--' . $this->boundary[1]); + $body .= $this->HeaderLine('Content-Type', 'multipart/related;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2].'"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', '', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll('inline', $this->boundary[2]); + $body .= $this->LE; + $body .= $this->AttachAll('attachment', $this->boundary[1]); + break; + case 'alt': + $body .= $this->GetBoundary($this->boundary[1], '', 'text/plain', ''); + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->GetBoundary($this->boundary[1], '', 'text/html', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + if(!empty($this->Ical)) { + $body .= $this->GetBoundary($this->boundary[1], '', 'text/calendar; method=REQUEST', ''); + $body .= $this->EncodeString($this->Ical, $this->Encoding); + $body .= $this->LE.$this->LE; + } + $body .= $this->EndBoundary($this->boundary[1]); + break; + case 'alt_inline': + $body .= $this->GetBoundary($this->boundary[1], '', 'text/plain', ''); + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->TextLine('--' . $this->boundary[1]); + $body .= $this->HeaderLine('Content-Type', 'multipart/related;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2].'"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', 'text/html', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll('inline', $this->boundary[2]); + $body .= $this->LE; + $body .= $this->EndBoundary($this->boundary[1]); + break; + case 'alt_attach': + $body .= $this->TextLine('--' . $this->boundary[1]); + $body .= $this->HeaderLine('Content-Type', 'multipart/alternative;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2].'"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', 'text/plain', ''); + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', 'text/html', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->EndBoundary($this->boundary[2]); + $body .= $this->LE; + $body .= $this->AttachAll('attachment', $this->boundary[1]); + break; + case 'alt_inline_attach': + $body .= $this->TextLine('--' . $this->boundary[1]); + $body .= $this->HeaderLine('Content-Type', 'multipart/alternative;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[2].'"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[2], '', 'text/plain', ''); + $body .= $this->EncodeString($this->AltBody, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->TextLine('--' . $this->boundary[2]); + $body .= $this->HeaderLine('Content-Type', 'multipart/related;'); + $body .= $this->TextLine("\tboundary=\"" . $this->boundary[3].'"'); + $body .= $this->LE; + $body .= $this->GetBoundary($this->boundary[3], '', 'text/html', ''); + $body .= $this->EncodeString($this->Body, $this->Encoding); + $body .= $this->LE.$this->LE; + $body .= $this->AttachAll('inline', $this->boundary[3]); + $body .= $this->LE; + $body .= $this->EndBoundary($this->boundary[2]); + $body .= $this->LE; + $body .= $this->AttachAll('attachment', $this->boundary[1]); + break; + default: + // catch case 'plain' and case '' + $body .= $this->EncodeString($this->Body, $this->Encoding); + break; + } + + if ($this->IsError()) { + $body = ''; + } elseif ($this->sign_key_file) { + try { + if (!defined('PKCS7_TEXT')) { + throw new phpmailerException($this->Lang('signing').' OpenSSL extension missing.'); + } + $file = tempnam(sys_get_temp_dir(), 'mail'); + file_put_contents($file, $body); //TODO check this worked + $signed = tempnam(sys_get_temp_dir(), 'signed'); + if (@openssl_pkcs7_sign($file, $signed, 'file://'.realpath($this->sign_cert_file), array('file://'.realpath($this->sign_key_file), $this->sign_key_pass), null)) { + @unlink($file); + $body = file_get_contents($signed); + @unlink($signed); + } else { + @unlink($file); + @unlink($signed); + throw new phpmailerException($this->Lang('signing').openssl_error_string()); + } + } catch (phpmailerException $e) { + $body = ''; + if ($this->exceptions) { + throw $e; + } + } + } + return $body; + } + + /** + * Returns the start of a message boundary. + * @access protected + * @param string $boundary + * @param string $charSet + * @param string $contentType + * @param string $encoding + * @return string + */ + protected function GetBoundary($boundary, $charSet, $contentType, $encoding) { + $result = ''; + if($charSet == '') { + $charSet = $this->CharSet; + } + if($contentType == '') { + $contentType = $this->ContentType; + } + if($encoding == '') { + $encoding = $this->Encoding; + } + $result .= $this->TextLine('--' . $boundary); + $result .= sprintf("Content-Type: %s; charset=%s", $contentType, $charSet); + $result .= $this->LE; + $result .= $this->HeaderLine('Content-Transfer-Encoding', $encoding); + $result .= $this->LE; + + return $result; + } + + /** + * Returns the end of a message boundary. + * @access protected + * @param string $boundary + * @return string + */ + protected function EndBoundary($boundary) { + return $this->LE . '--' . $boundary . '--' . $this->LE; + } + + /** + * Sets the message type. + * @access protected + * @return void + */ + protected function SetMessageType() { + $this->message_type = array(); + if($this->AlternativeExists()) $this->message_type[] = "alt"; + if($this->InlineImageExists()) $this->message_type[] = "inline"; + if($this->AttachmentExists()) $this->message_type[] = "attach"; + $this->message_type = implode("_", $this->message_type); + if($this->message_type == "") $this->message_type = "plain"; + } + + /** + * Returns a formatted header line. + * @access public + * @param string $name + * @param string $value + * @return string + */ + public function HeaderLine($name, $value) { + return $name . ': ' . $value . $this->LE; + } + + /** + * Returns a formatted mail line. + * @access public + * @param string $value + * @return string + */ + public function TextLine($value) { + return $value . $this->LE; + } + + ///////////////////////////////////////////////// + // CLASS METHODS, ATTACHMENTS + ///////////////////////////////////////////////// + + /** + * Adds an attachment from a path on the filesystem. + * Returns false if the file could not be found + * or accessed. + * @param string $path Path to the attachment. + * @param string $name Overrides the attachment name. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File extension (MIME) type. + * @throws phpmailerException + * @return bool + */ + public function AddAttachment($path, $name = '', $encoding = 'base64', $type = '') { + try { + if ( !@is_file($path) ) { + throw new phpmailerException($this->Lang('file_access') . $path, self::STOP_CONTINUE); + } + + //If a MIME type is not specified, try to work it out from the file name + if ($type == '') { + $type = self::filenameToType($path); + } + + $filename = basename($path); + if ( $name == '' ) { + $name = $filename; + } + + $this->attachment[] = array( + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => 'attachment', + 7 => 0 + ); + + } catch (phpmailerException $e) { + $this->SetError($e->getMessage()); + if ($this->exceptions) { + throw $e; + } + if ($this->SMTPDebug) { + $this->edebug($e->getMessage()."\n"); + } + return false; + } + return true; + } + + /** + * Return the current array of attachments + * @return array + */ + public function GetAttachments() { + return $this->attachment; + } + + /** + * Attaches all fs, string, and binary attachments to the message. + * Returns an empty string on failure. + * @access protected + * @param string $disposition_type + * @param string $boundary + * @return string + */ + protected function AttachAll($disposition_type, $boundary) { + // Return text of body + $mime = array(); + $cidUniq = array(); + $incl = array(); + + // Add all attachments + foreach ($this->attachment as $attachment) { + // CHECK IF IT IS A VALID DISPOSITION_FILTER + if($attachment[6] == $disposition_type) { + // Check for string attachment + $string = ''; + $path = ''; + $bString = $attachment[5]; + if ($bString) { + $string = $attachment[0]; + } else { + $path = $attachment[0]; + } + + $inclhash = md5(serialize($attachment)); + if (in_array($inclhash, $incl)) { continue; } + $incl[] = $inclhash; + $filename = $attachment[1]; + $name = $attachment[2]; + $encoding = $attachment[3]; + $type = $attachment[4]; + $disposition = $attachment[6]; + $cid = $attachment[7]; + if ( $disposition == 'inline' && isset($cidUniq[$cid]) ) { continue; } + $cidUniq[$cid] = true; + + $mime[] = sprintf("--%s%s", $boundary, $this->LE); + $mime[] = sprintf("Content-Type: %s; name=\"%s\"%s", $type, $this->EncodeHeader($this->SecureHeader($name)), $this->LE); + $mime[] = sprintf("Content-Transfer-Encoding: %s%s", $encoding, $this->LE); + + if($disposition == 'inline') { + $mime[] = sprintf("Content-ID: <%s>%s", $cid, $this->LE); + } + + //If a filename contains any of these chars, it should be quoted, but not otherwise: RFC2183 & RFC2045 5.1 + //Fixes a warning in IETF's msglint MIME checker + if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $name)) { + $mime[] = sprintf("Content-Disposition: %s; filename=\"%s\"%s", $disposition, $this->EncodeHeader($this->SecureHeader($name)), $this->LE.$this->LE); + } else { + $mime[] = sprintf("Content-Disposition: %s; filename=%s%s", $disposition, $this->EncodeHeader($this->SecureHeader($name)), $this->LE.$this->LE); + } + + // Encode as string attachment + if($bString) { + $mime[] = $this->EncodeString($string, $encoding); + if($this->IsError()) { + return ''; + } + $mime[] = $this->LE.$this->LE; + } else { + $mime[] = $this->EncodeFile($path, $encoding); + if($this->IsError()) { + return ''; + } + $mime[] = $this->LE.$this->LE; + } + } + } + + $mime[] = sprintf("--%s--%s", $boundary, $this->LE); + + return implode("", $mime); + } + + /** + * Encodes attachment in requested format. + * Returns an empty string on failure. + * @param string $path The full path to the file + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * @throws phpmailerException + * @see EncodeFile() + * @access protected + * @return string + */ + protected function EncodeFile($path, $encoding = 'base64') { + try { + if (!is_readable($path)) { + throw new phpmailerException($this->Lang('file_open') . $path, self::STOP_CONTINUE); + } + $magic_quotes = get_magic_quotes_runtime(); + if ($magic_quotes) { + if (version_compare(PHP_VERSION, '5.3.0', '<')) { + set_magic_quotes_runtime(0); + } else { + ini_set('magic_quotes_runtime', 0); + } + } + $file_buffer = file_get_contents($path); + $file_buffer = $this->EncodeString($file_buffer, $encoding); + if ($magic_quotes) { + if (version_compare(PHP_VERSION, '5.3.0', '<')) { + set_magic_quotes_runtime($magic_quotes); + } else { + ini_set('magic_quotes_runtime', $magic_quotes); + } + } + return $file_buffer; + } catch (Exception $e) { + $this->SetError($e->getMessage()); + return ''; + } + } + + /** + * Encodes string to requested format. + * Returns an empty string on failure. + * @param string $str The text to encode + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * @access public + * @return string + */ + public function EncodeString($str, $encoding = 'base64') { + $encoded = ''; + switch(strtolower($encoding)) { + case 'base64': + $encoded = chunk_split(base64_encode($str), 76, $this->LE); + break; + case '7bit': + case '8bit': + $encoded = $this->FixEOL($str); + //Make sure it ends with a line break + if (substr($encoded, -(strlen($this->LE))) != $this->LE) + $encoded .= $this->LE; + break; + case 'binary': + $encoded = $str; + break; + case 'quoted-printable': + $encoded = $this->EncodeQP($str); + break; + default: + $this->SetError($this->Lang('encoding') . $encoding); + break; + } + return $encoded; + } + + /** + * Encode a header string to best (shortest) of Q, B, quoted or none. + * @access public + * @param string $str + * @param string $position + * @return string + */ + public function EncodeHeader($str, $position = 'text') { + $x = 0; + + switch (strtolower($position)) { + case 'phrase': + if (!preg_match('/[\200-\377]/', $str)) { + // Can't use addslashes as we don't know what value has magic_quotes_sybase + $encoded = addcslashes($str, "\0..\37\177\\\""); + if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) { + return ($encoded); + } else { + return ("\"$encoded\""); + } + } + $x = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches); + break; + case 'comment': + $x = preg_match_all('/[()"]/', $str, $matches); + // Fall-through + case 'text': + default: + $x += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches); + break; + } + + if ($x == 0) { //There are no chars that need encoding + return ($str); + } + + $maxlen = 75 - 7 - strlen($this->CharSet); + // Try to select the encoding which should produce the shortest output + if ($x > strlen($str)/3) { //More than a third of the content will need encoding, so B encoding will be most efficient + $encoding = 'B'; + if (function_exists('mb_strlen') && $this->HasMultiBytes($str)) { + // Use a custom function which correctly encodes and wraps long + // multibyte strings without breaking lines within a character + $encoded = $this->Base64EncodeWrapMB($str, "\n"); + } else { + $encoded = base64_encode($str); + $maxlen -= $maxlen % 4; + $encoded = trim(chunk_split($encoded, $maxlen, "\n")); + } + } else { + $encoding = 'Q'; + $encoded = $this->EncodeQ($str, $position); + $encoded = $this->WrapText($encoded, $maxlen, true); + $encoded = str_replace('='.self::CRLF, "\n", trim($encoded)); + } + + $encoded = preg_replace('/^(.*)$/m', " =?".$this->CharSet."?$encoding?\\1?=", $encoded); + $encoded = trim(str_replace("\n", $this->LE, $encoded)); + + return $encoded; + } + + /** + * Checks if a string contains multibyte characters. + * @access public + * @param string $str multi-byte text to wrap encode + * @return bool + */ + public function HasMultiBytes($str) { + if (function_exists('mb_strlen')) { + return (strlen($str) > mb_strlen($str, $this->CharSet)); + } else { // Assume no multibytes (we can't handle without mbstring functions anyway) + return false; + } + } + + /** + * Correctly encodes and wraps long multibyte strings for mail headers + * without breaking lines within a character. + * Adapted from a function by paravoid at http://uk.php.net/manual/en/function.mb-encode-mimeheader.php + * @access public + * @param string $str multi-byte text to wrap encode + * @param string $lf string to use as linefeed/end-of-line + * @return string + */ + public function Base64EncodeWrapMB($str, $lf=null) { + $start = "=?".$this->CharSet."?B?"; + $end = "?="; + $encoded = ""; + if ($lf === null) { + $lf = $this->LE; + } + + $mb_length = mb_strlen($str, $this->CharSet); + // Each line must have length <= 75, including $start and $end + $length = 75 - strlen($start) - strlen($end); + // Average multi-byte ratio + $ratio = $mb_length / strlen($str); + // Base64 has a 4:3 ratio + $offset = $avgLength = floor($length * $ratio * .75); + + for ($i = 0; $i < $mb_length; $i += $offset) { + $lookBack = 0; + + do { + $offset = $avgLength - $lookBack; + $chunk = mb_substr($str, $i, $offset, $this->CharSet); + $chunk = base64_encode($chunk); + $lookBack++; + } + while (strlen($chunk) > $length); + + $encoded .= $chunk . $lf; + } + + // Chomp the last linefeed + $encoded = substr($encoded, 0, -strlen($lf)); + return $encoded; + } + + /** + * Encode string to RFC2045 (6.7) quoted-printable format + * @access public + * @param string $string The text to encode + * @param integer $line_max Number of chars allowed on a line before wrapping + * @return string + * @link PHP version adapted from http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 + */ + public function EncodeQP($string, $line_max = 76) { + if (function_exists('quoted_printable_encode')) { //Use native function if it's available (>= PHP5.3) + return quoted_printable_encode($string); + } + //Fall back to a pure PHP implementation + $string = str_replace(array('%20', '%0D%0A.', '%0D%0A', '%'), array(' ', "\r\n=2E", "\r\n", '='), rawurlencode($string)); + $string = preg_replace('/[^\r\n]{'.($line_max - 3).'}[^=\r\n]{2}/', "$0=\r\n", $string); + return $string; + } + + /** + * Wrapper to preserve BC for old QP encoding function that was removed + * @see EncodeQP() + * @access public + * @param string $string + * @param integer $line_max + * @param bool $space_conv + * @return string + */ + public function EncodeQPphp($string, $line_max = 76, $space_conv = false) { + return $this->EncodeQP($string, $line_max); + } + + /** + * Encode string to q encoding. + * @link http://tools.ietf.org/html/rfc2047 + * @param string $str the text to encode + * @param string $position Where the text is going to be used, see the RFC for what that means + * @access public + * @return string + */ + public function EncodeQ($str, $position = 'text') { + //There should not be any EOL in the string + $pattern = ''; + $encoded = str_replace(array("\r", "\n"), '', $str); + switch (strtolower($position)) { + case 'phrase': + $pattern = '^A-Za-z0-9!*+\/ -'; + break; + + case 'comment': + $pattern = '\(\)"'; + //note that we don't break here! + //for this reason we build the $pattern without including delimiters and [] + + case 'text': + default: + //Replace every high ascii, control =, ? and _ characters + //We put \075 (=) as first value to make sure it's the first one in being converted, preventing double encode + $pattern = '\075\000-\011\013\014\016-\037\077\137\177-\377' . $pattern; + break; + } + + if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) { + foreach (array_unique($matches[0]) as $char) { + $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded); + } + } + + //Replace every spaces to _ (more readable than =20) + return str_replace(' ', '_', $encoded); +} + + + /** + * Adds a string or binary attachment (non-filesystem) to the list. + * This method can be used to attach ascii or binary data, + * such as a BLOB record from a database. + * @param string $string String attachment data. + * @param string $filename Name of the attachment. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File extension (MIME) type. + * @return void + */ + public function AddStringAttachment($string, $filename, $encoding = 'base64', $type = '') { + //If a MIME type is not specified, try to work it out from the file name + if ($type == '') { + $type = self::filenameToType($filename); + } + // Append to $attachment array + $this->attachment[] = array( + 0 => $string, + 1 => $filename, + 2 => basename($filename), + 3 => $encoding, + 4 => $type, + 5 => true, // isStringAttachment + 6 => 'attachment', + 7 => 0 + ); + } + + /** + * Add an embedded attachment from a file. + * This can include images, sounds, and just about any other document type. + * @param string $path Path to the attachment. + * @param string $cid Content ID of the attachment; Use this to reference + * the content when using an embedded image in HTML. + * @param string $name Overrides the attachment name. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File MIME type. + * @return bool True on successfully adding an attachment + */ + public function AddEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '') { + if ( !@is_file($path) ) { + $this->SetError($this->Lang('file_access') . $path); + return false; + } + + //If a MIME type is not specified, try to work it out from the file name + if ($type == '') { + $type = self::filenameToType($path); + } + + $filename = basename($path); + if ( $name == '' ) { + $name = $filename; + } + + // Append to $attachment array + $this->attachment[] = array( + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => 'inline', + 7 => $cid + ); + return true; + } + + + /** + * Add an embedded stringified attachment. + * This can include images, sounds, and just about any other document type. + * Be sure to set the $type to an image type for images: + * JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'. + * @param string $string The attachment binary data. + * @param string $cid Content ID of the attachment; Use this to reference + * the content when using an embedded image in HTML. + * @param string $name + * @param string $encoding File encoding (see $Encoding). + * @param string $type MIME type. + * @return bool True on successfully adding an attachment + */ + public function AddStringEmbeddedImage($string, $cid, $name = '', $encoding = 'base64', $type = '') { + //If a MIME type is not specified, try to work it out from the name + if ($type == '') { + $type = self::filenameToType($name); + } + + // Append to $attachment array + $this->attachment[] = array( + 0 => $string, + 1 => $name, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => true, // isStringAttachment + 6 => 'inline', + 7 => $cid + ); + return true; + } + + /** + * Returns true if an inline attachment is present. + * @access public + * @return bool + */ + public function InlineImageExists() { + foreach($this->attachment as $attachment) { + if ($attachment[6] == 'inline') { + return true; + } + } + return false; + } + + /** + * Returns true if an attachment (non-inline) is present. + * @return bool + */ + public function AttachmentExists() { + foreach($this->attachment as $attachment) { + if ($attachment[6] == 'attachment') { + return true; + } + } + return false; + } + + /** + * Does this message have an alternative body set? + * @return bool + */ + public function AlternativeExists() { + return !empty($this->AltBody); + } + + ///////////////////////////////////////////////// + // CLASS METHODS, MESSAGE RESET + ///////////////////////////////////////////////// + + /** + * Clears all recipients assigned in the TO array. Returns void. + * @return void + */ + public function ClearAddresses() { + foreach($this->to as $to) { + unset($this->all_recipients[strtolower($to[0])]); + } + $this->to = array(); + } + + /** + * Clears all recipients assigned in the CC array. Returns void. + * @return void + */ + public function ClearCCs() { + foreach($this->cc as $cc) { + unset($this->all_recipients[strtolower($cc[0])]); + } + $this->cc = array(); + } + + /** + * Clears all recipients assigned in the BCC array. Returns void. + * @return void + */ + public function ClearBCCs() { + foreach($this->bcc as $bcc) { + unset($this->all_recipients[strtolower($bcc[0])]); + } + $this->bcc = array(); + } + + /** + * Clears all recipients assigned in the ReplyTo array. Returns void. + * @return void + */ + public function ClearReplyTos() { + $this->ReplyTo = array(); + } + + /** + * Clears all recipients assigned in the TO, CC and BCC + * array. Returns void. + * @return void + */ + public function ClearAllRecipients() { + $this->to = array(); + $this->cc = array(); + $this->bcc = array(); + $this->all_recipients = array(); + } + + /** + * Clears all previously set filesystem, string, and binary + * attachments. Returns void. + * @return void + */ + public function ClearAttachments() { + $this->attachment = array(); + } + + /** + * Clears all custom headers. Returns void. + * @return void + */ + public function ClearCustomHeaders() { + $this->CustomHeader = array(); + } + + ///////////////////////////////////////////////// + // CLASS METHODS, MISCELLANEOUS + ///////////////////////////////////////////////// + + /** + * Adds the error message to the error container. + * @access protected + * @param string $msg + * @return void + */ + protected function SetError($msg) { + $this->error_count++; + if ($this->Mailer == 'smtp' and !is_null($this->smtp)) { + $lasterror = $this->smtp->getError(); + if (!empty($lasterror) and array_key_exists('smtp_msg', $lasterror)) { + $msg .= '

' . $this->Lang('smtp_error') . $lasterror['smtp_msg'] . "

\n"; + } + } + $this->ErrorInfo = $msg; + } + + /** + * Returns the proper RFC 822 formatted date. + * @access public + * @return string + * @static + */ + public static function RFCDate() { + //Set the time zone to whatever the default is to avoid 500 errors + //Will default to UTC if it's not set properly in php.ini + date_default_timezone_set(@date_default_timezone_get()); + return date('D, j M Y H:i:s O'); + } + + /** + * Returns the server hostname or 'localhost.localdomain' if unknown. + * @access protected + * @return string + */ + protected function ServerHostname() { + if (!empty($this->Hostname)) { + $result = $this->Hostname; + } elseif (isset($_SERVER['SERVER_NAME'])) { + $result = $_SERVER['SERVER_NAME']; + } else { + $result = 'localhost.localdomain'; + } + + return $result; + } + + /** + * Returns a message in the appropriate language. + * @access protected + * @param string $key + * @return string + */ + protected function Lang($key) { + if(count($this->language) < 1) { + $this->SetLanguage('en'); // set the default language + } + + if(isset($this->language[$key])) { + return $this->language[$key]; + } else { + return 'Language string failed to load: ' . $key; + } + } + + /** + * Returns true if an error occurred. + * @access public + * @return bool + */ + public function IsError() { + return ($this->error_count > 0); + } + + /** + * Changes every end of line from CRLF, CR or LF to $this->LE. + * @access public + * @param string $str String to FixEOL + * @return string + */ + public function FixEOL($str) { + // condense down to \n + $nstr = str_replace(array("\r\n", "\r"), "\n", $str); + // Now convert LE as needed + if ($this->LE !== "\n") { + $nstr = str_replace("\n", $this->LE, $nstr); + } + return $nstr; + } + + /** + * Adds a custom header. $name value can be overloaded to contain + * both header name and value (name:value) + * @access public + * @param string $name custom header name + * @param string $value header value + * @return void + */ + public function AddCustomHeader($name, $value=null) { + if ($value === null) { + // Value passed in as name:value + $this->CustomHeader[] = explode(':', $name, 2); + } else { + $this->CustomHeader[] = array($name, $value); + } + } + + /** + * Creates a message from an HTML string, making modifications for inline images and backgrounds + * and creates a plain-text version by converting the HTML + * Overwrites any existing values in $this->Body and $this->AltBody + * @access public + * @param string $message HTML message string + * @param string $basedir baseline directory for path + * @param bool $advanced Whether to use the advanced HTML to text converter + * @return string $message + */ + public function MsgHTML($message, $basedir = '', $advanced = false) { + preg_match_all("/(src|background)=[\"'](.*)[\"']/Ui", $message, $images); + if (isset($images[2])) { + foreach ($images[2] as $i => $url) { + // do not change urls for absolute images (thanks to corvuscorax) + if (!preg_match('#^[A-z]+://#', $url)) { + $filename = basename($url); + $directory = dirname($url); + if ($directory == '.') { + $directory = ''; + } + $cid = md5($url).'@phpmailer.0'; //RFC2392 S 2 + if (strlen($basedir) > 1 && substr($basedir, -1) != '/') { + $basedir .= '/'; + } + if (strlen($directory) > 1 && substr($directory, -1) != '/') { + $directory .= '/'; + } + if ($this->AddEmbeddedImage($basedir.$directory.$filename, $cid, $filename, 'base64', self::_mime_types(self::mb_pathinfo($filename, PATHINFO_EXTENSION)))) { + $message = preg_replace("/".$images[1][$i]."=[\"']".preg_quote($url, '/')."[\"']/Ui", $images[1][$i]."=\"cid:".$cid."\"", $message); + } + } + } + } + $this->IsHTML(true); + if (empty($this->AltBody)) { + $this->AltBody = 'To view this email message, open it in a program that understands HTML!' . "\n\n"; + } + //Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better + $this->Body = $this->NormalizeBreaks($message); + $this->AltBody = $this->NormalizeBreaks($this->html2text($message, $advanced)); + return $this->Body; + } + + /** + * Convert an HTML string into a plain text version + * @param string $html The HTML text to convert + * @param bool $advanced Should this use the more complex html2text converter or just a simple one? + * @return string + */ + public function html2text($html, $advanced = false) { + if ($advanced) { + require_once 'extras/class.html2text.php'; + $h = new html2text($html); + return $h->get_text(); + } + return html_entity_decode(trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))), ENT_QUOTES, $this->CharSet); + } + + /** + * Gets the MIME type of the embedded or inline image + * @param string $ext File extension + * @access public + * @return string MIME type of ext + * @static + */ + public static function _mime_types($ext = '') { + $mimes = array( + 'xl' => 'application/excel', + 'hqx' => 'application/mac-binhex40', + 'cpt' => 'application/mac-compactpro', + 'bin' => 'application/macbinary', + 'doc' => 'application/msword', + 'word' => 'application/msword', + 'class' => 'application/octet-stream', + 'dll' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'exe' => 'application/octet-stream', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'psd' => 'application/octet-stream', + 'sea' => 'application/octet-stream', + 'so' => 'application/octet-stream', + 'oda' => 'application/oda', + 'pdf' => 'application/pdf', + 'ai' => 'application/postscript', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'mif' => 'application/vnd.mif', + 'xls' => 'application/vnd.ms-excel', + 'ppt' => 'application/vnd.ms-powerpoint', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'gtar' => 'application/x-gtar', + 'php3' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'php' => 'application/x-httpd-php', + 'phtml' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'js' => 'application/x-javascript', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'tar' => 'application/x-tar', + 'tgz' => 'application/x-tar', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'zip' => 'application/zip', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mpga' => 'audio/mpeg', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'ram' => 'audio/x-pn-realaudio', + 'rm' => 'audio/x-pn-realaudio', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'ra' => 'audio/x-realaudio', + 'wav' => 'audio/x-wav', + 'bmp' => 'image/bmp', + 'gif' => 'image/gif', + 'jpeg' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'png' => 'image/png', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'eml' => 'message/rfc822', + 'css' => 'text/css', + 'html' => 'text/html', + 'htm' => 'text/html', + 'shtml' => 'text/html', + 'log' => 'text/plain', + 'text' => 'text/plain', + 'txt' => 'text/plain', + 'rtx' => 'text/richtext', + 'rtf' => 'text/rtf', + 'xml' => 'text/xml', + 'xsl' => 'text/xml', + 'mpeg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mov' => 'video/quicktime', + 'qt' => 'video/quicktime', + 'rv' => 'video/vnd.rn-realvideo', + 'avi' => 'video/x-msvideo', + 'movie' => 'video/x-sgi-movie' + ); + return (!isset($mimes[strtolower($ext)])) ? 'application/octet-stream' : $mimes[strtolower($ext)]; + } + + /** + * Try to map a file name to a MIME type, default to application/octet-stream + * @param string $filename A file name or full path, does not need to exist as a file + * @return string + * @static + */ + public static function filenameToType($filename) { + //In case the path is a URL, strip any query string before getting extension + $qpos = strpos($filename, '?'); + if ($qpos !== false) { + $filename = substr($filename, 0, $qpos); + } + $pathinfo = self::mb_pathinfo($filename); + return self::_mime_types($pathinfo['extension']); + } + + /** + * Drop-in replacement for pathinfo(), but multibyte-safe, cross-platform-safe, old-version-safe. + * Works similarly to the one in PHP >= 5.2.0 + * @link http://www.php.net/manual/en/function.pathinfo.php#107461 + * @param string $path A filename or path, does not need to exist as a file + * @param integer|string $options Either a PATHINFO_* constant, or a string name to return only the specified piece, allows 'filename' to work on PHP < 5.2 + * @return string|array + * @static + */ + public static function mb_pathinfo($path, $options = null) { + $ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''); + $m = array(); + preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $m); + if(array_key_exists(1, $m)) { + $ret['dirname'] = $m[1]; + } + if(array_key_exists(2, $m)) { + $ret['basename'] = $m[2]; + } + if(array_key_exists(5, $m)) { + $ret['extension'] = $m[5]; + } + if(array_key_exists(3, $m)) { + $ret['filename'] = $m[3]; + } + switch($options) { + case PATHINFO_DIRNAME: + case 'dirname': + return $ret['dirname']; + break; + case PATHINFO_BASENAME: + case 'basename': + return $ret['basename']; + break; + case PATHINFO_EXTENSION: + case 'extension': + return $ret['extension']; + break; + case PATHINFO_FILENAME: + case 'filename': + return $ret['filename']; + break; + default: + return $ret; + } + } + + /** + * Set (or reset) Class Objects (variables) + * + * Usage Example: + * $page->set('X-Priority', '3'); + * + * @access public + * @param string $name + * @param mixed $value + * NOTE: will not work with arrays, there are no arrays to set/reset + * @throws phpmailerException + * @return bool + * @todo Should this not be using __set() magic function? + */ + public function set($name, $value = '') { + try { + if (isset($this->$name) ) { + $this->$name = $value; + } else { + throw new phpmailerException($this->Lang('variable_set') . $name, self::STOP_CRITICAL); + } + } catch (Exception $e) { + $this->SetError($e->getMessage()); + if ($e->getCode() == self::STOP_CRITICAL) { + return false; + } + } + return true; + } + + /** + * Strips newlines to prevent header injection. + * @access public + * @param string $str + * @return string + */ + public function SecureHeader($str) { + return trim(str_replace(array("\r", "\n"), '', $str)); + } + + /** + * Normalize UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format + * Defaults to CRLF (for message bodies) and preserves consecutive breaks + * @param string $text + * @param string $breaktype What kind of line break to use, defaults to CRLF + * @return string + * @access public + * @static + */ + public static function NormalizeBreaks($text, $breaktype = "\r\n") { + return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text); + } + + + /** + * Set the private key file and password to sign the message. + * + * @access public + * @param string $cert_filename + * @param string $key_filename + * @param string $key_pass Password for private key + */ + public function Sign($cert_filename, $key_filename, $key_pass) { + $this->sign_cert_file = $cert_filename; + $this->sign_key_file = $key_filename; + $this->sign_key_pass = $key_pass; + } + + /** + * Set the private key file and password to sign the message. + * + * @access public + * @param string $txt + * @return string + */ + public function DKIM_QP($txt) { + $line = ''; + for ($i = 0; $i < strlen($txt); $i++) { + $ord = ord($txt[$i]); + if ( ((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E)) ) { + $line .= $txt[$i]; + } else { + $line .= "=".sprintf("%02X", $ord); + } + } + return $line; + } + + /** + * Generate DKIM signature + * + * @access public + * @param string $s Header + * @throws phpmailerException + * @return string + */ + public function DKIM_Sign($s) { + if (!defined('PKCS7_TEXT')) { + if ($this->exceptions) { + throw new phpmailerException($this->Lang("signing").' OpenSSL extension missing.'); + } + return ''; + } + $privKeyStr = file_get_contents($this->DKIM_private); + if ($this->DKIM_passphrase != '') { + $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase); + } else { + $privKey = $privKeyStr; + } + if (openssl_sign($s, $signature, $privKey)) { + return base64_encode($signature); + } + return ''; + } + + /** + * Generate DKIM Canonicalization Header + * + * @access public + * @param string $s Header + * @return string + */ + public function DKIM_HeaderC($s) { + $s = preg_replace("/\r\n\s+/", " ", $s); + $lines = explode("\r\n", $s); + foreach ($lines as $key => $line) { + list($heading, $value) = explode(":", $line, 2); + $heading = strtolower($heading); + $value = preg_replace("/\s+/", " ", $value) ; // Compress useless spaces + $lines[$key] = $heading.":".trim($value) ; // Don't forget to remove WSP around the value + } + $s = implode("\r\n", $lines); + return $s; + } + + /** + * Generate DKIM Canonicalization Body + * + * @access public + * @param string $body Message Body + * @return string + */ + public function DKIM_BodyC($body) { + if ($body == '') return "\r\n"; + // stabilize line endings + $body = str_replace("\r\n", "\n", $body); + $body = str_replace("\n", "\r\n", $body); + // END stabilize line endings + while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") { + $body = substr($body, 0, strlen($body) - 2); + } + return $body; + } + + /** + * Create the DKIM header, body, as new header + * + * @access public + * @param string $headers_line Header lines + * @param string $subject Subject + * @param string $body Body + * @return string + */ + public function DKIM_Add($headers_line, $subject, $body) { + $DKIMsignatureType = 'rsa-sha1'; // Signature & hash algorithms + $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body + $DKIMquery = 'dns/txt'; // Query method + $DKIMtime = time() ; // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone) + $subject_header = "Subject: $subject"; + $headers = explode($this->LE, $headers_line); + $from_header = ''; + $to_header = ''; + $current = ''; + foreach($headers as $header) { + if (strpos($header, 'From:') === 0) { + $from_header = $header; + $current = 'from_header'; + } elseif (strpos($header, 'To:') === 0) { + $to_header = $header; + $current = 'to_header'; + } else { + if($current && strpos($header, ' =?') === 0){ + $current .= $header; + } else { + $current = ''; + } + } + } + $from = str_replace('|', '=7C', $this->DKIM_QP($from_header)); + $to = str_replace('|', '=7C', $this->DKIM_QP($to_header)); + $subject = str_replace('|', '=7C', $this->DKIM_QP($subject_header)) ; // Copied header fields (dkim-quoted-printable + $body = $this->DKIM_BodyC($body); + $DKIMlen = strlen($body) ; // Length of body + $DKIMb64 = base64_encode(pack("H*", sha1($body))) ; // Base64 of packed binary SHA-1 hash of body + $ident = ($this->DKIM_identity == '')? '' : " i=" . $this->DKIM_identity . ";"; + $dkimhdrs = "DKIM-Signature: v=1; a=" . $DKIMsignatureType . "; q=" . $DKIMquery . "; l=" . $DKIMlen . "; s=" . $this->DKIM_selector . ";\r\n". + "\tt=" . $DKIMtime . "; c=" . $DKIMcanonicalization . ";\r\n". + "\th=From:To:Subject;\r\n". + "\td=" . $this->DKIM_domain . ";" . $ident . "\r\n". + "\tz=$from\r\n". + "\t|$to\r\n". + "\t|$subject;\r\n". + "\tbh=" . $DKIMb64 . ";\r\n". + "\tb="; + $toSign = $this->DKIM_HeaderC($from_header . "\r\n" . $to_header . "\r\n" . $subject_header . "\r\n" . $dkimhdrs); + $signed = $this->DKIM_Sign($toSign); + return $dkimhdrs.$signed."\r\n"; + } + + /** + * Perform callback + * @param boolean $isSent + * @param string $to + * @param string $cc + * @param string $bcc + * @param string $subject + * @param string $body + * @param string $from + */ + protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from = null) { + if (!empty($this->action_function) && is_callable($this->action_function)) { + $params = array($isSent, $to, $cc, $bcc, $subject, $body, $from); + call_user_func_array($this->action_function, $params); + } + } +} + +/** + * Exception handler for PHPMailer + * @package PHPMailer + */ +class phpmailerException extends Exception { + /** + * Prettify error message output + * @return string + */ + public function errorMessage() { + $errorMsg = '' . $this->getMessage() . "
\n"; + return $errorMsg; + } +} diff --git a/classes/SendMail.php b/classes/SendMail.php new file mode 100644 index 0000000..2d62877 --- /dev/null +++ b/classes/SendMail.php @@ -0,0 +1,84 @@ +reciever_email = $reciever_email; + $this->from = $email_from; + $this->sender = $email_sender; + $this->subject = $email_subject; + $this->body = $email_body; + $this->attachments = $attachments; + + $mail = new PHPMailer; + + // please look into the config/config.php for much more info on how to use this! + // use SMTP or use mail() + if (EMAIL_USE_SMTP) { + // Set mailer to use SMTP + $mail->IsSMTP(); + //useful for debugging, shows full SMTP errors + $mail->SMTPDebug = 0; // debugging: 1 = errors and messages, 2 = messages only + // Enable SMTP authentication + $mail->SMTPAuth = EMAIL_SMTP_AUTH; + // Enable encryption, usually SSL/TLS + if (defined(EMAIL_SMTP_ENCRYPTION)) { + $mail->SMTPSecure = EMAIL_SMTP_ENCRYPTION; + } + $mail->SMTPSecure = "tls"; + // Specify host server + $mail->Host = EMAIL_SMTP_HOST; + $mail->Username = EMAIL_SMTP_USERNAME; + $mail->Password = EMAIL_SMTP_PASSWORD; + $mail->Port = EMAIL_SMTP_PORT; + } else { + $mail->IsMail(); + } + + if(trim($email_from) != "" && !empty($reciever_email)) { + $mail->From = $email_from; + $mail->FromName = $email_sender; + + $this->reciever_email[] = $reciever_email; + $mail->IsHTML(true); + foreach ($reciever_email as $rec) { + $mail->AddAddress($rec); + } + $mail->AddCC(RT); + $mail->AddCC(AB); + $mail->Subject = $email_subject; + $mail->Body = $email_body; + + if ($attachments!==null && is_array($attachments) && !empty($attachments)) { + foreach ($attachments as $attachment) { + $path_to_file = $attachment; + $arr = explode('/',$attachment); + $att_file = end($arr); + $mail->AddAttachment($path_to_file, $att_file); + } + } + + if(!$mail->Send()) { + $this->errors[] = "Mail could not be sent" . $mail->ErrorInfo; + return $this->errors; + } else { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/classes/Users.php b/classes/Users.php new file mode 100644 index 0000000..bf63905 --- /dev/null +++ b/classes/Users.php @@ -0,0 +1,235 @@ +db_connection != null) { + return true; + } else { + try { + $this->db_connection = new PDO('mysql:host='. DB_HOST .';dbname='. DB_NAME . ';charset=utf8', DB_USER, DB_PASS); + return true; + } catch (PDOException $e) { + $this->errors[] = MESSAGE_DATABASE_ERROR . $e->getMessage(); + } + } + return false; + } + + private function insert_balance($CustomerId, $AssetTypeId, $Balance) { + $now = $this->time_now(); + if ($this->databaseConnection()) { + $query = $this->db_connection->prepare("INSERT INTO ".CREDITS_TABLE."(`id`, `uid`, `bc`, `balance`, `insert_date`, `update_date`) + VALUES ('', :CustomerId,:AssetTypeId,:Balance,'$now','$now')"); + $query->bindValue(':CustomerId', $CustomerId, PDO::PARAM_STR); + $query->bindValue(':AssetTypeId', $AssetTypeId, PDO::PARAM_STR); + $query->bindValue(':Balance', $Balance, PDO::PARAM_STR); + + if($query->execute()) { + return true; + } + } + return false; + } + + public function is_fb_registered($fb_id) { + + if ($this->databaseConnection()) { + $now = $this->time_now(); + $query = $this->db_connection->prepare("SELECT * FROM ".USERS_TABLE." WHERE `fb_id`=:fb_id"); + $query->bindValue(':fb_id', $fb_id, PDO::PARAM_STR); + $query->execute(); + + $rowCount = $query->rowCount(); + + if($rowCount) { + + $user_obj = $query->fetchObject(); + + $update_query = $this->db_connection->prepare("UPDATE ".USERS_TABLE." + SET `last_activity`='$now' + WHERE `fb_id`=:fb_id + LIMIT 1"); + $update_query->bindValue(':fb_id', $fb_id, PDO::PARAM_STR); + $update_query->execute(); + + $_SESSION['user_id'] = $user_obj->id; + $_SESSION['user_name'] = $user_obj->uname; + $_SESSION['email'] = $user_obj->email; + + if (!isset($_SESSION['last_trade_date'])) { + $_SESSION['user_last_login'] = $user_obj->last_activity; + } + return true; + + } else { + + $this->user_name = $_SESSION['first_name'].time(); + $this->name = $_SESSION['full_name']; + $this->email = $_SESSION['email']; + + $query = $this->db_connection->prepare(" + INSERT INTO ".USERS_TABLE." (`id`, `fb_id`, `uname`, `email`, `name`, `registered_on`, `last_activity`, `is_active`) + VALUES ('',:fb_id,:Username,:Email,:Name,'$now','$now',0) + "); + + $query->bindValue(':fb_id', $fb_id, PDO::PARAM_INT); + $query->bindValue(':Username', $this->user_name, PDO::PARAM_STR); + $query->bindValue(':Email', $this->email, PDO::PARAM_STR); + $query->bindValue(':Name', $this->name, PDO::PARAM_STR); + if($query->execute()) { + $_SESSION['user_id'] = $this->db_connection->lastInsertId(); + $_SESSION['user_name'] = $this->user_name; + + $this->insert_balance($_SESSION['user_id'], $AssetTypeId=RMT, $Balance=0.00, $FrozenBalance=0.00); + $this->insert_balance($_SESSION['user_id'], $AssetTypeId=REBC, $Balance=0.00, $FrozenBalance=0.00); + $this->insert_balance($_SESSION['user_id'], $AssetTypeId=IBC, $Balance=0.00, $FrozenBalance=0.00); + $this->insert_balance($_SESSION['user_id'], $AssetTypeId=FLOBC, $Balance=0.00, $FrozenBalance=0.00); + $this->insert_balance($_SESSION['user_id'], $AssetTypeId=RSBC, $Balance=0.00, $FrozenBalance=0.00); + $this->insert_balance($_SESSION['user_id'], $AssetTypeId=INTBC, $Balance=0.00, $FrozenBalance=0.00); + $this->insert_balance($_SESSION['user_id'], $AssetTypeId=IHWBC, $Balance=0.00, $FrozenBalance=0.00); + $this->insert_balance($_SESSION['user_id'], $AssetTypeId=DBC, $Balance=0.00, $FrozenBalance=0.00); + $this->insert_balance($_SESSION['user_id'], $AssetTypeId=RBC, $Balance=0.00, $FrozenBalance=0.00); + $this->insert_balance($_SESSION['user_id'], $AssetTypeId=PBC, $Balance=0.00, $FrozenBalance=0.00); + $this->insert_balance($_SESSION['user_id'], $AssetTypeId=ARTBC, $Balance=0.00, $FrozenBalance=0.00); + + $user_exist = $this->check_user($_SESSION['user_id']); + if($user_exist) { + return true; + } + return false; + } + return false; + } + } else { + return false; + } + } + + public function check_user($customerId) { + + if ($this->databaseConnection()) { + + $query = $this->db_connection->prepare("SELECT * FROM ".USERS_TABLE." WHERE id = :customerId AND is_active = 1 LIMIT 1"); + $query->bindParam('customerId', $customerId); + + if ($query->execute()) { + $row_count = $query->rowCount(); + if ($row_count == 1) { + return $user_details = $query->fetchObject(); + } + return false; + } else { + return false; + } + } + return false; + } + + public function actions_user($u_id, $act=1) { + if ($this->databaseConnection()) { + if (!empty($u_id)) { + + $act = (int) $act; + $u_id = (int) $u_id; + + $query = $this->db_connection->prepare(" + UPDATE ".USERS_TABLE." SET `is_active`= $act + WHERE id = :u_id + LIMIT 1 + "); + $query->bindParam('u_id', $u_id); + + if ($query->execute()) { + return true; + } + } + } + return false; + } + + public function get_total_users_count() { + if ($this->databaseConnection()) { + $total_users = 0; + $query = $this->db_connection->query("SELECT COUNT(*) AS TOTAL_COUNT FROM ".USERS_TABLE." WHERE `is_active`=1"); + if ($query->rowCount()) { + $total_users = $query->fetchObject()->TOTAL_COUNT; + } + return (int) $total_users; + } + return false; + } + + public function time_now() { + $n = new DateTime("now", new DateTimeZone("Asia/Kolkata")); + $now = $n->format('Y-m-d H:i:s'); + return $now; + } + + public function get_username($customerId=0) { + + if ($this->databaseConnection()) { + $customerId = (int) $customerId; + $query = $this->db_connection->prepare("SELECT uname FROM ".USERS_TABLE." WHERE id = :id LIMIT 1"); + $query->bindParam('id', $customerId); + + $query->execute(); + $row_count = $query->rowCount(); + if ($row_count == 1) { + return $query->fetchObject()->uname; + } + } + return false; + } + + public function input_user_email($email=null, $user_id=null) { + if ($this->databaseConnection()) { + $query = $this->db_connection->prepare(" + UPDATE ".USERS_TABLE." SET `email`= :em WHERE id = :cid + "); + $query->bindParam('em', $email); + $query->bindParam('cid', $user_id); + + if ($query->execute()) { + return true; + } + } + return false; + } + + public function check_customer_balance($assetType, $user_id) { + + if ($this->databaseConnection()) { + + $customer_balance = null; + $query = $this->db_connection->prepare("SELECT `balance` + FROM ".CREDITS_TABLE." + WHERE `uid`= :user_id AND `bc`='$assetType'"); + $query->bindParam(":user_id", $user_id); + if ($query->execute()) { + if ($query->rowCount()) { + $customer_balance = $query->fetchObject(); + } + } + return $customer_balance; + } + return false; + } + +} \ No newline at end of file diff --git a/classes/class.smtp.php b/classes/class.smtp.php new file mode 100644 index 0000000..4b02f99 --- /dev/null +++ b/classes/class.smtp.php @@ -0,0 +1,1092 @@ +Debugoutput) { + case 'error_log': + error_log($str); + break; + case 'html': + //Cleans up output a bit for a better looking display that's HTML-safe + echo htmlentities(preg_replace('/[\r\n]+/', '', $str), ENT_QUOTES, 'UTF-8')."
\n"; + break; + case 'echo': + default: + //Just echoes exactly what was received + echo $str; + } + } + + /** + * Initialize the class so that the data is in a known state. + * @access public + * @return SMTP + */ + public function __construct() { + $this->smtp_conn = 0; + $this->error = null; + $this->helo_rply = null; + + $this->do_debug = 0; + } + + ///////////////////////////////////////////////// + // CONNECTION FUNCTIONS + ///////////////////////////////////////////////// + + /** + * Connect to an SMTP server + * + * SMTP CODE SUCCESS: 220 + * SMTP CODE FAILURE: 421 + * @access public + * @param string $host SMTP server IP or host name + * @param int $port The port number to connect to, or use the default port if not specified + * @param int $timeout How long to wait for the connection to open + * @param array $options An array of options compatible with stream_context_create() + * @return bool + */ + public function Connect($host, $port = 0, $timeout = 30, $options = array()) { + // Clear errors to avoid confusion + $this->error = null; + + // Make sure we are __not__ connected + if($this->connected()) { + // Already connected, generate error + $this->error = array('error' => 'Already connected to a server'); + return false; + } + + if(empty($port)) { + $port = $this->SMTP_PORT; + } + + // Connect to the SMTP server + $errno = 0; + $errstr = ''; + $socket_context = stream_context_create($options); + //Need to suppress errors here as connection failures can be handled at a higher level + $this->smtp_conn = @stream_socket_client($host.":".$port, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $socket_context); + + // Verify we connected properly + if(empty($this->smtp_conn)) { + $this->error = array('error' => 'Failed to connect to server', + 'errno' => $errno, + 'errstr' => $errstr); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ": $errstr ($errno)"); + } + return false; + } + + // SMTP server can take longer to respond, give longer timeout for first read + // Windows does not have support for this timeout function + if(substr(PHP_OS, 0, 3) != 'WIN') { + $max = ini_get('max_execution_time'); + if ($max != 0 && $timeout > $max) { // Don't bother if unlimited + @set_time_limit($timeout); + } + stream_set_timeout($this->smtp_conn, $timeout, 0); + } + + // get any announcement + $announce = $this->get_lines(); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $announce); + } + + return true; + } + + /** + * Initiate a TLS communication with the server. + * + * SMTP CODE 220 Ready to start TLS + * SMTP CODE 501 Syntax error (no parameters allowed) + * SMTP CODE 454 TLS not available due to temporary reason + * @access public + * @return bool success + */ + public function StartTLS() { + $this->error = null; # to avoid confusion + + if(!$this->connected()) { + $this->error = array('error' => 'Called StartTLS() without being connected'); + return false; + } + + $this->client_send('STARTTLS' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $rply); + } + + if($code != 220) { + $this->error = + array('error' => 'STARTTLS not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + + // Begin encrypted connection + if(!stream_socket_enable_crypto($this->smtp_conn, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { + return false; + } + + return true; + } + + /** + * Performs SMTP authentication. Must be run after running the + * Hello() method. Returns true if successfully authenticated. + * @access public + * @param string $username + * @param string $password + * @param string $authtype + * @param string $realm + * @param string $workstation + * @return bool + */ + public function Authenticate($username, $password, $authtype='LOGIN', $realm='', $workstation='') { + if (empty($authtype)) { + $authtype = 'LOGIN'; + } + + switch ($authtype) { + case 'PLAIN': + // Start authentication + $this->client_send('AUTH PLAIN' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 334) { + $this->error = + array('error' => 'AUTH not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + // Send encoded username and password + $this->client_send(base64_encode("\0".$username."\0".$password) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 235) { + $this->error = + array('error' => 'Authentication not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + break; + case 'LOGIN': + // Start authentication + $this->client_send('AUTH LOGIN' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 334) { + $this->error = + array('error' => 'AUTH not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + + // Send encoded username + $this->client_send(base64_encode($username) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 334) { + $this->error = + array('error' => 'Username not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + + // Send encoded password + $this->client_send(base64_encode($password) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 235) { + $this->error = + array('error' => 'Password not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + break; + case 'NTLM': + /* + * ntlm_sasl_client.php + ** Bundled with Permission + ** + ** How to telnet in windows: http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx + ** PROTOCOL Documentation http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication + */ + require_once 'extras/ntlm_sasl_client.php'; + $temp = new stdClass(); + $ntlm_client = new ntlm_sasl_client_class; + if(! $ntlm_client->Initialize($temp)){//let's test if every function its available + $this->error = array('error' => $temp->error); + if($this->do_debug >= 1) { + $this->edebug('You need to enable some modules in your php.ini file: ' . $this->error['error']); + } + return false; + } + $msg1 = $ntlm_client->TypeMsg1($realm, $workstation);//msg1 + + $this->client_send('AUTH NTLM ' . base64_encode($msg1) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 334) { + $this->error = + array('error' => 'AUTH not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + + $challenge = substr($rply, 3);//though 0 based, there is a white space after the 3 digit number....//msg2 + $challenge = base64_decode($challenge); + $ntlm_res = $ntlm_client->NTLMResponse(substr($challenge, 24, 8), $password); + $msg3 = $ntlm_client->TypeMsg3($ntlm_res, $username, $realm, $workstation);//msg3 + // Send encoded username + $this->client_send(base64_encode($msg3) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 235) { + $this->error = + array('error' => 'Could not authenticate', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + break; + case 'CRAM-MD5': + // Start authentication + $this->client_send('AUTH CRAM-MD5' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 334) { + $this->error = + array('error' => 'AUTH not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + + // Get the challenge + $challenge = base64_decode(substr($rply, 4)); + + // Build the response + $response = $username . ' ' . $this->hmac($challenge, $password); + + // Send encoded credentials + $this->client_send(base64_encode($response) . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($code != 235) { + $this->error = + array('error' => 'Credentials not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + break; + } + return true; + } + + /** + * Works like hash_hmac('md5', $data, $key) in case that function is not available + * @access protected + * @param string $data + * @param string $key + * @return string + */ + protected function hmac($data, $key) { + if (function_exists('hash_hmac')) { + return hash_hmac('md5', $data, $key); + } + + // The following borrowed from http://php.net/manual/en/function.mhash.php#27225 + + // RFC 2104 HMAC implementation for php. + // Creates an md5 HMAC. + // Eliminates the need to install mhash to compute a HMAC + // Hacked by Lance Rushing + + $b = 64; // byte length for md5 + if (strlen($key) > $b) { + $key = pack('H*', md5($key)); + } + $key = str_pad($key, $b, chr(0x00)); + $ipad = str_pad('', $b, chr(0x36)); + $opad = str_pad('', $b, chr(0x5c)); + $k_ipad = $key ^ $ipad ; + $k_opad = $key ^ $opad; + + return md5($k_opad . pack('H*', md5($k_ipad . $data))); + } + + /** + * Returns true if connected to a server otherwise false + * @access public + * @return bool + */ + public function Connected() { + if(!empty($this->smtp_conn)) { + $sock_status = stream_get_meta_data($this->smtp_conn); + if($sock_status['eof']) { + // the socket is valid but we are not connected + if($this->do_debug >= 1) { + $this->edebug('SMTP -> NOTICE: EOF caught while checking if connected'); + } + $this->Close(); + return false; + } + return true; // everything looks good + } + return false; + } + + /** + * Closes the socket and cleans up the state of the class. + * It is not considered good to use this function without + * first trying to use QUIT. + * @access public + * @return void + */ + public function Close() { + $this->error = null; // so there is no confusion + $this->helo_rply = null; + if(!empty($this->smtp_conn)) { + // close the connection and cleanup + fclose($this->smtp_conn); + $this->smtp_conn = 0; + } + } + + ///////////////////////////////////////////////// + // SMTP COMMANDS + ///////////////////////////////////////////////// + + /** + * Issues a data command and sends the msg_data to the server + * finializing the mail transaction. $msg_data is the message + * that is to be send with the headers. Each header needs to be + * on a single line followed by a with the message headers + * and the message body being separated by and additional . + * + * Implements rfc 821: DATA + * + * SMTP CODE INTERMEDIATE: 354 + * [data] + * . + * SMTP CODE SUCCESS: 250 + * SMTP CODE FAILURE: 552, 554, 451, 452 + * SMTP CODE FAILURE: 451, 554 + * SMTP CODE ERROR : 500, 501, 503, 421 + * @access public + * @param string $msg_data + * @return bool + */ + public function Data($msg_data) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + 'error' => 'Called Data() without being connected'); + return false; + } + + $this->client_send('DATA' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $rply); + } + + if($code != 354) { + $this->error = + array('error' => 'DATA command not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + + /* the server is ready to accept data! + * according to rfc 821 we should not send more than 1000 + * including the CRLF + * characters on a single line so we will break the data up + * into lines by \r and/or \n then if needed we will break + * each of those into smaller lines to fit within the limit. + * in addition we will be looking for lines that start with + * a period '.' and append and additional period '.' to that + * line. NOTE: this does not count towards limit. + */ + + // normalize the line breaks so we know the explode works + $msg_data = str_replace("\r\n", "\n", $msg_data); + $msg_data = str_replace("\r", "\n", $msg_data); + $lines = explode("\n", $msg_data); + + /* we need to find a good way to determine is headers are + * in the msg_data or if it is a straight msg body + * currently I am assuming rfc 822 definitions of msg headers + * and if the first field of the first line (':' sperated) + * does not contain a space then it _should_ be a header + * and we can process all lines before a blank "" line as + * headers. + */ + + $field = substr($lines[0], 0, strpos($lines[0], ':')); + $in_headers = false; + if(!empty($field) && !strstr($field, ' ')) { + $in_headers = true; + } + + $max_line_length = 998; // used below; set here for ease in change + + while(list(, $line) = @each($lines)) { + $lines_out = null; + if($line == '' && $in_headers) { + $in_headers = false; + } + // ok we need to break this line up into several smaller lines + while(strlen($line) > $max_line_length) { + $pos = strrpos(substr($line, 0, $max_line_length), ' '); + + // Patch to fix DOS attack + if(!$pos) { + $pos = $max_line_length - 1; + $lines_out[] = substr($line, 0, $pos); + $line = substr($line, $pos); + } else { + $lines_out[] = substr($line, 0, $pos); + $line = substr($line, $pos + 1); + } + + /* if processing headers add a LWSP-char to the front of new line + * rfc 822 on long msg headers + */ + if($in_headers) { + $line = "\t" . $line; + } + } + $lines_out[] = $line; + + // send the lines to the server + while(list(, $line_out) = @each($lines_out)) { + if(strlen($line_out) > 0) + { + if(substr($line_out, 0, 1) == '.') { + $line_out = '.' . $line_out; + } + } + $this->client_send($line_out . $this->CRLF); + } + } + + // message data has been sent + $this->client_send($this->CRLF . '.' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $rply); + } + + if($code != 250) { + $this->error = + array('error' => 'DATA not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + return true; + } + + /** + * Sends the HELO command to the smtp server. + * This makes sure that we and the server are in + * the same known state. + * + * Implements from rfc 821: HELO + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE ERROR : 500, 501, 504, 421 + * @access public + * @param string $host + * @return bool + */ + public function Hello($host = '') { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + 'error' => 'Called Hello() without being connected'); + return false; + } + + // if hostname for HELO was not specified send default + if(empty($host)) { + // determine appropriate default to send to server + $host = 'localhost'; + } + + // Send extended hello first (RFC 2821) + if(!$this->SendHello('EHLO', $host)) { + if(!$this->SendHello('HELO', $host)) { + return false; + } + } + + return true; + } + + /** + * Sends a HELO/EHLO command. + * @access protected + * @param string $hello + * @param string $host + * @return bool + */ + protected function SendHello($hello, $host) { + $this->client_send($hello . ' ' . $host . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER: ' . $rply); + } + + if($code != 250) { + $this->error = + array('error' => $hello . ' not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + + $this->helo_rply = $rply; + + return true; + } + + /** + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more Recipient + * commands may be called followed by a Data command. + * + * Implements rfc 821: MAIL FROM: + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE SUCCESS: 552, 451, 452 + * SMTP CODE SUCCESS: 500, 501, 421 + * @access public + * @param string $from + * @return bool + */ + public function Mail($from) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + 'error' => 'Called Mail() without being connected'); + return false; + } + + $useVerp = ($this->do_verp ? ' XVERP' : ''); + $this->client_send('MAIL FROM:<' . $from . '>' . $useVerp . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $rply); + } + + if($code != 250) { + $this->error = + array('error' => 'MAIL not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + return true; + } + + /** + * Sends the quit command to the server and then closes the socket + * if there is no error or the $close_on_error argument is true. + * + * Implements from rfc 821: QUIT + * + * SMTP CODE SUCCESS: 221 + * SMTP CODE ERROR : 500 + * @access public + * @param bool $close_on_error + * @return bool + */ + public function Quit($close_on_error = true) { + $this->error = null; // so there is no confusion + + if(!$this->connected()) { + $this->error = array( + 'error' => 'Called Quit() without being connected'); + return false; + } + + // send the quit command to the server + $this->client_send('quit' . $this->CRLF); + + // get any good-bye messages + $byemsg = $this->get_lines(); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $byemsg); + } + + $rval = true; + $e = null; + + $code = substr($byemsg, 0, 3); + if($code != 221) { + // use e as a tmp var cause Close will overwrite $this->error + $e = array('error' => 'SMTP server rejected quit command', + 'smtp_code' => $code, + 'smtp_rply' => substr($byemsg, 4)); + $rval = false; + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $e['error'] . ': ' . $byemsg); + } + } + + if(empty($e) || $close_on_error) { + $this->Close(); + } + + return $rval; + } + + /** + * Sends the command RCPT to the SMTP server with the TO: argument of $to. + * Returns true if the recipient was accepted false if it was rejected. + * + * Implements from rfc 821: RCPT TO: + * + * SMTP CODE SUCCESS: 250, 251 + * SMTP CODE FAILURE: 550, 551, 552, 553, 450, 451, 452 + * SMTP CODE ERROR : 500, 501, 503, 421 + * @access public + * @param string $to + * @return bool + */ + public function Recipient($to) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + 'error' => 'Called Recipient() without being connected'); + return false; + } + + $this->client_send('RCPT TO:<' . $to . '>' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $rply); + } + + if($code != 250 && $code != 251) { + $this->error = + array('error' => 'RCPT not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + return true; + } + + /** + * Sends the RSET command to abort and transaction that is + * currently in progress. Returns true if successful false + * otherwise. + * + * Implements rfc 821: RSET + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE ERROR : 500, 501, 504, 421 + * @access public + * @return bool + */ + public function Reset() { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array('error' => 'Called Reset() without being connected'); + return false; + } + + $this->client_send('RSET' . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $rply); + } + + if($code != 250) { + $this->error = + array('error' => 'RSET failed', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + + return true; + } + + /** + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more Recipient + * commands may be called followed by a Data command. This command + * will send the message to the users terminal if they are logged + * in and send them an email. + * + * Implements rfc 821: SAML FROM: + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE SUCCESS: 552, 451, 452 + * SMTP CODE SUCCESS: 500, 501, 502, 421 + * @access public + * @param string $from + * @return bool + */ + public function SendAndMail($from) { + $this->error = null; // so no confusion is caused + + if(!$this->connected()) { + $this->error = array( + 'error' => 'Called SendAndMail() without being connected'); + return false; + } + + $this->client_send('SAML FROM:' . $from . $this->CRLF); + + $rply = $this->get_lines(); + $code = substr($rply, 0, 3); + + if($this->do_debug >= 2) { + $this->edebug('SMTP -> FROM SERVER:' . $rply); + } + + if($code != 250) { + $this->error = + array('error' => 'SAML not accepted from server', + 'smtp_code' => $code, + 'smtp_msg' => substr($rply, 4)); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> ERROR: ' . $this->error['error'] . ': ' . $rply); + } + return false; + } + return true; + } + + /** + * This is an optional command for SMTP that this class does not + * support. This method is here to make the RFC821 Definition + * complete for this class and __may__ be implimented in the future + * + * Implements from rfc 821: TURN + * + * SMTP CODE SUCCESS: 250 + * SMTP CODE FAILURE: 502 + * SMTP CODE ERROR : 500, 503 + * @access public + * @return bool + */ + public function Turn() { + $this->error = array('error' => 'This method, TURN, of the SMTP '. + 'is not implemented'); + if($this->do_debug >= 1) { + $this->edebug('SMTP -> NOTICE: ' . $this->error['error']); + } + return false; + } + + /** + * Sends data to the server + * @param string $data + * @access public + * @return Integer number of bytes sent to the server or FALSE on error + */ + public function client_send($data) { + if ($this->do_debug >= 1) { + $this->edebug("CLIENT -> SMTP: $data"); + } + return fwrite($this->smtp_conn, $data); + } + + /** + * Get the current error + * @access public + * @return array + */ + public function getError() { + return $this->error; + } + + ///////////////////////////////////////////////// + // INTERNAL FUNCTIONS + ///////////////////////////////////////////////// + + /** + * Read in as many lines as possible + * either before eof or socket timeout occurs on the operation. + * With SMTP we can tell if we have more lines to read if the + * 4th character is '-' symbol. If it is a space then we don't + * need to read anything else. + * @access protected + * @return string + */ + protected function get_lines() { + $data = ''; + $endtime = 0; + /* If for some reason the fp is bad, don't inf loop */ + if (!is_resource($this->smtp_conn)) { + return $data; + } + stream_set_timeout($this->smtp_conn, $this->Timeout); + if ($this->Timelimit > 0) { + $endtime = time() + $this->Timelimit; + } + while(is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { + $str = @fgets($this->smtp_conn, 515); + if($this->do_debug >= 4) { + $this->edebug("SMTP -> get_lines(): \$data was \"$data\""); + $this->edebug("SMTP -> get_lines(): \$str is \"$str\""); + } + $data .= $str; + if($this->do_debug >= 4) { + $this->edebug("SMTP -> get_lines(): \$data is \"$data\""); + } + // if 4th character is a space, we are done reading, break the loop + if(substr($str, 3, 1) == ' ') { break; } + // Timed-out? Log and break + $info = stream_get_meta_data($this->smtp_conn); + if ($info['timed_out']) { + if($this->do_debug >= 4) { + $this->edebug('SMTP -> get_lines(): timed-out (' . $this->Timeout . ' seconds)'); + } + break; + } + // Now check if reads took too long + if ($endtime) { + if (time() > $endtime) { + if($this->do_debug >= 4) { + $this->edebug('SMTP -> get_lines(): timelimit reached (' . $this->Timelimit . ' seconds)'); + } + break; + } + } + } + return $data; + } + +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..7fba0f5 --- /dev/null +++ b/composer.json @@ -0,0 +1,7 @@ +{ + "require": { + "facebook/graph-sdk": "^5.6", + "slim/slim": "^3.0", + "nesbot/carbon": "~1.21" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..83f0229 --- /dev/null +++ b/composer.lock @@ -0,0 +1,549 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "d4de0a6f3b7d95a597083d01e7a0ab31", + "content-hash": "a0bae5c4058f9955a0318ad11a91411b", + "packages": [ + { + "name": "container-interop/container-interop", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/container-interop/container-interop.git", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "shasum": "" + }, + "require": { + "psr/container": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "homepage": "https://github.com/container-interop/container-interop", + "time": "2017-02-14 19:40:03" + }, + { + "name": "facebook/graph-sdk", + "version": "5.6.2", + "source": { + "type": "git", + "url": "https://github.com/facebook/php-graph-sdk.git", + "reference": "030f8c5b9b1a6c09e71719fd638b66ea4daa2f10" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facebook/php-graph-sdk/zipball/030f8c5b9b1a6c09e71719fd638b66ea4daa2f10", + "reference": "030f8c5b9b1a6c09e71719fd638b66ea4daa2f10", + "shasum": "" + }, + "require": { + "php": "^5.4|^7.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "~5.0", + "mockery/mockery": "~0.8", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "guzzlehttp/guzzle": "Allows for implementation of the Guzzle HTTP client", + "paragonie/random_compat": "Provides a better CSPRNG option in PHP 5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "Facebook\\": "src/Facebook/" + }, + "files": [ + "src/Facebook/polyfills.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Facebook Platform" + ], + "authors": [ + { + "name": "Facebook", + "homepage": "https://github.com/facebook/php-graph-sdk/contributors" + } + ], + "description": "Facebook SDK for PHP", + "homepage": "https://github.com/facebook/php-graph-sdk", + "keywords": [ + "facebook", + "sdk" + ], + "time": "2018-02-14 23:24:51" + }, + { + "name": "nesbot/carbon", + "version": "1.27.0", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "ef81c39b67200dcd7401c24363dcac05ac3a4fe9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/ef81c39b67200dcd7401c24363dcac05ac3a4fe9", + "reference": "ef81c39b67200dcd7401c24363dcac05ac3a4fe9", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/translation": "~2.6 || ~3.0 || ~4.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2", + "phpunit/phpunit": "^4.8.35 || ^5.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "http://nesbot.com" + } + ], + "description": "A simple API extension for DateTime.", + "homepage": "http://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "time": "2018-04-23 09:02:57" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "FastRoute\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "time": "2018-02-13 20:26:39" + }, + { + "name": "pimple/pimple", + "version": "v3.2.3", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32", + "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/container": "^1.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "psr-0": { + "Pimple": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "http://pimple.sensiolabs.org", + "keywords": [ + "container", + "dependency injection" + ], + "time": "2018-01-21 07:42:36" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14 16:28:37" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06 14:39:51" + }, + { + "name": "slim/slim", + "version": "3.10.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim.git", + "reference": "d8aabeacc3688b25e2f2dd2db91df91ec6fdd748" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim/zipball/d8aabeacc3688b25e2f2dd2db91df91ec6fdd748", + "reference": "d8aabeacc3688b25e2f2dd2db91df91ec6fdd748", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.2", + "nikic/fast-route": "^1.0", + "php": ">=5.5.0", + "pimple/pimple": "^3.0", + "psr/container": "^1.0", + "psr/http-message": "^1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0", + "squizlabs/php_codesniffer": "^2.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\": "Slim" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "http://akrabat.com" + }, + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "https://joshlockhart.com" + }, + { + "name": "Gabriel Manricks", + "email": "gmanricks@me.com", + "homepage": "http://gabrielmanricks.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "http://silentworks.co.uk" + } + ], + "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", + "homepage": "https://slimframework.com", + "keywords": [ + "api", + "framework", + "micro", + "router" + ], + "time": "2018-04-19 19:29:08" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b", + "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2018-01-30 19:27:44" + }, + { + "name": "symfony/translation", + "version": "v3.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "80e19eaf12cbb546ac40384e5c55c36306823e57" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/80e19eaf12cbb546ac40384e5c55c36306823e57", + "reference": "80e19eaf12cbb546ac40384e5c55c36306823e57", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.4", + "symfony/yaml": "<3.4" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/finder": "~2.8|~3.0|~4.0", + "symfony/intl": "^2.8.18|^3.2.5|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "psr/log": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Translation Component", + "homepage": "https://symfony.com", + "time": "2018-02-22 06:28:18" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/fbconfig.php b/fbconfig.php new file mode 100644 index 0000000..65f829b --- /dev/null +++ b/fbconfig.php @@ -0,0 +1,88 @@ + APP_ID, + 'app_secret' => APP_SECRET, + 'default_graph_version' => 'v2.12', +]); +$helper = $fb->getRedirectLoginHelper(); +if (isset($_GET['state'])) { + $helper->getPersistentDataHandler()->set('state', $_GET['state']); +} +//$helper = $fb->getRedirectLoginHelper(); +$permissions = ['email']; // optional + +try { + if (isset($_SESSION['facebook_access_token'])) { + $accessToken = $_SESSION['facebook_access_token']; + } else { + $accessToken = $helper->getAccessToken(); + } +} catch(Facebook\Exceptions\FacebookResponseException $e) { + // When Graph returns an error + echo 'Graph returned an error: ' . $e->getMessage(); + exit; +} catch(Facebook\Exceptions\FacebookSDKException $e) { + // When validation fails or other local issues + echo 'Facebook SDK returned an error: ' . $e->getMessage(); + exit; +} +if (isset($accessToken)) { + if (isset($_SESSION['facebook_access_token'])) { + $fb->setDefaultAccessToken($_SESSION['facebook_access_token']); + } else { + // getting short-lived access token + $_SESSION['facebook_access_token'] = (string) $accessToken; + // OAuth 2.0 client handler + $oAuth2Client = $fb->getOAuth2Client(); + // Exchanges a short-lived access token for a long-lived one + $longLivedAccessToken = $oAuth2Client->getLongLivedAccessToken($_SESSION['facebook_access_token']); + $_SESSION['facebook_access_token'] = (string) $longLivedAccessToken; + // setting default access token to be used in script + $fb->setDefaultAccessToken($_SESSION['facebook_access_token']); + } + // redirect the user back to the same page if it has "code" GET variable + if (isset($_GET['code'])) { + header('Location: ./'); + } + // getting basic info about user + try { + $profile_request = $fb->get('/me?fields=name,first_name,last_name,email'); + $profile = $profile_request->getGraphNode()->asArray(); + } catch(Facebook\Exceptions\FacebookResponseException $e) { + // When Graph returns an error + echo 'Graph returned an error: ' . $e->getMessage(); + session_destroy(); + // redirecting user back to app login page + header("Location: ./"); + exit; + } catch(Facebook\Exceptions\FacebookSDKException $e) { + // When validation fails or other local issues + echo 'Facebook SDK returned an error: ' . $e->getMessage(); + exit; + } + + // printing $profile array on the screen which holds the basic info about user + + $name = isset($profile['name']) ? $profile['name'] : null; + $first_name = isset($profile['first_name']) ? $profile['first_name'] : null; + $last_name = isset($profile['last_name']) ? $profile['last_name'] : null; + $email = isset($profile['email']) ? $profile['email'] : null; + //$gender = isset($profile['gender']) ? $profile['gender'] : null; + $fb_id = isset($profile['id']) ? $profile['id'] : null; + + $_SESSION['first_name'] = $first_name; + $_SESSION['full_name'] = $name; + $_SESSION['email'] = $email; + $_SESSION['fb_id'] = $fb_id; + + // Now you can redirect to another page and use the access token from $_SESSION['facebook_access_token'] + +} else { + // replace your website URL same as added in the developers.facebook.com/apps e.g. if you used http instead of https and you used non-www version or www version of your website then you must add the same here + $loginUrl = $helper->getLoginUrl('http://localhost/files/Bitcoin_Projects/bc_exchange/fbconfig.php', $permissions); +} \ No newline at end of file diff --git a/includes/autoload.php b/includes/autoload.php new file mode 100644 index 0000000..975e735 --- /dev/null +++ b/includes/autoload.php @@ -0,0 +1,7 @@ + +
+ +
+ + + + + + + + + + \ No newline at end of file diff --git a/includes/functions.php b/includes/functions.php new file mode 100644 index 0000000..b012e17 --- /dev/null +++ b/includes/functions.php @@ -0,0 +1,105 @@ + 0) && ($btc_usd_price > 0)) { + return (float) $usd/$btc_usd_price; + } + return false; +} + +function wapol_str($string) { + if(preg_match('/[^a-z:\-0-9]/i', $string)) { + return false; + } else { + return true; + } +} + +function sendReqtoURL($addr, $tokens) { + + $url = 'http://ranchimall.net/test/test.php'; + $myvars = 'addr=' . $addr . '&tokens=' . $tokens; + + $ch = curl_init( $url ); + curl_setopt( $ch, CURLOPT_POST, 1); + curl_setopt( $ch, CURLOPT_POSTFIELDS, $myvars); + curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt( $ch, CURLOPT_HEADER, 0); + curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1); + + $response = curl_exec( $ch ); + + curl_close($ch); + + return (int) $response; +} + +function is_email($email='') { + $email = trim($email); + if ($email != null) { + if (filter_var($email, FILTER_VALIDATE_EMAIL)) { + return true; + } + } + return false; +} + +function validate_decimal_place($num=0, $decimal_allowed=2) { + + $num = (float) $num; + $decimal_places = strlen(substr(strrchr($num, "."), 1)); + + //if(($decimal_places > 0) && ($decimal_places <= $decimal_allowed)) { + if($decimal_places <= $decimal_allowed) { + return true; + } + return false; +} \ No newline at end of file diff --git a/includes/header.php b/includes/header.php new file mode 100644 index 0000000..fe37086 --- /dev/null +++ b/includes/header.php @@ -0,0 +1,78 @@ +is_fb_registered($fb_id); + + if($validate_user == "" || $validate_user == false) { + redirect_to('index.php'); + } + endif; + + //$tradersList = $OrderClass->UserBalanceList(); + //$buy_list[] = $OrderClass->get_top_buy_sell_list(TOP_BUYS_TABLE, $asc_desc='DESC'); // buy + //$sell_list[] = $OrderClass->get_top_buy_sell_list(TOP_SELL_TABLE, $asc_desc='ASC'); // sell +} + +$fullName = isset($_SESSION['full_name']) ? $_SESSION['full_name'] : ""; +$user_logged_in = false; +$action_class_market = 'fb_log_in'; +$action_class_buy_sell = 'fb_log_in'; +if(checkLoginStatus()) { + $user_logged_in = true; + $action_class_market = 'market_submit_btn'; + $action_class_buy_sell = 'process'; +} +?> + + + + + + Home + + + + + + + + + + + diff --git a/includes/imp_files.php b/includes/imp_files.php new file mode 100644 index 0000000..548f9de --- /dev/null +++ b/includes/imp_files.php @@ -0,0 +1,47 @@ + + + + + + + + \ No newline at end of file diff --git a/js/bootstrap.min.js b/js/bootstrap.min.js new file mode 100644 index 0000000..516b6ac --- /dev/null +++ b/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v4.0.0 (https://getbootstrap.com) + * Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e(t.bootstrap={},t.jQuery,t.Popper)}(this,function(t,e,n){"use strict";function i(t,e){for(var n=0;n0?i:null}catch(t){return null}},reflow:function(t){return t.offsetHeight},triggerTransitionEnd:function(n){t(n).trigger(e.end)},supportsTransitionEnd:function(){return Boolean(e)},isElement:function(t){return(t[0]||t).nodeType},typeCheckConfig:function(t,e,n){for(var s in n)if(Object.prototype.hasOwnProperty.call(n,s)){var r=n[s],o=e[s],a=o&&i.isElement(o)?"element":(l=o,{}.toString.call(l).match(/\s([a-zA-Z]+)/)[1].toLowerCase());if(!new RegExp(r).test(a))throw new Error(t.toUpperCase()+': Option "'+s+'" provided type "'+a+'" but expected type "'+r+'".')}var l}};return e=("undefined"==typeof window||!window.QUnit)&&{end:"transitionend"},t.fn.emulateTransitionEnd=n,i.supportsTransitionEnd()&&(t.event.special[i.TRANSITION_END]={bindType:e.end,delegateType:e.end,handle:function(e){if(t(e.target).is(this))return e.handleObj.handler.apply(this,arguments)}}),i}(e),L=(a="alert",h="."+(l="bs.alert"),c=(o=e).fn[a],u={CLOSE:"close"+h,CLOSED:"closed"+h,CLICK_DATA_API:"click"+h+".data-api"},f="alert",d="fade",_="show",g=function(){function t(t){this._element=t}var e=t.prototype;return e.close=function(t){t=t||this._element;var e=this._getRootElement(t);this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},e.dispose=function(){o.removeData(this._element,l),this._element=null},e._getRootElement=function(t){var e=P.getSelectorFromElement(t),n=!1;return e&&(n=o(e)[0]),n||(n=o(t).closest("."+f)[0]),n},e._triggerCloseEvent=function(t){var e=o.Event(u.CLOSE);return o(t).trigger(e),e},e._removeElement=function(t){var e=this;o(t).removeClass(_),P.supportsTransitionEnd()&&o(t).hasClass(d)?o(t).one(P.TRANSITION_END,function(n){return e._destroyElement(t,n)}).emulateTransitionEnd(150):this._destroyElement(t)},e._destroyElement=function(t){o(t).detach().trigger(u.CLOSED).remove()},t._jQueryInterface=function(e){return this.each(function(){var n=o(this),i=n.data(l);i||(i=new t(this),n.data(l,i)),"close"===e&&i[e](this)})},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},s(t,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),t}(),o(document).on(u.CLICK_DATA_API,'[data-dismiss="alert"]',g._handleDismiss(new g)),o.fn[a]=g._jQueryInterface,o.fn[a].Constructor=g,o.fn[a].noConflict=function(){return o.fn[a]=c,g._jQueryInterface},g),R=(m="button",E="."+(v="bs.button"),T=".data-api",y=(p=e).fn[m],C="active",I="btn",A="focus",b='[data-toggle^="button"]',D='[data-toggle="buttons"]',S="input",w=".active",N=".btn",O={CLICK_DATA_API:"click"+E+T,FOCUS_BLUR_DATA_API:"focus"+E+T+" blur"+E+T},k=function(){function t(t){this._element=t}var e=t.prototype;return e.toggle=function(){var t=!0,e=!0,n=p(this._element).closest(D)[0];if(n){var i=p(this._element).find(S)[0];if(i){if("radio"===i.type)if(i.checked&&p(this._element).hasClass(C))t=!1;else{var s=p(n).find(w)[0];s&&p(s).removeClass(C)}if(t){if(i.hasAttribute("disabled")||n.hasAttribute("disabled")||i.classList.contains("disabled")||n.classList.contains("disabled"))return;i.checked=!p(this._element).hasClass(C),p(i).trigger("change")}i.focus(),e=!1}}e&&this._element.setAttribute("aria-pressed",!p(this._element).hasClass(C)),t&&p(this._element).toggleClass(C)},e.dispose=function(){p.removeData(this._element,v),this._element=null},t._jQueryInterface=function(e){return this.each(function(){var n=p(this).data(v);n||(n=new t(this),p(this).data(v,n)),"toggle"===e&&n[e]()})},s(t,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),t}(),p(document).on(O.CLICK_DATA_API,b,function(t){t.preventDefault();var e=t.target;p(e).hasClass(I)||(e=p(e).closest(N)),k._jQueryInterface.call(p(e),"toggle")}).on(O.FOCUS_BLUR_DATA_API,b,function(t){var e=p(t.target).closest(N)[0];p(e).toggleClass(A,/^focus(in)?$/.test(t.type))}),p.fn[m]=k._jQueryInterface,p.fn[m].Constructor=k,p.fn[m].noConflict=function(){return p.fn[m]=y,k._jQueryInterface},k),j=function(t){var e="carousel",n="bs.carousel",i="."+n,o=t.fn[e],a={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0},l={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean"},h="next",c="prev",u="left",f="right",d={SLIDE:"slide"+i,SLID:"slid"+i,KEYDOWN:"keydown"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i,TOUCHEND:"touchend"+i,LOAD_DATA_API:"load"+i+".data-api",CLICK_DATA_API:"click"+i+".data-api"},_="carousel",g="active",p="slide",m="carousel-item-right",v="carousel-item-left",E="carousel-item-next",T="carousel-item-prev",y={ACTIVE:".active",ACTIVE_ITEM:".active.carousel-item",ITEM:".carousel-item",NEXT_PREV:".carousel-item-next, .carousel-item-prev",INDICATORS:".carousel-indicators",DATA_SLIDE:"[data-slide], [data-slide-to]",DATA_RIDE:'[data-ride="carousel"]'},C=function(){function o(e,n){this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this._config=this._getConfig(n),this._element=t(e)[0],this._indicatorsElement=t(this._element).find(y.INDICATORS)[0],this._addEventListeners()}var C=o.prototype;return C.next=function(){this._isSliding||this._slide(h)},C.nextWhenVisible=function(){!document.hidden&&t(this._element).is(":visible")&&"hidden"!==t(this._element).css("visibility")&&this.next()},C.prev=function(){this._isSliding||this._slide(c)},C.pause=function(e){e||(this._isPaused=!0),t(this._element).find(y.NEXT_PREV)[0]&&P.supportsTransitionEnd()&&(P.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},C.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},C.to=function(e){var n=this;this._activeElement=t(this._element).find(y.ACTIVE_ITEM)[0];var i=this._getItemIndex(this._activeElement);if(!(e>this._items.length-1||e<0))if(this._isSliding)t(this._element).one(d.SLID,function(){return n.to(e)});else{if(i===e)return this.pause(),void this.cycle();var s=e>i?h:c;this._slide(s,this._items[e])}},C.dispose=function(){t(this._element).off(i),t.removeData(this._element,n),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},C._getConfig=function(t){return t=r({},a,t),P.typeCheckConfig(e,t,l),t},C._addEventListeners=function(){var e=this;this._config.keyboard&&t(this._element).on(d.KEYDOWN,function(t){return e._keydown(t)}),"hover"===this._config.pause&&(t(this._element).on(d.MOUSEENTER,function(t){return e.pause(t)}).on(d.MOUSELEAVE,function(t){return e.cycle(t)}),"ontouchstart"in document.documentElement&&t(this._element).on(d.TOUCHEND,function(){e.pause(),e.touchTimeout&&clearTimeout(e.touchTimeout),e.touchTimeout=setTimeout(function(t){return e.cycle(t)},500+e._config.interval)}))},C._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case 37:t.preventDefault(),this.prev();break;case 39:t.preventDefault(),this.next()}},C._getItemIndex=function(e){return this._items=t.makeArray(t(e).parent().find(y.ITEM)),this._items.indexOf(e)},C._getItemByDirection=function(t,e){var n=t===h,i=t===c,s=this._getItemIndex(e),r=this._items.length-1;if((i&&0===s||n&&s===r)&&!this._config.wrap)return e;var o=(s+(t===c?-1:1))%this._items.length;return-1===o?this._items[this._items.length-1]:this._items[o]},C._triggerSlideEvent=function(e,n){var i=this._getItemIndex(e),s=this._getItemIndex(t(this._element).find(y.ACTIVE_ITEM)[0]),r=t.Event(d.SLIDE,{relatedTarget:e,direction:n,from:s,to:i});return t(this._element).trigger(r),r},C._setActiveIndicatorElement=function(e){if(this._indicatorsElement){t(this._indicatorsElement).find(y.ACTIVE).removeClass(g);var n=this._indicatorsElement.children[this._getItemIndex(e)];n&&t(n).addClass(g)}},C._slide=function(e,n){var i,s,r,o=this,a=t(this._element).find(y.ACTIVE_ITEM)[0],l=this._getItemIndex(a),c=n||a&&this._getItemByDirection(e,a),_=this._getItemIndex(c),C=Boolean(this._interval);if(e===h?(i=v,s=E,r=u):(i=m,s=T,r=f),c&&t(c).hasClass(g))this._isSliding=!1;else if(!this._triggerSlideEvent(c,r).isDefaultPrevented()&&a&&c){this._isSliding=!0,C&&this.pause(),this._setActiveIndicatorElement(c);var I=t.Event(d.SLID,{relatedTarget:c,direction:r,from:l,to:_});P.supportsTransitionEnd()&&t(this._element).hasClass(p)?(t(c).addClass(s),P.reflow(c),t(a).addClass(i),t(c).addClass(i),t(a).one(P.TRANSITION_END,function(){t(c).removeClass(i+" "+s).addClass(g),t(a).removeClass(g+" "+s+" "+i),o._isSliding=!1,setTimeout(function(){return t(o._element).trigger(I)},0)}).emulateTransitionEnd(600)):(t(a).removeClass(g),t(c).addClass(g),this._isSliding=!1,t(this._element).trigger(I)),C&&this.cycle()}},o._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n),s=r({},a,t(this).data());"object"==typeof e&&(s=r({},s,e));var l="string"==typeof e?e:s.slide;if(i||(i=new o(this,s),t(this).data(n,i)),"number"==typeof e)i.to(e);else if("string"==typeof l){if("undefined"==typeof i[l])throw new TypeError('No method named "'+l+'"');i[l]()}else s.interval&&(i.pause(),i.cycle())})},o._dataApiClickHandler=function(e){var i=P.getSelectorFromElement(this);if(i){var s=t(i)[0];if(s&&t(s).hasClass(_)){var a=r({},t(s).data(),t(this).data()),l=this.getAttribute("data-slide-to");l&&(a.interval=!1),o._jQueryInterface.call(t(s),a),l&&t(s).data(n).to(l),e.preventDefault()}}},s(o,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),o}();return t(document).on(d.CLICK_DATA_API,y.DATA_SLIDE,C._dataApiClickHandler),t(window).on(d.LOAD_DATA_API,function(){t(y.DATA_RIDE).each(function(){var e=t(this);C._jQueryInterface.call(e,e.data())})}),t.fn[e]=C._jQueryInterface,t.fn[e].Constructor=C,t.fn[e].noConflict=function(){return t.fn[e]=o,C._jQueryInterface},C}(e),H=function(t){var e="collapse",n="bs.collapse",i="."+n,o=t.fn[e],a={toggle:!0,parent:""},l={toggle:"boolean",parent:"(string|element)"},h={SHOW:"show"+i,SHOWN:"shown"+i,HIDE:"hide"+i,HIDDEN:"hidden"+i,CLICK_DATA_API:"click"+i+".data-api"},c="show",u="collapse",f="collapsing",d="collapsed",_="width",g="height",p={ACTIVES:".show, .collapsing",DATA_TOGGLE:'[data-toggle="collapse"]'},m=function(){function i(e,n){this._isTransitioning=!1,this._element=e,this._config=this._getConfig(n),this._triggerArray=t.makeArray(t('[data-toggle="collapse"][href="#'+e.id+'"],[data-toggle="collapse"][data-target="#'+e.id+'"]'));for(var i=t(p.DATA_TOGGLE),s=0;s0&&(this._selector=o,this._triggerArray.push(r))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var o=i.prototype;return o.toggle=function(){t(this._element).hasClass(c)?this.hide():this.show()},o.show=function(){var e,s,r=this;if(!this._isTransitioning&&!t(this._element).hasClass(c)&&(this._parent&&0===(e=t.makeArray(t(this._parent).find(p.ACTIVES).filter('[data-parent="'+this._config.parent+'"]'))).length&&(e=null),!(e&&(s=t(e).not(this._selector).data(n))&&s._isTransitioning))){var o=t.Event(h.SHOW);if(t(this._element).trigger(o),!o.isDefaultPrevented()){e&&(i._jQueryInterface.call(t(e).not(this._selector),"hide"),s||t(e).data(n,null));var a=this._getDimension();t(this._element).removeClass(u).addClass(f),this._element.style[a]=0,this._triggerArray.length>0&&t(this._triggerArray).removeClass(d).attr("aria-expanded",!0),this.setTransitioning(!0);var l=function(){t(r._element).removeClass(f).addClass(u).addClass(c),r._element.style[a]="",r.setTransitioning(!1),t(r._element).trigger(h.SHOWN)};if(P.supportsTransitionEnd()){var _="scroll"+(a[0].toUpperCase()+a.slice(1));t(this._element).one(P.TRANSITION_END,l).emulateTransitionEnd(600),this._element.style[a]=this._element[_]+"px"}else l()}}},o.hide=function(){var e=this;if(!this._isTransitioning&&t(this._element).hasClass(c)){var n=t.Event(h.HIDE);if(t(this._element).trigger(n),!n.isDefaultPrevented()){var i=this._getDimension();if(this._element.style[i]=this._element.getBoundingClientRect()[i]+"px",P.reflow(this._element),t(this._element).addClass(f).removeClass(u).removeClass(c),this._triggerArray.length>0)for(var s=0;s0&&t(n).toggleClass(d,!i).attr("aria-expanded",i)}},i._getTargetFromElement=function(e){var n=P.getSelectorFromElement(e);return n?t(n)[0]:null},i._jQueryInterface=function(e){return this.each(function(){var s=t(this),o=s.data(n),l=r({},a,s.data(),"object"==typeof e&&e);if(!o&&l.toggle&&/show|hide/.test(e)&&(l.toggle=!1),o||(o=new i(this,l),s.data(n,o)),"string"==typeof e){if("undefined"==typeof o[e])throw new TypeError('No method named "'+e+'"');o[e]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),i}();return t(document).on(h.CLICK_DATA_API,p.DATA_TOGGLE,function(e){"A"===e.currentTarget.tagName&&e.preventDefault();var i=t(this),s=P.getSelectorFromElement(this);t(s).each(function(){var e=t(this),s=e.data(n)?"toggle":i.data();m._jQueryInterface.call(e,s)})}),t.fn[e]=m._jQueryInterface,t.fn[e].Constructor=m,t.fn[e].noConflict=function(){return t.fn[e]=o,m._jQueryInterface},m}(e),W=function(t){var e="dropdown",i="bs.dropdown",o="."+i,a=".data-api",l=t.fn[e],h=new RegExp("38|40|27"),c={HIDE:"hide"+o,HIDDEN:"hidden"+o,SHOW:"show"+o,SHOWN:"shown"+o,CLICK:"click"+o,CLICK_DATA_API:"click"+o+a,KEYDOWN_DATA_API:"keydown"+o+a,KEYUP_DATA_API:"keyup"+o+a},u="disabled",f="show",d="dropup",_="dropright",g="dropleft",p="dropdown-menu-right",m="dropdown-menu-left",v="position-static",E='[data-toggle="dropdown"]',T=".dropdown form",y=".dropdown-menu",C=".navbar-nav",I=".dropdown-menu .dropdown-item:not(.disabled)",A="top-start",b="top-end",D="bottom-start",S="bottom-end",w="right-start",N="left-start",O={offset:0,flip:!0,boundary:"scrollParent"},k={offset:"(number|string|function)",flip:"boolean",boundary:"(string|element)"},L=function(){function a(t,e){this._element=t,this._popper=null,this._config=this._getConfig(e),this._menu=this._getMenuElement(),this._inNavbar=this._detectNavbar(),this._addEventListeners()}var l=a.prototype;return l.toggle=function(){if(!this._element.disabled&&!t(this._element).hasClass(u)){var e=a._getParentFromElement(this._element),i=t(this._menu).hasClass(f);if(a._clearMenus(),!i){var s={relatedTarget:this._element},r=t.Event(c.SHOW,s);if(t(e).trigger(r),!r.isDefaultPrevented()){if(!this._inNavbar){if("undefined"==typeof n)throw new TypeError("Bootstrap dropdown require Popper.js (https://popper.js.org)");var o=this._element;t(e).hasClass(d)&&(t(this._menu).hasClass(m)||t(this._menu).hasClass(p))&&(o=e),"scrollParent"!==this._config.boundary&&t(e).addClass(v),this._popper=new n(o,this._menu,this._getPopperConfig())}"ontouchstart"in document.documentElement&&0===t(e).closest(C).length&&t("body").children().on("mouseover",null,t.noop),this._element.focus(),this._element.setAttribute("aria-expanded",!0),t(this._menu).toggleClass(f),t(e).toggleClass(f).trigger(t.Event(c.SHOWN,s))}}}},l.dispose=function(){t.removeData(this._element,i),t(this._element).off(o),this._element=null,this._menu=null,null!==this._popper&&(this._popper.destroy(),this._popper=null)},l.update=function(){this._inNavbar=this._detectNavbar(),null!==this._popper&&this._popper.scheduleUpdate()},l._addEventListeners=function(){var e=this;t(this._element).on(c.CLICK,function(t){t.preventDefault(),t.stopPropagation(),e.toggle()})},l._getConfig=function(n){return n=r({},this.constructor.Default,t(this._element).data(),n),P.typeCheckConfig(e,n,this.constructor.DefaultType),n},l._getMenuElement=function(){if(!this._menu){var e=a._getParentFromElement(this._element);this._menu=t(e).find(y)[0]}return this._menu},l._getPlacement=function(){var e=t(this._element).parent(),n=D;return e.hasClass(d)?(n=A,t(this._menu).hasClass(p)&&(n=b)):e.hasClass(_)?n=w:e.hasClass(g)?n=N:t(this._menu).hasClass(p)&&(n=S),n},l._detectNavbar=function(){return t(this._element).closest(".navbar").length>0},l._getPopperConfig=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t._config.offset(e.offsets)||{}),e}:e.offset=this._config.offset,{placement:this._getPlacement(),modifiers:{offset:e,flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}}},a._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(i);if(n||(n=new a(this,"object"==typeof e?e:null),t(this).data(i,n)),"string"==typeof e){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}})},a._clearMenus=function(e){if(!e||3!==e.which&&("keyup"!==e.type||9===e.which))for(var n=t.makeArray(t(E)),s=0;s0&&r--,40===e.which&&rdocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},p._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},p._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent"},f="show",d="out",_={HIDE:"hide"+o,HIDDEN:"hidden"+o,SHOW:"show"+o,SHOWN:"shown"+o,INSERTED:"inserted"+o,CLICK:"click"+o,FOCUSIN:"focusin"+o,FOCUSOUT:"focusout"+o,MOUSEENTER:"mouseenter"+o,MOUSELEAVE:"mouseleave"+o},g="fade",p="show",m=".tooltip-inner",v=".arrow",E="hover",T="focus",y="click",C="manual",I=function(){function a(t,e){if("undefined"==typeof n)throw new TypeError("Bootstrap tooltips require Popper.js (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var I=a.prototype;return I.enable=function(){this._isEnabled=!0},I.disable=function(){this._isEnabled=!1},I.toggleEnabled=function(){this._isEnabled=!this._isEnabled},I.toggle=function(e){if(this._isEnabled)if(e){var n=this.constructor.DATA_KEY,i=t(e.currentTarget).data(n);i||(i=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(n,i)),i._activeTrigger.click=!i._activeTrigger.click,i._isWithActiveTrigger()?i._enter(null,i):i._leave(null,i)}else{if(t(this.getTipElement()).hasClass(p))return void this._leave(null,this);this._enter(null,this)}},I.dispose=function(){clearTimeout(this._timeout),t.removeData(this.element,this.constructor.DATA_KEY),t(this.element).off(this.constructor.EVENT_KEY),t(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&t(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,null!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},I.show=function(){var e=this;if("none"===t(this.element).css("display"))throw new Error("Please use show on visible elements");var i=t.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){t(this.element).trigger(i);var s=t.contains(this.element.ownerDocument.documentElement,this.element);if(i.isDefaultPrevented()||!s)return;var r=this.getTipElement(),o=P.getUID(this.constructor.NAME);r.setAttribute("id",o),this.element.setAttribute("aria-describedby",o),this.setContent(),this.config.animation&&t(r).addClass(g);var l="function"==typeof this.config.placement?this.config.placement.call(this,r,this.element):this.config.placement,h=this._getAttachment(l);this.addAttachmentClass(h);var c=!1===this.config.container?document.body:t(this.config.container);t(r).data(this.constructor.DATA_KEY,this),t.contains(this.element.ownerDocument.documentElement,this.tip)||t(r).appendTo(c),t(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new n(this.element,r,{placement:h,modifiers:{offset:{offset:this.config.offset},flip:{behavior:this.config.fallbackPlacement},arrow:{element:v},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){e._handlePopperPlacementChange(t)}}),t(r).addClass(p),"ontouchstart"in document.documentElement&&t("body").children().on("mouseover",null,t.noop);var u=function(){e.config.animation&&e._fixTransition();var n=e._hoverState;e._hoverState=null,t(e.element).trigger(e.constructor.Event.SHOWN),n===d&&e._leave(null,e)};P.supportsTransitionEnd()&&t(this.tip).hasClass(g)?t(this.tip).one(P.TRANSITION_END,u).emulateTransitionEnd(a._TRANSITION_DURATION):u()}},I.hide=function(e){var n=this,i=this.getTipElement(),s=t.Event(this.constructor.Event.HIDE),r=function(){n._hoverState!==f&&i.parentNode&&i.parentNode.removeChild(i),n._cleanTipClass(),n.element.removeAttribute("aria-describedby"),t(n.element).trigger(n.constructor.Event.HIDDEN),null!==n._popper&&n._popper.destroy(),e&&e()};t(this.element).trigger(s),s.isDefaultPrevented()||(t(i).removeClass(p),"ontouchstart"in document.documentElement&&t("body").children().off("mouseover",null,t.noop),this._activeTrigger[y]=!1,this._activeTrigger[T]=!1,this._activeTrigger[E]=!1,P.supportsTransitionEnd()&&t(this.tip).hasClass(g)?t(i).one(P.TRANSITION_END,r).emulateTransitionEnd(150):r(),this._hoverState="")},I.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},I.isWithContent=function(){return Boolean(this.getTitle())},I.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-tooltip-"+e)},I.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},I.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(m),this.getTitle()),e.removeClass(g+" "+p)},I.setElementContent=function(e,n){var i=this.config.html;"object"==typeof n&&(n.nodeType||n.jquery)?i?t(n).parent().is(e)||e.empty().append(n):e.text(t(n).text()):e[i?"html":"text"](n)},I.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},I._getAttachment=function(t){return c[t.toUpperCase()]},I._setListeners=function(){var e=this;this.config.trigger.split(" ").forEach(function(n){if("click"===n)t(e.element).on(e.constructor.Event.CLICK,e.config.selector,function(t){return e.toggle(t)});else if(n!==C){var i=n===E?e.constructor.Event.MOUSEENTER:e.constructor.Event.FOCUSIN,s=n===E?e.constructor.Event.MOUSELEAVE:e.constructor.Event.FOCUSOUT;t(e.element).on(i,e.config.selector,function(t){return e._enter(t)}).on(s,e.config.selector,function(t){return e._leave(t)})}t(e.element).closest(".modal").on("hide.bs.modal",function(){return e.hide()})}),this.config.selector?this.config=r({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},I._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},I._enter=function(e,n){var i=this.constructor.DATA_KEY;(n=n||t(e.currentTarget).data(i))||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusin"===e.type?T:E]=!0),t(n.getTipElement()).hasClass(p)||n._hoverState===f?n._hoverState=f:(clearTimeout(n._timeout),n._hoverState=f,n.config.delay&&n.config.delay.show?n._timeout=setTimeout(function(){n._hoverState===f&&n.show()},n.config.delay.show):n.show())},I._leave=function(e,n){var i=this.constructor.DATA_KEY;(n=n||t(e.currentTarget).data(i))||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusout"===e.type?T:E]=!1),n._isWithActiveTrigger()||(clearTimeout(n._timeout),n._hoverState=d,n.config.delay&&n.config.delay.hide?n._timeout=setTimeout(function(){n._hoverState===d&&n.hide()},n.config.delay.hide):n.hide())},I._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},I._getConfig=function(n){return"number"==typeof(n=r({},this.constructor.Default,t(this.element).data(),n)).delay&&(n.delay={show:n.delay,hide:n.delay}),"number"==typeof n.title&&(n.title=n.title.toString()),"number"==typeof n.content&&(n.content=n.content.toString()),P.typeCheckConfig(e,n,this.constructor.DefaultType),n},I._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},I._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(l);null!==n&&n.length>0&&e.removeClass(n.join(""))},I._handlePopperPlacementChange=function(t){this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},I._fixTransition=function(){var e=this.getTipElement(),n=this.config.animation;null===e.getAttribute("x-placement")&&(t(e).removeClass(g),this.config.animation=!1,this.hide(),this.show(),this.config.animation=n)},a._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(i),s="object"==typeof e&&e;if((n||!/dispose|hide/.test(e))&&(n||(n=new a(this,s),t(this).data(i,n)),"string"==typeof e)){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}})},s(a,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return u}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return i}},{key:"Event",get:function(){return _}},{key:"EVENT_KEY",get:function(){return o}},{key:"DefaultType",get:function(){return h}}]),a}();return t.fn[e]=I._jQueryInterface,t.fn[e].Constructor=I,t.fn[e].noConflict=function(){return t.fn[e]=a,I._jQueryInterface},I}(e),x=function(t){var e="popover",n="bs.popover",i="."+n,o=t.fn[e],a=new RegExp("(^|\\s)bs-popover\\S+","g"),l=r({},U.Default,{placement:"right",trigger:"click",content:"",template:''}),h=r({},U.DefaultType,{content:"(string|element|function)"}),c="fade",u="show",f=".popover-header",d=".popover-body",_={HIDE:"hide"+i,HIDDEN:"hidden"+i,SHOW:"show"+i,SHOWN:"shown"+i,INSERTED:"inserted"+i,CLICK:"click"+i,FOCUSIN:"focusin"+i,FOCUSOUT:"focusout"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i},g=function(r){var o,g;function p(){return r.apply(this,arguments)||this}g=r,(o=p).prototype=Object.create(g.prototype),o.prototype.constructor=o,o.__proto__=g;var m=p.prototype;return m.isWithContent=function(){return this.getTitle()||this._getContent()},m.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-popover-"+e)},m.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},m.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(f),this.getTitle());var n=this._getContent();"function"==typeof n&&(n=n.call(this.element)),this.setElementContent(e.find(d),n),e.removeClass(c+" "+u)},m._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},m._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(a);null!==n&&n.length>0&&e.removeClass(n.join(""))},p._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n),s="object"==typeof e?e:null;if((i||!/destroy|hide/.test(e))&&(i||(i=new p(this,s),t(this).data(n,i)),"string"==typeof e)){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}})},s(p,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return l}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return n}},{key:"Event",get:function(){return _}},{key:"EVENT_KEY",get:function(){return i}},{key:"DefaultType",get:function(){return h}}]),p}(U);return t.fn[e]=g._jQueryInterface,t.fn[e].Constructor=g,t.fn[e].noConflict=function(){return t.fn[e]=o,g._jQueryInterface},g}(e),K=function(t){var e="scrollspy",n="bs.scrollspy",i="."+n,o=t.fn[e],a={offset:10,method:"auto",target:""},l={offset:"number",method:"string",target:"(string|element)"},h={ACTIVATE:"activate"+i,SCROLL:"scroll"+i,LOAD_DATA_API:"load"+i+".data-api"},c="dropdown-item",u="active",f={DATA_SPY:'[data-spy="scroll"]',ACTIVE:".active",NAV_LIST_GROUP:".nav, .list-group",NAV_LINKS:".nav-link",NAV_ITEMS:".nav-item",LIST_ITEMS:".list-group-item",DROPDOWN:".dropdown",DROPDOWN_ITEMS:".dropdown-item",DROPDOWN_TOGGLE:".dropdown-toggle"},d="offset",_="position",g=function(){function o(e,n){var i=this;this._element=e,this._scrollElement="BODY"===e.tagName?window:e,this._config=this._getConfig(n),this._selector=this._config.target+" "+f.NAV_LINKS+","+this._config.target+" "+f.LIST_ITEMS+","+this._config.target+" "+f.DROPDOWN_ITEMS,this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,t(this._scrollElement).on(h.SCROLL,function(t){return i._process(t)}),this.refresh(),this._process()}var g=o.prototype;return g.refresh=function(){var e=this,n=this._scrollElement===this._scrollElement.window?d:_,i="auto"===this._config.method?n:this._config.method,s=i===_?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),t.makeArray(t(this._selector)).map(function(e){var n,r=P.getSelectorFromElement(e);if(r&&(n=t(r)[0]),n){var o=n.getBoundingClientRect();if(o.width||o.height)return[t(n)[i]().top+s,r]}return null}).filter(function(t){return t}).sort(function(t,e){return t[0]-e[0]}).forEach(function(t){e._offsets.push(t[0]),e._targets.push(t[1])})},g.dispose=function(){t.removeData(this._element,n),t(this._scrollElement).off(i),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},g._getConfig=function(n){if("string"!=typeof(n=r({},a,n)).target){var i=t(n.target).attr("id");i||(i=P.getUID(e),t(n.target).attr("id",i)),n.target="#"+i}return P.typeCheckConfig(e,n,l),n},g._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},g._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},g._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},g._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var s=this._offsets.length;s--;){this._activeTarget!==this._targets[s]&&t>=this._offsets[s]&&("undefined"==typeof this._offsets[s+1]||t=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}(e),t.Util=P,t.Alert=L,t.Button=R,t.Carousel=j,t.Collapse=H,t.Dropdown=W,t.Modal=M,t.Popover=x,t.Scrollspy=K,t.Tab=V,t.Tooltip=U,Object.defineProperty(t,"__esModule",{value:!0})}); +//# sourceMappingURL=bootstrap.min.js.map \ No newline at end of file diff --git a/js/jquery-3.2.1.min.js b/js/jquery-3.2.1.min.js new file mode 100644 index 0000000..2216e7a --- /dev/null +++ b/js/jquery-3.2.1.min.js @@ -0,0 +1,4 @@ +/*! jQuery v3.2.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.2.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext;function B(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()}var C=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,D=/^.[^:#\[\.,]*$/;function E(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):D.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(E(this,a||[],!1))},not:function(a){return this.pushStack(E(this,a||[],!0))},is:function(a){return!!E(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var F,G=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,H=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||F,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:G.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),C.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};H.prototype=r.fn,F=r(d);var I=/^(?:parents|prev(?:Until|All))/,J={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function K(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return K(a,"nextSibling")},prev:function(a){return K(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return B(a,"iframe")?a.contentDocument:(B(a,"template")&&(a=a.content||a),r.merge([],a.childNodes))}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(J[a]||r.uniqueSort(e),I.test(a)&&e.reverse()),this.pushStack(e)}});var L=/[^\x20\t\r\n\f]+/g;function M(a){var b={};return r.each(a.match(L)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?M(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=e||a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function N(a){return a}function O(a){throw a}function P(a,b,c,d){var e;try{a&&r.isFunction(e=a.promise)?e.call(a).done(b).fail(c):a&&r.isFunction(e=a.then)?e.call(a,b,c):b.apply(void 0,[a].slice(d))}catch(a){c.apply(void 0,[a])}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==O&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:N,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:N)),c[2][3].add(g(0,a,r.isFunction(d)?d:O))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(P(a,g.done(h(c)).resolve,g.reject,!b),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)P(e[c],h(c),g.reject);return g.promise()}});var Q=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&Q.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var R=r.Deferred();r.fn.ready=function(a){return R.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||R.resolveWith(d,[r]))}}),r.ready.then=R.then;function S(){d.removeEventListener("DOMContentLoaded",S), + a.removeEventListener("load",S),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",S),a.addEventListener("load",S));var T=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)T(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){X.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=W.get(a,b),c&&(!d||Array.isArray(c)?d=W.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return W.get(a,c)||W.access(a,c,{empty:r.Callbacks("once memory").add(function(){W.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,la=/^$|\/(?:java|ecma)script/i,ma={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ma.optgroup=ma.option,ma.tbody=ma.tfoot=ma.colgroup=ma.caption=ma.thead,ma.th=ma.td;function na(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&B(a,b)?r.merge([a],c):c}function oa(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=na(l.appendChild(f),"script"),j&&oa(g),c){k=0;while(f=g[k++])la.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var ra=d.documentElement,sa=/^key/,ta=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ua=/^([^.]*)(?:\.(.+)|)/;function va(){return!0}function wa(){return!1}function xa(){try{return d.activeElement}catch(a){}}function ya(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ya(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=wa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(ra,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(L)||[""],j=b.length;while(j--)h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.hasData(a)&&W.get(a);if(q&&(i=q.events)){b=(b||"").match(L)||[""],j=b.length;while(j--)if(h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&W.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(W.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i\x20\t\r\n\f]*)[^>]*)\/>/gi,Aa=/\s*$/g;function Ea(a,b){return B(a,"table")&&B(11!==b.nodeType?b:b.firstChild,"tr")?r(">tbody",a)[0]||a:a}function Fa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Ga(a){var b=Ca.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ha(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(W.hasData(a)&&(f=W.access(a),g=W.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&Ba.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ja(f,b,c,d)});if(m&&(e=qa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(na(e,"script"),Fa),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=na(h),f=na(a),d=0,e=f.length;d0&&oa(g,!i&&na(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(U(c)){if(b=c[W.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[W.expando]=void 0}c[X.expando]&&(c[X.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ka(this,a,!0)},remove:function(a){return Ka(this,a)},text:function(a){return T(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.appendChild(a)}})},prepend:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(na(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return T(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!Aa.test(a)&&!ma[(ka.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function _a(a,b,c,d,e){return new _a.prototype.init(a,b,c,d,e)}r.Tween=_a,_a.prototype={constructor:_a,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=_a.propHooks[this.prop];return a&&a.get?a.get(this):_a.propHooks._default.get(this)},run:function(a){var b,c=_a.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):_a.propHooks._default.set(this),this}},_a.prototype.init.prototype=_a.prototype,_a.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},_a.propHooks.scrollTop=_a.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=_a.prototype.init,r.fx.step={};var ab,bb,cb=/^(?:toggle|show|hide)$/,db=/queueHooks$/;function eb(){bb&&(d.hidden===!1&&a.requestAnimationFrame?a.requestAnimationFrame(eb):a.setTimeout(eb,r.fx.interval),r.fx.tick())}function fb(){return a.setTimeout(function(){ab=void 0}),ab=r.now()}function gb(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ca[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function hb(a,b,c){for(var d,e=(kb.tweeners[b]||[]).concat(kb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?lb:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b), + null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&B(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(L);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),lb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=mb[b]||r.find.attr;mb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=mb[g],mb[g]=e,e=null!=c(a,b,d)?g:null,mb[g]=f),e}});var nb=/^(?:input|select|textarea|button)$/i,ob=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return T(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):nb.test(a.nodeName)||ob.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function pb(a){var b=a.match(L)||[];return b.join(" ")}function qb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,qb(this)))});if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,qb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,qb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(L)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=qb(this),b&&W.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":W.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+pb(qb(c))+" ").indexOf(b)>-1)return!0;return!1}});var rb=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":Array.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(rb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:pb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(Array.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var sb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!sb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,sb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(W.get(h,"events")||{})[b.type]&&W.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&U(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!U(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=W.access(d,b);e||d.addEventListener(a,c,!0),W.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=W.access(d,b)-1;e?W.access(d,b,e):(d.removeEventListener(a,c,!0),W.remove(d,b))}}});var tb=a.location,ub=r.now(),vb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var wb=/\[\]$/,xb=/\r?\n/g,yb=/^(?:submit|button|image|reset|file)$/i,zb=/^(?:input|select|textarea|keygen)/i;function Ab(a,b,c,d){var e;if(Array.isArray(b))r.each(b,function(b,e){c||wb.test(a)?d(a,e):Ab(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)Ab(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(Array.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)Ab(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&zb.test(this.nodeName)&&!yb.test(a)&&(this.checked||!ja.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:Array.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(xb,"\r\n")}}):{name:b.name,value:c.replace(xb,"\r\n")}}).get()}});var Bb=/%20/g,Cb=/#.*$/,Db=/([?&])_=[^&]*/,Eb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Fb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Gb=/^(?:GET|HEAD)$/,Hb=/^\/\//,Ib={},Jb={},Kb="*/".concat("*"),Lb=d.createElement("a");Lb.href=tb.href;function Mb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(L)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Nb(a,b,c,d){var e={},f=a===Jb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Ob(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Pb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Qb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:tb.href,type:"GET",isLocal:Fb.test(tb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Kb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Ob(Ob(a,r.ajaxSettings),b):Ob(r.ajaxSettings,a)},ajaxPrefilter:Mb(Ib),ajaxTransport:Mb(Jb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Eb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||tb.href)+"").replace(Hb,tb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(L)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Lb.protocol+"//"+Lb.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Nb(Ib,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Gb.test(o.type),f=o.url.replace(Cb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(Bb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(vb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Db,"$1"),n=(vb.test(f)?"&":"?")+"_="+ub++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Kb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Nb(Jb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Pb(o,y,d)),v=Qb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Rb={0:200,1223:204},Sb=r.ajaxSettings.xhr();o.cors=!!Sb&&"withCredentials"in Sb,o.ajax=Sb=!!Sb,r.ajaxTransport(function(b){var c,d;if(o.cors||Sb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Rb[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r("