diff --git a/.gitignore b/.gitignore index 697f64f1..b6c77831 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ /doc/articles/*.pdf /doc/presentations/*.pdf /yadisk-auth +/vendor *.swp diff --git a/api.php b/api.php index 53d26cb7..32d21c5a 100644 --- a/api.php +++ b/api.php @@ -1,86 +1,215 @@ API_VERSION, - 'answer' => null, - 'error' => null -); - -function json_encode_readable($arr) + +require_once('lib/common.php'); +require_once("lib/lib_users.php"); +require_once("lib/lib_achievements.php"); +require_once("lib/lib_annot.php"); + +function json($data) { + header('Content-type: application/json'); + echo json_encode($data); + die(); +} + +function require_fields($data, $fields) { - //convmap since 0x80 char codes so it takes all multibyte codes (above ASCII 127). So such characters are being "hidden" from normal json_encoding - array_walk_recursive($arr, function (&$item, $key) { if (is_string($item)) $item = mb_encode_numericentity($item, array (0x80, 0xffff, 0, 0xffff), 'UTF-8'); }); - return mb_decode_numericentity(json_encode($arr), array (0x80, 0xffff, 0, 0xffff), 'UTF-8'); + foreach ($fields as $field) { + if (!isset($data[$field])) { + throw new Exception("Action require '$field' field", 1); + } + } } +$config = parse_ini_file(__DIR__ . '/config.ini', true); +$pdo_db = new PDO(sprintf('mysql:host=%s;dbname=%s;charset=utf8', $config['mysql']['host'], $config['mysql']['dbname']), $config['mysql']['user'], $config['mysql']['passwd']); +$pdo_db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); +$pdo_db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); +$pdo_db->query("SET NAMES utf8"); -// check token for most action types -if (!in_array($action, array('search', 'login'))) { - $user_id = check_auth_token($_POST['user_id'], $_POST['token']); - if (!$user_id) - throw new Exception('Incorrect token'); -} +$anonActions = ['search', 'welcome', 'login', 'register', 'all_stat']; -try { -switch ($action) { - case 'search': - if (isset($_GET['all_forms'])) - $all_forms = (bool)$_GET['all_forms']; - else - $all_forms = false; +/* + * ACTIONS + */ + +// return is success +// throw Exception is error +$actions = [ + 'welcome' => function($data) { + return 'Welcome to opencorpora API v1.0!'; + }, + 'search' => function($data) { + require_fields($data, ['query']); - $answer['answer'] = get_search_results($_GET['query'], !$all_forms); + if (isset($data['all_forms'])) { + $all_forms = (bool)$data['all_forms']; + } else { + $all_forms = false; + } + $answer['answer'] = get_search_results($data['query'], !$all_forms); foreach ($answer['answer']['results'] as &$res) { - $parts = array(); + $parts = []; foreach (get_book_parents($res['book_id'], true) as $p) { $parts[] = $p['title']; } $res['text_fullname'] = join(': ', array_reverse($parts)); } - break; - case 'login': - $user_id = user_check_password($_POST['login'], $_POST['password']); + return $answer['answer']; + }, + 'login' => function($data) { + require_fields($data, ['login', 'password']); + + $user_id = user_check_password($data['login'], $data['password']); if ($user_id) { $token = remember_user($user_id, false, false); - $answer['answer'] = array('user_id' => $user_id, 'token' => $token); + return [ + 'token' => (string)$token, + 'user_id' => (int)$user_id, + ]; + } else { + throw new Exception("Incorrect login or password", 1); + } + }, + 'register' => function($data) { + require_fields($data, ['login', 'passwd', 'passwd_re', 'email']); + + $reg_status = user_register($data); + if ($reg_status == 1) { + return 'User created'; + } + throw new \Exception("Registration failed due to invalid data", 1); + }, + 'all_stat' => function($data) { + return get_user_stats(true, false); + }, + + // require token + 'get_available_morph_tasks' => function($data) { + require_fields($data, ['user_id']); + + return get_available_tasks($data['user_id'], true); + }, + 'get_morph_task' => function($data) { + require_fields($data, ['user_id', 'pool_id', 'size']); + + return get_annotation_packet($data['pool_id'], $data['size'], $data['user_id']); + }, + 'save_morph_task' => function($data) { + require_fields($data, ['user_id', 'answers']); + + $accepted = update_annot_instances($data['user_id'], $data['answers']); + if ($accepted != 0) { + return 'save task success'; + } else { + throw new Exception("Nothing save", 1); } - else - $answer['error'] = 'Incorrect login or password'; - break; - case 'get_available_morph_tasks': - $answer['answer'] = array('tasks' => get_available_tasks($user_id, true)); - break; - case 'get_morph_task': - if (empty($_POST['pool_id']) || empty($_POST['size'])) - throw new UnexpectedValueException("Wrong args"); - // timeout is in seconds - $answer['answer'] = get_annotation_packet($_POST['pool_id'], $_POST['size'], $user_id, $_POST['timeout']); - break; - case 'update_morph_task': - throw new Exception("Not implemented"); - // currently no backend - break; - case 'save_morph_task': - // answers is expected to be an array(array(id, answer), array(id, answer), ...) - update_annot_instances($user_id, $_POST['answers']); - break; - default: - throw new Exception('Unknown action'); + }, + + 'get_user' => function($data) { + require_fields($data, ['user_id']); + + $mgr = new UserOptionsManager(); + return [ + 'options' => $mgr->get_all_options(true), + 'current_email' => get_user_email($data['user_id']), + 'current_name' => get_user_shown_name($data['user_id']), + 'user_team' => get_user_team($data['user_id']), + ]; + + }, + 'save_user' => function($data) { + // update: + // shown_name OR email (+ passwd user_id) OR passwd (+old_passwd user_id) + if (isset($data['shown_name'])) { + if (user_change_shown_name($data['shown_name']) !== 1) { + throw new Exception("Error update 'shown_name' field", 1); + } + } + + if (isset($data['email'], $data['passwd'], $data['user_id'])) { + // NOTE: hotpatch + $r = sql_fetch_array(sql_query("SELECT user_name FROM users WHERE user_id = ".$data['user_id']." LIMIT 1")); + $login = $r['user_name']; + $email = strtolower(trim($data['email'])); + if (is_user_openid($data['user_id']) || user_check_password($login, $data['passwd'])) { + if (is_valid_email($email)) { + $res = sql_pe("SELECT user_id FROM users WHERE user_email=? LIMIT 1", array($email)); + if (sizeof($res) > 0) { + throw new Exception("Error update 'email' field", 1); + } + sql_pe("UPDATE `users` SET `user_email`=? WHERE `user_id`=? LIMIT 1", array($email, $data['user_id'])); + } else { + throw new Exception("Error update 'email' field", 1); + } + } else { + throw new Exception("Error update 'email' field", 1); + } + } + + if (isset($data['user_id'], $data['passwd'], $data['old_passwd'])) { + // NOTE: hotpatch + $r = sql_fetch_array(sql_query("SELECT user_name FROM users WHERE user_id = ".$data['user_id']." LIMIT 1")); + $login = $r['user_name']; + if (user_check_password($login, $data['old_passwd'])) { + if (!is_valid_password($data['passwd'])) { + throw new Exception("Error update 'passwd' field", 1); + } + $passwd = md5(md5($data['passwd']).substr($login, 0, 2)); + sql_query("UPDATE `users` SET `user_passwd`='$passwd' WHERE `user_id`=".$data['user_id']." LIMIT 1"); + } else { + throw new Exception("Error update 'passwd' field", 1); + } + } + return 'update user success'; + }, + + 'user_stat' => function($data) { + require_fields($data, ['user_id']); + + return get_user_info($data['user_id']); + }, + 'grab_badges' => function($data) { + require_fields($data, ['user_id']); + + $am2 = new AchievementsManager($data['user_id']); + return $am2->pull_all(); + }, +]; + +// action list +// var_dump(array_keys($actions)); die(); + + + +/* + * COMMON API CHECKS + */ + +if (!isset($_POST['action'])) { + json(['error' => '"action" field is required']); } -} catch (Exception $e) { - $answer['error'] = $e->getMessage(); +if (!in_array($_POST['action'], $anonActions)) { + $token = isset($_POST['token']) ? $_POST['token'] : false; + if (!$token) { + json(['error' => 'this API action require "token" field']); + } + $user_id = check_auth_token($token); + if (!$user_id) { + json(['error' => 'Incorrect token']); + } } -log_timing(); -die(json_encode_readable($answer)); -?> +// action REQUIRE, data OPTIONAL +$action = $_POST['action']; +$data = isset($_POST['data']) ? json_decode($_POST['data'], true) : null; + +if (isset($actions[$action])) { + try { + $answer = ['result' => $actions[$action]($data)]; + } catch (\Exception $e) { + $answer = ['error' => $e->getMessage()]; + } +} else { + $answer = ['error' => 'Unknown action']; +} +json($answer); diff --git a/index.php b/index.php index 20c1115c..08ad4334 100644 --- a/index.php +++ b/index.php @@ -8,6 +8,13 @@ return; } +// crutch for api +if ($_SERVER['REQUEST_URI'] == '/v1.0') { + require_once('api.php'); + log_timing(); + die(); +} + if (isset($_GET['page'])) { $page = $_GET['page']; switch ($page) { @@ -100,4 +107,3 @@ $smarty->display('index.tpl'); } log_timing(); -?> diff --git a/lib/lib_achievements.php b/lib/lib_achievements.php index 0543dcbb..23869161 100644 --- a/lib/lib_achievements.php +++ b/lib/lib_achievements.php @@ -326,4 +326,3 @@ public function dispatch($args) { $this->push(); } } - diff --git a/lib/lib_annot.php b/lib/lib_annot.php index c081d7fa..760f1aa9 100644 --- a/lib/lib_annot.php +++ b/lib/lib_annot.php @@ -460,7 +460,7 @@ function get_context_for_word($tf_id, $delta, $dir=0, $include_self=1) { $left_c = -1; //if there is left context to be added $right_c = 0; //same for right context $mw_pos = 0; - + static $query1 = NULL; // prepare the 1st query if ($query1 == NULL) diff --git a/lib/lib_dict.php b/lib/lib_dict.php index eea1f6a8..f71c6871 100644 --- a/lib/lib_dict.php +++ b/lib/lib_dict.php @@ -339,7 +339,7 @@ function smart_update_pending_token($parse_set, $rev_id) { // - deleted lemma // - lemma text change // - lemma gramset change - + $res = sql_pe("SELECT lemma_id, rev_text FROM dict_revisions WHERE rev_id=? LIMIT 1", array($rev_id)); if (!sizeof($res)) throw new Exception(); diff --git a/lib/lib_morph_pools.php b/lib/lib_morph_pools.php index 7a9114e4..d17e4275 100644 --- a/lib/lib_morph_pools.php +++ b/lib/lib_morph_pools.php @@ -887,7 +887,7 @@ function get_annotation_packet($pool_id, $size, $user_id=0, $timeout=0) { $r = sql_fetch_array(sql_query("SELECT status, t.gram_descr, revision, pool_type, doc_link FROM morph_annot_pools p LEFT JOIN morph_annot_pool_types t ON (p.pool_type = t.type_id) WHERE pool_id=$pool_id")); if ($r['status'] != MA_POOLS_STATUS_IN_PROGRESS) - throw new Exception(); + throw new Exception('This task is in progress'); $packet = array( 'my' => 0, 'editable' => 1, @@ -1165,4 +1165,3 @@ function get_pool_manual_page($type_id) { $res = sql_pe("SELECT doc_link FROM morph_annot_pool_types WHERE type_id=? LIMIT 1", array($type_id)); return $res[0]['doc_link']; } - diff --git a/lib/lib_users.php b/lib/lib_users.php index f0c1d2cf..be60e78a 100644 --- a/lib/lib_users.php +++ b/lib/lib_users.php @@ -53,8 +53,8 @@ function is_valid_email($string) { return preg_match('/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/i', $string); //we took the regexp from regular-expressions.info } -function check_auth_token($user_id, $token) { - $res = sql_pe("SELECT user_id FROM user_tokens WHERE user_id=? AND token=? LIMIT 1", array($user_id, $token)); +function check_auth_token($token) { + $res = sql_pe("SELECT user_id FROM user_tokens WHERE token=? LIMIT 1", [$token]); if (sizeof($res) > 0) { return $res[0]['user_id']; } @@ -112,6 +112,7 @@ function remember_user($user_id, $auth_token=false, $set_cookie=true) { return $token; } function make_new_user($login, $passwd, $email, $shown_name) { + // fix: add show_game = 0 sql_pe("INSERT INTO `users` VALUES(NULL, ?, ?, ?, ?, ?, 0, 1, 1, 0)", array($login, $passwd, $email, time(), $shown_name)); $user_id = sql_insert_id(); diff --git a/lib/timer.php b/lib/timer.php index cbe15e57..068fe8d9 100644 --- a/lib/timer.php +++ b/lib/timer.php @@ -1,4 +1,5 @@