api de gestion de ticket, basé sur php-crud-api. Le but est de décorrélé les outils de gestion des données, afin
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

api.php 42KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371
  1. <?php
  2. interface DatabaseInterface {
  3. public function get_sql($name);
  4. public function connectDatabase($hostname,$username,$password,$database,$port,$socket,$charset);
  5. public function query($sql,$params);
  6. public function fetch_assoc($result);
  7. public function fetch_row($result);
  8. public function insert_id($result);
  9. public function affected_rows($result);
  10. public function close($result);
  11. public function fetch_fields($result);
  12. public function add_limit_to_sql($sql,$limit,$offset);
  13. public function likeEscape($string);
  14. public function is_binary_type($field);
  15. public function base64_encode($string);
  16. public function getDefaultCharset();
  17. }
  18. class MySQL implements DatabaseInterface {
  19. protected $db;
  20. protected $queries;
  21. public function __construct() {
  22. $this->queries = array(
  23. 'reflect_table'=>'SELECT
  24. "TABLE_NAME"
  25. FROM
  26. "INFORMATION_SCHEMA"."TABLES"
  27. WHERE
  28. "TABLE_NAME" COLLATE \'utf8_bin\' = ? AND
  29. "TABLE_SCHEMA" = ?',
  30. 'reflect_pk'=>'SELECT
  31. "COLUMN_NAME"
  32. FROM
  33. "INFORMATION_SCHEMA"."COLUMNS"
  34. WHERE
  35. "COLUMN_KEY" = \'PRI\' AND
  36. "TABLE_NAME" = ? AND
  37. "TABLE_SCHEMA" = ?',
  38. 'reflect_belongs_to'=>'SELECT
  39. "TABLE_NAME","COLUMN_NAME",
  40. "REFERENCED_TABLE_NAME","REFERENCED_COLUMN_NAME"
  41. FROM
  42. "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE"
  43. WHERE
  44. "TABLE_NAME" COLLATE \'utf8_bin\' = ? AND
  45. "REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' IN ? AND
  46. "TABLE_SCHEMA" = ? AND
  47. "REFERENCED_TABLE_SCHEMA" = ?',
  48. 'reflect_has_many'=>'SELECT
  49. "TABLE_NAME","COLUMN_NAME",
  50. "REFERENCED_TABLE_NAME","REFERENCED_COLUMN_NAME"
  51. FROM
  52. "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE"
  53. WHERE
  54. "TABLE_NAME" COLLATE \'utf8_bin\' IN ? AND
  55. "REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' = ? AND
  56. "TABLE_SCHEMA" = ? AND
  57. "REFERENCED_TABLE_SCHEMA" = ?',
  58. 'reflect_habtm'=>'SELECT
  59. k1."TABLE_NAME", k1."COLUMN_NAME",
  60. k1."REFERENCED_TABLE_NAME", k1."REFERENCED_COLUMN_NAME",
  61. k2."TABLE_NAME", k2."COLUMN_NAME",
  62. k2."REFERENCED_TABLE_NAME", k2."REFERENCED_COLUMN_NAME"
  63. FROM
  64. "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" k1,
  65. "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" k2
  66. WHERE
  67. k1."TABLE_SCHEMA" = ? AND
  68. k2."TABLE_SCHEMA" = ? AND
  69. k1."REFERENCED_TABLE_SCHEMA" = ? AND
  70. k2."REFERENCED_TABLE_SCHEMA" = ? AND
  71. k1."TABLE_NAME" COLLATE \'utf8_bin\' = k2."TABLE_NAME" COLLATE \'utf8_bin\' AND
  72. k1."REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' = ? AND
  73. k2."REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' IN ?'
  74. );
  75. }
  76. public function get_sql($name) {
  77. return isset($this->queries[$name])?$this->queries[$name]:false;
  78. }
  79. public function connectDatabase($hostname,$username,$password,$database,$port,$socket,$charset) {
  80. $db = mysqli_connect($hostname,$username,$password,$database,$port,$socket);
  81. if (mysqli_connect_errno()) {
  82. throw new \Exception('Connect failed. '.mysqli_connect_error());
  83. }
  84. if (!mysqli_set_charset($db,$charset)) {
  85. throw new \Exception('Error setting charset. '.mysqli_error($db));
  86. }
  87. if (!mysqli_query($db,'SET SESSION sql_mode = \'ANSI_QUOTES\';')) {
  88. throw new \Exception('Error setting ANSI quotes. '.mysqli_error($db));
  89. }
  90. $this->db = $db;
  91. }
  92. public function query($sql,$params) {
  93. $db = $this->db;
  94. $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params) {
  95. $param = array_shift($params);
  96. if ($matches[0]=='!') return preg_replace('/[^a-zA-Z0-9\-_=<>]/','',$param);
  97. if (is_array($param)) return '('.implode(',',array_map(function($v) use (&$db) {
  98. return "'".mysqli_real_escape_string($db,$v)."'";
  99. },$param)).')';
  100. if (is_object($param) && $param->type=='base64') {
  101. return "x'".bin2hex(base64_decode($param->data))."'";
  102. }
  103. if ($param===null) return 'NULL';
  104. return "'".mysqli_real_escape_string($db,$param)."'";
  105. }, $sql);
  106. //if (!strpos($sql,'INFORMATION_SCHEMA')) echo "\n$sql\n";
  107. return mysqli_query($db,$sql);
  108. }
  109. public function fetch_assoc($result) {
  110. return mysqli_fetch_assoc($result);
  111. }
  112. public function fetch_row($result) {
  113. return mysqli_fetch_row($result);
  114. }
  115. public function insert_id($result) {
  116. return mysqli_insert_id($this->db);
  117. }
  118. public function affected_rows($result) {
  119. return mysqli_affected_rows($this->db);
  120. }
  121. public function close($result) {
  122. return mysqli_free_result($result);
  123. }
  124. public function fetch_fields($result) {
  125. return mysqli_fetch_fields($result);
  126. }
  127. public function add_limit_to_sql($sql,$limit,$offset) {
  128. return "$sql LIMIT $limit OFFSET $offset";
  129. }
  130. public function likeEscape($string) {
  131. return addcslashes($string,'%_');
  132. }
  133. public function is_binary_type($field) {
  134. //echo "$field->name: $field->type ($field->flags)\n";
  135. return (($field->flags & 128) && ($field->type>=249) && ($field->type<=252));
  136. }
  137. public function base64_encode($string) {
  138. return base64_encode($string);
  139. }
  140. public function getDefaultCharset() {
  141. return 'utf8';
  142. }
  143. }
  144. class PostgreSQL implements DatabaseInterface {
  145. protected $db;
  146. protected $queries;
  147. public function __construct() {
  148. $this->queries = array(
  149. 'reflect_table'=>'select
  150. "table_name"
  151. from
  152. "information_schema"."tables"
  153. where
  154. "table_name" like ? and
  155. "table_catalog" = ?',
  156. 'reflect_pk'=>'select
  157. "column_name"
  158. from
  159. "information_schema"."table_constraints" tc,
  160. "information_schema"."key_column_usage" ku
  161. where
  162. tc."constraint_type" = \'PRIMARY KEY\' and
  163. tc."constraint_name" = ku."constraint_name" and
  164. ku."table_name" = ? and
  165. ku."table_catalog" = ?',
  166. 'reflect_belongs_to'=>'select
  167. cu1."table_name",cu1."column_name",
  168. cu2."table_name",cu2."column_name"
  169. from
  170. "information_schema".referential_constraints rc,
  171. "information_schema".key_column_usage cu1,
  172. "information_schema".key_column_usage cu2
  173. where
  174. cu1."constraint_name" = rc."constraint_name" and
  175. cu2."constraint_name" = rc."unique_constraint_name" and
  176. cu1."table_name" = ? and
  177. cu2."table_name" in ? and
  178. cu1."table_catalog" = ? and
  179. cu2."table_catalog" = ?',
  180. 'reflect_has_many'=>'select
  181. cu1."table_name",cu1."column_name",
  182. cu2."table_name",cu2."column_name"
  183. from
  184. "information_schema".referential_constraints rc,
  185. "information_schema".key_column_usage cu1,
  186. "information_schema".key_column_usage cu2
  187. where
  188. cu1."constraint_name" = rc."constraint_name" and
  189. cu2."constraint_name" = rc."unique_constraint_name" and
  190. cu1."table_name" in ? and
  191. cu2."table_name" = ? and
  192. cu1."table_catalog" = ? and
  193. cu2."table_catalog" = ?',
  194. 'reflect_habtm'=>'select
  195. cua1."table_name",cua1."column_name",
  196. cua2."table_name",cua2."column_name",
  197. cub1."table_name",cub1."column_name",
  198. cub2."table_name",cub2."column_name"
  199. from
  200. "information_schema".referential_constraints rca,
  201. "information_schema".referential_constraints rcb,
  202. "information_schema".key_column_usage cua1,
  203. "information_schema".key_column_usage cua2,
  204. "information_schema".key_column_usage cub1,
  205. "information_schema".key_column_usage cub2
  206. where
  207. cua1."constraint_name" = rca."constraint_name" and
  208. cua2."constraint_name" = rca."unique_constraint_name" and
  209. cub1."constraint_name" = rcb."constraint_name" and
  210. cub2."constraint_name" = rcb."unique_constraint_name" and
  211. cua1."table_catalog" = ? and
  212. cub1."table_catalog" = ? and
  213. cua2."table_catalog" = ? and
  214. cub2."table_catalog" = ? and
  215. cua1."table_name" = cub1."table_name" and
  216. cua2."table_name" = ? and
  217. cub2."table_name" in ?'
  218. );
  219. }
  220. public function get_sql($name) {
  221. return isset($this->queries[$name])?$this->queries[$name]:false;
  222. }
  223. public function connectDatabase($hostname,$username,$password,$database,$port,$socket,$charset) {
  224. $e = function ($v) { return str_replace(array('\'','\\'),array('\\\'','\\\\'),$v); };
  225. $conn_string = '';
  226. if ($hostname || $socket) {
  227. if ($socket) $hostname = $e($socket);
  228. else $hostname = $e($hostname);
  229. $conn_string.= " host='$hostname'";
  230. }
  231. if ($port) {
  232. $port = ($port+0);
  233. $conn_string.= " port='$port'";
  234. }
  235. if ($database) {
  236. $database = $e($database);
  237. $conn_string.= " dbname='$database'";
  238. }
  239. if ($username) {
  240. $username = $e($username);
  241. $conn_string.= " user='$username'";
  242. }
  243. if ($password) {
  244. $password = $e($password);
  245. $conn_string.= " password='$password'";
  246. }
  247. if ($charset) {
  248. $charset = $e($charset);
  249. $conn_string.= " options='--client_encoding=$charset'";
  250. }
  251. $db = pg_connect($conn_string);
  252. $this->db = $db;
  253. }
  254. public function query($sql,$params) {
  255. $db = $this->db;
  256. $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params) {
  257. $param = array_shift($params);
  258. if ($matches[0]=='!') return preg_replace('/[^a-zA-Z0-9\-_=<>]/','',$param);
  259. if (is_array($param)) return '('.implode(',',array_map(function($v) use (&$db) {
  260. return "'".pg_escape_string($db,$v)."'";
  261. },$param)).')';
  262. if (is_object($param) && $param->type=='base64') {
  263. return "'\x".bin2hex(base64_decode($param->data))."'";
  264. }
  265. if ($param===null) return 'NULL';
  266. return "'".pg_escape_string($db,$param)."'";
  267. }, $sql);
  268. if (strtoupper(substr($sql,0,6))=='INSERT') {
  269. $sql .= ' RETURNING id;';
  270. }
  271. //echo "\n$sql\n";
  272. return @pg_query($db,$sql);
  273. }
  274. public function fetch_assoc($result) {
  275. return pg_fetch_assoc($result);
  276. }
  277. public function fetch_row($result) {
  278. return pg_fetch_row($result);
  279. }
  280. public function insert_id($result) {
  281. list($id) = pg_fetch_row($result);
  282. return (int)$id;
  283. }
  284. public function affected_rows($result) {
  285. return pg_affected_rows($result);
  286. }
  287. public function close($result) {
  288. return pg_free_result($result);
  289. }
  290. public function fetch_fields($result) {
  291. $keys = array();
  292. for($i=0;$i<pg_num_fields($result);$i++) {
  293. $field = array();
  294. $field['name'] = pg_field_name($result,$i);
  295. $field['type'] = pg_field_type($result,$i);
  296. $keys[$i] = (object)$field;
  297. }
  298. return $keys;
  299. }
  300. public function add_limit_to_sql($sql,$limit,$offset) {
  301. return "$sql LIMIT $limit OFFSET $offset";
  302. }
  303. public function likeEscape($string) {
  304. return addcslashes($string,'%_');
  305. }
  306. public function is_binary_type($field) {
  307. return $field->type == 'bytea';
  308. }
  309. public function base64_encode($string) {
  310. return base64_encode(hex2bin(substr($string,2)));
  311. }
  312. public function getDefaultCharset() {
  313. return 'UTF8';
  314. }
  315. }
  316. class SQLServer implements DatabaseInterface {
  317. protected $db;
  318. protected $queries;
  319. public function __construct() {
  320. $this->queries = array(
  321. 'reflect_table'=>'SELECT
  322. "TABLE_NAME"
  323. FROM
  324. "INFORMATION_SCHEMA"."TABLES"
  325. WHERE
  326. "TABLE_NAME" LIKE ? AND
  327. "TABLE_CATALOG" = ?',
  328. 'reflect_pk'=>'SELECT
  329. "COLUMN_NAME"
  330. FROM
  331. "INFORMATION_SCHEMA"."TABLE_CONSTRAINTS" tc,
  332. "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" ku
  333. WHERE
  334. tc."CONSTRAINT_TYPE" = \'PRIMARY KEY\' AND
  335. tc."CONSTRAINT_NAME" = ku."CONSTRAINT_NAME" AND
  336. ku."TABLE_NAME" = ? AND
  337. ku."TABLE_CATALOG" = ?',
  338. 'reflect_belongs_to'=>'SELECT
  339. cu1."TABLE_NAME",cu1."COLUMN_NAME",
  340. cu2."TABLE_NAME",cu2."COLUMN_NAME"
  341. FROM
  342. "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rc,
  343. "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu1,
  344. "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu2
  345. WHERE
  346. cu1."CONSTRAINT_NAME" = rc."CONSTRAINT_NAME" AND
  347. cu2."CONSTRAINT_NAME" = rc."UNIQUE_CONSTRAINT_NAME" AND
  348. cu1."TABLE_NAME" = ? AND
  349. cu2."TABLE_NAME" IN ? AND
  350. cu1."TABLE_CATALOG" = ? AND
  351. cu2."TABLE_CATALOG" = ?',
  352. 'reflect_has_many'=>'SELECT
  353. cu1."TABLE_NAME",cu1."COLUMN_NAME",
  354. cu2."TABLE_NAME",cu2."COLUMN_NAME"
  355. FROM
  356. "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rc,
  357. "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu1,
  358. "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu2
  359. WHERE
  360. cu1."CONSTRAINT_NAME" = rc."CONSTRAINT_NAME" AND
  361. cu2."CONSTRAINT_NAME" = rc."UNIQUE_CONSTRAINT_NAME" AND
  362. cu1."TABLE_NAME" IN ? AND
  363. cu2."TABLE_NAME" = ? AND
  364. cu1."TABLE_CATALOG" = ? AND
  365. cu2."TABLE_CATALOG" = ?',
  366. 'reflect_habtm'=>'SELECT
  367. cua1."TABLE_NAME",cua1."COLUMN_NAME",
  368. cua2."TABLE_NAME",cua2."COLUMN_NAME",
  369. cub1."TABLE_NAME",cub1."COLUMN_NAME",
  370. cub2."TABLE_NAME",cub2."COLUMN_NAME"
  371. FROM
  372. "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rca,
  373. "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rcb,
  374. "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cua1,
  375. "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cua2,
  376. "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cub1,
  377. "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cub2
  378. WHERE
  379. cua1."CONSTRAINT_NAME" = rca."CONSTRAINT_NAME" AND
  380. cua2."CONSTRAINT_NAME" = rca."UNIQUE_CONSTRAINT_NAME" AND
  381. cub1."CONSTRAINT_NAME" = rcb."CONSTRAINT_NAME" AND
  382. cub2."CONSTRAINT_NAME" = rcb."UNIQUE_CONSTRAINT_NAME" AND
  383. cua1."TABLE_CATALOG" = ? AND
  384. cub1."TABLE_CATALOG" = ? AND
  385. cua2."TABLE_CATALOG" = ? AND
  386. cub2."TABLE_CATALOG" = ? AND
  387. cua1."TABLE_NAME" = cub1."TABLE_NAME" AND
  388. cua2."TABLE_NAME" = ? AND
  389. cub2."TABLE_NAME" IN ?'
  390. );
  391. }
  392. public function get_sql($name) {
  393. return isset($this->queries[$name])?$this->queries[$name]:false;
  394. }
  395. public function connectDatabase($hostname,$username,$password,$database,$port,$socket,$charset) {
  396. $connectionInfo = array();
  397. if ($port) $hostname.=','.$port;
  398. if ($username) $connectionInfo['UID']=$username;
  399. if ($password) $connectionInfo['PWD']=$password;
  400. if ($database) $connectionInfo['Database']=$database;
  401. if ($charset) $connectionInfo['CharacterSet']=$charset;
  402. $connectionInfo['QuotedId']=1;
  403. $connectionInfo['ReturnDatesAsStrings']=1;
  404. $db = sqlsrv_connect($hostname, $connectionInfo);
  405. if (!$db) {
  406. throw new \Exception('Connect failed. '.print_r( sqlsrv_errors(), true));
  407. }
  408. if ($socket) {
  409. throw new \Exception('Socket connection is not supported.');
  410. }
  411. $this->db = $db;
  412. }
  413. public function query($sql,$params) {
  414. $args = array();
  415. $db = $this->db;
  416. $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params,&$args) {
  417. static $i=-1;
  418. $i++;
  419. $param = $params[$i];
  420. if ($matches[0]=='!') {
  421. return preg_replace('/[^a-zA-Z0-9\-_=<>]/','',$param);
  422. }
  423. // This is workaround because SQLSRV cannot accept NULL in a param
  424. if ($matches[0]=='?' && is_null($param)) {
  425. return 'NULL';
  426. }
  427. if (is_array($param)) {
  428. $args = array_merge($args,$param);
  429. return '('.implode(',',str_split(str_repeat('?',count($param)))).')';
  430. }
  431. if (is_object($param)) {
  432. switch($param->type) {
  433. case 'base64':
  434. $args[] = bin2hex(base64_decode($param->data));
  435. return 'CONVERT(VARBINARY(MAX),?,2)';
  436. }
  437. }
  438. $args[] = $param;
  439. return '?';
  440. }, $sql);
  441. //var_dump($params);
  442. //echo "\n$sql\n";
  443. //var_dump($args);
  444. if (strtoupper(substr($sql,0,6))=='INSERT') {
  445. $sql .= ';SELECT SCOPE_IDENTITY()';
  446. }
  447. return sqlsrv_query($db,$sql,$args)?:null;
  448. }
  449. public function fetch_assoc($result) {
  450. $values = sqlsrv_fetch_array($result, SQLSRV_FETCH_ASSOC);
  451. if ($values) $values = array_map(function($v){ return is_null($v)?null:(string)$v; },$values);
  452. return $values;
  453. }
  454. public function fetch_row($result) {
  455. $values = sqlsrv_fetch_array($result, SQLSRV_FETCH_NUMERIC);
  456. if ($values) $values = array_map(function($v){ return is_null($v)?null:(string)$v; },$values);
  457. return $values;
  458. }
  459. public function insert_id($result) {
  460. sqlsrv_next_result($result);
  461. sqlsrv_fetch($result);
  462. return (int)sqlsrv_get_field($result, 0);
  463. }
  464. public function affected_rows($result) {
  465. return sqlsrv_rows_affected($result);
  466. }
  467. public function close($result) {
  468. return sqlsrv_free_stmt($result);
  469. }
  470. public function fetch_fields($result) {
  471. //var_dump(sqlsrv_field_metadata($result));
  472. return array_map(function($a){
  473. $p = array();
  474. foreach ($a as $k=>$v) {
  475. $p[strtolower($k)] = $v;
  476. }
  477. return (object)$p;
  478. },sqlsrv_field_metadata($result));
  479. }
  480. public function add_limit_to_sql($sql,$limit,$offset) {
  481. return "$sql OFFSET $offset ROWS FETCH NEXT $limit ROWS ONLY";
  482. }
  483. public function likeEscape($string) {
  484. return str_replace(array('%','_'),array('[%]','[_]'),$string);
  485. }
  486. public function is_binary_type($field) {
  487. return ($field->type>=-4 && $field->type<=-2);
  488. }
  489. public function base64_encode($string) {
  490. return base64_encode($string);
  491. }
  492. public function getDefaultCharset() {
  493. return 'UTF-8';
  494. }
  495. }
  496. class PHP_CRUD_API {
  497. protected $db;
  498. protected $settings;
  499. protected function mapMethodToAction($method,$key) {
  500. switch ($method) {
  501. case 'OPTIONS': return 'headers';
  502. case 'GET': return $key?'read':'list';
  503. case 'PUT': return 'update';
  504. case 'POST': return 'create';
  505. case 'DELETE': return 'delete';
  506. default: $this->exitWith404('method');
  507. }
  508. return false;
  509. }
  510. protected function parseRequestParameter(&$request,$characters) {
  511. if (!$request) return false;
  512. $pos = strpos($request,'/');
  513. $value = $pos?substr($request,0,$pos):$request;
  514. $request = $pos?substr($request,$pos+1):'';
  515. if (!$characters) return $value;
  516. return preg_replace("/[^$characters]/",'',$value);
  517. }
  518. protected function parseGetParameter($get,$name,$characters) {
  519. $value = isset($get[$name])?$get[$name]:false;
  520. return $characters?preg_replace("/[^$characters]/",'',$value):$value;
  521. }
  522. protected function parseGetParameterArray($get,$name,$characters) {
  523. $values = isset($get[$name])?$get[$name]:false;
  524. if (!is_array($values)) $values = array($values);
  525. if ($characters) {
  526. foreach ($values as &$value) {
  527. $value = preg_replace("/[^$characters]/",'',$value);
  528. }
  529. }
  530. return $values;
  531. }
  532. protected function applyTableAuthorizer($callback,$action,$database,&$tables) {
  533. if (is_callable($callback,true)) foreach ($tables as $i=>$table) {
  534. if (!$callback($action,$database,$table)) {
  535. if ($i) unset($tables[$i]);
  536. else $this->exitWith404('entity');
  537. }
  538. }
  539. }
  540. protected function applyRecordFilter($callback,$action,$database,$tables,&$filters) {
  541. if (is_callable($callback,true)) foreach ($tables as $i=>$table) {
  542. $f = $this->convertFilters($callback($action,$database,$table));
  543. if ($f) {
  544. if (!isset($filters[$table])) $filters[$table] = array();
  545. if (!isset($filters[$table]['and'])) $filters[$table]['and'] = array();
  546. $filters[$table]['and'] = array_merge($filters[$table]['and'],$f);
  547. }
  548. }
  549. }
  550. protected function applyTenancyFunction($callback,$action,$database,$fields,&$filters) {
  551. if (is_callable($callback,true)) foreach ($fields as $table=>$keys) {
  552. foreach ($keys as $field) {
  553. $v = $callback($action,$database,$table,$field->name);
  554. if ($v!==null) {
  555. if (!isset($filters[$table])) $filters[$table] = array();
  556. if (!isset($filters[$table]['and'])) $filters[$table]['and'] = array();
  557. $filters[$table]['and'][] = array($field->name,is_array($v)?'IN':'=',$v);
  558. }
  559. }
  560. }
  561. }
  562. protected function applyColumnAuthorizer($callback,$action,$database,&$fields) {
  563. if (is_callable($callback,true)) foreach ($fields as $table=>$keys) {
  564. foreach ($keys as $field) {
  565. if (!$callback($action,$database,$table,$field->name)) {
  566. unset($fields[$table][$field->name]);
  567. }
  568. }
  569. }
  570. }
  571. protected function applyInputTenancy($callback,$action,$database,$table,&$input,$keys) {
  572. if (is_callable($callback,true)) foreach ((array)$input as $key=>$value) {
  573. if (isset($keys[$key])) {
  574. $v = $callback($action,$database,$table,$key);
  575. if ($v!==null) {
  576. if (is_array($v)) {
  577. if (in_array($input->$key,$v)) {
  578. $v = $input->$key;
  579. } else {
  580. $v = null;
  581. }
  582. }
  583. $input->$key = $v;
  584. }
  585. }
  586. }
  587. }
  588. protected function applyInputSanitizer($callback,$action,$database,$table,&$input,$keys) {
  589. if (is_callable($callback,true)) foreach ((array)$input as $key=>$value) {
  590. if (isset($keys[$key])) {
  591. $input->$key = $callback($action,$database,$table,$key,$keys[$key]->type,$value);
  592. }
  593. }
  594. }
  595. protected function applyInputValidator($callback,$action,$database,$table,$input,$keys,$context) {
  596. $errors = array();
  597. if (is_callable($callback,true)) foreach ((array)$input as $key=>$value) {
  598. if (isset($keys[$key])) {
  599. $error = $callback($action,$database,$table,$key,$keys[$key]->type,$value,$context);
  600. if ($error!==true && $error!==null) $errors[$key] = $error;
  601. }
  602. }
  603. if (!empty($errors)) $this->exitWith422($errors);
  604. }
  605. protected function processTablesParameter($database,$tables,$action) {
  606. $blacklist = array('information_schema','mysql','sys','pg_catalog');
  607. if (in_array(strtolower($database), $blacklist)) return array();
  608. $table_array = explode(',',$tables);
  609. $table_list = array();
  610. foreach ($table_array as $table) {
  611. if ($result = $this->db->query($this->db->get_sql('reflect_table'),array($table,$database))) {
  612. while ($row = $this->db->fetch_row($result)) $table_list[] = $row[0];
  613. $this->db->close($result);
  614. if ($action!='list') break;
  615. }
  616. }
  617. if (empty($table_list)) $this->exitWith404('entity');
  618. return $table_list;
  619. }
  620. protected function exitWith404($type) {
  621. if (isset($_SERVER['REQUEST_METHOD'])) {
  622. header('Content-Type:',true,404);
  623. die("Not found ($type)");
  624. } else {
  625. throw new \Exception("Not found ($type)");
  626. }
  627. }
  628. protected function exitWith422($object) {
  629. if (isset($_SERVER['REQUEST_METHOD'])) {
  630. header('Content-Type:',true,422);
  631. die(json_encode($object));
  632. } else {
  633. throw new \Exception(json_encode($object));
  634. }
  635. }
  636. protected function headersCommand($parameters) {
  637. $headers = array();
  638. $headers[]='Access-Control-Allow-Headers: Content-Type';
  639. $headers[]='Access-Control-Allow-Methods: OPTIONS, GET, PUT, POST, DELETE';
  640. $headers[]='Access-Control-Max-Age: 1728000';
  641. if (isset($_SERVER['REQUEST_METHOD'])) {
  642. foreach ($headers as $header) header($header);
  643. } else {
  644. echo json_encode($headers);
  645. }
  646. }
  647. protected function startOutput($callback) {
  648. if ($callback) {
  649. if (isset($_SERVER['REQUEST_METHOD'])) {
  650. header('Content-Type: application/javascript');
  651. }
  652. echo $callback.'(';
  653. } else {
  654. if (isset($_SERVER['REQUEST_METHOD'])) {
  655. header('Content-Type: application/json');
  656. }
  657. }
  658. }
  659. protected function endOutput($callback) {
  660. if ($callback) {
  661. echo ');';
  662. }
  663. }
  664. protected function processKeyParameter($key,$tables,$database) {
  665. if (!$key) return false;
  666. $count = 0;
  667. $field = false;
  668. if ($result = $this->db->query($this->db->get_sql('reflect_pk'),array($tables[0],$database))) {
  669. while ($row = $this->db->fetch_row($result)) {
  670. $count++;
  671. $field = $row[0];
  672. }
  673. $this->db->close($result);
  674. }
  675. if ($count!=1 || $field==false) $this->exitWith404('1pk');
  676. return array($key,$field);
  677. }
  678. protected function processOrderParameter($order) {
  679. if (!$order) return false;
  680. $order = explode(',',$order,2);
  681. if (count($order)<2) $order[1]='ASC';
  682. if (!strlen($order[0])) return false;
  683. $order[1] = strtoupper($order[1])=='DESC'?'DESC':'ASC';
  684. return $order;
  685. }
  686. protected function convertFilter($field, $comparator, $value) {
  687. switch (strtolower($comparator)) {
  688. case 'cs': $comparator = 'LIKE'; $value = '%'.$this->db->likeEscape($value).'%'; break;
  689. case 'sw': $comparator = 'LIKE'; $value = $this->db->likeEscape($value).'%'; break;
  690. case 'ew': $comparator = 'LIKE'; $value = '%'.$this->db->likeEscape($value); break;
  691. case 'eq': $comparator = '='; break;
  692. case 'ne': $comparator = '<>'; break;
  693. case 'lt': $comparator = '<'; break;
  694. case 'le': $comparator = '<='; break;
  695. case 'ge': $comparator = '>='; break;
  696. case 'gt': $comparator = '>'; break;
  697. case 'in': $comparator = 'IN'; $value = explode(',',$value); break;
  698. }
  699. return array($field, $comparator, $value);
  700. }
  701. protected function convertFilters($filters) {
  702. $result = array();
  703. if ($filters) {
  704. for ($i=0;$i<count($filters);$i++) {
  705. $filter = explode(',',$filters[$i],3);
  706. if (count($filter)==3) {
  707. $result[] = $this->convertFilter($filter[0],$filter[1],$filter[2]);
  708. }
  709. }
  710. }
  711. return $result;
  712. }
  713. protected function processFiltersParameter($tables,$satisfy,$filters) {
  714. $result = $this->convertFilters($filters);
  715. if (!$result) return array();
  716. $and = ($satisfy && strtolower($satisfy)=='any')?'or':'and';
  717. return array($tables[0]=>array($and=>$result));
  718. }
  719. protected function processPageParameter($page) {
  720. if (!$page) return false;
  721. $page = explode(',',$page,2);
  722. if (count($page)<2) $page[1]=20;
  723. $page[0] = ($page[0]-1)*$page[1];
  724. return $page;
  725. }
  726. protected function retrieveObject($key,$fields,$filters,$tables) {
  727. if (!$key) return false;
  728. $table = $tables[0];
  729. $sql = 'SELECT ';
  730. $sql .= '"'.implode('","',array_keys($fields[$table])).'"';
  731. $sql .= ' FROM "!"';
  732. $params = array($table);
  733. if (!isset($filters[$table])) $filters[$table] = array();
  734. if (!isset($filters[$table]['or'])) $filters[$table]['or'] = array();
  735. $filters[$table]['or'][] = array($key[1],'=',$key[0]);
  736. $this->addWhereFromFilters($filters[$table],$sql,$params);
  737. if ($result = $this->db->query($sql,$params)) {
  738. $object = $this->db->fetch_assoc($result);
  739. foreach ($fields[$table] as $field) {
  740. if ($this->db->is_binary_type($field) && $object[$field->name]) {
  741. $object[$field->name] = $this->db->base64_encode($object[$field->name]);
  742. }
  743. }
  744. $this->db->close($result);
  745. }
  746. return $object;
  747. }
  748. protected function createObject($input,$tables) {
  749. if (!$input) return false;
  750. $input = (array)$input;
  751. $keys = implode('","',str_split(str_repeat('!', count($input))));
  752. $values = implode(',',str_split(str_repeat('?', count($input))));
  753. $params = array_merge(array_keys($input),array_values($input));
  754. array_unshift($params, $tables[0]);
  755. $result = $this->db->query('INSERT INTO "!" ("'.$keys.'") VALUES ('.$values.')',$params);
  756. if (!$result) return null;
  757. return $this->db->insert_id($result);
  758. }
  759. protected function updateObject($key,$input,$filters,$tables) {
  760. if (!$input) return false;
  761. $input = (array)$input;
  762. $table = $tables[0];
  763. $sql = 'UPDATE "!" SET ';
  764. $params = array($table);
  765. foreach (array_keys($input) as $i=>$k) {
  766. if ($i) $sql .= ',';
  767. $v = $input[$k];
  768. $sql .= '"!"=?';
  769. $params[] = $k;
  770. $params[] = $v;
  771. }
  772. if (!isset($filters[$table])) $filters[$table] = array();
  773. if (!isset($filters[$table]['or'])) $filters[$table]['or'] = array();
  774. $filters[$table]['or'][] = array($key[1],'=',$key[0]);
  775. $this->addWhereFromFilters($filters[$table],$sql,$params);
  776. $result = $this->db->query($sql,$params);
  777. return $this->db->affected_rows($result);
  778. }
  779. protected function deleteObject($key,$filters,$tables) {
  780. $table = $tables[0];
  781. $sql = 'DELETE FROM "!"';
  782. $params = array($table);
  783. if (!isset($filters[$table])) $filters[$table] = array();
  784. if (!isset($filters[$table]['or'])) $filters[$table]['or'] = array();
  785. $filters[$table]['or'][] = array($key[1],'=',$key[0]);
  786. $this->addWhereFromFilters($filters[$table],$sql,$params);
  787. $result = $this->db->query($sql,$params);
  788. return $this->db->affected_rows($result);
  789. }
  790. protected function findRelations($tables,$database) {
  791. $tableset = array();
  792. $collect = array();
  793. $select = array();
  794. while (count($tables)>1) {
  795. $table0 = array_shift($tables);
  796. $tableset[] = $table0;
  797. $result = $this->db->query($this->db->get_sql('reflect_belongs_to'),array($table0,$tables,$database,$database));
  798. while ($row = $this->db->fetch_row($result)) {
  799. $collect[$row[0]][$row[1]]=array();
  800. $select[$row[2]][$row[3]]=array($row[0],$row[1]);
  801. if (!in_array($row[0],$tableset)) $tableset[] = $row[0];
  802. }
  803. $result = $this->db->query($this->db->get_sql('reflect_has_many'),array($tables,$table0,$database,$database));
  804. while ($row = $this->db->fetch_row($result)) {
  805. $collect[$row[2]][$row[3]]=array();
  806. $select[$row[0]][$row[1]]=array($row[2],$row[3]);
  807. if (!in_array($row[2],$tableset)) $tableset[] = $row[2];
  808. }
  809. $result = $this->db->query($this->db->get_sql('reflect_habtm'),array($database,$database,$database,$database,$table0,$tables));
  810. while ($row = $this->db->fetch_row($result)) {
  811. $collect[$row[2]][$row[3]]=array();
  812. $select[$row[0]][$row[1]]=array($row[2],$row[3]);
  813. $collect[$row[4]][$row[5]]=array();
  814. $select[$row[6]][$row[7]]=array($row[4],$row[5]);
  815. if (!in_array($row[2],$tableset)) $tableset[] = $row[2];
  816. if (!in_array($row[4],$tableset)) $tableset[] = $row[4];
  817. }
  818. }
  819. $tableset[] = array_shift($tables);
  820. return array($tableset,$collect,$select);
  821. }
  822. protected function retrieveInput($post) {
  823. $input = (object)array();
  824. $data = trim(file_get_contents($post));
  825. if (strlen($data)>0) {
  826. if ($data[0]=='{') {
  827. $input = json_decode($data);
  828. } else {
  829. parse_str($data, $input);
  830. foreach ($input as $key => $value) {
  831. if (substr($key,-9)=='__is_null') {
  832. $input[substr($key,0,-9)] = null;
  833. unset($input[$key]);
  834. }
  835. }
  836. $input = (object)$input;
  837. }
  838. }
  839. return $input;
  840. }
  841. protected function findFields($tables,$columns,$database) {
  842. $fields = array();
  843. foreach ($tables as $i=>$table) {
  844. $fields[$table] = $this->findTableFields($table,$database);
  845. if ($i==0) $fields[$table] = $this->filterFieldsByColumns($fields[$table],$columns);
  846. }
  847. return $fields;
  848. }
  849. protected function filterFieldsByColumns($fields,$columns) {
  850. if ($columns) {
  851. $columns = explode(',',$columns);
  852. foreach (array_keys($fields) as $key) {
  853. if (!in_array($key, $columns)) {
  854. unset($fields[$key]);
  855. }
  856. }
  857. }
  858. return $fields;
  859. }
  860. protected function findTableFields($table,$database) {
  861. $fields = array();
  862. $result = $this->db->query('SELECT * FROM "!" WHERE 1=2;',array($table));
  863. foreach ($this->db->fetch_fields($result) as $field) {
  864. $fields[$field->name] = $field;
  865. }
  866. return $fields;
  867. }
  868. protected function filterInputByFields($input,$fields) {
  869. if ($fields) foreach (array_keys((array)$input) as $key) {
  870. if (!isset($fields[$key])) {
  871. unset($input->$key);
  872. }
  873. }
  874. return $input;
  875. }
  876. protected function convertBinary(&$input,$keys) {
  877. foreach ($keys as $key=>$field) {
  878. if (isset($input->$key) && $input->$key && $this->db->is_binary_type($field)) {
  879. $data = $input->$key;
  880. $data = str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT);
  881. $input->$key = (object)array('type'=>'base64','data'=>$data);
  882. }
  883. }
  884. }
  885. protected function getParameters($settings) {
  886. extract($settings);
  887. $tables = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_,');
  888. $key = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_,'); // auto-increment or uuid
  889. $action = $this->mapMethodToAction($method,$key);
  890. $callback = $this->parseGetParameter($get, 'callback', 'a-zA-Z0-9\-_');
  891. $page = $this->parseGetParameter($get, 'page', '0-9,');
  892. $filters = $this->parseGetParameterArray($get, 'filter', false);
  893. $satisfy = $this->parseGetParameter($get, 'satisfy', 'a-zA-Z');
  894. $columns = $this->parseGetParameter($get, 'columns', 'a-zA-Z0-9\-_,');
  895. $order = $this->parseGetParameter($get, 'order', 'a-zA-Z0-9\-_,');
  896. $transform = $this->parseGetParameter($get, 'transform', '1');
  897. $tables = $this->processTablesParameter($database,$tables,$action);
  898. $key = $this->processKeyParameter($key,$tables,$database);
  899. $filters = $this->processFiltersParameter($tables,$satisfy,$filters);
  900. $page = $this->processPageParameter($page);
  901. $order = $this->processOrderParameter($order);
  902. // reflection
  903. list($tables,$collect,$select) = $this->findRelations($tables,$database);
  904. $fields = $this->findFields($tables,$columns,$database);
  905. // permissions
  906. if ($table_authorizer) $this->applyTableAuthorizer($table_authorizer,$action,$database,$tables);
  907. if ($record_filter) $this->applyRecordFilter($record_filter,$action,$database,$tables,$filters);
  908. if ($column_authorizer) $this->applyColumnAuthorizer($column_authorizer,$action,$database,$fields);
  909. if ($tenancy_function) $this->applyTenancyFunction($tenancy_function,$action,$database,$fields,$filters);
  910. if ($post) {
  911. // input
  912. $context = $this->retrieveInput($post);
  913. $input = $this->filterInputByFields($context,$fields[$tables[0]]);
  914. if ($tenancy_function) $this->applyInputTenancy($tenancy_function,$action,$database,$tables[0],$input,$fields[$tables[0]]);
  915. if ($input_sanitizer) $this->applyInputSanitizer($input_sanitizer,$action,$database,$tables[0],$input,$fields[$tables[0]]);
  916. if ($input_validator) $this->applyInputValidator($input_validator,$action,$database,$tables[0],$input,$fields[$tables[0]],$context);
  917. $this->convertBinary($input,$fields[$tables[0]]);
  918. }
  919. return compact('action','database','tables','key','callback','page','filters','fields','order','transform','input','collect','select');
  920. }
  921. protected function addWhereFromFilters($filters,&$sql,&$params) {
  922. $first = true;
  923. if (isset($filters['or'])) {
  924. $first = false;
  925. $sql .= ' WHERE (';
  926. foreach ($filters['or'] as $i=>$filter) {
  927. $sql .= $i==0?'':' OR ';
  928. $sql .= '"!" ! ?';
  929. $params[] = $filter[0];
  930. $params[] = $filter[1];
  931. $params[] = $filter[2];
  932. }
  933. $sql .= ')';
  934. }
  935. if (isset($filters['and'])) {
  936. foreach ($filters['and'] as $i=>$filter) {
  937. $sql .= $first?' WHERE ':' AND ';
  938. $sql .= '"!" ! ?';
  939. $params[] = $filter[0];
  940. $params[] = $filter[1];
  941. $params[] = $filter[2];
  942. $first = false;
  943. }
  944. }
  945. }
  946. protected function listCommandInternal($parameters) {
  947. extract($parameters);
  948. echo '{';
  949. $table = array_shift($tables);
  950. // first table
  951. $count = false;
  952. echo '"'.$table.'":{';
  953. if (is_array($order) && is_array($page)) {
  954. $params = array();
  955. $sql = 'SELECT COUNT(*) FROM "!"';
  956. $params[] = $table;
  957. if (isset($filters[$table])) {
  958. $this->addWhereFromFilters($filters[$table],$sql,$params);
  959. }
  960. if ($result = $this->db->query($sql,$params)) {
  961. while ($pages = $this->db->fetch_row($result)) {
  962. $count = $pages[0];
  963. }
  964. }
  965. }
  966. $params = array();
  967. $sql = 'SELECT ';
  968. $sql .= '"'.implode('","',array_keys($fields[$table])).'"';
  969. $sql .= ' FROM "!"';
  970. $params[] = $table;
  971. if (isset($filters[$table])) {
  972. $this->addWhereFromFilters($filters[$table],$sql,$params);
  973. }
  974. if (is_array($order)) {
  975. $sql .= ' ORDER BY "!" !';
  976. $params[] = $order[0];
  977. $params[] = $order[1];
  978. }
  979. if (is_array($order) && is_array($page)) {
  980. $sql = $this->db->add_limit_to_sql($sql,$page[1],$page[0]);
  981. }
  982. if ($result = $this->db->query($sql,$params)) {
  983. echo '"columns":';
  984. $keys = array();
  985. $base64 = array();
  986. foreach ($fields[$table] as $field) {
  987. $base64[] = $this->db->is_binary_type($field);
  988. $keys[] = $field->name;
  989. }
  990. echo json_encode($keys);
  991. $keys = array_flip($keys);
  992. echo ',"records":[';
  993. $first_row = true;
  994. while ($row = $this->db->fetch_row($result)) {
  995. if ($first_row) $first_row = false;
  996. else echo ',';
  997. if (isset($collect[$table])) {
  998. foreach (array_keys($collect[$table]) as $field) {
  999. $collect[$table][$field][] = $row[$keys[$field]];
  1000. }
  1001. }
  1002. foreach ($base64 as $k=>$v) {
  1003. if ($v && $row[$k]) {
  1004. $row[$k] = $this->db->base64_encode($row[$k]);
  1005. }
  1006. }
  1007. echo json_encode($row);
  1008. }
  1009. $this->db->close($result);
  1010. echo ']';
  1011. if ($count) echo ',';
  1012. }
  1013. if ($count) echo '"results":'.$count;
  1014. echo '}';
  1015. // other tables
  1016. foreach ($tables as $t=>$table) {
  1017. echo ',';
  1018. echo '"'.$table.'":{';
  1019. $params = array();
  1020. $sql = 'SELECT ';
  1021. $sql .= '"'.implode('","',array_keys($fields[$table])).'"';
  1022. $sql .= ' FROM "!"';
  1023. $params[] = $table;
  1024. if (isset($select[$table])) {
  1025. echo '"relations":{';
  1026. $first_row = true;
  1027. foreach ($select[$table] as $field => $path) {
  1028. $values = $collect[$path[0]][$path[1]];
  1029. if (!isset($filters[$table])) $filters[$table] = array();
  1030. if (!isset($filters[$table]['or'])) $filters[$table]['or'] = array();
  1031. $filters[$table]['or'][] = array($field,'IN',$values);
  1032. if ($first_row) $first_row = false;
  1033. else echo ',';
  1034. echo '"'.$field.'":"'.implode('.',$path).'"';
  1035. }
  1036. echo '}';
  1037. $this->addWhereFromFilters($filters[$table],$sql,$params);
  1038. }
  1039. if ($result = $this->db->query($sql,$params)) {
  1040. if (isset($select[$table])) echo ',';
  1041. echo '"columns":';
  1042. $keys = array();
  1043. $base64 = array();
  1044. foreach ($fields[$table] as $field) {
  1045. $base64[] = $this->db->is_binary_type($field);
  1046. $keys[] = $field->name;
  1047. }
  1048. echo json_encode($keys);
  1049. $keys = array_flip($keys);
  1050. echo ',"records":[';
  1051. $first_row = true;
  1052. while ($row = $this->db->fetch_row($result)) {
  1053. if ($first_row) $first_row = false;
  1054. else echo ',';
  1055. if (isset($collect[$table])) {
  1056. foreach (array_keys($collect[$table]) as $field) {
  1057. $collect[$table][$field][]=$row[$keys[$field]];
  1058. }
  1059. }
  1060. foreach ($base64 as $k=>$v) {
  1061. if ($v && $row[$k]) {
  1062. $row[$k] = $this->db->base64_encode($row[$k]);
  1063. }
  1064. }
  1065. echo json_encode($row);
  1066. }
  1067. $this->db->close($result);
  1068. echo ']';
  1069. }
  1070. echo '}';
  1071. }
  1072. echo '}';
  1073. }
  1074. protected function readCommand($parameters) {
  1075. extract($parameters);
  1076. $object = $this->retrieveObject($key,$fields,$filters,$tables);
  1077. if (!$object) $this->exitWith404('object');
  1078. $this->startOutput($callback);
  1079. echo json_encode($object);
  1080. $this->endOutput($callback);
  1081. }
  1082. protected function createCommand($parameters) {
  1083. extract($parameters);
  1084. if (!$input) $this->exitWith404('input');
  1085. $this->startOutput($callback);
  1086. echo json_encode($this->createObject($input,$tables));
  1087. $this->endOutput($callback);
  1088. }
  1089. protected function updateCommand($parameters) {
  1090. extract($parameters);
  1091. if (!$input) $this->exitWith404('subject');
  1092. $this->startOutput($callback);
  1093. echo json_encode($this->updateObject($key,$input,$filters,$tables));
  1094. $this->endOutput($callback);
  1095. }
  1096. protected function deleteCommand($parameters) {
  1097. extract($parameters);
  1098. $this->startOutput($callback);
  1099. echo json_encode($this->deleteObject($key,$filters,$tables));
  1100. $this->endOutput($callback);
  1101. }
  1102. protected function listCommand($parameters) {
  1103. extract($parameters);
  1104. $this->startOutput($callback);
  1105. if ($transform) {
  1106. ob_start();
  1107. }
  1108. $this->listCommandInternal($parameters);
  1109. if ($transform) {
  1110. $content = ob_get_contents();
  1111. ob_end_clean();
  1112. $data = json_decode($content,true);
  1113. echo json_encode(self::php_crud_api_transform($data));
  1114. }
  1115. $this->endOutput($callback);
  1116. }
  1117. public function __construct($config) {
  1118. extract($config);
  1119. // initialize
  1120. $hostname = isset($hostname)?$hostname:null;
  1121. $username = isset($username)?$username:null;
  1122. $password = isset($password)?$password:null;
  1123. $database = isset($database)?$database:null;
  1124. $port = isset($port)?$port:null;
  1125. $socket = isset($socket)?$socket:null;
  1126. $charset = isset($charset)?$charset:null;
  1127. $table_authorizer = isset($table_authorizer)?$table_authorizer:null;
  1128. $record_filter = isset($record_filter)?$record_filter:null;
  1129. $column_authorizer = isset($column_authorizer)?$column_authorizer:null;
  1130. $tenancy_function = isset($tenancy_function)?$tenancy_function:null;
  1131. $input_sanitizer = isset($input_sanitizer)?$input_sanitizer:null;
  1132. $input_validator = isset($input_validator)?$input_validator:null;
  1133. $dbengine = isset($dbengine)?$dbengine:null;
  1134. $db = isset($db)?$db:null;
  1135. $method = isset($method)?$method:null;
  1136. $request = isset($request)?$request:null;
  1137. $get = isset($get)?$get:null;
  1138. $post = isset($post)?$post:null;
  1139. // defaults
  1140. if (!$dbengine) {
  1141. $dbengine = 'MySQL';
  1142. }
  1143. if (!$method) {
  1144. $method = $_SERVER['REQUEST_METHOD'];
  1145. }
  1146. if (!$request) {
  1147. $request = isset($_SERVER['PATH_INFO'])?$_SERVER['PATH_INFO']:'';
  1148. }
  1149. if (!$get) {
  1150. $get = $_GET;
  1151. }
  1152. if (!$post) {
  1153. $post = 'php://input';
  1154. }
  1155. // connect
  1156. $request = trim($request,'/');
  1157. if (!$database) {
  1158. $database = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_');
  1159. }
  1160. if (!$db) {
  1161. $db = new $dbengine();
  1162. if (!$charset) {
  1163. $charset = $db->getDefaultCharset();
  1164. }
  1165. $db->connectDatabase($hostname,$username,$password,$database,$port,$socket,$charset);
  1166. }
  1167. $this->db = $db;
  1168. $this->settings = compact('method', 'request', 'get', 'post', 'database', 'table_authorizer', 'record_filter', 'column_authorizer', 'tenancy_function', 'input_sanitizer', 'input_validator');
  1169. }
  1170. public static function php_crud_api_transform(&$tables) {
  1171. $get_objects = function (&$tables,$table_name,$where_index=false,$match_value=false) use (&$get_objects) {
  1172. $objects = array();
  1173. foreach ($tables[$table_name]['records'] as $record) {
  1174. if ($where_index===false || $record[$where_index]==$match_value) {
  1175. $object = array();
  1176. foreach ($tables[$table_name]['columns'] as $index=>$column) {
  1177. $object[$column] = $record[$index];
  1178. foreach ($tables as $relation=>$reltable) {
  1179. if (isset($reltable['relations'])) {
  1180. foreach ($reltable['relations'] as $key=>$target) {
  1181. if ($target == "$table_name.$column") {
  1182. $column_indices = array_flip($reltable['columns']);
  1183. $object[$relation] = $get_objects($tables,$relation,$column_indices[$key],$record[$index]);
  1184. }
  1185. }
  1186. }
  1187. }
  1188. }
  1189. $objects[] = $object;
  1190. }
  1191. }
  1192. return $objects;
  1193. };
  1194. $tree = array();
  1195. foreach ($tables as $name=>$table) {
  1196. if (!isset($table['relations'])) {
  1197. $tree[$name] = $get_objects($tables,$name);
  1198. if (isset($table['results'])) {
  1199. $tree['_results'] = $table['results'];
  1200. }
  1201. }
  1202. }
  1203. return $tree;
  1204. }
  1205. public function executeCommand() {
  1206. if (isset($_SERVER['REQUEST_METHOD'])) {
  1207. header('Access-Control-Allow-Origin: *');
  1208. }
  1209. $parameters = $this->getParameters($this->settings);
  1210. switch($parameters['action']){
  1211. case 'list': $this->listCommand($parameters); break;
  1212. case 'read': $this->readCommand($parameters); break;
  1213. case 'create': $this->createCommand($parameters); break;
  1214. case 'update': $this->updateCommand($parameters); break;
  1215. case 'delete': $this->deleteCommand($parameters); break;
  1216. case 'headers': $this->headersCommand($parameters); break;
  1217. }
  1218. }
  1219. }
  1220. // uncomment the lines below when running in stand-alone mode:
  1221. // $api = new PHP_CRUD_API(array(
  1222. // 'hostname'=>'localhost',
  1223. // 'username'=>'xxx',
  1224. // 'password'=>'xxx',
  1225. // 'database'=>'xxx',
  1226. // 'charset'=>'utf8'
  1227. // ));
  1228. // $api->executeCommand();
  1229. // For Microsoft SQL Server use:
  1230. // $api = new PHP_CRUD_API(array(
  1231. // 'hostname'=>'(local)',
  1232. // 'username'=>'',
  1233. // 'password'=>'',
  1234. // 'database'=>'xxx',
  1235. // 'charset'=>'UTF-8'
  1236. // ));
  1237. // $api->executeCommand();
  1238. // For PostgreSQL use:
  1239. // $api = new PHP_CRUD_API(array(
  1240. // 'hostname'=>'localhost',
  1241. // 'username'=>'xxx',
  1242. // 'password'=>'xxx',
  1243. // 'database'=>'xxx',
  1244. // 'charset'=>'UTF8'
  1245. // ));
  1246. // $api->executeCommand();