sync.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. <?php
  2. /**
  3. * Gasteizko Margolariak API v3 - Sync
  4. *
  5. * Used to sync data with apps with persistent storage (i.e. no web apps).
  6. * This file is to be called directly from a URL request.
  7. *
  8. * @link https://margolariak.com/API/v3/help/
  9. *
  10. * @since 1.0.0
  11. */
  12. // Database section identifiers
  13. define('SEC_ALL', 'all');
  14. define('SEC_BLOG', 'blog');
  15. define('SEC_ACTIVITIES', 'activities');
  16. define('SEC_GALLERY', 'gallery');
  17. define('SEC_LABLANCA', 'lablanca');
  18. // Tables
  19. define('TAB_ACTIVITY', 'activity');
  20. define('TAB_ACTIVITY_COMMENT', 'activity_comment');
  21. define('TAB_ACTIVITY_IMAGE', 'activity_image');
  22. define('TAB_ACTIVITY_ITINERARY', 'activity_itinerary');
  23. define('TAB_ACTIVITY_TAG', 'activity_tag');
  24. define('TAB_ALBUM', 'album');
  25. define('TAB_FESTIVAL', 'festival');
  26. define('TAB_FESTIVAL_DAY', 'festival_day');
  27. define('TAB_FESTIVAL_EVENT_CITY', 'festival_event_city');
  28. define('TAB_FESTIVAL_EVENT_GM', 'festival_event_gm');
  29. define('TAB_FESTIVAL_OFFER', 'festival_offer');
  30. define('TAB_PEOPLE', 'people');
  31. define('TAB_PHOTO', 'photo');
  32. define('TAB_PHOTO_ALBUM', 'photo_album');
  33. define('TAB_PHOTO_COMMENT', 'photo_comment');
  34. define('TAB_PLACE', 'place');
  35. define('TAB_POST', 'post');
  36. define('TAB_POST_COMMENT', 'post_comment');
  37. define('TAB_POST_IMAGE', 'post_image');
  38. define('TAB_POST_TAG', 'post_tag');
  39. define('TAB_ROUTE', 'route');
  40. define('TAB_ROUTE_POINT', 'route_point');
  41. define('TAB_SETTINGS', 'settings');
  42. define('TAB_SPONSOR', 'sponsor');
  43. // $_GET key parameters
  44. define('GET_CLIENT', 'client');
  45. define('GET_USER', 'user');
  46. define('GET_FOREGROUND', 'foreground');
  47. // Error messages
  48. define('ERR_CLIENT', 'CLIENT');
  49. /**
  50. * List of all tables that can be synced, sorted by priority / dependencies.
  51. *
  52. * @var string $tab_list
  53. */
  54. $tab_list = [TAB_SETTINGS, TAB_PLACE, TAB_ROUTE_POINT,
  55. TAB_ROUTE, TAB_PEOPLE, TAB_FESTIVAL_EVENT_GM,
  56. TAB_FESTIVAL, TAB_FESTIVAL_DAY, TAB_FESTIVAL_OFFER,
  57. TAB_FESTIVAL_EVENT_CITY, TAB_ACTIVITY, TAB_ACTIVITY_IMAGE,
  58. TAB_ACTIVITY_ITINERARY, TAB_SPONSOR, TAB_ALBUM,
  59. TAB_PHOTO, TAB_PHOTO_ALBUM, TAB_POST,
  60. TAB_POST_IMAGE, TAB_PHOTO_COMMENT, TAB_POST_COMMENT,
  61. TAB_ACTIVITY_COMMENT, TAB_ACTIVITY_TAG, TAB_POST_TAG];
  62. /**
  63. * Initializes the MySQL database connection.
  64. *
  65. * Called at the beggining of the script. It connects to the database using the
  66. * parameters in the .htpasswd file. It also sets database and page encodings.
  67. *
  68. * @since 1.0.0
  69. * @return object Database connection.
  70. */
  71. function startdb(){
  72. //Include the db configuration file. It's somehow like this
  73. /*
  74. <?php
  75. $host = 'XXXX';
  76. $db_name = 'XXXX';
  77. $username_ro = 'XXXX';
  78. $username_rw = 'XXXX';
  79. $pass_ro = 'XXXX';
  80. $pass_rw = 'XXXX';
  81. ?>
  82. */
  83. include('../../.htpasswd');
  84. //Connect to to database
  85. $con = mysqli_connect($host, $username_rw, $pass_rw, $db_name);
  86. //Set encoding options
  87. mysqli_set_charset($con, 'utf-8');
  88. header('Content-Type: text/html; charset=utf8');
  89. mysqli_query($con, 'SET NAMES utf8;');
  90. //Return the db connection
  91. return $con;
  92. }
  93. /**
  94. * Extracts a request parameter.
  95. *
  96. * Extracts the value of a parameter from the list of GET parameters,
  97. * sanitizing it to prevent SQL injections.
  98. *
  99. * @since 3.0.0
  100. * @param object $con Open database connection.
  101. * @param string $param Key of the parameter to retrieve.
  102. * @return string Value of the parameter or an empty string if it was not found.
  103. */
  104. function extract_param($con, $param){
  105. if(isset($_GET[$param])){
  106. return mysqli_real_escape_string($con, $_GET[$param]);
  107. }
  108. else{
  109. return "";
  110. }
  111. }
  112. /**
  113. * Gets request info.
  114. *
  115. * Gets information about the request by reading it's parameters.
  116. *
  117. * @since 3.0.0
  118. * @param object $con Open database connection.
  119. * @param array $get Array with the request parameters.
  120. * @return array {
  121. * @type string client Client identifier. Empty if not provided.
  122. * @type string user User identifier. Empty if not provided.
  123. * @type int foreground 1 if the sync is being made in the app
  124. * foreground, 0 otherwise.
  125. * @type string ip Client IP.
  126. * @type string os Client operating system identifier. Empty if not
  127. * found.
  128. * @type string browser Client browser identifier. Empty if not found.
  129. * @type string uagent Client user agent. Empty if not found.
  130. * @type string error Will contain ERR_CLIENT if the client was not
  131. * specified, empty otherwise.
  132. * }
  133. */
  134. function get_user_info($con, $get){
  135. $info = array();
  136. $error = "";
  137. $info["client"] = extract_param($con, GET_CLIENT);
  138. if(strlen($info["client"]) == 0) {
  139. error_log("SYNC ERROR: Trying to sync with no client name.");
  140. $error = ERR_CLIENT;
  141. }
  142. $info["user"] = extract_param($con, GET_USER);
  143. $info["foreground"] = (int) extract_param($con, GET_FOREGROUND);
  144. if($info["foreground"] != 1){
  145. $info["foreground"] = 0;
  146. }
  147. $info["ip"] = get_user_ip();
  148. $browser_data = get_browser(null, true);
  149. $info["os"] = $browser_data['platform'];
  150. $info["browser"] = $browser_data['browser'];
  151. $info["uagent"] = $browser_data['browser_name_pattern'];
  152. $info["error"] = $error;
  153. return $info;
  154. }
  155. /**
  156. * Reads the table version in the client app.
  157. *
  158. * Reads the version of the tables reported by the user as GET parameters.
  159. * Those parameters must be the same as the table names listed in
  160. * {@see $tab_list}.
  161. *
  162. * @since 3.0.0
  163. * @global array $tab_list Array with the names of the tables to sync.
  164. * @param object $con Open database connection.
  165. * @param array $get Array with the request parameters.
  166. * @return array Integer array with the version of the tables reported in
  167. * the request, keyed with the table names. If no table
  168. * version was specified, the array will be empty.
  169. */
  170. function get_user_versions($con, $get){
  171. global $tab_list;
  172. $versions = array();
  173. foreach($tab_list as $tab){
  174. $versions[$tab] = intval(extract_param($con, $tab));
  175. }
  176. return $versions;
  177. }
  178. /**
  179. * Reads the table version in the server.
  180. *
  181. * Reads from the database the version of the tables that sync with the
  182. * clients.
  183. *
  184. * @since 3.0.0
  185. * @param object $con Open database connection.
  186. * @return array Integer array with the version of the tables in the
  187. * database, keyed with the table names.
  188. */
  189. function get_server_versions($con){
  190. $versions = array();
  191. $q = mysqli_query($con, "SELECT section, version FROM version;");
  192. while($r = mysqli_fetch_array($q)){
  193. $versions[$r['section']] = $r['version'];
  194. }
  195. return $versions;
  196. }
  197. /**
  198. * Select the tables that need to be synced.
  199. *
  200. * Determines the tables in {@see $tab_list} that are out of sync between
  201. * the server and the client.
  202. *
  203. * @since 3.0.0
  204. * @global array $tab_list Array with the names of the tables that sync
  205. * whit clients.
  206. * @param object $con Open database connection.
  207. * @param array $user Integer array with the version of the tables reported
  208. * in the request, keyed with the table names.
  209. * @param array $server Integer array with the version of the tables in the
  210. * database, keyed with the table names.
  211. * @return array String array with the name of the tables present in $user whose
  212. * versions are lower than the ones in $server.
  213. */
  214. function select_tables($user, $server){
  215. global $tab_list;
  216. $tables = array();
  217. foreach($tab_list as $table){
  218. if ((int) $user[$table] < (int) $server[$table]){
  219. array_push($tables, $table);
  220. }
  221. }
  222. return $tables;
  223. }
  224. /**
  225. * JSON-izes the versions of the tables to sync.
  226. *
  227. * Generates a JSON-formatted string with the versions of all the tables
  228. * to sync.
  229. *
  230. * @since 3.0.0
  231. * @param object $con Open database connection.
  232. * @param array $tables String array with the names of the tables.
  233. * @return string JSON-formatted string with the version of the tables.
  234. * Empty string if no valid table names were passes in $tables.
  235. */
  236. function get_table_version($con, $tables){
  237. // Build query, showing only tables to sync
  238. $s = "SELECT * FROM version WHERE ";
  239. foreach($tables as $table){
  240. $s = $s . "section = '$table' OR ";
  241. }
  242. $s = $s . "1 = 2;";
  243. $q = mysqli_query($con, $s);
  244. //If no rows, return
  245. if (mysqli_num_rows($q) == 0){
  246. return "";
  247. }
  248. //Create result JSON
  249. $str = "";
  250. $str = $str. "\"version\":[";
  251. while($r = mysqli_fetch_assoc($q)) {
  252. $str = $str . json_encode($r) . ",";
  253. }
  254. $str = rtrim($str,',');
  255. $str = $str . "],";
  256. return $str;
  257. }
  258. /**
  259. * JSON-izes the data in a table.
  260. *
  261. * Generates a JSON-formatted string with the data in a table. Inaccessible
  262. * or sensitive tables or fields are not returned.
  263. *
  264. * @since 1.0.0
  265. * @param object $con Open database connection.
  266. * @param string $table Table name.
  267. * @return string JSON-formatted string with the data in the table. Empty
  268. * string if $table was not a valid table name.
  269. */
  270. function get_table($con, $table){
  271. $table = strtolower($table);
  272. switch ($table){
  273. case TAB_ACTIVITY:
  274. $q = mysqli_query($con, "SELECT id, permalink, date, city, title_es, title_en, title_eu, text_es, text_eu, text_en, after_es, after_en, after_eu, price, inscription, max_people, album FROM activity WHERE visible = 1;");
  275. break;
  276. case TAB_ACTIVITY_COMMENT:
  277. $q = mysqli_query($con, "SELECT id, activity, text, dtime, username, lang FROM activity_comment WHERE approved = 1;");
  278. break;
  279. case TAB_ALBUM:
  280. $q = mysqli_query($con, "SELECT id, permalink, title_es, title_en, title_eu, description_es, description_en, description_eu, open FROM album;");
  281. break;
  282. case TAB_PHOTO:
  283. $q = mysqli_query($con, "SELECT photo.id AS id, file, permalink, title_es, title_en, title_eu, description_es, description_en, description_eu, uploaded, place, width, height, size, CONCAT(photo.username, user) AS username FROM photo, user WHERE user.id = photo.user AND approved = 1;");
  284. break;
  285. case TAB_POST:
  286. $q = mysqli_query($con, "SELECT post.id AS id, permalink, title_es, title_en, title_eu, text_es, text_en, text_eu, comments, username, dtime FROM post, user WHERE user.id = user AND visible = 1;");
  287. break;
  288. case TAB_POST_COMMENT:
  289. $q = mysqli_query($con, "SELECT post_comment.id AS id, post, text, dtime, username, lang FROM post_comment WHERE approved = 1;");
  290. break;
  291. case TAB_PHOTO_COMMENT:
  292. $q = mysqli_query($con, "SELECT photo_comment.id AS id, post, text, dtime, username, lang FROM photo_comment WHERE approved = 1;");
  293. break;
  294. case TAB_SPONSOR:
  295. $q = mysqli_query($con, "SELECT id, name_es, name_en, name_eu, text_es, text_en, text_eu, image, address_es, address_en, address_eu, link, lat, lon FROM sponsor;");
  296. break;
  297. case TAB_SETTINGS:
  298. $q = mysqli_query($con, "SELECT name, value FROM settings;");
  299. break;
  300. //Other cases:
  301. default:
  302. $q = mysqli_query($con, "SELECT * FROM $table;");
  303. }
  304. //If no rows, return
  305. if (mysqli_num_rows($q) == 0){
  306. return "";
  307. }
  308. //Create result array
  309. $str = "";
  310. $str = $str. "\"$table\":[";
  311. while($r = mysqli_fetch_assoc($q)) {
  312. $str = $str . json_encode($r) . ",";
  313. }
  314. $str = rtrim($str,",");
  315. $str = $str . "]";
  316. return $str;
  317. }
  318. /**
  319. * Gets the data on the requested tables.
  320. *
  321. * Builds a JSON string with the data in all the requested tables.
  322. * Inaccessible or sensitive tables or fields are not returned.
  323. *
  324. * @since 1.0.0
  325. * @see get_table($con, $table)
  326. * @param object $con Open database connection.
  327. * @param array $tables String array with the names of the tables to sync.
  328. * @return string JSON-formatted string with the data in the requested
  329. * tables. Empty string if no valid table names were
  330. * provided in $tables.
  331. */
  332. function sync($con, $tables){
  333. $str = "";
  334. if(sizeof($tables) > 0){
  335. $str = "{" . get_table_version($con, $tables);
  336. foreach($tables as $table){
  337. $str = $str . get_table($con, $table) . ",";
  338. }
  339. $str = rtrim($str, ",");
  340. $str = $str . "}";
  341. $str = str_replace(",,", ",", $str);
  342. if (strlen($str) > 0){
  343. log_sync($con, $user, $synced);
  344. echo($str);
  345. http_response_code(200);
  346. }
  347. else{
  348. http_response_code(204);
  349. }
  350. return true;
  351. }
  352. http_response_code(204);
  353. return false;
  354. }
  355. /**
  356. * Gets the user IP address.
  357. *
  358. * @since 1.0.0
  359. * @return string User IP address.
  360. */
  361. function get_user_ip(){
  362. $client = @$_SERVER['HTTP_CLIENT_IP'];
  363. $forward = @$_SERVER['HTTP_X_FORWARDED_FOR'];
  364. $remote = $_SERVER['REMOTE_ADDR'];
  365. if(filter_var($client, FILTER_VALIDATE_IP)){
  366. $ip = $client;
  367. }
  368. elseif(filter_var($forward, FILTER_VALIDATE_IP)){
  369. $ip = $forward;
  370. }
  371. else{
  372. $ip = $remote;
  373. }
  374. return $ip;
  375. }
  376. /**
  377. * Logs a request to the database.
  378. *
  379. * Creates an entry in the table 'sync' with the details of the request.
  380. *
  381. * @since 1.0.0
  382. * @param object $con Open database connection.
  383. * @param array $user {
  384. * @type string client Client identifier. Empty if not provided.
  385. * @type string user User identifier. Empty if not provided.
  386. * @type int foreground 1 if the sync is being made in the app
  387. * foreground, 0 otherwise.
  388. * @type string ip Client IP.
  389. * @type string os Client operating system identifier. Empty if not
  390. * found.
  391. * @type string browser Client browser identifier. Empty if not found.
  392. * @type string uagent Client user agent. Empty if not found.
  393. * }
  394. * @param int synced 1 if sync data was finally sent, 0 otherwise.
  395. */
  396. function log_sync($con, $user, $synced){
  397. mysqli_query($con, "INSERT INTO sync (client, user, fg, synced, ip, os, uagent) VALUES ('$user[client]', '$user[user]', $user[foreground], $synced, '$user[ip]', '$user[os]', '$user[uagent]');");
  398. }
  399. /**
  400. * Logs a failed request to the database.
  401. *
  402. * Creates an entry in the table 'sync' with the details of the failed request.
  403. *
  404. * @since 1.0.0
  405. * @param object $con Open database connection.
  406. * @param array $user {
  407. * @type string client Client identifier. Empty if not provided.
  408. * @type string user User identifier. Empty if not provided.
  409. * @type int foreground 1 if the sync is being made in the app
  410. * foreground, 0 otherwise.
  411. * @type string ip Client IP.
  412. * @type string os Client operating system identifier. Empty if not
  413. * found.
  414. * @type string browser Client browser identifier. Empty if not found.
  415. * @type string uagent Client user agent. Empty if not found.
  416. * @type string error Error code.
  417. * }
  418. */
  419. function log_error($con, $user){
  420. mysqli_query($con, "INSERT INTO sync (client, user, fg, error, ip, os, uagent) VALUES ('$user[client]', '$user[user]', $user[foreground], $user[error], '$user[ip]', '$user[os]', '$user[uagent]');");
  421. }
  422. // SCRIPT START
  423. // Connect to the database
  424. $con = startdb('rw');
  425. // Get info about the user
  426. $user = get_user_info($con, $_GET);
  427. if(strlen($user["error"]) > 0){
  428. log_error($con, $user);
  429. http_response_code(400);
  430. exit(-1);
  431. }
  432. // Get tables to sync
  433. $v_user = get_user_versions($con, $_GET);
  434. $v_server = get_server_versions($con);
  435. $tables = select_tables($v_user, $v_server);
  436. $synced = sync($con, $tables);
  437. ?>