[READ-ONLY] Mirror of https://github.com/andrioid/travian.scrolls.org. Retired project. I got a bit overly engaged in an online game called Travian.
9.3 kB
332 lines
1<?php
2
3function fromCache($key) {
4 if (function_exists('apc_fetch') and ($result = @apc_fetch($key))) {
5 return $result;
6 }
7 return false;
8}
9
10function toCache($key, $data, $ttl=-1) {
11 global $Config;
12 if (!function_exists('apc_store')) { return false; }
13 if ($ttl == -1) { $ttl = 60*60*12; }
14 if (!@apc_store($key, $data, $ttl)) { return false; }
15 return true;
16}
17
18function arraytolist ($array) {
19 $list = '';
20 if (!is_array($array)) { return false; }
21 $count = count($array);
22 foreach ($array as $i=>$item) {
23 $list .= $item;
24 if ($i != $count-1) { $list .= ","; }
25 }
26 return $list;
27}
28
29function listtoarray ($list) {
30 $strip = array(' ');
31 $list = str_replace($strip, '', $list);
32 $return = explode(',', $list);
33 return $return;
34}
35
36
37
38// A list parent class that should help with paging and maybe rss/json feeds
39class SystemList {
40 public $list = array(); // our object list
41 public $q = ''; // This is the base query (can be overwritten)
42 private $cache = true;
43 private $conditions = array();
44 private $page = 0; // defaults
45 private $perPage = 0; // defaults
46 private $order = null; // asc/desc (no defaults, skipped if null)
47 private $orderBy = null; // field name (no defaults, skipped if null)
48 private $groupBy = null;
49 private $fields = array();
50 private $fieldTypes = '';
51 private $identifier = null; // if null, we'll use the md5 of the sql query (used for caching)
52 public $total = 0; // Total result (SQL_COUNT_ROWS)
53 public $count = 0; // Current list count
54 public $cacheTime = 0; // Cache time in seconds
55
56 public function __construct() {
57 global $Config;
58 $this->cacheTime = $Config['cacheTime'];
59 }
60
61 // We will handle WHERE and LIMIT for the query here
62 public function populate () {
63 $where = '';
64 $order = '';
65 $paging = '';
66 $paraType = '';
67 $query = '';
68 $this->list = array(); // We must clear it
69 $para = array();
70 if (empty($this->q)) { throw new Exception("Cannot populate a list without a query"); }
71
72
73 // Conditions into "where"
74 $where = $this->processConditions();
75
76 // Paging
77 if ($this->page > 0) {
78 $from = (int)($this->page-1) * (int)$this->perPage;
79 } else {
80 $from = 0;
81 }
82 if ($this->perPage > 0) { // Skip limit if not defined
83 $paging = sprintf("LIMIT %d,%d", $from, $this->perPage);
84 }
85
86 // Order
87 if ($this->orderBy != null) {
88 $order = sprintf("ORDER BY %s", $this->orderBy);
89 if ($this->order != null) {
90 $order .= " ".$this->order;
91 }
92 }
93
94 // Group By
95 if ($this->groupBy != null) {
96 $groupby = sprintf("GROUP BY %s", $this->groupBy);
97 } else { $groupby = ''; }
98
99 // Create the new query
100 $query = sprintf("%s %s %s %s %s", $this->q, $where, $groupby, $order, $paging);
101 //printf("Query: %s<br>", htmlspecialchars($query));
102
103 // Caching
104 if ($this->identifier != null) {
105 $cacheName = $this->identifier;
106 } else {
107 $cacheName = md5($query.$this->fieldTypes.arraytolist($this->fields));
108 }
109
110 if ($this->cache == true) {
111 list($this->list, $this->count, $this->total) = fromCache($cacheName);
112 if (is_array($this->list) and count($this->list) > 0) {
113 return true;
114 }
115 }
116 //printf("Using database for: %s <br>", $this->q);
117 $db = Database::getInstance();
118
119
120 // Prepare the query
121 if (!$stmt = $db->mysql->prepare($query)) {
122 throw new Exception("Could not prepare statement: ".$db->mysql->error);
123 }
124
125 // Do some binding to the statement
126 // - This is some voodoo shit, we have to dynamically handle all parameters and output variables
127 // - Read comments on: http://www.php.net/manual/en/mysqli-stmt.bind-result.php
128 if (strlen($this->fieldTypes) > 0) {
129 call_user_func_array('mysqli_stmt_bind_param', array_merge(array($stmt, &$this->fieldTypes), $this->fields));
130 }
131 //echo "<pre>"; print_r($stmt); echo "</pre>";
132 if (!$stmt->execute()) { throw new Exception("Failed to execute: ".$stmt->error); }
133
134 $result = mysqli_stmt_result_metadata($stmt);
135 $data = array(); // Store the results in
136 $args = array();
137
138 while ($field = mysqli_fetch_field($result)) {
139 $name = $field->name;
140 $args[$name] =& $data[$field->name];
141 }
142 if (!@call_user_func_array(array(&$stmt, "bind_result"), $args)) {
143 printf("SystemList: Name conflict in query, please fix.");
144 exit(0);
145 }
146
147
148 while ($stmt->fetch()) {
149 $this->processRow($args);
150 }
151 // Update count
152 $this->count = count($this->list);
153 // Update total count
154 if (!$result = $db->mysql->query("SELECT FOUND_ROWS()")) { throw new SystemException("List failed to update total"); }
155 list ($this->total) = $result->fetch_row();
156
157 if (!toCache($cacheName,array($this->list, $this->count, $this->total),$this->cacheTime)) {
158 throw new Exception("Failed to store cache.");
159 }
160
161 if ($this->list == null) { // In case the processing method does something weird
162 $this->list = array();
163 }
164
165
166 $stmt->close();
167
168 }
169 protected function processRow() {
170 // This should be replaced with your own object method
171 // - this prototype simply converts to stdclass and adds to list
172 /* Important note:
173 * - Each row is stored in $data as a reference. When the next row is called, those values change.
174 * - We need to copy the values before exiting this function
175 */
176 $args = func_get_args();
177 $out = array();
178 $numarg = count($args);
179 // de-referencing the array (damn this is ugly)
180 foreach ($args[0] as $key=>$value) {
181 $out[$key] = $value;
182 }
183
184 $this->list[] = (object)$out;
185 //echo "<pre>"; print_r($this->list); echo "</pre>";
186 return true;
187 }
188
189
190 public function setCache($cache) {
191 if (is_bool($cache)) {
192 $this->cache = $cache;
193 return true;
194 }
195 if (is_numeric($cache)) {
196 if ($cache == 0) {
197 $this->cache = false;
198 return true;
199 } else if ($cache == 1) {
200 $this->cache = true;
201 return true;
202 }
203 }
204 return false;
205 }
206
207 // Offer lists to name their caching value (so they can clear it)
208 public function setIdentifier($identifier) {
209 $this->identifier = $identifier;
210 }
211
212 public function addCondition ($field, $value, $sign='=', $op='AND', $opjoin=false, $internal=false) {
213 // Example: ('username', 'moocow', '=', 'AND', false)
214 // - opjoin dictates if the value should be put into previous parenthesis or left seperate.
215 // - the $op is ignored for the first value (as it doesn't matter)
216 if (is_array($value)) {
217 foreach ($value as $v) {
218 $c = new stdClass();
219 $c->field = $field;
220 $c->value = $v;
221 $c->sign = $sign;
222 $c->op = $op;
223 $c->opjoin = $opjoin;
224 $c->internal = $internal;
225 $this->conditions[] = $c;
226 //printf("Array value %s: %s<br>\n", $field, $v);
227 }
228 } else {
229 $c = new stdClass();
230 $c->field = $field;
231 $c->value = $value;
232 $c->sign = $sign;
233 $c->op = $op;
234 $c->opjoin = $opjoin;
235 $c->internal = $internal;
236 $this->conditions[] = $c;
237 }
238 }
239
240 public function processConditions() {
241 $p =& $this->conditions;
242 $i = 0;
243 $num = count($p);
244 $where = '';
245 $this->fieldTypes = ''; // We must reinitialize
246 $this->fields = array(); // We must reinitialize
247 if ($num > 0) { $where = 'WHERE '; }
248 foreach ($p as $key=>$v) {
249 if ($i != 0 && $v->opjoin == false) { $where .= ') '; } // Close any lingering parenthesis
250 if ($i != 0) { $where .= sprintf(" %s ", strtoupper($v->op)); }
251 if ($i == 0 || $v->opjoin == false) { $where .= '('; } // Initial condition, open with parenthesis
252 if ($v->internal == false) {
253 $where .= sprintf("%s %s ?", $v->field, $v->sign);
254 // Fix binding
255 if (is_double($v->value)) {
256 $this->fieldTypes .= 'd';
257 } elseif (is_int($v->value)) {
258 $this->fieldTypes .= 'i';
259 } else {
260 $this->fieldTypes .= 's';
261 }
262 $this->fields[] = &$v->value;
263 } else {
264 $where .= sprintf("%s %s %s", $v->field, $v->sign, $v->value);
265 }
266 if ($i+1 >= $num) { $where .= ")"; } // Last line, close anything remaining
267
268 // Increment
269 $i++;
270 }
271
272 // Debug
273 //printf("<h3>Debug: %s</h3>\n", $this->fieldTypes);
274 //echo "<pre>"; print_r($this->fields); echo "</pre>";
275
276 return $where;
277 }
278
279 public function setPaging ($page, $perPage=20) {
280 $this->page = (int)$page;
281 $this->perPage = (int)$perPage;
282 return true;
283 }
284
285 public function setOrder ($orderBy, $first='asc') {
286 if (strtolower($first) == 'asc') {
287 $this->order = 'ASC';
288 } else {
289 $this->order = 'DESC';
290 }
291 $this->orderBy = $orderBy;
292 return true;
293 }
294
295 public function setQuery ($query) {
296 $query = preg_replace('/^SELECT/i', 'SELECT SQL_CALC_FOUND_ROWS', $query);
297 $this->q = $query;
298 }
299
300 public function setNavigation ($nav) { // Allows us to set paging and order with the nav variable
301 $this->page = (int)$nav['page'];
302 $this->perPage = (int)$nav['number'];
303 $this->orderBy = $nav['sort'];
304 $this->order = $nav['order'];
305 }
306
307 public function random($num=null) {
308 shuffle($this->list);
309 $count = count($this->list);
310 if ($num != null) {
311 if ($count<$num) { $num=$count; }
312 $return = array();
313 for ($i=0; $i<$num; $i++) {
314 $return[] = $this->list[$i];
315 }
316 $this->list = $return;
317 }
318 }
319
320 public function showConditions() {
321 echo "<pre>"; print_r($this->conditions); echo "</pre>";
322 }
323
324 public function setGroupBy ($gb) {
325 $this->groupBy = $gb;
326 }
327
328 public function setCacheTime ($time) {
329 $this->cacheTime =$time;
330 }
331}
332?>