sync.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  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 Optional. Array with the request parameters. Default
  120. * is $_GET.
  121. * @return array {
  122. * @type string client Client identifier. Empty if not provided.
  123. * @type string user User identifier. Empty if not provided.
  124. * @type int foreground 1 if the sync is being made in the app
  125. * foreground, 0 otherwise.
  126. * @type string ip Client IP.
  127. * @type string os Client operating system identifier. Empty if not
  128. * found.
  129. * @type string browser Client browser identifier. Empty if not found.
  130. * @type string uagent Client user agent. Empty if not found.
  131. * @type string error Will contain ERR_CLIENT if the client was not
  132. * specified, empty otherwise.
  133. * }
  134. */
  135. function get_user_info($con, $get = $_GET){
  136. $info = array();
  137. $error = "";
  138. $info["client"] = extract_param($con, GET_CLIENT);
  139. if(strlen($info["client"]) == 0) {
  140. error_log("SYNC ERROR: Trying to sync with no client name.");
  141. $error = ERR_CLIENT;
  142. }
  143. $info["user"] = extract_param($con, GET_USER);
  144. $info["foreground"] = (int) extract_param($con, GET_FOREGROUND);
  145. if($info["foreground"] != 1){
  146. $info["foreground"] = 0;
  147. }
  148. $info["ip"] = get_user_ip();
  149. $browser_data = get_browser(null, true);
  150. $info["os"] = $browser_data['platform'];
  151. $info["browser"] = $browser_data['browser'];
  152. $info["uagent"] = $browser_data['browser_name_pattern'];
  153. $info["error"] = $error;
  154. return $info;
  155. }
  156. /**
  157. * Reads the table version in the client app.
  158. *
  159. * Reads the version of the tables reported by the user as GET parameters.
  160. * Those parameters must be the same as the table names listed in
  161. * {@see $tab_list}.
  162. *
  163. * @since 3.0.0
  164. * @global array $tab_list Array with the names of the tables to sync.
  165. * @param object $con Open database connection.
  166. * @param array $get Optional. Array with the request parameters. Default
  167. * is $_GET.
  168. * @return array Integer array with the version of the tables reported in
  169. * the request, keyed with the table names. If no table
  170. * version was specified, the array will be empty.
  171. */
  172. function get_user_versions($con, $get = $_GET){
  173. global $tab_list;
  174. $versions = array();
  175. foreach($tab_list as $tab){
  176. $versions[$tab] = intval(extract_param($con, $tab));
  177. }
  178. return $versions;
  179. }
  180. /**
  181. * Reads the table version in the server.
  182. *
  183. * Reads from the database the version of the tables that sync with the
  184. * clients.
  185. *
  186. * @since 3.0.0
  187. * @param object $con Open database connection.
  188. * @return array Integer array with the version of the tables in the
  189. * database, keyed with the table names.
  190. */
  191. function get_server_versions($con){
  192. $versions = array();
  193. $q = mysqli_query($con, "SELECT section, version FROM version;");
  194. while($r = mysqli_fetch_array($q)){
  195. $versions[$r['section']] = $r['version'];
  196. }
  197. return $versions;
  198. }
  199. /**
  200. * Select the tables that need to be synced.
  201. *
  202. * Determines the tables in {@see $tab_list} that are out of sync between
  203. * the server and the client.
  204. *
  205. * @since 3.0.0
  206. * @global array $tab_list Array with the names of the tables that sync
  207. * whit clients.
  208. * @param object $con Open database connection.
  209. * @param array $user Integer array with the version of the tables reported
  210. * in the request, keyed with the table names.
  211. * @param array $server Integer array with the version of the tables in the
  212. * database, keyed with the table names.
  213. * @return array String array with the name of the tables present in $user whose
  214. * versions are lower than the ones in $server.
  215. */
  216. function select_tables($user, $server){
  217. global $tab_list;
  218. $tables = array();
  219. foreach($tab_list as $table){
  220. if ((int) $user[$table] < (int) $server[$table]){
  221. array_push($tables, $table);
  222. }
  223. }
  224. return $tables;
  225. }
  226. /**
  227. * JSON-izes the versions of the tables to sync.
  228. *
  229. * Generates a JSON-formatted string with the versions of all the tables
  230. * to sync.
  231. *
  232. * @since 3.0.0
  233. * @param object $con Open database connection.
  234. * @param array $tables String array with the names of the tables.
  235. * @return string JSON-formatted string with the version of the tables.
  236. * Empty string if no valid table names were passes in $tables.
  237. */
  238. function get_table_version($con, $tables){
  239. // Build query, showing only tables to sync
  240. $s = "SELECT * FROM version WHERE ";
  241. foreach($tables as $table){
  242. $s = $s . "section = '$table' OR ";
  243. }
  244. $s = $s . "1 = 2;";
  245. $q = mysqli_query($con, $s);
  246. //If no rows, return
  247. if (mysqli_num_rows($q) == 0){
  248. return "";
  249. }
  250. //Create result JSON
  251. $str = "";
  252. $str = $str. "\"version\":[";
  253. while($r = mysqli_fetch_assoc($q)) {
  254. $str = $str . json_encode($r) . ",";
  255. }
  256. $str = rtrim($str,',');
  257. $str = $str . "],";
  258. return $str;
  259. }
  260. /**
  261. * JSON-izes the data in a table.
  262. *
  263. * Generates a JSON-formatted string with the data in a table. Inaccessible
  264. * or sensitive tables or fields are not returned.
  265. *
  266. * @since 1.0.0
  267. * @param object $con Open database connection.
  268. * @param string $table Table name.
  269. * @return string JSON-formatted string with the data in the table. Empty
  270. * string if $table was not a valid table name.
  271. */
  272. function get_table($con, $table){
  273. $table = strtolower($table);
  274. switch ($table){
  275. case TAB_ACTIVITY:
  276. $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;");
  277. break;
  278. case TAB_ACTIVITY_COMMENT:
  279. $q = mysqli_query($con, "SELECT id, activity, text, dtime, username, lang FROM activity_comment WHERE approved = 1;");
  280. break;
  281. case TAB_ALBUM:
  282. $q = mysqli_query($con, "SELECT id, permalink, title_es, title_en, title_eu, description_es, description_en, description_eu, open FROM album;");
  283. break;
  284. case TAB_PHOTO:
  285. $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;");
  286. break;
  287. case TAB_POST:
  288. $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;");
  289. break;
  290. case TAB_POST_COMMENT:
  291. $q = mysqli_query($con, "SELECT post_comment.id AS id, post, text, dtime, username, lang FROM post_comment WHERE approved = 1;");
  292. break;
  293. case TAB_PHOTO_COMMENT:
  294. $q = mysqli_query($con, "SELECT photo_comment.id AS id, post, text, dtime, username, lang FROM photo_comment WHERE approved = 1;");
  295. break;
  296. case TAB_SPONSOR:
  297. $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;");
  298. break;
  299. case TAB_SETTINGS:
  300. $q = mysqli_query($con, "SELECT name, value FROM settings;");
  301. break;
  302. //Other cases:
  303. default:
  304. $q = mysqli_query($con, "SELECT * FROM $table;");
  305. }
  306. //If no rows, return
  307. if (mysqli_num_rows($q) == 0){
  308. return "";
  309. }
  310. //Create result array
  311. $str = "";
  312. $str = $str. "\"$table\":[";
  313. while($r = mysqli_fetch_assoc($q)) {
  314. $str = $str . json_encode($r) . ",";
  315. }
  316. $str = rtrim($str,",");
  317. $str = $str . "]";
  318. return $str;
  319. }
  320. /**
  321. * Gets the data on the requested tables.
  322. *
  323. * Builds a JSON string with the data in all the requested tables.
  324. * Inaccessible or sensitive tables or fields are not returned.
  325. *
  326. * @since 1.0.0
  327. * @see get_table($con, $table)
  328. * @param object $con Open database connection.
  329. * @param array $tables String array with the names of the tables to sync.
  330. * @return string JSON-formatted string with the data in the requested
  331. * tables. Empty string if no valid table names were
  332. * provided in $tables.
  333. */
  334. function sync($con, $tables){
  335. $str = "";
  336. if(sizeof($tables) > 0){
  337. $str = "{" . get_table_version($con, $tables);
  338. foreach($tables as $table){
  339. $str = $str . get_table($con, $table) . ",";
  340. }
  341. $str = rtrim($str, ",");
  342. $str = $str . "}";
  343. $str = str_replace(",,", ",", $str);
  344. echo($str);
  345. return true;
  346. }
  347. return false;
  348. }
  349. /**
  350. * Gets the user IP address.
  351. *
  352. * @since 1.0.0
  353. * @return string User IP address.
  354. */
  355. function get_user_ip(){
  356. $client = @$_SERVER['HTTP_CLIENT_IP'];
  357. $forward = @$_SERVER['HTTP_X_FORWARDED_FOR'];
  358. $remote = $_SERVER['REMOTE_ADDR'];
  359. if(filter_var($client, FILTER_VALIDATE_IP)){
  360. $ip = $client;
  361. }
  362. elseif(filter_var($forward, FILTER_VALIDATE_IP)){
  363. $ip = $forward;
  364. }
  365. else{
  366. $ip = $remote;
  367. }
  368. return $ip;
  369. }
  370. /**
  371. * Logs a request to the database.
  372. *
  373. * Creates an entry in the table 'sync' with the details of the request.
  374. *
  375. * @since 1.0.0
  376. * @param object $con Open database connection.
  377. * @param array $user {
  378. * @type string client Client identifier. Empty if not provided.
  379. * @type string user User identifier. Empty if not provided.
  380. * @type int foreground 1 if the sync is being made in the app
  381. * foreground, 0 otherwise.
  382. * @type string ip Client IP.
  383. * @type string os Client operating system identifier. Empty if not
  384. * found.
  385. * @type string browser Client browser identifier. Empty if not found.
  386. * @type string uagent Client user agent. Empty if not found.
  387. * }
  388. * @param int synced 1 if sync data was finally sent, 0 otherwise.
  389. */
  390. function log_sync($con, $user, $synced){
  391. 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]');");
  392. }
  393. /**
  394. * Logs a failed request to the database.
  395. *
  396. * Creates an entry in the table 'sync' with the details of the failed request.
  397. *
  398. * @since 1.0.0
  399. * @param object $con Open database connection.
  400. * @param array $user {
  401. * @type string client Client identifier. Empty if not provided.
  402. * @type string user User identifier. Empty if not provided.
  403. * @type int foreground 1 if the sync is being made in the app
  404. * foreground, 0 otherwise.
  405. * @type string ip Client IP.
  406. * @type string os Client operating system identifier. Empty if not
  407. * found.
  408. * @type string browser Client browser identifier. Empty if not found.
  409. * @type string uagent Client user agent. Empty if not found.
  410. * @type string error Error code.
  411. * }
  412. */
  413. function log_error($con, $user){
  414. 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]');");
  415. }
  416. // SCRIPT START
  417. // Connect to the database
  418. $con = startdb('rw');
  419. // Get info about the user
  420. $user = get_user_info($con, $_GET);
  421. if(strlen($user["error"]) > 0){
  422. log_error($con, $user);
  423. http_response_code(400);
  424. exit(-1);
  425. }
  426. // Get tables to sync
  427. $v_user = get_user_versions($con, $_GET);
  428. $v_server = get_server_versions($con);
  429. $tables = select_tables($v_user, $v_server);
  430. $synced = sync($con, $tables);
  431. //Log the sync in the database
  432. log_sync($con, $user, $synced);
  433. ?>