PageRenderTime 44ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/StringQuery.php

https://github.com/dougwollison/StringQuery
PHP | 514 lines | 224 code | 54 blank | 236 comment | 44 complexity | 6d9e386afcce151cb20e427b93354fc8 MD5 | raw file
  1. <?php
  2. /*
  3. Built with StringQuery (https://github.com/dougwollison/StringQuery/)
  4. Version 1.1.4; Released 29/05/2013
  5. Copyright Š 2012 Richard Cornwell & Doug Wollison
  6. For conditions of distribution and use, see copyright notice in LICENSE
  7. */
  8. /**
  9. * StringQuery is a 2 pronged system for manipulating the DOM.
  10. * This PHP class is used to build an array of instructions
  11. * for the JS class to follow and make changes to the DOM.
  12. *
  13. * Instructions are sent in this basic structure:
  14. * {
  15. * target: {
  16. * prop: value
  17. * }
  18. * }
  19. *
  20. * target: a jQuery selector (to directly edit an element)
  21. * or a number to call a predefined parser function
  22. * within the JS class.
  23. * (set via StringQuery.parsers[@NAME] = function(){//do something})
  24. *
  25. * prop: a DOM property or attribute to manipulate through
  26. * jQuery or directly through the DOM object. This can
  27. * also be the name of a jQuery function such as addClass,
  28. * or a predefined parser function.
  29. * (set via StringQuery.parsers.X = function(){//do something})
  30. *
  31. * value: mixed data, such as a boolean, a string, or an object.
  32. * For jQuery functions that take multiple arguments, pass
  33. * an array of the arguments.
  34. * (set via StringQuery.functions.[oneArg|multiArg|noArg].X = function(){//do something})
  35. *
  36. * These instructions are then sent back in the form of a JSON object,
  37. * which is composed of the following:
  38. * {
  39. * i: <instrution data>
  40. * r: <repeat boolean>
  41. * u: <update interval>
  42. * k: <session key>
  43. * t: <execution time>
  44. * v: <verbose boolean>
  45. * }
  46. *
  47. * instruction data: The instructions for what changes to make to the DOM
  48. *
  49. * repeat boolean: Wether or not to repeat this action again; this
  50. * is for something like a ping action that regularly
  51. * checks if there are new changes available
  52. *
  53. * update interval: How long for the JS class to wait before sending
  54. * again, assuming repeat is TRUE
  55. *
  56. * session key: The key of StringQuery's $_SESSION entry that this
  57. * particulat connection is related to.
  58. *
  59. * execution time: An estimate of how long it took the server to execute
  60. * this, used in the debug logs if verbose is true.
  61. *
  62. * verbose boolean: Wether or not to log debug messages in the console,
  63. * useful in tracking down what data was sent back and
  64. * ensuring it's processed properly
  65. *
  66. * StringQuery's uses a section of the $_SESSION variable to store
  67. * information such as previously sent instructions so as to cut out
  68. * redundant changes (unless they are marked as forced, in which
  69. * case it will send that change no matter what).
  70. *
  71. * StringQuery's session variable supports multiple instances; if
  72. * the user has multiple windows open, the changes will only affect
  73. * the specific window. In an attempt to prevent memory hogging,
  74. * StringQuery regularly clears out sessions that haven't been touched
  75. * for the last 60 seconds.
  76. */
  77. ini_set('session.use_cookies', 1);
  78. ini_set('session.use_only_cookies', 1);
  79. class StringQuery
  80. {
  81. public $repeat = false; //specify that pings for this should be repeated
  82. public $forced = false; //Default force setting
  83. public $verbose = false; //Default js verbose setting
  84. public $min_update_interval = 0; //The minimum update interval, added to the actual upate_interval
  85. private $actions = array(); //List of function calls for each action
  86. private $data = array(); //For collecting multiple replies to send
  87. private $force = array(); //For forcing changes on any targets logged here.
  88. public $update_interval = 1000; //How long to wait before repeating if repeat is TRUE
  89. public $session_lifetime = 60; //How many seconds a session can last without being touched before getting deleted
  90. public $start; //The start time of when StringQuery was initialized
  91. public $key; //The session entry key that holds relevant StringQuery data
  92. /**
  93. * Retrive a value from the current StringQuery session.
  94. *
  95. * Passing nothing will retrieve the whole session array,
  96. * otherwise, pass a number of arguments to "drill" down into
  97. * the session array.
  98. *
  99. * Example, to access $_SESSION['StringQuery'][$this->key]['changes']['#myelement']
  100. * You would call $this->get('changes', '#myelement')
  101. *
  102. * @since 1.1.3
  103. *
  104. * @return mixed $value The resulting value form the "drill".
  105. */
  106. public function get(){
  107. //If the session or even the key is not set, return null
  108. if(is_null($this->key) || !isset($_SESSION['StringQuery'][$this->key])) return null;
  109. //Get the list of keys to dig through
  110. $vars = func_get_args();
  111. //If no arguments are passed, return whole session array
  112. if(count($vars) == 0){
  113. return $_SESSION['StringQuery'][$this->key];
  114. }else{
  115. //If the session array isn't actually an array, return null
  116. if(!is_array($_SESSION['StringQuery'][$this->key])){
  117. return null;
  118. }
  119. //Prep variables
  120. $value = null; //the value that will be returned
  121. $_var = array_shift($vars); //the upper most key to access
  122. //If the first key exists, load $value with that value
  123. if(isset($_SESSION['StringQuery'][$this->key][$_var])){
  124. $value = $_SESSION['StringQuery'][$this->key][$_var];
  125. }
  126. //Loop through the remaining keys (if present),
  127. //and load $value wit it if present, otherwise break
  128. while($_var = array_shift($vars)){
  129. if(is_array($value) && isset($value[$_var])){
  130. $value = $value[$_var];
  131. }else{
  132. break;
  133. }
  134. }
  135. return $value;
  136. }
  137. }
  138. /**
  139. * Set a value from the current StringQuery session.
  140. *
  141. * Passing a single argument will ovwerrite the whole session array,
  142. * otherwise, pass a number of arguments to "drill" down into
  143. * the session array, with the last argument being the value to set.
  144. *
  145. * Example, to set $_SESSION['StringQuery'][$this->key]['changes']['@log'] = 'Hello world!'
  146. * You would call $this->set('changes', '@log', 'Hello world!')
  147. *
  148. * @since 1.1.3
  149. */
  150. public function set(){
  151. //Get the list of keys passed
  152. $keys = func_get_args();
  153. $value = array_pop($keys); //The last one will be the value to be set
  154. //If the session isn't set, set it as an empty array
  155. if(is_null($this->get()))
  156. $_SESSION['StringQuery'][$this->key] = array();
  157. //If only 1 or fewer arguments are passed, abort
  158. if(func_num_args() <= 1) return;
  159. //Pass the session by reference to the $target alias
  160. $target =& $_SESSION['StringQuery'][$this->key];
  161. //Loop through the $keys and reassign $target to the deeper value
  162. while(($_key = array_shift($keys)) !== null){
  163. //If the value isn't set or isn't an array, make it one
  164. if(!isset($target[$_key]) || !is_array($target[$_key]))
  165. $target[$_key] = array();
  166. //Update the $target alias to the deeper value
  167. $target =& $target[$_key];
  168. }
  169. //Finally, set the target with the desired value
  170. $target = $value;
  171. }
  172. /**
  173. * Load or generate the key for the current session.
  174. *
  175. * Also sets the birth time of the session,
  176. * and updates the touched timestamp.
  177. *
  178. * @since 1.0
  179. */
  180. private function make_key(){
  181. //Set the key to either be the one passed in the AJAX call, or create one based on their IP and the microtime
  182. if(!empty($_REQUEST['StringQuery']['k']) && $_REQUEST['StringQuery']['k'] != 'null'){
  183. $this->key = $_REQUEST['StringQuery']['k'];
  184. }else{
  185. $this->key = md5($_SERVER['REMOTE_ADDR']).md5(microtime());
  186. }
  187. //Check if StringQuery data is setup for this session, setup with birth time if not
  188. if(!$this->get('birth')){
  189. $this->set('birth', time());
  190. }
  191. //Set/Update the touched timestamp on this session to renew it
  192. $this->set('touched', time());
  193. }
  194. /**
  195. * Run through all sessions and delete any that are haven't been touched
  196. * within the specified session lifetime
  197. *
  198. * @since 1.0
  199. */
  200. private function clean_session(){
  201. foreach($_SESSION['StringQuery'] as $key => $session){
  202. if($key != $this->key && isset($session['touched']) && time() - intval($session['touched']) > $this->session_lifetime){
  203. unset($_SESSION['StringQuery'][$key]);
  204. }
  205. }
  206. }
  207. /**
  208. * Constructor; setup object, key, change properties, etc.
  209. *
  210. * @since 1.0
  211. *
  212. * @param array $args Optional An array of properties and their new values
  213. */
  214. public function __construct($args = null){
  215. $this->start = microtime(true);
  216. //Generate/Load session key and clean out the session
  217. $this->make_key();
  218. $this->clean_session();
  219. //Check the system load and adjust the update interval accordingly
  220. $this->sysLoadTime();
  221. foreach($args as $arg => $value){
  222. $this->$arg = $value;
  223. }
  224. //If StringQuery data is sent to the server, process it based on the action parameter
  225. if(isset($_REQUEST['StringQuery'])){
  226. header('HTTP/1.0 200 OK');
  227. //If no action is actually passed, send log to the browser
  228. if(!isset($_REQUEST['StringQuery']['action'])) $this->call('log', "No action specified", true, false);
  229. $action = $_REQUEST['StringQuery']['action'];
  230. $data = null;
  231. if(isset($_REQUEST['StringQuery']['data'])) $data = $_REQUEST['StringQuery']['data'];
  232. //Check if data was sent as a serialized string, then parse if so.
  233. if(is_array($data) && isset($data['__serialized'])){
  234. $string = $data['__serialized'];
  235. parse_str($string, $data);
  236. }
  237. if(isset($this->actions[$action])){
  238. //An processor function for this action is set, call the function.
  239. $func = $this->actions[$action];
  240. $func($this, $data, $action);
  241. //call_user_func($this->actions[$action], &$this, $data, $action);
  242. }elseif(isset($this->actions['default'])){
  243. //A defualt action process is set, call it.
  244. $func = $this->actions['default'];
  245. $func($this, $data, $action);
  246. //call_user_func($this->actions['default'], &$this, $data, $action);
  247. }else{
  248. //Dammit, nothing, send a log to the browser
  249. $this->call('log', "Unrecognized action: $action, no default function set.", true);
  250. }
  251. $this->reply();
  252. }
  253. }
  254. /**
  255. * Print out the JSON form of the data to the browser
  256. *
  257. * @since 1.0
  258. *
  259. * @param mixed $data Optional The changes data to pass,
  260. * if literally TRUE, uses StringQuery::$data
  261. */
  262. private function reply($data = true){
  263. $data = $data === true ? $this->data : $data;
  264. //Load the relevant session changes data
  265. $changes = $this->get('changes');
  266. //Run through the changes and compare to the $_SESSION copy
  267. //to see if any of the changes are new compared to last request.
  268. foreach($data as $target => $change){
  269. if( isset($changes[$target]) &&
  270. json_encode($changes[$target]) === json_encode($change) &&
  271. !in_array($target, $this->force)){
  272. //If the target is the same, AND there's no force change
  273. //enabled for that target, unset it, lightening $data
  274. unset($data[$target]);
  275. }else{
  276. $this->set('changes', $target, $change);
  277. }
  278. }
  279. //Check the system load and adjust the update interval accordingly
  280. $this->sysLoadTime();
  281. //Print out the JSON data
  282. echo json_encode(array(
  283. 'i' => $data,
  284. 'r' => $this->repeat,
  285. 'u' => $this->update_interval + $this->min_update_interval,
  286. 'k' => $this->key,
  287. 't' => round(microtime(true) - $this->start, 4),
  288. 'v' => $this->verbose,
  289. 's' => $this->get()
  290. ));
  291. exit; //Adding anything after the JSON will break it.
  292. }
  293. /**
  294. * Update a single target with multiple properties
  295. *
  296. * @since 1.0
  297. *
  298. * @param string $target The jQuery selector or @function name
  299. * @param mixed $data An array (usually) of data to assign to the target
  300. * @param bool $force Optional To force this change or not,
  301. * passing NULL will have it use StringQuery::$force
  302. */
  303. public function update($target, $data, $force = null){
  304. if($force === null) //set $force to the default setting
  305. $force = $this->forced;
  306. if($force) //Add target to list of forced changes
  307. $this->force[] = $target;
  308. //Add it to the $data array
  309. if(isset($this->data[$target]) && is_array($this->data[$target])){
  310. //changes for this target exist, add/ovewrite properties
  311. $this->data[$target] = array_merge($this->data[$target], $data);
  312. }else{
  313. //create new $data entry for this target
  314. $this->data[$target] = $data;
  315. }
  316. }
  317. /**
  318. * Update a specific property on a target
  319. *
  320. * @since 1.0
  321. *
  322. * @param string $target The jQuery selector or @function name
  323. * @param string $prop The property to change
  324. * @param mixed $value The new value of the property
  325. * @param bool $force Optional To force this change or not,
  326. * passing NULL will have it use StringQuery::$force
  327. */
  328. public function updateProp($target, $prop, $value, $force = null){
  329. if($force === null) //set $force to the default setting
  330. $force = $this->forced;
  331. if($force) //Add target to list of forced changes
  332. $this->force[] = $target;
  333. //Add it to the $data array
  334. if(isset($this->data[$target]) && is_array($this->data[$target])){
  335. //changes for this target exist, add/ovewrite property
  336. $this->data[$target][$prop] = $value;
  337. }else{
  338. //create new $data entry for this target
  339. $this->data[$target] = array(
  340. $prop => $value
  341. );
  342. }
  343. }
  344. /**
  345. * Call a StringQuery.methods function
  346. *
  347. * @since 1.0
  348. *
  349. * @param string $func The name of the function to call (no need to include @ prefix)
  350. * @param mixed $data The data to send to the function
  351. * @param bool $force Optional To force this change or not,
  352. * passing NULL will have it use StringQuery::$force
  353. */
  354. public function call($func, $data, $force = null){
  355. $func = "@$func";
  356. if($force === null) //set $force to the default setting
  357. $force = $this->forced;
  358. if($force) //Add target to list of forced changes
  359. $this->force[] = $func;
  360. //Add it to the $data array
  361. $this->data[$func] = $data;
  362. }
  363. /**
  364. * Update numerous targets with numerour properties
  365. *
  366. * @since 1.0
  367. *
  368. * @param array $targets An array of targets and their properties => values
  369. * @param bool $force Optional To force this change or not,
  370. * passing NULL will have it use StringQuery::$force
  371. */
  372. public function bulkUpdate($targets, $force = null){
  373. if($force === null) //set $force to the default setting
  374. $force = $this->forced;
  375. if($force) //Add targets to list of forced changes
  376. foreach($targets as $target => $data){
  377. $this->force[] = $target;
  378. }
  379. //Add it to the $data array
  380. $this->data = array_merge_recursive($this->data, $targets);
  381. }
  382. /**
  383. * Overloading call method
  384. *
  385. * Accepts update_PROPERTY to directly update a property.
  386. * Accepts bulkdUpdate_PROPERTY to bulk update a single property on multiple targets (target=>value style).
  387. * Accepts FUNCTION to call a StringQuery.methods function
  388. *
  389. * @since 1.1.0
  390. *
  391. * @param string $name The name of the method desired
  392. * @param array $arguments An array of arguments passed to the method
  393. */
  394. public function __call($name, $arguments){
  395. for($i = 0; $i < 3; $i++){
  396. if(!isset($arguments[$i]))
  397. $arguments[$i] = null;
  398. }
  399. if(preg_match('/^update_(\w+)$/', $name, $matches)){
  400. list($target, $value, $force) = $arguments;
  401. $this->updateProp($target, $matches[1], $value, $force);
  402. }elseif(preg_match('/^bulkUpdate_(\w+)$/', $name, $matches)){
  403. list($targets, $force) = $arguments;
  404. $_targets = array();
  405. foreach($targets as $target => $value){
  406. $_targets[$target] = array($matches[1] => $value);
  407. }
  408. $this->bulkUpdate($_targets, $force);
  409. }else{
  410. list($data, $force) = $arguments;
  411. $this->call($name, $data, $force);
  412. }
  413. }
  414. /**
  415. * Detect system load and adjust the update_interval property accordingly
  416. *
  417. * NOTE: Strongly encouraged to replace this method with your own version;
  418. * this one was designed for a 4 core server
  419. *
  420. * @since 1.0
  421. */
  422. private function sysLoadTime(){
  423. if(@file_exists('/proc/loadavg')){
  424. $load = explode(' ', file_get_contents('/proc/loadavg'));
  425. $load = (float) $load[0];
  426. }elseif(function_exists('shell_exec')){
  427. $load = explode(' ', `uptime`);
  428. $load = (float) $load[0];
  429. }else{
  430. $load = 1;
  431. }
  432. if($load <= 1)
  433. $u = mt_rand(1000, 2000);
  434. elseif($load <= 1.5)
  435. $u = mt_rand(1000, 2000);
  436. elseif($load <= 2.5)
  437. $u = mt_rand(1500, 2500);
  438. elseif($load <= 3)
  439. $u = mt_rand(2000, 3000);
  440. elseif($load <= 3.25)
  441. $u = mt_rand(2500, 3500);
  442. elseif($load <= 3.45)
  443. $u = mt_rand(3500, 4500);
  444. elseif($load <= 3.55)
  445. $u = mt_rand(4500, 5500);
  446. elseif($load <= 3.65)
  447. $u = mt_rand(5500, 6500);
  448. elseif($load <= 3.75)
  449. $u = mt_rand(6500, 7500);
  450. elseif($load <= 3.85)
  451. $u = mt_rand(7500, 8500);
  452. elseif($load <= 3.95)
  453. $u = mt_rand(9000, 11000);
  454. else
  455. $u = mt_rand(19000, 21000);
  456. $this->update_interval = $u;
  457. }
  458. }