Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 1275
0.00% covered (danger)
0.00%
0 / 97
CRAP
n/a
0 / 0
getStoreFromID
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
getBuyQueueByName
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
dbConnectByName
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
56
getJSONQueue
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
getJSONQueueLightCached
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
12
getJSONQueueLight
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
rebuildQueueLightCache
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
getJSONCompletedCached
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
12
rebuildCompletedCache
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
getJSONCompleted
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
getJSONItem
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
getStoreDirectory
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
getStoreType
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
getCompletedBuysArray
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
getLastTenBuysJSON
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
getBuyerStatsArray
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
getSorterStatsArray
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
getPrintLogArray
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
getBuyersArray
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
30
sendBuySMS
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
getQueueItemArray
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
getJSONCurrentTime
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
checkAccessAndReturnStoreObject
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
getJSONWaitTime
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
getJSONWaitTimeCached
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
getCachedWaitTime
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
rebuildCachedWaitTime
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
getJSONShowLoop
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
getJSONTablets
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
getJSONStoreTypeNum
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
getJSONReferrals
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
secondsToReadable
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
secondsToMinHour
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
20
titleCase
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
42
getCurrentDateRange
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
getDateRange
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
isSSL
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
verifyLoginToken
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
30
verifyStoreLogin
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
20
createSecurityToken
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
destroySecurityToken
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
getAllStoresData
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
30
insertStoreStat
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
90
insertEmployeeDaily
0.00% covered (danger)
0.00%
0 / 61
0.00% covered (danger)
0.00%
0 / 1
1056
getSecondsFromADifference
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
getStoresTxtCount
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
6
getStoreInfo
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
12
getStoreLogo
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
90
checkAccessToAPIs
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
getNumBuyers
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
110
base64UrlEncode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
getStoreSlides
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
6
getCorpSlides
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
6
getHBSlides
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
6
getSlideItemArray
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
6
getTopCustomers
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
20
getAllCustomers
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
30
getCustomerAlert
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
getCustomerData
0.00% covered (danger)
0.00%
0 / 65
0.00% covered (danger)
0.00%
0 / 1
56
formatPhoneNumber
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
20
getBuyerKioskStats
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
42
getDashboardStats
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
getDashboardBusyTimes
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
sendDemoEmail
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
sendDailyEmail
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
6
sendRequestEmail
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
getEmployeeInfo
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
6
getEmployeeNameArray
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
expireSlides
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
getStoreTypeName
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
sendMassTwilioTxt
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
12
getStoreProcessAverages
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
12
getStoreSortAverages
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
6
compressLogFiles
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
getLoyaltyCustomerByPhone
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
30
obfuscateCustomerData
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
12
obfuscateDigits
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
6
obfuscateEmail
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
gen_uuid
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
filterMessage
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
30
similarity
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
42
isCurrency
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
generateRemazeKey
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
validateAPIKey
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
isSecure
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
getAllLoyaltyCustomersByPhone
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
30
sendSMStoNumber
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
20
convertUTCToLocalTimeStamp
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
getSurveyMessages
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
getSurveyLinks
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
getRandomSurveyLink
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
getActiveStoreCount
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
getActiveStores
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
getInactiveStores
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
2
getStoreStatus
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
getBKStats
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
6
sendSurverySMS
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 1
156
1<?php
2/**
3 * BaseModel.php - Core Model Utilities
4 *
5 * This file provides core database connection functions and queue helpers.
6 *
7 * NOTE: All domain models are now loaded via PSR-4 autoloading.
8 * The BuyerKiosk namespace maps to userfrosting/src/BuyerKiosk/
9 * Legacy class names are aliased in src/BuyerKiosk/Compatibility/LegacyAliases.php
10 *
11 * @see composer.json for PSR-4 configuration
12 * @see src/BuyerKiosk/Compatibility/LegacyAliases.php for backward compatibility
13 */
14
15// PSR-4 autoloading handles all BuyerKiosk\* classes via Composer
16// Legacy class names (Store, BuyQueue, Customer, etc.) are aliased automatically
17
18function getStoreFromID($id) {
19    global $db_name;
20    $store = new Store();
21    if($db = dbConnectByName($db_name)) {
22        if(!$store->createStore($id, $db)) {
23            echo "ERROR STORE DOES NOT EXIST (ERROR CODE: 1005)[".$id."]";
24        }
25    } else {
26        echo "ERROR COULD NOT CONNECT (ERROR CODE: 1000)";
27    }
28    return $store;
29
30}
31function getBuyQueueByName($dbName) {
32    $buyQueue = new BuyQueue();
33    if($db = dbConnectByName($dbName)) {
34        $buyQueue->getAllCurrentQueueItems($db);
35    } else {
36        echo "ERROR COULD NOT CONNECT (ERROR CODE: 1001)";
37    }
38    return $buyQueue;
39}
40
41// Global array to store database connections
42if (!isset($GLOBALS['db_connections'])) {
43    $GLOBALS['db_connections'] = [];
44}
45
46function dbConnectByName($dbname) {
47    global $db_user,$db_pass;
48    if (empty($dbname)) {
49        error_log("dbConnectByName: Database name is empty");
50        return NULL;
51    }
52    
53    // Check if we already have a connection for this database
54    if (isset($GLOBALS['db_connections'][$dbname]) && $GLOBALS['db_connections'][$dbname] instanceof PDO) {
55        try {
56            // Test if connection is still alive
57            $GLOBALS['db_connections'][$dbname]->query('SELECT 1');
58            return $GLOBALS['db_connections'][$dbname];
59        } catch (PDOException $e) {
60            // Connection is dead, remove it
61            unset($GLOBALS['db_connections'][$dbname]);
62        }
63    }
64    
65    try {
66        $db = new PDO('mysql:host='.$_ENV['DB_HOST'].';dbname='.$dbname.';charset=utf8', $_ENV['DB_USER'], $_ENV['DB_PASS']);
67        $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
68        
69        // Store connection for reuse (limit to 50 connections)
70        if (count($GLOBALS['db_connections']) < 50) {
71            $GLOBALS['db_connections'][$dbname] = $db;
72        }
73        
74        return $db;
75    } catch(PDOException $e) {
76        error_log("dbConnectByName: Connection failed for database '$dbname': " . $e->getMessage());
77        echo "ERROR COULD NOT CONNECT (ERROR CODE: 1002)";
78        echo $e->getMessage();
79    }
80    return NULL;
81}
82function getJSONQueue(Store $store, $app = null) {
83    $dbName = $store->getDbName();
84    $db = dbConnectByName($dbName);
85    $BuyQueue = new BuyQueue();
86    $BuyQueue->createStore($store->getTypeNum());
87    $queueItemArray = $BuyQueue->getAllCurrentQueueItems($db, $app);
88    return json_encode($queueItemArray);
89}
90function getJSONQueueLightCached(Store $store)
91{
92    //Get Current Queue JSON from predis cache if it is less than 5 minutes old
93    $client = new Predis\Client($_ENV['REDIS_URL']);
94    $date = new DateTime('now', new DateTimeZone('UTC'));
95    if ($client->hexists($store->getTypeNum(), "current_queue_date")) {
96        $ts = new DateTime($client->hget($store->getTypeNum(), "current_queue_date"));
97        $to_time = strtotime($date->format("Y-m-d H:i:s"));
98        $from_time = strtotime($ts->format("Y-m-d H:i:s"));
99        $minutes = round(abs($to_time - $from_time) / 60, 2);
100        if ($minutes < 5) {
101            return $client->hget($store->getTypeNum(), "current_queue");
102        } else {
103            $json = getJSONQueueLight($store);
104            $client->hset($store->getTypeNum(), "current_queue", $json);
105            $client->hset($store->getTypeNum(), "current_queue_date", $date->format("Y-m-d H:i:s"));
106            return $json;
107        }
108    } else {
109        $json = getJSONQueueLight($store);
110        $client->hset($store->getTypeNum(), "current_queue", $json);
111        $client->hset($store->getTypeNum(), "current_queue_date", $date->format("Y-m-d H:i:s"));
112        return $json;
113    }
114}
115function getJSONQueueLight($store) {
116    $dbName = $store->getDbName();
117    $db = dbConnectByName($dbName);
118    $BuyQueue = new BuyQueue();
119    $BuyQueue->createStore($store->getTypeNum());
120    $queueItemArray = $BuyQueue->getAllCurrentQueueItemsLight($db);
121    return json_encode($queueItemArray);
122}
123function rebuildQueueLightCache($store) {
124    $client = new Predis\Client($_ENV['REDIS_URL']);
125    $date = new DateTime('now', new DateTimeZone('UTC'));
126    $json = getJSONQueueLight($store);
127    $client->hset($store->getTypeNum(), "current_queue", $json);
128    $client->hset($store->getTypeNum(), "current_queue_date", $date->format("Y-m-d H:i:s"));
129}
130function getJSONCompletedCached(Store $store)
131{
132    $client = new Predis\Client($_ENV['REDIS_URL']);
133    $date = new DateTime('now', new DateTimeZone('UTC'));
134    if ($client->hexists($store->getTypeNum(), "completed_date")) {
135        $ts = new DateTime($client->hget($store->getTypeNum(), "completed_date"));
136        $to_time = strtotime($date->format("Y-m-d H:i:s"));
137        $from_time = strtotime($ts->format("Y-m-d H:i:s"));
138        $minutes = round(abs($to_time - $from_time) / 60, 2);
139        if ($minutes < 5) {
140            return $client->hget($store->getTypeNum(), "completed");
141        } else {
142            $completedBuys = new CompletedBuys();
143            $completedBuys->getCompletedBuysLight($store);
144            $client->hset($store->getTypeNum(), "completed", json_encode($completedBuys->completedBuys));
145            $client->hset($store->getTypeNum(), "completed_date", $date->format("Y-m-d H:i:s"));
146            return json_encode($completedBuys->completedBuys);
147        }
148    } else {
149        $completedBuys = new CompletedBuys();
150        $completedBuys->getCompletedBuysLight($store);
151        $client->hset($store->getTypeNum(), "completed", json_encode($completedBuys->completedBuys));
152        $client->hset($store->getTypeNum(), "completed_date", $date->format("Y-m-d H:i:s"));
153        return json_encode($completedBuys->completedBuys);
154    }
155}
156function rebuildCompletedCache(Store $store) {
157    $client = new Predis\Client($_ENV['REDIS_URL']);
158    $date = new DateTime('now', new DateTimeZone('UTC'));
159    $completedBuys = new CompletedBuys();
160    $completedBuys->getCompletedBuysLight($store);
161    $client->hset($store->getTypeNum(), "completed", json_encode($completedBuys->completedBuys));
162    $client->hset($store->getTypeNum(), "completed_date", $date->format("Y-m-d H:i:s"));
163}
164function getJSONCompleted(Store $store)
165{
166    $completedBuys = new CompletedBuys();
167    $completedBuys->gatherCompletedBuys($store);
168    echo json_encode($completedBuys->completedBuys);
169}
170function getJSONItem(Store $store, $buyID) {
171    $dbName = $store->getDbName();
172    $db = dbConnectByName($dbName);
173    $BuyQueue = new BuyQueue();
174    $BuyQueue->createStore($store->getTypeNum());
175    $queueItem = $BuyQueue->getOneQueueItem($db, $buyID);
176    return json_encode($queueItem);
177}
178function getStoreDirectory(Store $store) {
179    global $db_name;
180    $storeType = $store->getStoreType();
181    $db = dbConnectByName($db_name);
182    $store = $db->query("SELECT * FROM storetypes WHERE type = '".$storeType."'");
183    $row = $store->fetch(PDO::FETCH_ASSOC);
184    return $row['dir'];
185}
186function getStoreType(Store $store) {
187    global $db_name;
188    $storeType = $store->getStoreType();
189    $db = dbConnectByName($db_name);
190    $store = $db->query("SELECT * FROM storetypes WHERE type = '".$storeType."'");
191    $row = $store->fetch(PDO::FETCH_ASSOC);
192    return $row['name'];
193}
194
195function getCompletedBuysArray(Store $store) {
196    $CompletedBuys = new CompletedBuys();
197    $CompletedBuys->gatherCompletedBuys($store);
198    $completedBuysArray = $CompletedBuys->getCompletedBuys();
199    return $completedBuysArray;
200}
201function getLastTenBuysJSON(Store $store) {
202    $CompletedBuys = new CompletedBuys();
203    $CompletedBuys->gatherLastTen($store);
204    $completedBuysArray = $CompletedBuys->getCompletedBuys();
205    return json_encode($completedBuysArray);
206}
207function getBuyerStatsArray(Store $store, $date = null) {
208    $BuyerStats = new BuyerStats();
209    $BuyerStats->gatherDailyBuyerStatsArray($store, $date);
210    $buyerStatsArray = $BuyerStats->getBuyerStatsArray();
211    return $buyerStatsArray;
212}
213function getSorterStatsArray(Store $store, $date = null) {
214    $BuyerStats = new BuyerStats();
215    $BuyerStats->gatherDailySorterStatsArray($store, $date);
216    $sorterStatsArray = $BuyerStats->SorterStatsArray;
217    return $sorterStatsArray;
218}
219function getPrintLogArray(Store $store) {
220    $PrintLog = new PrintLog();
221    $PrintLog->gatherPrintLogArray($store);
222    $printLogArray = $PrintLog->getPrintLogArray();
223    return $printLogArray;
224}
225function getBuyersArray(Store $store) {
226    try{
227        $db = dbConnectByName($store->getDbName());
228        if (!$db) {
229            error_log("Failed to connect to database: " . $store->getDbName() . " for store: " . $store->getTypeNum());
230            return array(); // Return empty array instead of null
231        }
232        $query = $db->query("SELECT * FROM employees WHERE active = 1 ORDER BY employeeLastName ASC");
233        $buyerArray = array();
234        if($query) {
235            while($row = $query->fetch(PDO::FETCH_ASSOC)) {
236                $buyerArray[] = $row;
237            }
238        }
239        return $buyerArray;
240    } catch(PDOException $e) {
241        error_log("Database error in getBuyersArray for store " . $store->getTypeNum() . ": " . $e->getMessage());
242        return array(); // Return empty array instead of null
243    }
244
245}
246function sendBuySMS($buyID, $typeNum)
247{
248    //@var $store \Store
249    $storeController = new \BuyerKiosk\StoreController($typeNum);
250    $store = $storeController->getStore();
251    if($store->postBuySMS == 0) {
252        //Store has buy texts turned off
253        return null;
254    }
255    $db = dbConnectByName($store->getDbName());
256    $buyArray = getQueueItemArray($store, $buyID);
257    $customerController = new \BuyerKiosk\CustomerController(null, $store);
258    $customer = $customerController->getCustomerById($buyArray['customerID']);
259    $result = $store->textMessageService->sendBuyText($customer);
260    if($result['status'] == "success") {
261        $db->query("INSERT INTO texts (customerID, type, employeeNumber, message) VALUES (".$buyArray['customerID'].", 0, 0, '".$result['id']."')");
262        $db->query("UPDATE buyQueue SET twilioSent = 1 WHERE buyID = ".$buyArray['buyID']);
263
264    }
265    return $result;
266}
267function getQueueItemArray(Store $store, $buyID) {
268    $db = dbConnectByName($store->getDbName());
269    if(substr($buyID,0,1) == "B") {
270        $query = $db->query("SELECT * FROM buyQueue, customers WHERE buyQueue.drsBuyID = '".$buyID."' AND customers.customerID = buyQueue.customerID");
271    } else {
272        $query = $db->query("SELECT * FROM buyQueue, customers WHERE buyQueue.buyID = ".$buyID." AND customers.customerID = buyQueue.customerID");
273    }
274
275    $row = $query->fetch(PDO::FETCH_ASSOC);
276    return $row;
277}
278function getJSONCurrentTime() {
279    ini_set("date.timezone", "UTC");
280    $timeNow = new DateTime("NOW", new DateTimeZone("UTC"));
281    $time = array(
282        'timeNow' => $timeNow->format("Y-m-d H:i:s")
283    );
284    return json_encode($time);
285}
286function checkAccessAndReturnStoreObject($app, $typeNum, $access) {
287    if (!$app->user->checkStoreGroup($typeNum) || !getStoreStatus($typeNum)) {
288        return null;
289    }
290    $storeController = new \BuyerKiosk\StoreController($typeNum);
291    $store = $storeController->getStore();
292    if($store instanceof \Store) {
293        if (!$app->user->checkAccess($access)){
294            return null;
295        }
296        return $store;
297    }
298    return null;
299}
300function getJSONWaitTime(Store $store) {
301    $EstimatedWaitTime = new EstimatedWaitTime();
302    $EstimatedWaitTime->getEstimatedWaitTime($store);
303    if($store->getEnableInterval()) {
304        $temp = $EstimatedWaitTime->waitArray;
305    } else {
306        $waitTime = $EstimatedWaitTime->waitMinutes;
307        $temp = array("waitTime" => $waitTime);
308    }
309    return json_encode($temp);
310}
311function getJSONWaitTimeCached(Store $store) {
312    return getCachedWaitTime($store);
313}
314function getCachedWaitTime(Store $store) {
315    $client = new Predis\Client($_ENV['REDIS_URL']);
316    $date = new DateTime('now', new DateTimeZone('UTC'));
317    if($client->hexists($store->getTypeNum(), "waitTime_date")) {
318        $ts = new DateTime($client->hget($store->getTypeNum(), "waitTime_date"));
319        $to_time = strtotime($date->format("Y-m-d H:i:s"));
320        $from_time = strtotime($ts->format("Y-m-d H:i:s"));
321        $minutes = round(abs($to_time - $from_time) / 60, 2);
322        if ($minutes < 5) {
323            return $client->hget($store->getTypeNum(), "waitTime");
324        } else {
325            rebuildCachedWaitTime($store);
326            return $client->hget($store->getTypeNum(), "waitTime");
327        }
328    } else {
329        rebuildCachedWaitTime($store);
330        return $client->hget($store->getTypeNum(), "waitTime");
331    }
332}
333function rebuildCachedWaitTime(Store $store) {
334    $client = new Predis\Client($_ENV['REDIS_URL']);
335    $date = new DateTime('now', new DateTimeZone('UTC'));
336    $EstimatedWaitTime = new EstimatedWaitTime();
337    $EstimatedWaitTime->getEstimatedWaitTime($store);
338
339    if($store->getEnableInterval()) {
340        $temp = $EstimatedWaitTime->waitArray;
341    } else {
342        $waitTime = $EstimatedWaitTime->waitMinutes;
343        $temp = array("waitTime" => $waitTime);
344    }
345    $client->hset($store->getTypeNum(), "waitTime", json_encode($temp));
346    $client->hset($store->getTypeNum(), "waitTime_date", $date->format("Y-m-d H:i:s"));
347}
348function getJSONShowLoop(Store $store) {
349    $DigitalSignLoop = new StoreLoop($store);
350    $DigitalSignLoop->getCurrentSignLoopForSign();
351    $loopArray = $DigitalSignLoop->getLoopArray();
352    return json_encode($loopArray);
353
354}
355function getJSONTablets(Store $store) {
356    $db = dbConnectByName($store->getDbName());
357    $query = $db->query("SELECT * FROM tablets");
358    $tabletsArray = array();
359    while($tablet = $query->fetch(PDO::FETCH_ASSOC)) {
360        $tabletsArray[] = $tablet;
361    }
362    return json_encode($tabletsArray);
363}
364function getJSONStoreTypeNum(Store $store) {
365    return json_encode(array("type" => $store->getStoreType(),"num" =>$store->getStoreNum()));
366}
367function getJSONReferrals(Store $store, $referralCode) {
368    $referrals = new Referrals();
369    $referralsArray = $referrals->getReferralsByCode($store, $referralCode);
370    if($referralsArray == "none") {
371        return json_encode($referralsArray);
372    } else {
373        return json_encode($referralsArray);
374    }
375
376}
377function secondsToReadable($seconds) {
378    if($seconds > 0){
379        $hours = floor($seconds/3600);
380        $seconds -= ($hours * 3600);
381        $minutes = floor($seconds/60);
382        $seconds -= ($minutes * 60);
383        if ($hours > 0) {
384            $result = new DateInterval("PT{$hours}H{$minutes}M{$seconds}S");
385            return $result->format("%hh %Im %Ss");
386        } else {
387            if($minutes > 0) {
388                $result = new DateInterval("PT{$minutes}M{$seconds}S");
389                return $result->format("%im %Ss");
390            } else {
391                $result = new DateInterval("PT{$seconds}S");
392                return $result->format("%Ss");
393            }
394        }
395    } else {
396        $result = new DateInterval('PT0S');
397        return $result->format("%Ss");
398        return $result->format("%Ss");
399    }
400}
401function secondsToMinHour($seconds) {
402    if($seconds > 0){
403        $hours = floor($seconds/3600);
404        $seconds -= ($hours * 3600);
405        $minutes = floor($seconds/60);
406        $seconds -= ($minutes * 60);
407        if ($hours > 0) {
408            $result = new DateInterval("PT{$hours}H{$minutes}M{$seconds}S");
409            return $result->format("%hh %Im");
410        } else {
411            if($minutes > 0) {
412                $result = new DateInterval("PT{$minutes}M{$seconds}S");
413                return $result->format("%im");
414            } else {
415                return "0m";
416            }
417        }
418    } else {
419        return "0m";
420    }
421}
422function titleCase($string)
423{
424    $word_splitters = array(' ', '-', "O'", "L'", "D'", 'St.', 'Mc');
425    $lowercase_exceptions = array('the', 'den', 'von', 'und', 'der', 'de', 'da', 'of', 'and', "l'", "d'");
426    $uppercase_exceptions = array('III', 'IV', 'VI', 'VII', 'VIII', 'IX');
427
428    $string = strtolower($string);
429    foreach ($word_splitters as $delimiter)
430    {
431        $words = explode($delimiter, $string);
432        $newwords = array();
433        foreach ($words as $word)
434        {
435            if (in_array(strtoupper($word), $uppercase_exceptions))
436                $word = strtoupper($word);
437            else
438                if (!in_array($word, $lowercase_exceptions))
439                    $word = ucfirst($word);
440
441            $newwords[] = $word;
442        }
443
444        if (in_array(strtolower($delimiter), $lowercase_exceptions))
445            $delimiter = strtolower($delimiter);
446
447        $string = join($delimiter, $newwords);
448    }
449    return $string;
450}
451function getCurrentDateRange() {
452    $date = new DateTime("now", new DateTimeZone("UTC"));
453    $date->sub(new DateInterval("PT5H"));
454    $date = $date->format("Y-m-d");
455
456    $dateStart = new DateTime($date, new DateTimeZone("UTC"));
457    $dateStart->add(new DateInterval("PT5H"));
458    $dateEnd = new DateTime($dateStart->format("Y-m-d H:i:s"),new DateTimeZone("UTC"));
459    $dateEnd->add(new DateInterval("PT24H"));
460
461    $dateRange = array(
462        "dateStart" => $dateStart,
463        "dateEnd" => $dateEnd
464    );
465    return $dateRange;
466}
467function getDateRange($d) {
468    $date = new DateTime($d, new DateTimeZone("UTC"));
469    $date->sub(new DateInterval("PT5H"));
470    $date = $date->format("Y-m-d");
471
472    $dateStart = new DateTime($date, new DateTimeZone("UTC"));
473    $dateStart->add(new DateInterval("PT5H"));
474    $dateEnd = new DateTime($dateStart->format("Y-m-d H:i:s"), new DateTimeZone("UTC"));
475    $dateEnd->add(new DateInterval("PT24H"));
476
477    $dateRange = array(
478        "dateStart" => $dateStart,
479        "dateEnd" => $dateEnd
480    );
481    return $dateRange;
482}
483function isSSL()
484{
485    return
486        (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
487        || $_SERVER['SERVER_PORT'] == 443;
488}
489function verifyLoginToken($store, $token) {
490    global $db_name;
491
492    $store=htmlspecialchars(strip_tags($store));
493    $token = htmlspecialchars(strip_tags($token));
494    $db = dbConnectByName($db_name);
495    try {
496        $query = $db->prepare("SELECT * FROM tokens WHERE token = ? AND typeNum = ? LIMIT 1");
497        $query->execute(array($token,$store));
498        $thisToken = $query->fetch(PDO::FETCH_ASSOC);
499
500        $dateRange = getCurrentDateRange();
501
502        $tokenCreated = new DateTime($thisToken['created']);
503
504        if($tokenCreated > $dateRange['dateStart'] && $tokenCreated < $dateRange['dateEnd'] && $store == $thisToken['typeNum']) {
505            return true;
506        } else {
507            destroySecurityToken($store, $token);
508        }
509    } catch(PDOException $e) {
510        echo $e->getMessage();
511    }
512    return false;
513}
514function verifyStoreLogin($store, $password) {
515    global $db_name;
516    $store = htmlspecialchars(strip_tags($store));
517    $password = htmlspecialchars(strip_tags($password));
518
519    $pwdHasher = new PasswordHash(8, FALSE);
520    $db = dbConnectByName($db_name);
521    try {
522        $storeQuery = $db->prepare("SELECT password FROM stores WHERE typeNum = ?");
523        $storeQuery->execute(array($store));
524        if($storeQuery->rowCount() > 0) {
525            $store = $storeQuery->fetch(PDO::FETCH_ASSOC);
526            $checked = $pwdHasher->CheckPassword($password, $store['password']);
527            if($checked) {
528                return true;
529            } else {
530                return false;
531            }
532        } else {
533            return false;
534        }
535    } catch(PDOException $e) {
536
537        echo $e->getMessage();
538        return false;
539    }
540}
541function createSecurityToken($typeNum) {
542    global $db_name;
543    $storeController = new \BuyerKiosk\StoreController($typeNum);
544    $store = $storeController->getStore();
545
546    $RandString = new RandomStringGenerator();
547    $token = $RandString->generate(60);
548
549    $db = dbConnectByName($db_name);
550    try {
551        $query = $db->prepare("INSERT INTO  `tokens` (`id` ,`token` ,`typeNum` ,`created` ,`lastSeen`)VALUES (NULL ,  ?,  ?,CURRENT_TIMESTAMP , CURRENT_TIMESTAMP);");
552        $query->execute(array($token,$store->getTypeNum()));
553        return $token;
554    } catch (PDOException $e) {
555        echo $e->getMessage();
556    }
557    return 0;
558}
559function destroySecurityToken($store, $token) {
560    global $db_name;
561    $db = dbConnectByName($db_name);
562
563    $deleteQuery = $db->prepare("DELETE FROM tokens WHERE token = ? AND typeNum = ?");
564    $deleteQuery->execute(array($token, $store));
565
566    session_destroy();
567
568}
569function getAllStoresData($dev = 0, $active = 1) {
570    global $db_name;
571    $stores = array();
572    $db = dbConnectByName($db_name);
573    if($active) {
574        if($dev) {
575            $storesQuery = $db->query("SELECT * FROM stores");
576        } else {
577            $storesQuery = $db->query("SELECT * FROM stores WHERE dev = 0 AND active = 1");
578        }
579    } else {
580        if($dev) {
581            $storesQuery = $db->query("SELECT * FROM stores");
582        } else {
583            $storesQuery = $db->query("SELECT * FROM stores WHERE dev = 0");
584        }
585    }
586
587    while($row = $storesQuery->fetch(PDO::FETCH_ASSOC)) {
588        $newStore = new Store();
589        $newStore->createStoreFromRowArray($row);
590        $stores[] = $newStore;
591    }
592    return $stores;
593}
594
595function insertStoreStat(StoreStat $storeStat) {
596    $log = new KLogger($_ENV['LOG_DIR']."dev_log.txt", KLogger::DEBUG);
597    $date = $storeStat->getDate();
598    $store = getStoreFromID($storeStat->typeNum);
599    $db = dbConnectByName($store->getDbName());
600
601    $dateRange = getDateRange($date->format("Y-m-d H:i:s"));
602    $log->LogDebug($date->format("Y-m-d H:i:s"));
603
604    $query = "SELECT id FROM statsStoreDaily WHERE date BETWEEN '".$dateRange['dateStart']->format("Y-m-d H:i:s")."' AND '".$dateRange['dateEnd']->format("Y-m-d H:i:s")."'";
605    $dateQuery = $db->query($query);
606    $log->LogDebug($query);
607    if($dateQuery->rowCount() > 0) {
608        try {
609            $updateQuery = $db->prepare("UPDATE statsStoreDaily SET date=?,avgDelay=?,avgTotal=?,sortDelay=?,sortTime=?,avgPerContainer=?,numBuys=?,numContainers=?,numBuyers=?,firstBuy=?,lastBuy=?,buysStranded=?,uniqueCustomers=? WHERE date BETWEEN '".$dateRange['dateStart']->format("Y-m-d H:i:s")."' AND '".$dateRange['dateEnd']->format("Y-m-d H:i:s")."'");
610            $params = array($date->format("Y-m-d H:i:s"),$storeStat->avgDelay,$storeStat->avgTotal,$storeStat->sortDelay,$storeStat->sortTime,$storeStat->avgPerContainer,$storeStat->numBuys,$storeStat->numContainers,$storeStat->numBuyers,$storeStat->firstBuy->format("Y-m-d H:i:s"),$storeStat->lastBuy->format("Y-m-d H:i:s"),$storeStat->buysStranded,$storeStat->uniqueCustomers);
611            $log->LogDebug($params);
612            if($updateQuery) {
613                $result = $updateQuery->execute($params);
614            } else {
615                $log->LogError($updateQuery->errorInfo());
616            }
617
618        } catch (PDOException $e) {
619            $log->LogError($e);
620            return false;
621        }
622        if(isset($result) && $result) {
623            return true;
624        } else {
625            $log->LogDebug("Did not add. Update");
626            return false;
627        }
628
629    } else {
630        try {
631            $insertQuery = $db->prepare("INSERT INTO statsStoreDaily (`date`, `avgDelay`, `avgTotal`, `sortDelay`, `sortTime`, `avgPerContainer`, `numBuys`, `numContainers`, `numBuyers`, `firstBuy`, `lastBuy`, `buysStranded`, `uniqueCustomers`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)");
632            $params = array($date->format("Y-m-d H:i:s"),$storeStat->avgDelay,$storeStat->avgTotal,$storeStat->sortDelay,$storeStat->sortTime,$storeStat->avgPerContainer,$storeStat->numBuys,$storeStat->numContainers,$storeStat->numBuyers,$storeStat->firstBuy->format("Y-m-d H:i:s"),$storeStat->lastBuy->format("Y-m-d H:i:s"),$storeStat->buysStranded,$storeStat->uniqueCustomers);
633            /*foreach ($params as $param => $value) {
634                $log->LogDebug($value);
635            }*/
636            if($insertQuery) {
637                $insertQuery->execute($params);
638            } else {
639                $log->LogDebug($insertQuery->errorInfo());
640            }
641        } catch (PDOException $e) {
642            $log->LogError($e);
643            return false;
644        }
645        if($insertQuery->rowCount() > 0) {
646            return true;
647        } else {
648            $log->LogDebug("Did not add. Insert");
649            return false;
650        }
651
652    }
653}
654function insertEmployeeDaily(EmployeeDaily $employeeDaily) {
655    $log = KLoggerFactory::getLogger($_ENV['LOG_DIR']."dev_log.txt", KLogger::DEBUG);
656    $date = $employeeDaily->getDate();
657    $store = getStoreFromID($employeeDaily->getTypeNum());
658    $db = dbConnectByName($store->getDbName());
659
660    $dateRange = getDateRange($date->format("Y-m-d H:i:s"));
661
662    $dateQuery = $db->query("SELECT id FROM statsEmployeeDaily WHERE employeeID = ".$employeeDaily->getEmployeeID()." AND date BETWEEN '".$dateRange['dateStart']->format("Y-m-d H:i:s")."' AND '".$dateRange['dateEnd']->format("Y-m-d H:i:s")."'");
663    if($row = $dateQuery->fetch(PDO::FETCH_ASSOC)) {
664        $firstSort = null;$lastSort = null;
665        if($employeeDaily->getFirstSort() !== null && $employeeDaily->getLastSort() !== null && 
666           $employeeDaily->getFirstSort() instanceof DateTime && $employeeDaily->getLastSort() instanceof DateTime) {
667            $firstSort = $employeeDaily->getFirstSort()->format("Y-m-d H:i:s");
668            $lastSort = $employeeDaily->getLastSort()->format("Y-m-d H:i:s");
669        }
670        $firstBuy = null;$lastBuy=NULL;
671        if($employeeDaily->getFirstBuy() !== null && $employeeDaily->getLastBuy() !== null && 
672           $employeeDaily->getFirstBuy() instanceof DateTime && $employeeDaily->getLastBuy() instanceof DateTime) {
673            $firstBuy = $employeeDaily->getFirstBuy()->format("Y-m-d H:i:s");
674            $lastBuy = $employeeDaily->getLastBuy()->format("Y-m-d H:i:s");
675        }
676        //Update Record
677        try {
678            $updateQuery = $db->prepare("UPDATE statsEmployeeDaily SET date=?,avgTotal=?,avgPerContainer=?,numBuys=?,numContainers=?,firstBuy=?,lastBuy=?,employeeID=?,sortTime=?,sortDelay=?,firstSort=?,lastSort=?,numSorts=?,sortContainers=?,sortTimePerContainer=? WHERE id = ?");
679            if($updateQuery) {
680                $params = array($date->format("Y-m-d H:i:s"),$employeeDaily->getAvgTotal(),$employeeDaily->getAvgPerContainer(),$employeeDaily->getNumBuys(),$employeeDaily->getNumContainers(),$firstBuy,$lastBuy,$employeeDaily->getEmployeeID(), $employeeDaily->getSortTime(),$employeeDaily->getSortDelay(),$firstSort,$lastSort,$employeeDaily->getNumSorts(), $employeeDaily->getSortContainers(), $employeeDaily->getSortTimePerContainer(), $row['id']);
681                foreach ($params as $param) {
682                    $log->LogDebug($param);
683                }
684                $updateQuery->execute($params);
685                // Check for out of range error (deleted employee)
686                $errorInfo = $updateQuery->errorInfo();
687                if($errorInfo[0] !== '00000' && $errorInfo[1] == 1264) {
688                    //$log->LogDebug("Skipping stats for deleted employeeID: " . $employeeDaily->getEmployeeID());
689                    return false;
690                }
691            } else {
692                $log->LogError($updateQuery->errorInfo());
693            }
694        } catch(PDOException $e) {
695            // Check if it's an out of range error for employeeID (likely deleted employee)
696            if($e->getCode() == '22003' || strpos($e->getMessage(), 'Out of range') !== false) {
697                //$log->LogDebug("Skipping stats for deleted employeeID: " . $employeeDaily->getEmployeeID());
698                return false;
699            }
700            return false;
701        }
702        if($updateQuery->rowCount() > 0) {
703            return true;
704        } else {
705            $log->LogDebug("Could not Update Record - ".$row['id']);
706            return false;
707        }
708    } else {
709        try {
710            $firstSort = null;$lastSort = null;
711            if($employeeDaily->getFirstSort() !== null && $employeeDaily->getLastSort() !== null &&
712               $employeeDaily->getFirstSort() instanceof DateTime && $employeeDaily->getLastSort() instanceof DateTime) {
713                $firstSort = $employeeDaily->getFirstSort()->format("Y-m-d H:i:s");
714                $lastSort = $employeeDaily->getLastSort()->format("Y-m-d H:i:s");
715            }
716            $firstBuy = null;$lastBuy=NULL;
717            if($employeeDaily->getFirstBuy() !== null && $employeeDaily->getLastBuy() !== null &&
718               $employeeDaily->getFirstBuy() instanceof DateTime && $employeeDaily->getLastBuy() instanceof DateTime) {
719                $firstBuy = $employeeDaily->getFirstBuy()->format("Y-m-d H:i:s");
720                $lastBuy = $employeeDaily->getLastBuy()->format("Y-m-d H:i:s");
721            }
722            $insertQuery = $db->prepare("INSERT INTO statsEmployeeDaily (`date`, `avgTotal`, `avgPerContainer`, `numBuys`, `numContainers`, `firstBuy`, `lastBuy`,`employeeID`,`sortTime`,`sortDelay`,`firstSort`,`lastSort`,`numSorts`,`sortContainers`,`sortTimePerContainer`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
723            $params = array($date->format("Y-m-d H:i:s"),$employeeDaily->getAvgTotal(),$employeeDaily->getAvgPerContainer(),$employeeDaily->getNumBuys(),$employeeDaily->getNumContainers(),$firstBuy,$lastBuy, $employeeDaily->getEmployeeID(), $employeeDaily->getSortTime(),$employeeDaily->getSortDelay(),$firstSort,$lastSort,$employeeDaily->getNumSorts(), $employeeDaily->getSortContainers(), $employeeDaily->getSortTimePerContainer());
724            if($insertQuery) {
725                $insertQuery->execute($params);
726                // Check for out of range error (deleted employee)
727                $errorInfo = $insertQuery->errorInfo();
728                if($errorInfo[0] !== '00000' && $errorInfo[1] == 1264) {
729                    $log->LogDebug("Skipping stats for deleted employeeID: " . $employeeDaily->getEmployeeID());
730                    return false;
731                }
732            } else {
733                $log->LogDebug($insertQuery->errorInfo());
734            }
735        } catch(PDOException $e) {
736            // Check if it's an out of range error for employeeID (likely deleted employee)
737            if($e->getCode() == '22003' || strpos($e->getMessage(), 'Out of range') !== false) {
738                $log->LogDebug("Skipping stats for deleted employeeID: " . $employeeDaily->getEmployeeID());
739                return false;
740            }
741            $log->LogDebug($e);
742            return false;
743        }
744        return $insertQuery->rowCount() > 0;
745    }
746}
747function getSecondsFromADifference($diff) {
748    return  ((int)$diff->format('%h') * 60 * 60) + ((int)$diff->format('%i') * 60) + ((int)$diff->format('%s'));
749}
750function getStoresTxtCount(DateTime $end, DateTime $start) {
751    $log = new KLogger($_ENV['LOG_DIR']."dev_log.txt", KLogger::DEBUG);
752    $start = getDateRange($start->format("Y-m-d H:i:s"));
753    $end = getDateRange($end->format("Y-m-d H:i:s"));
754
755    global $db_name;
756    $textsArray = array();
757
758    $stores = getAllStoresData();
759
760    foreach ($stores as $store) {
761        $db = dbConnectByName($store->getDbName());
762        $log->LogDebug($start['dateStart']->format("Y-m-d H:i:s")." - ".$end['dateEnd']->format("Y-m-d H:i:s"));
763        $txtsQ = $db->query("SELECT count(id) as textCount, SUM(surveyText) as surveyTexts FROM (SELECT CEIL(ROUND((UNIX_TIMESTAMP(timestamp)/customerID)*100000)/100000) as combined, id, timestamp, customerID, IF(type = 5,1,0) as surveyText FROM texts WHERE timestamp BETWEEN '".$start['dateStart']->format("Y-m-d H:i:s")."' AND '".$end['dateEnd']->format("Y-m-d H:i:s")."' GROUP BY combined ORDER BY id DESC) as B");
764        $totalTexts = $txtsQ->fetch(PDO::FETCH_ASSOC);
765        $microBlinkQuery = $db->query("SELECT count(*) as numLicenses FROM tablets WHERE microBlinkAPI IS NOT NULL");
766        $totalLicenses = $microBlinkQuery->fetch(PDO::FETCH_ASSOC);
767        $tempArray = array(
768            'storeCity' => $store->getCity(),
769            'storeNum' => $store->getTypeNum(),
770            'totalTexts' => $totalTexts['textCount'],
771            'surveyText' => $totalTexts['surveyTexts'],
772            'microBlinkLicenses' => $microBlinkQuery['numLicenses']
773        );
774        $textsArray[] = $tempArray;
775    }
776    return json_encode($textsArray);
777}
778function getStoreInfo(Store $store) {
779    $tokenEnabled = 0;
780    $token = $store->getWiwToken();
781    if ($token !== null && strlen($token) > 0) {
782        $tokenEnabled = 1;
783    }
784    $output = array(
785        'typeNum' => $store->getTypeNum(),
786        'type' => getStoreType($store),
787        'city' => $store->getCity(),
788        'logo' => getStoreLogo($store, true),
789        'timeZone' => $store->getTimeZone(),
790        'zipcode' => $store->getZipCode(),
791        'dir' => getStoreDirectory($store),
792        'state' => $store->getState(),
793        'storeGroup' => null,
794        'enableWaitTime' => $store->getEnableWaitTime(),
795        'api' => $store->getApiKey(),
796        'storeNum' => $store->getStoreNum(),
797        'postBuySMS' => $store->getPostBuySMS(),
798        'active' => $store->getActive(),
799        'wiwTokenEnabled' => $tokenEnabled,
800        'wiwEnabled' => $store->getWiwEnable(),
801        'wiwLocationID' => $store->getWiwLocationID(),
802        'yellowTime' => $store->getYellowTime(),
803        'redTime' => $store->getRedTime(),
804        'dsWeatherBar' => $store->dsWeatherBar
805    );
806    return $output;
807}
808function getStoreLogo(Store $store, $alt = false) {
809    $storeType = $store->getStoreType();
810    $output = "";
811    switch($storeType) {
812        case 0:
813            $output = "cm";
814            break;
815        case 1:
816            if($alt) {
817                $output = "pc-alt2";
818            } else {
819                $output = "pc";
820            }
821            break;
822        case 2:
823            $output = "ouac";
824            break;
825        case 3:
826            $output = "se";
827            break;
828        case 4:
829            $output = "pias";
830            break;
831        case 5:
832            $output = "hom";
833            break;
834        default:
835            $output = "bk";
836            break;
837    }
838    return $output;
839}
840function checkAccessToAPIs($app, $typeNum) {
841
842    $jwt = base64_decode($_COOKIE['pineapple']);
843    try {
844        $token = \JWT::decode($jwt, base64_decode(SECRET_KEY));
845        if($token->iat + 86400 < time()) {
846            return false;
847        }
848        if($token->iss !== serverName) {
849            return false;
850        }
851    } catch (Exception $e) {
852        echo $e->getMessage();
853        return false;
854    }
855    if(!$app->user->checkStoreGroup($typeNum)) {
856        return false;
857    }
858    return true;
859}
860
861function getNumBuyers(Store $store)
862{
863    //$log = new KLogger("../logs/dev_log2.txt", KLogger::DEBUG);
864    $dbName = $store->getDbName();
865    if (empty($dbName)) {
866        error_log("getNumBuyers: Store database name is empty for store: " . $store->getStoreNum());
867        return 0;
868    }
869    $db = dbConnectByName($dbName);
870    if (!$db) {
871        error_log("getNumBuyers: Failed to connect to database: " . $dbName);
872        return 0;
873    }
874    $query = $db->query("SELECT buyersTimeStamp, numBuyers FROM realTimeData WHERE id = 1");
875    if($query) {
876        $result = $query->fetch(PDO::FETCH_ASSOC);
877        $dateRange = getCurrentDateRange();
878        $numBuyers = 0;
879        if($result && strtotime($result['buyersTimeStamp']) < strtotime("-2 minutes")) {
880            $b = $db->query("SELECT DISTINCT buyerID FROM buyQueue WHERE isProcessed = 0 AND buyerID IS NOT NULL AND timeEntered BETWEEN '".$dateRange['dateStart']->format("Y-m-d H:i:s")."' AND '" . $dateRange['dateEnd']->format("Y-m-d H:i:s")."'");
881            while ($buyers = $b->fetch(PDO::FETCH_ASSOC)) {
882                $numBuyers++;
883            }
884            if ($store->getAddSortersToEstimate()) { //usually a 0
885                $s = $db->query("SELECT DISTINCT sorterID FROM buyQueue WHERE isProcessed = 0 AND sorterID >0 AND sortCompleted = '0000-00-00 00:00:00' AND timeEntered BETWEEN '" . $dateRange['dateStart']->format("Y-m-d H:i:s") . "' AND '" . $dateRange['dateEnd']->format("Y-m-d H:i:s") . "'");
886                while ($sorters = $s->fetch(PDO::FETCH_ASSOC)) {
887                    $numBuyers++;
888                }
889            }
890            $stmt = $db->prepare("UPDATE realTimeData SET buyersTimeStamp = CURRENT_TIMESTAMP, numBuyers = :numBuyers WHERE id = 1");
891            $stmt->bindValue(":numBuyers", $numBuyers);
892            $stmt->execute();
893            return $numBuyers;
894
895        } else {
896            return $result ? (int)$result['numBuyers'] : 0;
897        }
898    } else {
899        return 0;
900    }
901
902}
903function base64UrlEncode($_input)
904{
905    return str_replace(array('=','+','/'),array('_','-',','),base64_encode($_input));
906}
907
908function getStoreSlides(Store $store)
909{
910
911// Grab Store Specific Slides
912    $db = dbConnectByName($store->getDbName());
913    $selectQuery = $db->query("SELECT * FROM dsSlides WHERE enabled = 1 ORDER BY dateAdded DESC");
914    $slidesArray = array();
915    while ($slides = $selectQuery->fetch(PDO::FETCH_ASSOC)) {
916        $dateAdded = new DateTime($slides['dateAdded']);
917        $dateAdded->setTimezone(new DateTimeZone($store->getTimeZone()));
918        $slide = array(
919            'slideID' => $slides['id'],
920            'type' => $slides['type'],
921            'slideName' => $slides['slideName'],
922            'fileName' => $slides['fileName'],
923            'dateAdded' => $dateAdded->format('F j, Y'),
924            'plays' => $slides['plays']
925
926        );
927        $slidesArray[] = $slide;
928    }
929    return $slidesArray;
930}
931
932function getCorpSlides(Store $store)
933{
934    global $db_name;
935//Grab Corporate Slides
936    $storeType = $store->getStoreType();
937    $db = dbConnectByName($db_name);
938    $selectQuery = $db->query("SELECT * FROM corpSlides WHERE enabled = 1 AND storeType = " . $storeType . " ORDER BY dateAdded DESC");
939    $corpSlidesArray = array();
940    while ($slides = $selectQuery->fetch(PDO::FETCH_ASSOC)) {
941        $dateAdded = new DateTime($slides['dateAdded']);
942        $dateAdded->setTimezone(new DateTimeZone($store->getTimeZone()));
943        $slide = array(
944            'slideID' => $slides['id'],
945            'type' => $slides['type'],
946            'slideName' => $slides['slideName'],
947            'fileName' => $slides['fileName'],
948            'dateAdded' => $dateAdded->format('F j, Y'),
949            'plays' => $slides['plays']
950        );
951        $corpSlidesArray[] = $slide;
952    }
953    return $corpSlidesArray;
954}
955function getHBSlides(Store $store) {
956    global $db_name;
957//Grab HipBone Slides
958    $storeType = $store->getStoreType();
959    $db = dbConnectByName($db_name);
960    $selectQuery = $db->query("SELECT * FROM hbSlides WHERE enabled = 1 AND storeType = " . $storeType . " ORDER BY dateAdded DESC");
961    $hbSlidesArray = array();
962    while ($slides = $selectQuery->fetch(PDO::FETCH_ASSOC)) {
963        $dateAdded = new DateTime($slides['dateAdded']);
964        $dateAdded->setTimezone(new DateTimeZone($store->getTimeZone()));
965        $slide = array(
966            'slideID' => $slides['id'],
967            'type' => $slides['type'],
968            'slideName' => $slides['slideName'],
969            'fileName' => $slides['fileName'],
970            'dateAdded' => $dateAdded->format('F j, Y'),
971        );
972        $hbSlidesArray[] = $slide;
973    }
974    return $hbSlidesArray;
975}
976function getSlideItemArray(Store $store)
977{
978    $db = dbConnectByName($store->getDbName());
979    $slideItemArray = array();
980    $loopQuery = $db->query("SELECT
981                                    dsLoop.id,
982                                    dsLoop.position,
983                                    dsLoop.duration,
984                                    dsLoop.slideUploader,
985                                    dsLoop.slideID,
986                                    IF(slideUploader = 0, dsSlides.fileName, IF(slideUploader = 1, `kiosk_buykiosk`.corpSlides.fileName, `kiosk_buykiosk`.hbSlides.fileName)) as fileName,
987                                    IF(slideUploader = 0, dsSlides.slideName, IF(slideUploader = 1, `kiosk_buykiosk`.corpSlides.slideName, `kiosk_buykiosk`.hbSlides.slideName)) as slideName,
988                                    IF(slideUploader = 0, dsSlides.type, IF(slideUploader = 1, `kiosk_buykiosk`.corpSlides.type, `kiosk_buykiosk`.hbSlides.type)) as type,
989                                    IF(slideUploader = 0, dsSlides.dateAdded, IF(slideUploader = 1, `kiosk_buykiosk`.corpSlides.dateAdded, `kiosk_buykiosk`.hbSlides.dateAdded)) as dateAdded
990                                FROM
991                                    dsLoop
992                                    LEFT JOIN `kiosk_buykiosk`.hbSlides ON ( `kiosk_buykiosk`.hbSlides.id = dsLoop.slideID AND dsLoop.slideUploader = 2 )
993                                    LEFT JOIN `kiosk_buykiosk`.corpSlides ON (  `kiosk_buykiosk`.corpSlides.id = dsLoop.slideID AND dsLoop.slideUploader = 1 )
994                                    LEFT JOIN dsSlides ON (  dsSlides.id = dsLoop.slideID AND dsLoop.slideUploader = 0 ) ORDER BY position ASC");
995    while ($loopItem = $loopQuery->fetch(PDO::FETCH_ASSOC)) {
996        $tempSlide = array(
997            'id' => $loopItem['id'],
998            'slideID' => $loopItem['slideID'],
999            'position' => $loopItem['position'],
1000            'duration' => $loopItem['duration'],
1001            'slideUploader' => $loopItem['slideUploader'],
1002            'slideName' => $loopItem['slideName'],
1003            'fileName' => $loopItem['fileName'],
1004            'thumbName' => $loopItem['fileName'],
1005            'dateAdded' => $loopItem['dateAdded'],
1006            'type' => $loopItem['type'],
1007        );
1008        $slideItemArray[] = $tempSlide;
1009    }
1010    return $slideItemArray;
1011}
1012function getTopCustomers(Store $store, $dateStart = null, $dateEnd = null) {
1013    $log = new KLogger($_ENV['LOG_DIR']."dev_log.txt", KLogger::DEBUG);
1014
1015
1016    $db = dbConnectByName($store->getDbName());
1017    if($dateStart == null || $dateEnd == null) {
1018        $query = $db->query("
1019    SELECT buyQueue.customerID, customers.firstName,customers.phone, customers.lastName, COUNT( * ) AS count
1020        FROM buyQueue
1021        INNER JOIN customers ON buyQueue.customerID = customers.customerID
1022        GROUP BY buyQueue.customerID
1023        ORDER BY count DESC
1024        LIMIT 0 , 50
1025    ");
1026    } else {
1027        $dateStart = getDateRange($dateStart);
1028        $dateEnd = getDateRange($dateEnd);
1029        $query = $db->query("
1030    SELECT buyQueue.customerID, customers.firstName,customers.phone, customers.lastName, COUNT( * ) AS count
1031        FROM buyQueue timeEntered BETWEEN '".$dateStart['dateStart']->format("Y-m-d H:i:s")."' AND '".$dateEnd['dateEnd']->format("Y-m-d H:i:s")."'
1032        INNER JOIN customers ON buyQueue.customerID = customers.customerID
1033        GROUP BY buyQueue.customerID
1034        ORDER BY count DESC
1035        LIMIT 0 , 50
1036    ");
1037    }
1038    $customerArray = array();
1039    while($row=$query->fetch(PDO::FETCH_ASSOC)) {
1040        $tempCustomer = [];
1041        $tempCustomer['customerID'] = $row['customerID'];
1042        $tempCustomer['phone'] = $row['phone'];
1043        $tempCustomer['name'] = $row['firstName']." ".$row['lastName'];
1044        $tempCustomer['numBuys'] = $row['count'];
1045        $tempDate = getCurrentDateRange();
1046        $tempDate['dateStart']->sub(new DateInterval('P30D'));
1047        $log->LogDebug("Last 30: ".$tempDate['dateStart']->format("Y-m-d H:i:s")." to ".$tempDate['dateEnd']->format("Y-m-d H:i:s"));
1048        $query2 = $db->query("SELECT COUNT(*) FROM buyQueue WHERE customerID = ".$row['customerID']." AND timeEntered BETWEEN '".$tempDate['dateStart']->format("Y-m-d H:i:s")."' AND '".$tempDate['dateEnd']->format("Y-m-d H:i:s")."'");
1049        $row2 = $query2->fetch(PDO::FETCH_ASSOC);
1050        $tempCustomer['last30'] = $row2['COUNT(*)'];
1051        $customerArray[] = $tempCustomer;
1052    }
1053    return $customerArray;
1054}
1055function getAllCustomers(Store $store) {
1056    $db = dbConnectByName($store->getDbName());
1057
1058    $query = "SELECT 
1059                customers.customerID,
1060                MAX(COALESCE(buyQueue.timeEntered, deletedBuys.timeEntered)) as latestVisit,
1061                customers.firstName,
1062                customers.phone,
1063                customers.lastName,
1064                customers.driversLicense,
1065                COUNT(CASE WHEN buyQueue.buyID IS NOT NULL OR deletedBuys.buyID IS NOT NULL THEN 1 END) AS count,
1066                ROUND(AVG(customerRatings.rating), 1) as avg_rating
1067            FROM customers
1068            LEFT JOIN buyQueue ON customers.customerID = buyQueue.customerID
1069            LEFT JOIN deletedBuys ON customers.customerID = deletedBuys.customerID
1070            LEFT JOIN customerRatings ON customers.customerID = customerRatings.customerID
1071            GROUP BY 
1072                customers.customerID,
1073                customers.firstName,
1074                customers.phone,
1075                customers.lastName,
1076                customers.driversLicense
1077            ORDER BY customers.customerID DESC";
1078
1079    // Debug: Check if query string is empty before execution
1080    if (empty(trim($query))) {
1081        error_log("EMPTY QUERY STRING DETECTED in getAllCustomers() before execution");
1082        error_log("Stack trace: " . print_r(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), true));
1083        return [];
1084    }
1085    $queryResult = $db->query($query);
1086    $customerArray = [];
1087
1088    while($row = $queryResult->fetch(PDO::FETCH_ASSOC)) {
1089        $rating = $row['avg_rating'] ?: "NR";
1090        $tempArray = $row;
1091
1092        if ($row['latestVisit'] !== null) {
1093            $lastestVisit = DateTime::createFromFormat('Y-m-d H:i:s', $row['latestVisit'], new DateTimeZone($store->getTimeZone()));
1094            $latestVisitArray = [
1095                'display' => $lastestVisit->format("M jS, Y  g:i a"),
1096                'timeStamp' => $lastestVisit->getTimestamp()
1097            ];
1098        } else {
1099            $latestVisitArray = [
1100                'display' => 'No Buys Recorded',
1101                'timeStamp' => 0
1102            ];
1103        }
1104
1105        $tempArray['driversLicense'] = mb_substr($row['driversLicense'],0,2)."******".mb_substr($row['driversLicense'], -4,4);
1106        $tempArray['customerID'] = "<a href='/admin/".$store->getTypeNum()."/customers/id/".$row['customerID']."'>".$row['customerID']."</a>";
1107        $tempArray['fullName'] = "<a href='/admin/".$store->getTypeNum()."/customers/id/".$row['customerID']."'>".$row['firstName']." ".$row['lastName']."</a>";
1108        $tempArray['phone'] = formatPhoneNumber($row['phone']);
1109        $tempArray['rating'] = $rating;
1110        $tempArray['latestVisit'] = $latestVisitArray;
1111        $customerArray[] = $tempArray;
1112    }
1113
1114    return $customerArray;
1115}
1116function getCustomerAlert(Store $store, $cust_id = null, $phone = null) {
1117    $alertArray = [];
1118    $db = dbConnectByName($store->getDbName());
1119    if($cust_id !== null) {
1120        $stmt = $db->prepare("SELECT customerAlerts.id as id, customerAlerts.customerID as customerID, customerAlerts.type as type, comment, class, customerAlerts.dateAdded as dateAdded, customers.firstName as firstName, customers.lastName as lastName, customers.phone, customers.driversLicense as driversLicense FROM customerAlerts JOIN customers ON customerAlerts.customerID = customers.customerID WHERE customers.customerID = :customerID ORDER BY customerAlerts.dateAdded DESC");
1121        $stmt->bindValue(":customerID", $cust_id);
1122    } else {
1123        $stmt = $db->prepare("SELECT customerAlerts.id as id, customerAlerts.customerID as customerID, customerAlerts.type as type, comment, class, customerAlerts.dateAdded as dateAdded, customers.firstName as firstName, customers.lastName as lastName, customers.phone, customers.driversLicense as driversLicense FROM customerAlerts JOIN customers ON customerAlerts.customerID = customers.customerID WHERE customers.phone = :phone ORDER BY customerAlerts.dateAdded DESC");
1124        $stmt->bindValue(":phone", $phone);
1125    }
1126    $stmt->execute();
1127    while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
1128        $alertArray[] = $row;
1129    }
1130    return $alertArray;
1131}
1132function getCustomerData(Store $store, $cust_id = null, $phone = null) {
1133    $db = dbConnectByName($store->getDbName());
1134    if($cust_id !== null) {
1135        $query = $db->prepare("SELECT * FROM customers WHERE customerID = :cust_id");
1136        $query->bindValue(":cust_id", $cust_id);
1137    } else {
1138        $query = $db->prepare("SELECT * FROM customers WHERE phone = :phone");
1139        $query->bindValue(":phone", $phone);
1140    }
1141    $customerData = array();
1142    $employees = getEmployeeNameArray($store);
1143
1144    if($query->execute()) {
1145        $customerInfo = [];
1146        $customer = $query->fetch(PDO::FETCH_ASSOC);
1147        if(!isset($customer['customerID'])) {
1148            return null;
1149        }
1150        $customerInfo['customerID'] = $customer['customerID'];
1151        $customerInfo['name'] = $customer['firstName']." ".$customer['lastName'];
1152        $customerInfo['phone'] = formatPhoneNumber($customer['phone']);
1153        $customerInfo['email'] = $customer['email'];
1154        $customerInfo['onSMS'] = $customer['onSMS'];
1155        $customerInfo['onEmail'] = $customer['onEmail'];
1156        $customerInfo['driversLicense'] = $customer['driversLicense'];
1157        $customerInfo['address'] = $customer['address'];
1158        $customerInfo['city'] = $customer['city'];
1159        $customerInfo['state'] = $customer['state'];
1160        $customerInfo['zipcode'] = $customer['zipcode'];
1161        $dateAdded = new DateTime($customer['dateAdded'], new DateTimeZone('UTC'));
1162        $dateAdded->setTimezone(new DateTimeZone($store->getTimeZone()));
1163        $customerInfo['dateAdded'] = $dateAdded->format("Y, M jS  g:i a");
1164        $tempDate = getCurrentDateRange();
1165        $tempDate['dateStart']->sub(new DateInterval('P30D'));
1166        $query2 = $db->query("SELECT COUNT(*) FROM (
1167                                        SELECT timeEntered FROM buyQueue WHERE customerID = ".$customer['customerID'].
1168                                        UNION ALL 
1169                                        SELECT timeEntered FROM deletedBuys WHERE customerID = ".$customer['customerID']."
1170                                    ) combined WHERE timeEntered BETWEEN '".$tempDate['dateStart']->format("Y-m-d H:i:s")."' AND '".$tempDate['dateEnd']->format("Y-m-d H:i:s")."'");
1171        $row2 = $query2->fetch(PDO::FETCH_ASSOC);
1172        $customerInfo['last30'] = $row2['COUNT(*)'];
1173
1174        $buyData = array();
1175        $first = true;
1176        $query3 = $db->query("(
1177                    SELECT 
1178                        buyID,
1179                        customerID,
1180                        timeEntered,
1181                        dailyNum,
1182                        numContainers,
1183                        twilioSent,
1184                        buyerID,
1185                        0 as deleted 
1186                    FROM buyQueue 
1187                    WHERE customerID = ".$customer['customerID']."
1188                    UNION ALL
1189                    SELECT 
1190                        buyID,
1191                        customerID,
1192                        timeEntered,
1193                        dailyNum,
1194                        numContainers,
1195                        twilioSent,
1196                        buyerID,
1197                        1 as deleted 
1198                    FROM deletedBuys 
1199                    WHERE customerID = ".$customer['customerID']."
1200                ) ORDER BY timeEntered DESC");
1201        while($row = $query3->fetch(PDO::FETCH_ASSOC)) {
1202            if($first) {
1203                $lastSeen = new DateTime($row['timeEntered'], new DateTimeZone('UTC'));
1204                $lastSeen->setTimezone(new DateTimeZone($store->getTimeZone()));
1205                $customerInfo['lastSeen'] = $lastSeen->format("Y, M jS  g:i a");
1206                $first = false;
1207            }
1208            $empName = isset($employees[$row['buyerID']]) ? $employees[$row['buyerID']] : 'Unknown';
1209            $tempBuy = array();
1210            $tempBuy['buyID'] = $row['buyID'];
1211            $time = new DateTime($row['timeEntered'], new DateTimeZone('UTC'));
1212            $time->setTimezone(new DateTimeZone($store->getTimeZone()));
1213            $tempBuy['deleted'] = $row['deleted'];
1214            $tempBuy['date'] = $time->format("Y, M jS");
1215            $tempBuy['linkDate'] = $time->format("m_d_y");
1216            $tempBuy['time'] = $time->format("g:i A");
1217            $tempBuy['dailyNum'] = $row['dailyNum'];
1218            $tempBuy['containers'] = $row['numContainers'];
1219            $tempBuy['textSent'] = $row['twilioSent'];
1220            $tempBuy['buyerName'] = $empName;
1221            $tempBuy['buyerID'] = $row['buyerID'];
1222
1223            $buyData[] = $tempBuy;
1224        }
1225
1226    }
1227    $customerData['customer'] = $customerInfo;
1228    $customerData['buyData'] = $buyData;
1229    return $customerData;
1230}
1231function formatPhoneNumber($phoneNumber) {
1232    $phoneNumber = preg_replace('/[^0-9]/','',$phoneNumber);
1233
1234    if(strlen($phoneNumber) > 10) {
1235        $countryCode = substr($phoneNumber, 0, strlen($phoneNumber)-10);
1236        $areaCode = substr($phoneNumber, -10, 3);
1237        $nextThree = substr($phoneNumber, -7, 3);
1238        $lastFour = substr($phoneNumber, -4, 4);
1239
1240        $phoneNumber = '+'.$countryCode.' ('.$areaCode.') '.$nextThree.'-'.$lastFour;
1241    }
1242    else if(strlen($phoneNumber) == 10) {
1243        $areaCode = substr($phoneNumber, 0, 3);
1244        $nextThree = substr($phoneNumber, 3, 3);
1245        $lastFour = substr($phoneNumber, 6, 4);
1246
1247        $phoneNumber = '('.$areaCode.') '.$nextThree.'-'.$lastFour;
1248    }
1249    else if(strlen($phoneNumber) == 7) {
1250        $nextThree = substr($phoneNumber, 0, 3);
1251        $lastFour = substr($phoneNumber, 3, 4);
1252
1253        $phoneNumber = $nextThree.'-'.$lastFour;
1254    }
1255
1256    return $phoneNumber;
1257}
1258function getBuyerKioskStats() {
1259
1260    $stores = getAllStoresData();
1261    $totalBuys = 0;$totalSMS=0;$totalEmails=0;$totalTexts=0;
1262    foreach ($stores as $store) {
1263        $db = dbConnectByName($store->getDbName());
1264        $buysQuery = $db->query("SELECT COUNT(*) as buysCount FROM buyQueue");
1265
1266        if($buysQuery) {
1267            $result = $buysQuery->fetch(PDO::FETCH_ASSOC);
1268            $totalBuys += (int)$result['buysCount'];
1269        }
1270        $smsQuery = $db->query("SELECT COUNT(*) as smsCount FROM customers WHERE onSMS = 1");
1271
1272        if($smsQuery) {
1273            $result = $smsQuery->fetch(PDO::FETCH_ASSOC);
1274            $totalSMS += (int)$result['smsCount'];
1275        }
1276        $textQuery = $db->query("SELECT COUNT(*) as textCount FROM buyQueue WHERE textMe = 1");
1277
1278        if($textQuery) {
1279            $result = $textQuery->fetch(PDO::FETCH_ASSOC);
1280            $totalTexts += (int)$result['textCount'];
1281        }
1282        $emailQuery = $db->query("SELECT COUNT(*) as emailCount FROM customers WHERE onEmail = 1");
1283
1284        if($emailQuery) {
1285            $result = $emailQuery->fetch(PDO::FETCH_ASSOC);
1286            $totalEmails += (int)$result['emailCount'];
1287        }
1288    }
1289
1290    return array(
1291        'totalBuys' => $totalBuys,
1292        'totalSMS' => $totalSMS,
1293        'totalEmails' => $totalEmails,
1294        'totalTexts' => $totalTexts,
1295    );
1296}
1297function getDashboardStats($typeNum) {
1298    $dS = new DashboardStats($typeNum);
1299    $dateStart = new DateTime('first day of this month');
1300    $dateStart = getDateRange($dateStart->format("Y-m-d H:i:s"));
1301    $dateEnd = getCurrentDateRange();
1302    $log = new KLogger($_ENV['LOG_DIR']."dev_log2.txt",KLogger::DEBUG);
1303    $log->LogDebug("Model: ".$dateStart['dateStart']->format("Y-m-d H:i:s")." - ".$dateEnd['dateEnd']->format("Y-m-d H:i:s"));
1304    $dS->setDates($dateStart['dateStart']->format("Y-m-d H:i:s"), $dateEnd['dateEnd']->format("Y-m-d H:i:s"));
1305    $dS->getTopStats();
1306    return $dS;
1307}
1308function getDashboardBusyTimes($typeNum)
1309{
1310    $dateEnd = getCurrentDateRange();
1311    $dateStart = getCurrentDateRange();
1312    $dateStart['dateStart']->sub(new DateInterval("P29D"));
1313    $dS = new DashboardStats($typeNum);
1314    $dS->setDates($dateStart['dateStart']->format("Y-m-d H:i:s"), $dateEnd['dateEnd']->format("Y-m-d H:i:s"));
1315    return $dS->getBusyTimesArray();
1316
1317}
1318function sendDemoEmail($html) {
1319    $mail = new PHPMailer;
1320    $log = new KLogger($_ENV['LOG_DIR']."dev_log.txt",KLogger::DEBUG);
1321    $mail->setFrom('admin@buyerkiosk.com', 'BuyerKiosk');
1322    $mail->addAddress('demo@buyerkiosk.com', 'Demo');
1323    $mail->isHTML(true);
1324    $mail->Subject = 'New Demo Request';
1325    $mail->Body    = $html;
1326    if($mail->send()) {
1327        return true;
1328    } else {
1329        $log->LogError($mail->ErrorInfo);
1330        return false;
1331    }
1332}
1333function sendDailyEmail($html, $email, Store $store, $fileLocation) {
1334    $mail = new PHPMailer;
1335    $mail->isSMTP();                                      // Set mailer to use SMTP
1336    $mail->Host = 'smtp.sendgrid.net';  // Specify main and backup SMTP servers
1337    $mail->SMTPAuth = true;                               // Enable SMTP authentication
1338    $mail->Username = 'apikey';                 // SMTP username
1339    $mail->Password = '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';                           // SMTP password
1340    $mail->SMTPSecure = 'ssl';                            // Enable TLS encryption, `ssl` also accepted
1341    $mail->Port = 465;
1342    $log = new KLogger($_ENV['LOG_DIR']."dev_log.txt",KLogger::DEBUG);
1343    $mail->setFrom('admin@buyerkiosk.com', 'BuyerKiosk');
1344    $mail->addAddress($email);
1345    $mail->isHTML(true);
1346    $mail->Subject = 'BuyerKiosk Daily Summary - '.$store->getStoreNum();
1347    $mail->Body    = $html;
1348    $mail->addAttachment($fileLocation,"s-file.txt");
1349    if($mail->send()) {
1350        echo "Sent";
1351        return true;
1352    } else {
1353        echo "Not Sent";
1354        $log->LogError($mail->ErrorInfo);
1355        return false;
1356    }
1357}
1358function sendRequestEmail($html, $email, $name) {
1359    $mail = new PHPMailer;
1360    $log = new KLogger($_ENV['LOG_DIR']."dev_log.txt",KLogger::DEBUG);
1361    $mail->setFrom('demo@buyerkiosk.com');
1362    $mail->addAddress($email);
1363    $mail->isHTML(true);
1364    $mail->Subject = 'Your BuyerKiosk Demo Request Has Been Received';
1365    $mail->Body    = $html;
1366    if($mail->send()) {
1367        return true;
1368    } else {
1369        $log->LogError($mail->ErrorInfo);
1370        return false;
1371    }
1372}
1373function getEmployeeInfo(Store $store, $employeeID) {
1374    $db = dbConnectByName($store->getDbName());
1375    $infoQuery = $db->query("SELECT * FROM employees WHERE employeeID = ".$employeeID);
1376    $employeeArray = [];
1377
1378    if($infoQuery) {
1379        $row = $infoQuery->fetch(PDO::FETCH_ASSOC);
1380        $buyq = $db->query("SELECT MIN(date) as firstBuy, MAX(date) as lastBuy FROM statsEmployeeDaily WHERE employeeID = ".$employeeID);
1381        $buy = $buyq->fetch(PDO::FETCH_ASSOC);
1382
1383        $firstBuy = new DateTime($buy['firstBuy'], new DateTimeZone('UTC'));
1384        $firstBuy->setTimezone(new DateTimeZone($store->getTimeZone()));
1385        $lastBuy = new DateTime($buy['lastBuy'], new DateTimeZone('UTC'));
1386        $lastBuy->setTimezone(new DateTimeZone($store->getTimeZone()));
1387        $employeeArray['name'] = $row['employeeFirstName']." ".$row['employeeLastName'];
1388        $employeeArray['firstBuy'] = $firstBuy->format("M jS Y");
1389        $employeeArray['lastBuy'] = $lastBuy->format("M jS Y");
1390        $employeeArray['id'] = $employeeID;
1391    }
1392    return $employeeArray;
1393}
1394function getEmployeeNameArray(Store $store) {
1395    $db = dbConnectByName($store->getDbName());
1396    $empQuery = $db->query("SELECT employeeID, employeeFirstName as firstName, employeeLastName as lastName FROM employees WHERE 1");
1397    $employees = [];
1398
1399    while($row = $empQuery->fetch(PDO::FETCH_ASSOC)) {
1400        $employees[$row['employeeID']] = $row['firstName']." ".$row['lastName'];
1401    }
1402    return $employees;
1403}
1404function expireSlides() {
1405    $stores = getAllStoresData();
1406
1407    foreach ($stores as $store) {
1408
1409        $db = dbConnectByName($store->getDbName());
1410        $query = $db->query("SELECT * FROM dsLoop WHERE expireDate <= CURDATE()");
1411        $row = $query->fetch(PDO::FETCH_ASSOC);
1412        $db->query("DELETE FROM dsLoop WHERE id = ".$row['id']);
1413    }
1414
1415}
1416function getStoreTypeName(Store $store) {
1417    global $db_name;
1418    $storeType = $store->getStoreType();
1419    $db = dbConnectByName($db_name);
1420    $query = $db->query("SELECT * FROM storetypes WHERE type = ".$storeType);
1421    $storeTypeName = $query->fetch(PDO::FETCH_ASSOC);
1422
1423    return $storeTypeName['name'];
1424}
1425/*
1426function checkBuyQueueTextAlerts(Store $store) {
1427    $n = $db->query("SELECT * FROM textAlerts WHERE buyQueueAlerts = 1");
1428    while($row = $n->fetch(PDO::FETCH_ASSOC)) {
1429        if(strtotime(result['textTimeStamp']))
1430    }
1431}*/
1432function sendMassTwilioTxt($message) {
1433    include("/home/ryanvv/userfrosting/lib/twilio/Services/Twilio.php");
1434    $log = new KLogger($_ENV['LOG_DIR']."twilio_log.txt", KLogger::DEBUG);
1435    $AccountSid = "ACa0d8f40468057e6bc520846fba454f4c";
1436    $AuthToken = "af23ab5c293dbff72fe9308a701ad92e";
1437
1438    global $contactDBName;
1439    $db = dbConnectByName($contactDBName);
1440    $query = $db->query("SELECT * FROM numbers WHERE 1");
1441    $client = new Services_Twilio($AccountSid, $AuthToken);
1442
1443    while($row = $query->fetch(PDO::FETCH_ASSOC)) {
1444        try {
1445            $message2 = $client->account->messages->create(array(
1446                "From" => '+19723474696',
1447                "To" => "+1".$row['number'],
1448                "Body" => $message,
1449            ));
1450        } catch (Services_Twilio_RestException $e) {
1451            $log->LogError($e->getMessage());
1452        }
1453    }
1454
1455
1456}
1457function getStoreProcessAverages(Store $store, $dateStart, $dateEnd) {
1458    $db = dbConnectByName($store->getDbName());
1459    $statsArray = array();
1460    $query = $db->prepare("SELECT (SELECT ROUND(AVG(UNIX_TIMESTAMP(timeCompleted) - UNIX_TIMESTAMP(timeStarted)),1) as processTime FROM buyQueue WHERE timeCompleted > timeStarted AND isProcessed > 0 AND timeEntered BETWEEN :dateStart AND :dateEnd AND (UNIX_TIMESTAMP(timeCompleted) - UNIX_TIMESTAMP(timeStarted) BETWEEN :minSeconds AND :maxSeconds)) as processTime, (SELECT ROUND(AVG((UNIX_TIMESTAMP(timeCompleted) - UNIX_TIMESTAMP(timeStarted))/IF(remainingContainers>0,remainingContainers,numContainers)),1) as ptc FROM buyQueue WHERE timeCompleted > timeStarted AND isProcessed > 0 AND timeEntered BETWEEN :dateStart AND :dateEnd AND (UNIX_TIMESTAMP(timeCompleted) - UNIX_TIMESTAMP(timeStarted) BETWEEN :minSeconds AND :maxSeconds)) as ptc, (SELECT COUNT(d) as totalDays FROM (SELECT DATE(timeEntered) as d FROM buyQueue WHERE timeEntered BETWEEN :dateStart AND :dateEnd AND isProcessed = 1 AND (UNIX_TIMESTAMP(timeCompleted) - UNIX_TIMESTAMP(timeStarted) BETWEEN :minSeconds AND :maxSeconds) GROUP BY d)as H) as totalDays, (SELECT SUM(totalContainers) as sumContainers FROM (SELECT SUM(IF(remainingContainers > 0, remainingContainers, numContainers)) as totalContainers,DATE(timeEntered) as d FROM buyQueue WHERE timeEntered BETWEEN :dateStart AND :dateEnd AND isProcessed = 1 AND (UNIX_TIMESTAMP(timeCompleted) - UNIX_TIMESTAMP(timeStarted) BETWEEN :minSeconds AND :maxSeconds) GROUP BY d)as F) as totalContainers, (SELECT SUM(totalBuys) as sumBuys FROM (SELECT COUNT(buyID) as totalBuys, DATE(timeEntered) as d FROM buyQueue WHERE timeEntered BETWEEN :dateStart AND :dateEnd AND isProcessed = 1 AND (UNIX_TIMESTAMP(timeCompleted) - UNIX_TIMESTAMP(timeStarted) BETWEEN :minSeconds AND :maxSeconds) GROUP BY d)as G) as totalBuys");
1461    $result = $query->execute(array(':dateStart' => $dateStart, ':dateEnd'=> $dateEnd, ":minSeconds"=>$store->getStatMinSeconds(),":maxSeconds"=>$store->getStatMaxSeconds()));
1462    if($result) {
1463        $row = $query->fetch(PDO::FETCH_ASSOC);
1464        $statsArray = array(
1465            'sortTime' => isset($row['sortTime']) ? $row['sortTime'] : null,
1466            'processTime' => $row['processTime'],
1467            'ptc' => $row['ptc'],
1468            'totalDays' => $row['totalDays'],
1469            'totalContainers'=>$row['totalContainers'],
1470            'totalBuys'=>$row['totalBuys']
1471        );
1472    }
1473    return $statsArray;
1474
1475}
1476function getStoreSortAverages(Store $store, $dateStart, $dateEnd) {
1477    $db = dbConnectByName($store->getDbName());
1478    $statsArray = array();
1479    $query = $db->prepare("SELECT (SELECT ROUND(AVG(UNIX_TIMESTAMP(sortCompleted) - UNIX_TIMESTAMP(sortStarted)),1) as sortTime FROM buyQueue WHERE sortCompleted > sortStarted AND isProcessed = 1 AND timeEntered BETWEEN :dateStart AND :dateEnd AND (UNIX_TIMESTAMP(sortCompleted) - UNIX_TIMESTAMP(sortStarted) BETWEEN :minSeconds AND :maxSeconds)) as sortTime,
1480
1481       (SELECT ROUND(AVG((UNIX_TIMESTAMP(sortCompleted) - UNIX_TIMESTAMP(sortStarted))/numContainers),1) FROM buyQueue WHERE sortCompleted > sortStarted AND isProcessed = 1 AND timeEntered BETWEEN :dateStart AND :dateEnd AND (UNIX_TIMESTAMP(sortCompleted) - UNIX_TIMESTAMP(sortStarted) BETWEEN :minSeconds AND :maxSeconds)) as stc,
1482
1483       (SELECT SUM(numContainers) FROM buyQueue WHERE sortCompleted > sortStarted AND isProcessed = 1 AND timeEntered BETWEEN :dateStart AND :dateEnd AND (UNIX_TIMESTAMP(sortCompleted) - UNIX_TIMESTAMP(sortStarted) BETWEEN :minSeconds AND :maxSeconds)) as totalContainers,
1484
1485       (SELECT COUNT(buyID) FROM buyQueue WHERE sortCompleted > sortStarted AND isProcessed = 1 AND timeEntered BETWEEN :dateStart AND :dateEnd AND (UNIX_TIMESTAMP(sortCompleted) - UNIX_TIMESTAMP(sortStarted)BETWEEN :minSeconds AND :maxSeconds)) as totalBuys,
1486
1487       (SELECT COUNT(d) as numDays FROM (SELECT DATE(timeEntered) as d FROM buyQueue WHERE sortCompleted > sortStarted AND isProcessed = 1 AND timeEntered BETWEEN :dateStart AND :dateEnd AND (UNIX_TIMESTAMP(sortCompleted) - UNIX_TIMESTAMP(sortStarted) BETWEEN :minSeconds AND :maxSeconds) GROUP BY d) as H) as numDays");
1488    $result = $query->execute(array(':dateStart' => $dateStart, ':dateEnd'=> $dateEnd, ":minSeconds"=>$store->getStatMinSeconds(),":maxSeconds"=>$store->getStatMaxSeconds()));
1489    if($result) {
1490        $row = $query->fetch(PDO::FETCH_ASSOC);
1491        $statsArray = array(
1492            'sortTime' => $row['sortTime'],
1493            'stc' => $row['stc'],
1494            'totalContainers' => $row['totalContainers'],
1495            'totalBuys' => $row['totalBuys'],
1496            'numDays' => $row['numDays']
1497
1498        );
1499    }
1500    return $statsArray;
1501
1502}
1503function compressLogFiles() {
1504    $log = new KLogger($_ENV['LOG_DIR']."dev_log2.txt",KLogger::DEBUG);
1505    global $home_dir;
1506    $date = getCurrentDateRange();
1507
1508    $file = $home_dir."logs/dev_log.txt";
1509    $dest = $home_dir."logs/dev_log/".$date['dateStart']->format("Y_m_d").'.gz';
1510    $log->LogDebug($file);
1511    $log->LogDebug($dest);
1512
1513    $result = gzCompressFile($file,$dest);
1514    if(!$result) {
1515        $log->LogDebug('Error');
1516    } else {
1517        $log->LogDebug($result);
1518    }
1519
1520
1521
1522}
1523function getLoyaltyCustomerByPhone($phone, $storeGroup , $extraDigits = false) {
1524    //Remove the +1 at the beginning of the number
1525    if($extraDigits) {
1526        $phone = substr($phone, 2,11);
1527    }
1528    global $db_name;
1529    $db = dbConnectByName($db_name);
1530    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
1531    try {
1532        $stmt = $db->prepare("SELECT * FROM loyaltyCustomers WHERE phone = :phone AND storeGroup = :storeGroup");
1533        $stmt->bindParam(":phone", $phone);
1534        $stmt->bindParam(":storeGroup", $storeGroup);
1535        if($stmt->execute()) {
1536            $row = $stmt->fetch(PDO::FETCH_ASSOC);
1537            if(isset($row['id'])) {
1538                return $customer = new LoyaltyCustomer($row['id']);
1539            } else {
1540                return null;
1541            }
1542        }
1543    } catch (PDOException $e) {
1544        error_log($e->getMessage());
1545        return null;
1546    }
1547    return null;
1548}
1549function obfuscateCustomerData($typeNum) {
1550    $storeController = new \BuyerKiosk\StoreController($typeNum);
1551    $store = $storeController->getStore();
1552    if($store->getDev()) {
1553        $db = dbConnectByName($store->getDbName());
1554        $stmt = $db->prepare("SELECT * FROM customers");
1555        $stmt->execute();
1556        while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
1557            $update = $db->prepare("UPDATE customers SET email = :email, phone = :phone, driversLicense = :driversLicense, address = :address WHERE customerID = :customerID");
1558            $driversLicense = obfuscateDigits($row['driversLicense']);
1559            $phone = obfuscateDigits($row['phone']);
1560            $email = obfuscateEmail($row['email']);
1561            $customerID = $row['customerID'];
1562            $address = "101 Main Street";
1563            $update->bindParam(":phone", $phone);
1564            $update->bindParam(":driversLicense", $driversLicense);
1565            $update->bindParam(":address", $address);
1566            $update->bindParam(":customerID", $customerID);
1567            $update->bindParam(":email", $email);
1568            $update->execute();
1569        }
1570    }
1571}
1572function obfuscateDigits($num) {
1573    $array = array (
1574        "0" => "9",
1575        "1" => "3",
1576        "2" => "5",
1577        "3" => "7",
1578        "4" => "0",
1579        "5" => "2",
1580        "6" => '8',
1581        "7" => '3',
1582        "8" => '1',
1583        "9" => '4'
1584    );
1585    foreach($array as $number => $val) {
1586        $num = str_replace($number, $val, $num);
1587    }
1588    return $num;
1589}
1590function obfuscateEmail($email) {
1591    return 'xxx'. substr($email, 4);
1592
1593}
1594function gen_uuid($len=8) {
1595
1596    $hex = md5("C9uGiVUbdaiukeRaZH9E" . uniqid("", true));
1597
1598    $pack = pack('H*', $hex);
1599    $tmp =  base64_encode($pack);
1600
1601    $uid = preg_replace("#(*UTF8)[^A-Za-z0-9]#", "", $tmp);
1602
1603    $len = max(4, min(128, $len));
1604
1605    while (strlen($uid) < $len)
1606        $uid .= gen_uuid(22);
1607
1608    return substr($uid, 0, $len);
1609}
1610function filterMessage($message, $origin, $vars = null, ?LoyaltyCoupon $coupon = null) {
1611    $store = new Store();
1612    $store->createStore($origin);
1613    $customer = new LoyaltyCustomer(52);
1614
1615    if(!isset($vars)) {
1616        $vars = array (
1617            "%company%" => $store->getCompanyName(),
1618            "%store%" => $store->getCity(),
1619            "%store_number%" => $store->getStoreNum(),
1620            "%customer%" => $customer->getFirstName(),
1621            "%first_name%" => $customer->getFirstName(),
1622            "%last_name%" => $customer->getLastName(),
1623            "%birthday%" => $customer->getBirthday(),
1624            "%new_line%" => "\n"
1625        );
1626    }
1627    if(isset($coupon)) {
1628        $uuid = $coupon->createUUID($customer->getLoyaltyID());
1629        $couponURL = serverName."/c/".$uuid;
1630        $expireDate = $coupon->getExpireDate();
1631        if(isset($expireDate)) {
1632            //Get the expiration date of coupon
1633            $expireDate = new DateTime($coupon->getExpireDate(), new DateTimeZone('utc'));
1634            $expireDate->setTimezone(new DateTimeZone($store->getTimeZone()));
1635            $expireDate = $expireDate->format("m-d-Y");
1636        } else {
1637            $expireDate = new DateTime('now');
1638            $expireDate->add(new DateInterval("P".$coupon->getExpireDays()."D"));
1639            $expireDate = $expireDate->format("m-d-Y");
1640        }
1641
1642        $vars["%coupon_url%"] = $couponURL;
1643        $vars["%coupon_expire%"] = $expireDate;
1644    }
1645
1646    foreach ($vars as $key => $value) {
1647        $message = str_replace($key, $value, $message);
1648    }
1649    return $message;
1650}
1651function similarity($str1, $str2) {
1652    $len1 = strlen($str1);
1653    $len2 = strlen($str2);
1654
1655    $max = max($len1, $len2);
1656    $similarity = $i = $j = 0;
1657
1658    while (($i < $len1) && isset($str2[$j])) {
1659        if ($str1[$i] == $str2[$j]) {
1660            $similarity++;
1661            $i++;
1662            $j++;
1663        } elseif ($len1 < $len2) {
1664            $len1++;
1665            $j++;
1666        } elseif ($len1 > $len2) {
1667            $i++;
1668            $len1--;
1669        } else {
1670            $i++;
1671            $j++;
1672        }
1673    }
1674
1675    return round($similarity / $max, 2);
1676}
1677function isCurrency($number)
1678{
1679    return preg_match("/\b\d{1,3}(?:,?\d{3})*(?:\.\d{2})?\b/", $number);
1680}
1681function generateRemazeKey(Store $store) {
1682    return hash_hmac('sha256', $store->getTypeNum() . ':' . $store->getTypeNum()."@buyerkiosk.com", '237096c8032433965675bc4dc3c3411390221503ec67ab3d332e0dcdebb68676');
1683}
1684function validateAPIKey($key, $typeNum) {
1685    $store = new Store();
1686    //Remove any non alpha-numeric characters. This will cause the character count to fail
1687    $key = preg_replace("/[^A-Za-z0-9]/", "", $key);
1688    //If we do not have 60 characters we know this is a bad key.
1689    if(strlen($key) !== 60) {
1690        return null;
1691    }
1692    //Make sure this store exists.
1693    if(!$store->createStore($typeNum)) {
1694        return null;
1695    }
1696    //Make sire this store is active
1697    if(!(int)$store->getActive()) {
1698        return null;
1699    }
1700    $storeAPI = $store->getApiKey();
1701    //Check if the two strings are the same
1702    if($key !== $storeAPI) {
1703        return null;
1704    }
1705    return $store;
1706}
1707function isSecure() {
1708    return
1709        (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
1710}
1711function getAllLoyaltyCustomersByPhone($phone, $extraDigits = false) {
1712    //Remove the +1 at the beginning of the number
1713    if($extraDigits) {
1714        $phone = substr($phone, 1,10);
1715    }
1716    global $db_name;
1717    $db = dbConnectByName($db_name);
1718    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
1719    try {
1720        $stmt = $db->prepare("SELECT * FROM loyaltyCustomers WHERE phone = :phone ORDER BY lastTextTimestamp DESC");
1721        $stmt->bindParam(":phone", $phone);
1722        if($stmt->execute()) {
1723            $customerArray = [];
1724            while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
1725                $customerArray[] = new LoyaltyCustomer($row['id']);
1726                return $customerArray;
1727            }
1728            return null;
1729        }
1730    } catch (PDOException $e) {
1731        error_log($e->getMessage());
1732        return null;
1733    }
1734    return null;
1735}
1736function sendSMStoNumber($number,$shortName = null,$message=null) {
1737    $log = new KLogger($_ENV['LOG_DIR']."nexmo.log", KLogger::DEBUG);
1738    $log->LogDebug("[".$number."] - ".$shortName." - ".$message);
1739    if($shortName !== null) {
1740        $message = new LoyaltyMessage(null, $shortName);
1741        if(strlen($message->getMessage()) > 0) {
1742            $text = $message->getMessage();
1743        }
1744    } elseif($message !== null) {
1745        $text = $message;
1746    } else {
1747        return null;
1748    }
1749
1750    $postfields = array(
1751        'api_key'=>'67e71b29',
1752        'api_secret'=>'47f48c40b1586e71',
1753        'from' => '91904',
1754        'text' => $text,
1755        'to' => "+1".$number,
1756    );
1757    $ch = curl_init();
1758    curl_setopt($ch, CURLOPT_URL, 'https://rest.nexmo.com/sms/json');
1759    curl_setopt($ch, CURLOPT_POST, 1);
1760    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1761    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postfields));
1762    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
1763    $result = curl_exec($ch);
1764    $log->LogDebug(print_r($result));
1765    $response = json_decode($result);
1766    $log->LogDebug(print_r($response));
1767    curl_close($ch);
1768    return $response;
1769}
1770function convertUTCToLocalTimeStamp($timezone, $timeStampString) {
1771    if($timeStampString == "0000-00-00 00:00:00") {
1772        return $timeStampString;
1773    }
1774    $output = new DateTime($timeStampString, new DateTimeZone("UTC"));
1775    return $output->setTimezone(new DateTimeZone($timezone))->format("Y-m-d H:i:s");
1776}
1777
1778function getSurveyMessages(Store $store) {
1779    global $db_name;
1780    $db = dbConnectByName($db_name);
1781    $stmt = $db->query("SELECT surveyThankYou, surveyMinRatingForReview, surveyReviewLink, surveyReviewText, buySurvey, surveyMinRatingForReview, surveySMSText FROM stores WHERE typeNum = '".$store->getTypeNum()."'");
1782    $row = $stmt->fetch(PDO::FETCH_ASSOC);
1783    $result['thankYou'] = $row['surveyThankYou'];
1784    $result['minRating'] = $row['surveyMinRatingForReview'];
1785    $result['reviewText'] = $row['surveyReviewText'];
1786    $result['buySurvey'] = $row['buySurvey'];
1787    $result['smsText'] = $row['surveySMSText'];
1788    return $result;
1789}
1790function getSurveyLinks(Store $store) {
1791    $storeDB = dbConnectByName($store->getDbName());
1792    $stmt = $storeDB->query("SELECT * FROM surveyLinks WHERE active = 1");
1793    $row = $stmt->fetch(PDO::FETCH_ASSOC);
1794    return $row;
1795}
1796function getRandomSurveyLink(Store $store) {
1797    $storeDB = dbConnectByName($store->getDbName());
1798    try {
1799        $stmt = $storeDB->query("SELECT * FROM surveyLinks WHERE active = 1 ORDER BY RAND() LIMIT 1");
1800        $row = $stmt->fetch(PDO::FETCH_ASSOC);
1801        return $row;
1802    } catch (PDOException $e) {
1803        error_log("surveyLinks table not found in database: " . $store->getDbName());
1804        return false;
1805    }
1806}
1807
1808function getActiveStoreCount() {
1809    global $db_name;
1810    $db = dbConnectByName($db_name);
1811    $stmt = $db->query("SELECT COUNT(DISTINCT sb.origin) as active_count 
1812                                FROM kiosk_buys.systemBuys sb
1813                                WHERE sb.timeStamp >= DATE_SUB(CURDATE(), INTERVAL 7 DAY);");
1814    $row = $stmt->fetch(PDO::FETCH_ASSOC);
1815    return (int)$row['active_count'];
1816}
1817
1818function getActiveStores() {
1819    global $db_name;
1820    $db = dbConnectByName($db_name);
1821    $stmt = $db->query("SELECT s.typeNum as origin, 
1822                                       s.city, 
1823                                       s.state, 
1824                                       s.storeType, 
1825                                       COUNT(*) as buy_count
1826                                FROM kiosk_buys.systemBuys sb
1827                                STRAIGHT_JOIN kiosk_buykiosk.stores s ON sb.origin = s.typeNum
1828                                WHERE sb.timeStamp >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
1829                                GROUP BY s.typeNum, s.city, s.state, s.storeType
1830                                ORDER BY buy_count DESC;");
1831    $activeStores = $stmt->fetchAll(PDO::FETCH_ASSOC);
1832
1833    foreach ($activeStores as &$store) {
1834        $storeObj = new Store();
1835        $storeObj->setStoreType($store['storeType']);
1836        $store['typeNum'] = $store['origin'];
1837        $store['storeTypeName'] = getStoreTypeName($storeObj);
1838    }
1839
1840    return $activeStores;
1841}
1842
1843function getInactiveStores() {
1844    $allStores = getAllStoresData(1,0);
1845    $activeStores = getActiveStores();
1846    $activeOrigins = array_column($activeStores, 'origin');
1847    $inactiveStores = array_filter($allStores, function($store) use ($activeOrigins) {
1848        return !in_array($store->typeNum, $activeOrigins);
1849    });
1850
1851    $inactiveStoresWithNames = array_map(function($store) {
1852        return [
1853            'typeNum' => $store->typeNum,
1854            'city' => $store->city,
1855            'state' => $store->state,
1856            'storeType' => $store->storeType,
1857            'storeTypeName' => getStoreTypeName($store)
1858        ];
1859    }, array_values($inactiveStores));
1860
1861    return $inactiveStoresWithNames;
1862}
1863function getStoreStatus($typeNum) {
1864    $storeController = new \BuyerKiosk\BuyerKiosk\Controllers\StoreController($typeNum);
1865    $store = $storeController->getStore();
1866    if($store->getActive() == 0) {
1867        $log = new KLogger($_ENV['LOG_DIR']."inactive.log", KLogger::DEBUG);
1868        $log->LogDebug("Inactive Store Accessed: ".$typeNum);
1869    }
1870    return (int)$store->getActive();
1871}
1872
1873function getBKStats() {
1874    global $buydb_name;
1875    $db = dbConnectByName($buydb_name);
1876    $result['totalBuys'] = 0;
1877    $result['totalTexts'] = 0;
1878    $result['totalCustomers'] = 0;
1879    $result['totalEmails'] = 0;
1880    $stores = getAllStoresData(0,0);
1881    foreach($stores as $store) {
1882        $storeDB = dbConnectByName($store->getDbName());
1883        $query1 = "show table status like 'texts'";
1884        $stmt = $storeDB->query($query1);
1885        $row = $stmt->fetch(PDO::FETCH_ASSOC);
1886        $result['totalTexts'] += $row['Rows'];
1887        $query2 = "show table status like 'customers'";
1888        $stmt = $storeDB->query($query2);
1889        $row = $stmt->fetch(PDO::FETCH_ASSOC);
1890        $result['totalCustomers'] += $row['Rows'];
1891        $query3 = "SELECT count(*) FROM customers WHERE onEmail = 1";
1892        $stmt = $storeDB->query($query3);
1893        $row = $stmt->fetch(PDO::FETCH_ASSOC);
1894        $result['totalEmails'] += $row['count(*)'];
1895        $query4 = "show table status like 'buyQueue'";
1896        $stmt = $storeDB->query($query4);
1897        $row = $stmt->fetch(PDO::FETCH_ASSOC);
1898        $result['totalBuys'] += $row['Rows'];
1899    }
1900    return $result;
1901}
1902function sendSurverySMS($buyID, $typeNum) {
1903    $log = new KLogger($_ENV['LOG_DIR']."survey.log", KLogger::DEBUG);
1904    $log->LogDebug("New Survey for ".$typeNum);
1905    
1906    global $buydb_name;
1907    $db = dbConnectByName($buydb_name);
1908    $stmt = $db->prepare("SELECT customerID, uuid FROM systemBuys WHERE buyID = :buyID AND origin = :typeNum");
1909    $stmt->bindValue(":buyID", $buyID);
1910    $stmt->bindValue(":typeNum", $typeNum);
1911    
1912    if($stmt->execute()) {
1913        $log->LogDebug("BuyID Check Executed");
1914        if($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
1915            $log->LogDebug("BuyID Check Passed");
1916            $storeController = new \BuyerKiosk\StoreController($typeNum);
1917            $store = $storeController->getStore();
1918            $storeDB = dbConnectByName($store->getDbName());
1919            
1920            // Check to see if customer consented to text message
1921            $check = $storeDB->prepare("SELECT textMe FROM buyQueue WHERE buyID = :buyID");
1922            $check->bindValue(":buyID", $buyID);
1923            if($check->execute()) {
1924                $log->LogDebug("Text Me Check Execute");
1925                if($checkRow = $check->fetch(PDO::FETCH_ASSOC)) {
1926                    if((int)$checkRow['textMe'] == 1) {
1927                        $log->LogDebug("Text Me Is a Go");
1928                        $customerQuery = $storeDB->prepare("SELECT phone FROM customers WHERE customerID = :customerID");
1929                        $customerQuery->bindValue(":customerID", $row['customerID']);
1930                        
1931                        if($customerQuery->execute()) {
1932                            $customer = $customerQuery->fetch(PDO::FETCH_ASSOC);
1933                            $surveyUrl = "https://v2ts.co/s/".$row['uuid'];
1934                            $log->LogDebug("Survey URL: ".$surveyUrl);
1935                            $log->LogDebug("Phone: ". $customer['phone']);
1936
1937                            // Use the textMessageService for all SMS sending (both Nexmo/Vonage and Twilio)
1938                            $log->LogDebug("Sending survey SMS via store's text messaging service");
1939                            $customerController = new \BuyerKiosk\CustomerController(null, $store);
1940                            $customerObj = $customerController->getCustomerById($row['customerID']);
1941                            $response = null;
1942                            
1943                            if($customerObj) {
1944                                $result = $store->textMessageService->sendSurveyText($customerObj, $surveyUrl);
1945                                if($result['status'] == "success") {
1946                                    $response = (object)['status' => 'success', 'id' => $result['id']];
1947                                } else {
1948                                    $log->LogError("Survey SMS failed: " . json_encode($result));
1949                                }
1950                            } else {
1951                                $log->LogError("Could not load customer object for customerID: " . $row['customerID']);
1952                            }
1953                            
1954                            // Add to texts database if SMS was sent successfully
1955                            if($response && $response->status == 'success') {
1956                                $log->LogDebug("Add Text To Database");
1957                                $actualMessage = $store->getSurveySMSText() . $surveyUrl;
1958                                // Truncate message if too long for database column (assuming 255 char limit)
1959                                $actualMessage = mb_substr($actualMessage, 0, 255);
1960                                $stmt = $storeDB->prepare("INSERT INTO texts (customerID, type, employeeNumber, message) VALUES (:customerID, 5, 99, :message)");
1961                                $stmt->bindValue(":customerID", $row['customerID']);
1962                                $stmt->bindValue(":message", $actualMessage);
1963                                if($stmt->execute()) {
1964                                    $log->LogDebug("Text was counted");
1965                                } else {
1966                                    $log->LogDebug("Text was not counted");
1967                                }
1968                            }
1969                            return $response;
1970                        }
1971                    } else {
1972                        $log->LogDebug("Text Me Is a No Go");
1973                    }
1974                }
1975            }
1976        }
1977    }
1978    return null;
1979}
1980
1981
1982?>