517 lines
17 KiB
PHP
517 lines
17 KiB
PHP
<?php
|
|
// Datenbank-Verbindungsparameter
|
|
require_once ('config.php');
|
|
require_once ('globals.php');
|
|
require_once ('dbutils.php');
|
|
require_once ('bill.php');
|
|
require_once ('closing.php');
|
|
|
|
class PrintQueue {
|
|
var $dbutils;
|
|
var $userrights;
|
|
var $admin;
|
|
|
|
function __construct() {
|
|
$this->dbutils = new DbUtils();
|
|
$this->userrights = new Userrights();
|
|
$this->admin = new Admin();
|
|
}
|
|
|
|
function handleCommand($command) {
|
|
$fl = null;
|
|
if (isset($_GET['fl'])) {
|
|
$fl = $_GET['fl'];
|
|
}
|
|
// these command are only allowed for user with waiter rights
|
|
if ($command == 'getNextReceiptPrintJobs') {
|
|
if(isset($_GET['printers'])) {
|
|
$this->getNextReceiptPrintJobs($_POST['pass'],$_GET['language'],$_GET['printers'],$fl);
|
|
} else {
|
|
$this->getNextReceiptPrintJobs($_POST['pass'],$_GET['language'],"1,2,3,4,5,6",$fl);
|
|
}
|
|
} else if ($command == 'getNextClosingPrintJobs') {
|
|
$this->getNextClosingPrintJobs($_POST['pass'],$_GET['language']);
|
|
} else if ($command == 'getNextFoodWorkPrintJobs') {
|
|
if (isset($_GET['printer'])) {
|
|
$this->getNextFoodWorkPrintJobs($_GET['printer'],$_POST['pass'],$fl);
|
|
} else {
|
|
$this->getNextFoodWorkPrintJobs(null,$_POST['pass'],$fl);
|
|
}
|
|
} else if ($command == 'getNextDrinkWorkPrintJobs') {
|
|
if (isset($_GET['printer'])) {
|
|
$this->getNextDrinkWorkPrintJobs($_GET['printer'],$_POST['pass'],$fl);
|
|
} else {
|
|
$this->getNextDrinkWorkPrintJobs(null,$_POST['pass'],$fl);
|
|
}
|
|
} else if ($command == 'deletePrintJob') {
|
|
$this->deletePrintJob($_POST['pass'],$_POST['id']);
|
|
} else if ($command == 'queueReceiptPrintJob') {
|
|
if (isset($_POST['useaddrecprinter'])) {
|
|
$this->queueReceiptPrintJob($_POST['billid'],$_POST['useaddrecprinter']);
|
|
} else {
|
|
$this->queueReceiptPrintJob($_POST['billid'],0);
|
|
}
|
|
} else if ($command == 'queueClosingSummary') {
|
|
$this->queueClosingSummary($_GET['closingid']);
|
|
} else if ($command == 'testConnection') {
|
|
$this->testConnection($_POST['pass']);
|
|
} else if ($command == 'getReceiptConfig') {
|
|
$this->getReceiptConfig();
|
|
} else if ($command == 'getLogoAsPng') {
|
|
$this->getLogoAsPng();
|
|
} else if ($command == 'getLogoAsWbmp') {
|
|
$this->getLogoAsWbmp();
|
|
} else if ($command == 'getPrintJobOverview') {
|
|
$pdo = DbUtils::openDbAndReturnPdoStatic();
|
|
$this->getPrintJobOverview($pdo);
|
|
} else if ($command == 'clearprintjobs') {
|
|
$pdo = DbUtils::openDbAndReturnPdoStatic();
|
|
$this->clearprintjobs($pdo);
|
|
} else if ($command == 'batchReceiptPrintJob') {
|
|
$this->batchReceiptPrintJob($_POST['start'],$_POST['end']);
|
|
} else {
|
|
echo "Kommando nicht erkannt!";
|
|
}
|
|
}
|
|
|
|
private function saveLastPrintServerAccess($pdo) {
|
|
date_default_timezone_set(DbUtils::getTimeZone());
|
|
$date = new DateTime();
|
|
$unixTimeStamp = $date->getTimestamp();
|
|
$sql = "SELECT count(id) as countid FROM %work% WHERE item=?";
|
|
$stmt = $pdo->prepare(DbUtils::substTableAlias($sql));
|
|
$stmt->execute(array('lastprtserveraccess'));
|
|
$row = $stmt->fetchObject();
|
|
if ($row->countid == 0) {
|
|
$sql = "INSERT INTO %work% (item,value,signature) VALUES(?,?,?)";
|
|
$stmt = $pdo->prepare(DbUtils::substTableAlias($sql));
|
|
$stmt->execute(array('lastprtserveraccess',$unixTimeStamp,null));
|
|
} else {
|
|
$sql = "UPDATE %work% SET value=? WHERE item=?";
|
|
$stmt = $pdo->prepare(DbUtils::substTableAlias($sql));
|
|
$stmt->execute(array($unixTimeStamp,'lastprtserveraccess'));
|
|
}
|
|
}
|
|
|
|
function testConnection($md5pass) {
|
|
header( "Expires: Mon, 20 Dec 1998 01:00:00 GMT" );
|
|
header( "Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT" );
|
|
header( "Cache-Control: no-cache, must-revalidate" );
|
|
header( "Pragma: no-cache" );
|
|
header( "Content-Type: text/html; charset=utf8" );
|
|
|
|
$isCorrect = $this->isPasswordCorrect($md5pass,true);
|
|
if ($isCorrect) {
|
|
echo "ok";
|
|
} else {
|
|
// Output from isPasswordCorrect method already
|
|
// echo " - false -";
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Insert a "work" (food or drink) job into the printjob queue. The POS Print Server will
|
|
* pick these jobs and delete them after successful printing
|
|
*/
|
|
public static function queueWorkPrintJob($pdo,$table,$timestamp,$prods,$kind,$printer,$username) {
|
|
$table .= " ($username)";
|
|
|
|
$content = json_encode(array("table" => $table, "time" => $timestamp, "products" => $prods));
|
|
|
|
$printInsertSql = "INSERT INTO `%printjobs%` (`id` , `content`,`type`,`printer`) VALUES ( NULL,?,?,?)";
|
|
$stmt = $pdo->prepare(DbUtils::substTableAlias($printInsertSql));
|
|
$stmt->execute(array($content,intval($kind) + 1,$printer));
|
|
}
|
|
|
|
function getPrintJobOverview($pdo) {
|
|
if (!($this->userrights->hasCurrentUserRight('right_manager')) &&
|
|
!($this->userrights->hasCurrentUserRight('is_admin'))
|
|
) {
|
|
echo json_encode(array("status" => "ERROR", "code" => ERROR_DB_PRIVS_MISSING, "msg" => ERROR_DB_PRIVS_MISSING_MSG));
|
|
return;
|
|
}
|
|
|
|
$jobs = array();
|
|
for ($printer=1;$printer<7;$printer++) {
|
|
$sql = "SELECT count(id) as count FROM %printjobs% WHERE printer=? AND type != '1' AND type != '2'";
|
|
$stmt = $pdo->prepare(DbUtils::substTableAlias($sql));
|
|
$stmt->execute(array($printer));
|
|
$result = $stmt->fetchObject();
|
|
$jobs[] = array("printer" => $printer, "jobs" => $result->count);
|
|
}
|
|
|
|
$sql = "SELECT count(id) as count FROM %printjobs% WHERE type=?";
|
|
$stmt = $pdo->prepare(DbUtils::substTableAlias($sql));
|
|
$stmt->execute(array(1));
|
|
$result = $stmt->fetchObject();
|
|
$jobs[] = array("printer" => 7, "jobs" => $result->count);
|
|
$stmt->execute(array(2));
|
|
$result = $stmt->fetchObject();
|
|
$jobs[] = array("printer" => 8, "jobs" => $result->count);
|
|
|
|
echo json_encode(array("status" => "OK", "msg" => $jobs));
|
|
}
|
|
|
|
function clearprintjobs($pdo) {
|
|
if (!($this->userrights->hasCurrentUserRight('right_manager')) &&
|
|
!($this->userrights->hasCurrentUserRight('is_admin'))
|
|
) {
|
|
echo json_encode(array("status" => "ERROR", "code" => ERROR_DB_PRIVS_MISSING, "msg" => ERROR_DB_PRIVS_MISSING_MSG));
|
|
return;
|
|
}
|
|
$sql = "DELETE FROM %printjobs%";
|
|
$stmt = $pdo->prepare(DbUtils::substTableAlias($sql));
|
|
$stmt->execute();
|
|
$this->getPrintJobOverview($pdo);
|
|
}
|
|
|
|
function batchReceiptPrintJob($start,$end) {
|
|
try {
|
|
$start = intval($start);
|
|
$end = intval($end);
|
|
} catch (Exception $ex) {
|
|
echo json_encode(array("status" => "ERROR", "code" => NUMBERFORMAT_ERROR, "msg" => NUMBERFORMAT_ERROR_MSG));
|
|
return;
|
|
}
|
|
if(!($this->userrights->hasCurrentUserRight('right_bill'))) {
|
|
echo json_encode(array("status" => "ERROR", "code" => ERROR_BILL_NOT_AUTHOTRIZED, "msg" => ERROR_BILL_NOT_AUTHOTRIZED_MSG));
|
|
} else {
|
|
if ($start > $end) {
|
|
$tmp = $end;
|
|
$end = $start;
|
|
$start = $tmp;
|
|
}
|
|
if(session_id() == '') {
|
|
session_start();
|
|
}
|
|
$printer = $_SESSION['receiptprinter'];
|
|
$pdo = DbUtils::openDbAndReturnPdoStatic();
|
|
|
|
for($jobId=$start;$jobId <= $end;$jobId++) {
|
|
$sql = "SELECT count(id) as countid FROM %bill% WHERE id=?";
|
|
$stmt = $pdo->prepare(DbUtils::substTableAlias($sql));
|
|
$stmt->execute(array($jobId));
|
|
$row =$stmt->fetchObject();
|
|
if ($row->countid == 1) {
|
|
$printInsertSql = "INSERT INTO `%printjobs%` (`id` , `content`,`type`,`printer`) VALUES ( NULL,?,?,?)";
|
|
$stmt = $pdo->prepare(DbUtils::substTableAlias($printInsertSql));
|
|
$stmt->execute(array($jobId,'3',$printer));
|
|
}
|
|
}
|
|
echo json_encode(array("status" => "OK"));
|
|
}
|
|
}
|
|
|
|
function queueReceiptPrintJob($billid,$useaddrecprinter) {
|
|
// waiter, or manager, bill, admin rights required
|
|
if (!($this->userrights->hasCurrentUserRight('right_paydesk')) &&
|
|
!($this->userrights->hasCurrentUserRight('right_manager')) &&
|
|
!($this->userrights->hasCurrentUserRight('right_bill')) &&
|
|
!($this->userrights->hasCurrentUserRight('right_waiter')) &&
|
|
!($this->userrights->hasCurrentUserRight('is_admin'))
|
|
) {
|
|
echo "Benutzerrechte nicht ausreichend!";
|
|
return false;
|
|
} else {
|
|
// PAY_PRINT_TYPE = 3 means printing as paydesk print -> choose the printer
|
|
// (print type is misused also for selection of printer)
|
|
if(session_id() == '') {
|
|
session_start();
|
|
}
|
|
$printer = $_SESSION['receiptprinter'];
|
|
|
|
// now get receipt info from bill table
|
|
$pdo = $this->dbutils->openDbAndReturnPdo();
|
|
|
|
$sql = "SELECT setting FROM %config% WHERE name=?";
|
|
$stmt = $pdo->prepare(DbUtils::substTableAlias($sql));
|
|
$stmt->execute(array("addreceipttoprinter"));
|
|
$row = $stmt->fetchObject();
|
|
$addprinter = $row->setting;
|
|
|
|
$printInsertSql = "INSERT INTO `%printjobs%` (`id` , `content`,`type`,`printer`) VALUES ( NULL,?,?,?)";
|
|
$stmt = $pdo->prepare($this->dbutils->resolveTablenamesInSqlString($printInsertSql));
|
|
$stmt->execute(array((string)($billid),'3',$printer));
|
|
|
|
if (!is_null($addprinter) && ($useaddrecprinter == 1)) {
|
|
$stmt->execute(array((string)($billid),'3',$addprinter));
|
|
}
|
|
|
|
echo json_encode("OK");
|
|
}
|
|
}
|
|
|
|
public function queueClosingSummary($closingid) {
|
|
if (!($this->userrights->hasCurrentUserRight('right_paydesk')) &&
|
|
!($this->userrights->hasCurrentUserRight('right_manager')) &&
|
|
!($this->userrights->hasCurrentUserRight('right_bill')) &&
|
|
!($this->userrights->hasCurrentUserRight('right_closing')) &&
|
|
!($this->userrights->hasCurrentUserRight('right_waiter')) &&
|
|
!($this->userrights->hasCurrentUserRight('is_admin'))
|
|
) {
|
|
echo "Benutzerrechte nicht ausreichend!";
|
|
return false;
|
|
} else {
|
|
if(session_id() == '') {
|
|
session_start();
|
|
}
|
|
$printer = $_SESSION['receiptprinter'];
|
|
|
|
$pdo = $this->dbutils->openDbAndReturnPdo();
|
|
|
|
$printInsertSql = "INSERT INTO `%printjobs%` (`id` , `content`,`type`,`printer`) VALUES ( NULL,?,?,?)";
|
|
$stmt = $pdo->prepare($this->dbutils->resolveTablenamesInSqlString($printInsertSql));
|
|
$stmt->execute(array((string)($closingid),'4',$printer));
|
|
echo json_encode("OK");
|
|
}
|
|
}
|
|
|
|
function getBigFontWorkReceiptSetting($pdo) {
|
|
$sql = "SELECT setting FROM %config% WHERE name=?";
|
|
$stmt = $pdo->prepare($this->dbutils->resolveTablenamesInSqlString($sql));
|
|
$stmt->execute(array("bigfontworkreceipt"));
|
|
$row =$stmt->fetchObject();
|
|
return $row->setting;
|
|
}
|
|
|
|
function isPasswordCorrect($pass,$verbose) {
|
|
$sql = "SELECT setting FROM %config% WHERE name=?";
|
|
$pdo = $this->dbutils->openDbAndReturnPdo();
|
|
$stmt = $pdo->prepare($this->dbutils->resolveTablenamesInSqlString($sql));
|
|
$stmt->execute(array("printpass"));
|
|
$row =$stmt->fetchObject();
|
|
|
|
if ($row != null) {
|
|
$passInDb = $row->setting;
|
|
if ($passInDb != null) {
|
|
// plain comparison
|
|
if ($pass == $passInDb) {
|
|
return true;
|
|
} else {
|
|
if ($verbose) {
|
|
echo "Error: Falscher Printpass!";
|
|
}
|
|
}
|
|
} else {
|
|
if ($verbose) {
|
|
echo "Error: kein Printpass in DB gesetzt!";
|
|
}
|
|
}
|
|
}
|
|
if ($verbose) {
|
|
echo "Error: DB konnte nicht abgefragt werden!";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
function getLogoAsPng() {
|
|
$pdo = $this->dbutils->openDbAndReturnPdo();
|
|
|
|
header("Content-Disposition: attachment; filename=logo.png");
|
|
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
|
|
header("Pragma: no-cache");
|
|
header("Expires: Mon, 20 Dec 1998 01:00:00 GMT" );
|
|
header('Content-Type: ' . image_type_to_mime_type(IMAGETYPE_PNG));
|
|
|
|
$sql = "SELECT setting from %logo% WHERE name=?";
|
|
|
|
$stmt = $pdo->prepare($this->dbutils->resolveTablenamesInSqlString($sql));
|
|
$stmt->execute(array('logoimg'));
|
|
$row = $stmt->fetchObject();
|
|
|
|
if ($stmt->rowCount() > 0) {
|
|
$img = $row->setting;
|
|
$php_img = imagecreatefromstring($img);
|
|
imagepng($php_img, NULL);
|
|
imagedestroy($php_img);
|
|
}
|
|
}
|
|
|
|
function getLogoAsWbmp() {
|
|
$pdo = $this->dbutils->openDbAndReturnPdo();
|
|
$genInfo = $this->admin->getGeneralConfigItems(false,$pdo);
|
|
|
|
header("Content-Disposition: attachment; filename=logo.wbmp");
|
|
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
|
|
header("Pragma: no-cache");
|
|
header("Expires: Mon, 20 Dec 1998 01:00:00 GMT" );
|
|
header('Content-Type: ' . image_type_to_mime_type(IMAGETYPE_WBMP));
|
|
|
|
$logourl = $genInfo["logourl"];
|
|
$img = file_get_contents("../" . $logourl);
|
|
$php_img = imagecreatefromstring($img);
|
|
|
|
$foreground_color = imagecolorallocate($im, 255, 0, 0);
|
|
imagewbmp($php_img, NULL, $foreground_color);
|
|
|
|
imagedestroy($php_img);
|
|
}
|
|
|
|
function getReceiptConfig() {
|
|
$pdo = $this->dbutils->openDbAndReturnPdo();
|
|
$genInfo = $this->admin->getGeneralConfigItems(false,$pdo);
|
|
|
|
$retArray = array("decpoint" => $genInfo["decpoint"],
|
|
"billlanguage" => $genInfo["billlanguage"],
|
|
"version" => $genInfo["version"],
|
|
"currency" => $genInfo["currency"],
|
|
"companyinfo" => $genInfo["companyinfo"]
|
|
);
|
|
|
|
echo json_encode($retArray);
|
|
}
|
|
|
|
function getNextClosingPrintJobs($md5pass,$language) {
|
|
$isCorrect = $this->isPasswordCorrect($md5pass,false);
|
|
if ($isCorrect) {
|
|
ob_start();
|
|
$pdo = $this->dbutils->openDbAndReturnPdo();
|
|
$this->saveLastPrintServerAccess($pdo);
|
|
|
|
$closing = new Closing();
|
|
$sql = "SELECT id,content,type,printer FROM %printjobs% WHERE type=? ORDER BY id";
|
|
|
|
$stmt = $pdo->prepare($this->dbutils->resolveTablenamesInSqlString($sql));
|
|
$stmt->execute(array(4));
|
|
|
|
$result = $stmt->fetchAll();
|
|
$closingarray = array();
|
|
foreach($result as $aClos) {
|
|
$jobid = $aClos['id'];
|
|
$closid = $aClos["content"];
|
|
$printer = $aClos["printer"];
|
|
$theClosing = $closing->getClosingSummaryWoSign($closid, $pdo, false);
|
|
$aClosing = array("id" => $jobid,"closing" => $theClosing, "printer" => $printer);
|
|
$closingarray[] = $aClosing;
|
|
}
|
|
echo json_encode($closingarray);
|
|
ob_end_flush();
|
|
} else {
|
|
echo json_encode(array());
|
|
}
|
|
}
|
|
|
|
function getTemplate($pdo,$templatekey) {
|
|
$sql = "SELECT setting FROM %config% WHERE name=?";
|
|
$stmt = $pdo->prepare(DbUtils::substTableAlias($sql));
|
|
$stmt->execute(array($templatekey));
|
|
$row =$stmt->fetchObject();
|
|
return $row->setting;
|
|
}
|
|
|
|
function getNextReceiptPrintJobs($md5pass,$language,$printers,$fl) {
|
|
$isCorrect = $this->isPasswordCorrect($md5pass,false);
|
|
if ($isCorrect) {
|
|
ob_start();
|
|
$printersArr = explode ( ',', $printers );
|
|
|
|
$pdo = $this->dbutils->openDbAndReturnPdo();
|
|
$this->saveLastPrintServerAccess($pdo);
|
|
|
|
$template = $this->getTemplate($pdo, "rectemplate");
|
|
|
|
if (intval($language) > 2) {
|
|
$genInfo = $this->admin->getGeneralConfigItems(false,$pdo);
|
|
$language = $genInfo["billlanguage"];
|
|
}
|
|
|
|
$bill = new Bill();
|
|
|
|
$sql = "SELECT id,content,type,printer FROM %printjobs% WHERE type=? ORDER BY id";
|
|
|
|
$stmt = $pdo->prepare($this->dbutils->resolveTablenamesInSqlString($sql));
|
|
$stmt->execute(array(3));
|
|
|
|
$result = $stmt->fetchAll();
|
|
|
|
$billarray = array();
|
|
foreach($result as $aBill) {
|
|
$printJobId = $aBill['id'];
|
|
$aBillId = $aBill["content"];
|
|
$printer = $aBill["printer"];
|
|
|
|
if (in_array($printer, $printersArr)) {
|
|
if (is_null($fl)) {
|
|
$receiptJob = array("id" => $printJobId,"bill" => $bill->getBillWithId($pdo,$aBillId,$language,$printer));
|
|
} else if ($fl >= 1) {
|
|
$receiptJob = array("id" => $printJobId,"bill" => $bill->getBillWithId($pdo,$aBillId,$language,$printer), "template" => $template);
|
|
}
|
|
$billarray[] = $receiptJob;
|
|
}
|
|
}
|
|
echo json_encode($billarray);
|
|
ob_end_flush();
|
|
} else {
|
|
echo json_encode(array());
|
|
}
|
|
}
|
|
|
|
function getNextFoodWorkPrintJobs($printer,$md5pass,$fl) {
|
|
$this->getNextWorkPrintJobs($md5pass,1,$printer,$fl);
|
|
}
|
|
|
|
function getNextDrinkWorkPrintJobs($printer,$md5pass,$fl) {
|
|
$this->getNextWorkPrintJobs($md5pass,2,$printer,$fl);
|
|
}
|
|
|
|
function getNextWorkPrintJobs($md5pass,$theType,$printer,$fl) {
|
|
$isCorrect = $this->isPasswordCorrect($md5pass,false);
|
|
|
|
if ($isCorrect) {
|
|
$pdo = $this->dbutils->openDbAndReturnPdo();
|
|
$this->saveLastPrintServerAccess($pdo);
|
|
|
|
$bigFontWorkReceipt = $this->getBigFontWorkReceiptSetting($pdo);
|
|
$templatekey = "foodtemplate";
|
|
if ($theType === 2) {
|
|
$templatekey = "drinktemplate";
|
|
}
|
|
$template = $this->getTemplate($pdo, $templatekey);
|
|
|
|
if (is_null($printer)) {
|
|
$sql = "SELECT id,content,type FROM %printjobs% WHERE type=? ORDER BY id";
|
|
$stmt = $pdo->prepare($this->dbutils->resolveTablenamesInSqlString($sql));
|
|
$stmt->execute(array($theType));
|
|
} else {
|
|
$sql = "SELECT id,content,type FROM %printjobs% WHERE type=? AND printer=? ORDER BY id";
|
|
$stmt = $pdo->prepare($this->dbutils->resolveTablenamesInSqlString($sql));
|
|
$stmt->execute(array($theType,$printer));
|
|
}
|
|
|
|
$result = $stmt->fetchAll();
|
|
|
|
$workarray = array();
|
|
foreach($result as $aWorkJob) {
|
|
$aWork = json_decode($aWorkJob["content"]); // is in json format
|
|
if ($fl >= 2) {
|
|
$workarray[] = array("id" => $aWorkJob["id"],"content" => $aWork, "bigfontworkreceipt" => intval($bigFontWorkReceipt), "template" => $template);
|
|
} else {
|
|
// default without template
|
|
$workarray[] = array("id" => $aWorkJob["id"],"content" => $aWork, "bigfontworkreceipt" => intval($bigFontWorkReceipt));
|
|
}
|
|
}
|
|
|
|
echo json_encode($workarray);
|
|
} else {
|
|
echo json_encode(array());
|
|
}
|
|
}
|
|
|
|
|
|
function deletePrintJob($pass,$id) {
|
|
$isCorrect = $this->isPasswordCorrect($pass,false);
|
|
if ($isCorrect) {
|
|
$pdo = $this->dbutils->openDbAndReturnPdo();
|
|
$this->saveLastPrintServerAccess($pdo);
|
|
$sql = "DELETE FROM %printjobs% WHERE id=?";
|
|
|
|
$stmt = $pdo->prepare($this->dbutils->resolveTablenamesInSqlString($sql));
|
|
$stmt->execute(array($id));
|
|
echo json_encode(array("status" => "OK", "code" => OK, "msg" => "Druckauftrag erfolgreich gelöscht."));
|
|
} else {
|
|
echo json_encode(array("status" => "ERROR", "code" => ERROR_NOT_AUTHOTRIZED, "msg" => ERROR_NOT_AUTHOTRIZED_MSG));
|
|
}
|
|
}
|
|
}
|
|
?>
|