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
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

api.php 42KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374
  1. <?php
  2. interface DatabaseInterface {
  3. public function getSql($name);
  4. public function connect($hostname,$username,$password,$database,$port,$socket,$charset);
  5. public function query($sql,$params);
  6. public function fetchAssoc($result);
  7. public function fetchRow($result);
  8. public function insertId($result);
  9. public function affectedRows($result);
  10. public function close($result);
  11. public function fetchFields($result);
  12. public function addLimitToSql($sql,$limit,$offset);
  13. public function likeEscape($string);
  14. public function isBinaryType($field);
  15. public function base64Encode($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 getSql($name) {
  77. return isset($this->queries[$name])?$this->queries[$name]:false;
  78. }
  79. public function connect($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 fetchAssoc($result) {
  110. return mysqli_fetch_assoc($result);
  111. }
  112. public function fetchRow($result) {
  113. return mysqli_fetch_row($result);
  114. }
  115. public function insertId($result) {
  116. return mysqli_insert_id($this->db);
  117. }
  118. public function affectedRows($result) {
  119. return mysqli_affected_rows($this->db);
  120. }
  121. public function close($result) {
  122. return mysqli_free_result($result);
  123. }
  124. public function fetchFields($result) {
  125. return mysqli_fetch_fields($result);
  126. }
  127. public function addLimitToSql($sql,$limit,$offset) {
  128. return "$sql LIMIT $limit OFFSET $offset";
  129. }
  130. public function likeEscape($string) {
  131. return addcslashes($string,'%_');
  132. }
  133. public function isBinaryType($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 base64Encode($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 getSql($name) {
  221. return isset($this->queries[$name])?$this->queries[$name]:false;
  222. }
  223. public function connect($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 fetchAssoc($result) {
  275. return pg_fetch_assoc($result);
  276. }
  277. public function fetchRow($result) {
  278. return pg_fetch_row($result);
  279. }
  280. public function insertId($result) {
  281. list($id) = pg_fetch_row($result);
  282. return (int)$id;
  283. }
  284. public function affectedRows($result) {
  285. return pg_affected_rows($result);
  286. }
  287. public function close($result) {
  288. return pg_free_result($result);
  289. }
  290. public function fetchFields($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 addLimitToSql($sql,$limit,$offset) {
  301. return "$sql LIMIT $limit OFFSET $offset";
  302. }
  303. public function likeEscape($string) {
  304. return addcslashes($string,'%_');
  305. }
  306. public function isBinaryType($field) {
  307. return $field->type == 'bytea';
  308. }
  309. public function base64Encode($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 getSql($name) {
  393. return isset($this->queries[$name])?$this->queries[$name]:false;
  394. }
  395. public function connect($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 fetchAssoc($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 fetchRow($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 insertId($result) {
  460. sqlsrv_next_result($result);
  461. sqlsrv_fetch($result);
  462. return (int)sqlsrv_get_field($result, 0);
  463. }
  464. public function affectedRows($result) {
  465. return sqlsrv_rows_affected($result);
  466. }
  467. public function close($result) {
  468. return sqlsrv_free_stmt($result);
  469. }
  470. public function fetchFields($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 addLimitToSql($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 isBinaryType($field) {
  487. return ($field->type>=-4 && $field->type<=-2);
  488. }
  489. public function base64Encode($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->getSql('reflect_table'),array($table,$database))) {
  612. while ($row = $this->db->fetchRow($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->getSql('reflect_pk'),array($tables[0],$database))) {
  669. while ($row = $this->db->fetchRow($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->fetchAssoc($result);
  739. foreach ($fields[$table] as $field) {
  740. if ($this->db->isBinaryType($field) && $object[$field->name]) {
  741. $object[$field->name] = $this->db->base64Encode($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->insertId($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->affectedRows($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->affectedRows($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->getSql('reflect_belongs_to'),array($table0,$tables,$database,$database));
  798. while ($row = $this->db->fetchRow($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->getSql('reflect_has_many'),array($tables,$table0,$database,$database));
  804. while ($row = $this->db->fetchRow($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->getSql('reflect_habtm'),array($database,$database,$database,$database,$table0,$tables));
  810. while ($row = $this->db->fetchRow($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->fetchFields($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->isBinaryType($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->fetchRow($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->addLimitToSql($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->isBinaryType($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->fetchRow($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->base64Encode($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->isBinaryType($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->fetchRow($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->base64Encode($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. $dbengine = isset($dbengine)?$dbengine:null;
  1121. $hostname = isset($hostname)?$hostname:null;
  1122. $username = isset($username)?$username:null;
  1123. $password = isset($password)?$password:null;
  1124. $database = isset($database)?$database:null;
  1125. $port = isset($port)?$port:null;
  1126. $socket = isset($socket)?$socket:null;
  1127. $charset = isset($charset)?$charset:null;
  1128. $table_authorizer = isset($table_authorizer)?$table_authorizer:null;
  1129. $record_filter = isset($record_filter)?$record_filter:null;
  1130. $column_authorizer = isset($column_authorizer)?$column_authorizer:null;
  1131. $tenancy_function = isset($tenancy_function)?$tenancy_function:null;
  1132. $input_sanitizer = isset($input_sanitizer)?$input_sanitizer:null;
  1133. $input_validator = isset($input_validator)?$input_validator: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->connect($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. // 'dbengine'=>'MySQL',
  1223. // 'hostname'=>'localhost',
  1224. // 'username'=>'xxx',
  1225. // 'password'=>'xxx',
  1226. // 'database'=>'xxx',
  1227. // 'charset'=>'utf8'
  1228. // ));
  1229. // $api->executeCommand();
  1230. // For Microsoft SQL Server 2012 use:
  1231. // $api = new PHP_CRUD_API(array(
  1232. // 'dbengine'=>'SQLServer',
  1233. // 'hostname'=>'(local)',
  1234. // 'username'=>'',
  1235. // 'password'=>'',
  1236. // 'database'=>'xxx',
  1237. // 'charset'=>'UTF-8'
  1238. // ));
  1239. // $api->executeCommand();
  1240. // For PostgreSQL 9 use:
  1241. // $api = new PHP_CRUD_API(array(
  1242. // 'dbengine'=>'PostgreSQL',
  1243. // 'hostname'=>'localhost',
  1244. // 'username'=>'xxx',
  1245. // 'password'=>'xxx',
  1246. // 'database'=>'xxx',
  1247. // 'charset'=>'UTF8'
  1248. // ));
  1249. // $api->executeCommand();