1: <?php
  2: 
  3:   4:   5: 
  6: class Quform_Session
  7: {
  8:       9:  10: 
 11:     protected $name;
 12: 
 13:      14:  15:  16:  17: 
 18:     protected $table;
 19: 
 20:      21:  22: 
 23:     protected $id;
 24: 
 25:      26:  27:  28:  29: 
 30:     protected $data = array();
 31: 
 32:      33:  34:  35:  36: 
 37:     protected $lifetime;
 38: 
 39:      40:  41:  42:  43: 
 44:     protected $started = false;
 45: 
 46:      47:  48:  49:  50: 
 51:     protected $dirty = false;
 52: 
 53:      54:  55:  56:  57: 
 58:     protected function getTableName()
 59:     {
 60:         global $wpdb;
 61: 
 62:         return $wpdb->prefix . 'quform_sessions';
 63:     }
 64: 
 65:      66:  67:  68:  69:  70: 
 71:     protected function read($sessionId)
 72:     {
 73:         global $wpdb;
 74:         $data = '';
 75: 
 76:         $session = $wpdb->get_row($wpdb->prepare("SELECT * FROM " . $this->getTableName() . " WHERE id = %s", $sessionId), ARRAY_A);
 77: 
 78:         if ( ! is_null($session) && isset($session['payload'])) {
 79:             $data = base64_decode($session['payload']);
 80:         }
 81: 
 82:         return $data;
 83:     }
 84: 
 85:      86:  87:  88:  89:  90: 
 91:     protected function write($sessionId, $data)
 92:     {
 93:         global $wpdb;
 94: 
 95:         if (apply_filters('quform_suppress_session_write_errors', true)) {
 96:             $suppress_errors = $wpdb->suppress_errors();
 97:         }
 98: 
 99:         $query = "INSERT INTO {$this->getTableName()} (`id`, `payload`, `last_activity`) VALUES (%s, %s, %s)
100:                   ON DUPLICATE KEY UPDATE `payload` = VALUES(`payload`), `last_activity` = VALUES(`last_activity`)";
101: 
102:         $wpdb->query($wpdb->prepare($query, $sessionId, base64_encode($data), time()));
103: 
104:         if (isset($suppress_errors)) {
105:             $wpdb->suppress_errors($suppress_errors);
106:         }
107:     }
108: 
109:     110: 111: 112: 113: 
114:     protected function destroy($sessionId)
115:     {
116:         global $wpdb;
117: 
118:         $wpdb->delete($this->getTableName(), array('id' => $sessionId));
119:     }
120: 
121:     122: 123: 
124:     public function gc()
125:     {
126:         global $wpdb;
127: 
128:         $wpdb->query("DELETE FROM " . $this->getTableName() . " WHERE last_activity <= " . (time() - $this->lifetime));
129:     }
130: 
131:     132: 133: 134: 135: 
136:     public function setId($id)
137:     {
138:         $this->id = $id;
139:     }
140: 
141:     142: 143: 144: 145: 
146:     public function getId()
147:     {
148:         return $this->id;
149:     }
150: 
151:     152: 153: 154: 155: 156: 
157:     public function isValidId($id)
158:     {
159:         return is_string($id) && preg_match('/^[a-zA-Z0-9]{40}$/', $id);
160:     }
161: 
162:     163: 164: 165: 166: 
167:     protected function generateSessionId()
168:     {
169:         return Quform::randomString(40);
170:     }
171: 
172:     173: 174: 175: 176: 
177:     public function start()
178:     {
179:         $this->name = 'quform_session_' . COOKIEHASH;
180:         $this->lifetime = apply_filters('quform_session_lifetime', 86400); 
181: 
182:         $id = Quform::get($_COOKIE, $this->name);
183: 
184:         if ( ! $this->isValidId($id)) {
185:             $id = $this->generateSessionId();
186: 
187:             $action = is_admin() ? 'admin_init' : 'send_headers';
188: 
189:             add_action($action, array($this, 'setSessionCookie'));
190:         }
191: 
192:         $this->setId($id);
193: 
194:         $data = $this->read($this->getId());
195: 
196:         $this->data = Quform::isNonEmptyString($data) ? unserialize($data) : array();
197: 
198:         if ( ! $this->has('_token')) {
199:             $this->regenerateToken();
200:         }
201: 
202:         return $this->started = true;
203:     }
204: 
205:     206: 207: 
208:     public function setSessionCookie() {
209:         $set = true;
210: 
211:         if (wp_doing_ajax() && Quform::get($_GET, 'action') != 'quform_support_page_caching') {
212:             $set = false;
213:         }
214: 
215:         if (!is_admin() && is_404()) {
216:             $set = false;
217:         }
218: 
219:         if (apply_filters('quform_set_session_cookie', $set)) {
220:             $this->setSessionIdCookie($this->getId());
221:         }
222:     }
223: 
224:     225: 226: 227: 228: 
229:     protected function setSessionIdCookie($id)
230:     {
231:         $expire = apply_filters('quform_session_cookie_expire', 0);
232:         $secure = apply_filters('quform_session_cookie_secure', is_ssl());
233:         $httpOnly = apply_filters('quform_session_cookie_http_only', true);
234:         $sameSite = apply_filters('quform_session_cookie_same_site', $secure ? 'None' : 'Lax');
235: 
236:         Quform::setCookieHeader($this->name, $id, $expire, $secure, $httpOnly, $sameSite);
237:     }
238: 
239:     240: 241: 
242:     public function save()
243:     {
244:         if (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '/.well-known/acme-challenge/') === 0) {
245:             return; 
246:         }
247: 
248:         if ($this->dirty && $this->started) {
249:             $this->write($this->id, serialize($this->data));
250:             $this->dirty = false;
251:         }
252:     }
253: 
254:     255: 256: 
257:     public function regenerateToken()
258:     {
259:         $this->put('_token', Quform::randomString(40));
260:     }
261: 
262:     263: 264: 265: 266: 
267:     public function getToken()
268:     {
269:         return $this->get('_token');
270:     }
271: 
272:     273: 274: 275: 276: 277: 
278:     public function has($key)
279:     {
280:         return ! is_null($this->get($key));
281:     }
282: 
283:     284: 285: 286: 287: 288: 289: 
290:     public function get($key = null, $default = null)
291:     {
292:         return Quform::get($this->data, $key, $default);
293:     }
294: 
295:     296: 297: 298: 299: 300: 
301:     public function set($key, $value)
302:     {
303:         Quform::set($this->data, $key, $value);
304:         $this->dirty = true;
305:     }
306: 
307:     308: 309: 310: 311: 312: 313: 
314:     public function put($key, $value = null)
315:     {
316:         if ( ! is_array($key)) $key = array($key => $value);
317: 
318:         foreach ($key as $arrayKey => $arrayValue) {
319:             $this->set($arrayKey, $arrayValue);
320:         }
321:     }
322: 
323:     324: 325: 326: 327: 
328:     public function forget($keys)
329:     {
330:         Quform::forget($this->data, $keys);
331:         $this->dirty = true;
332:     }
333: 
334:     335: 336: 
337:     protected function scheduleGc()
338:     {
339:         if ( ! wp_next_scheduled('quform_session_gc')) {
340:             wp_schedule_event(time() + (12 * HOUR_IN_SECONDS), 'twicedaily', 'quform_session_gc');
341:         }
342:     }
343: 
344:     345: 346: 
347:     protected function unscheduleGc()
348:     {
349:         if ($timestamp = wp_next_scheduled('quform_session_gc')) {
350:             wp_unschedule_event($timestamp, 'quform_session_gc');
351:         }
352:     }
353: 
354:     355: 356: 
357:     public function activate()
358:     {
359:         global $wpdb;
360: 
361:         require_once ABSPATH . 'wp-admin/includes/upgrade.php';
362: 
363:         $sql = "CREATE TABLE " . $this->getTableName() . " (
364:             id VARCHAR(40) NOT NULL,
365:             payload longtext NOT NULL,
366:             last_activity INT UNSIGNED NOT NULL,
367:             UNIQUE KEY id (id)
368:         ) " . $wpdb->get_charset_collate() . ";";
369: 
370:         dbDelta($sql);
371: 
372:         $this->scheduleGc();
373:     }
374: 
375:     376: 377: 
378:     public function deactivate()
379:     {
380:         $this->unscheduleGc();
381:         $this->gc();
382:     }
383: 
384:     385: 386: 
387:     public function uninstall()
388:     {
389:         global $wpdb;
390: 
391:         $this->unscheduleGc();
392: 
393:         $wpdb->query("DROP TABLE IF EXISTS " . $this->getTableName());
394:     }
395: 
396:     397: 398: 399: 400: 401: 
402:     public function dropTableOnSiteDeletion($tables)
403:     {
404:         $tables[] = $this->getTableName();
405: 
406:         return $tables;
407:     }
408: }
409: