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 76KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394
  1. <?php
  2. //var_dump($_SERVER['REQUEST_METHOD'],$_SERVER['PATH_INFO']); die();
  3. interface DatabaseInterface {
  4. public function getSql($name);
  5. public function connect($hostname,$username,$password,$database,$port,$socket,$charset);
  6. public function query($sql,$params=array());
  7. public function fetchAssoc($result,$fields=false);
  8. public function fetchRow($result,$fields=false);
  9. public function insertId($result);
  10. public function affectedRows($result);
  11. public function close($result);
  12. public function fetchFields($table);
  13. public function addLimitToSql($sql,$limit,$offset);
  14. public function likeEscape($string);
  15. public function isNumericType($field);
  16. public function isBinaryType($field);
  17. public function isGeometryType($field);
  18. public function getDefaultCharset();
  19. public function beginTransaction();
  20. public function commitTransaction();
  21. public function rollbackTransaction();
  22. }
  23. class MySQL implements DatabaseInterface {
  24. protected $db;
  25. protected $queries;
  26. public function __construct() {
  27. $this->queries = array(
  28. 'list_tables'=>'SELECT
  29. "TABLE_NAME","TABLE_COMMENT"
  30. FROM
  31. "INFORMATION_SCHEMA"."TABLES"
  32. WHERE
  33. "TABLE_SCHEMA" = ?',
  34. 'reflect_table'=>'SELECT
  35. "TABLE_NAME"
  36. FROM
  37. "INFORMATION_SCHEMA"."TABLES"
  38. WHERE
  39. "TABLE_NAME" COLLATE \'utf8_bin\' = ? AND
  40. "TABLE_SCHEMA" = ?',
  41. 'reflect_pk'=>'SELECT
  42. "COLUMN_NAME"
  43. FROM
  44. "INFORMATION_SCHEMA"."COLUMNS"
  45. WHERE
  46. "COLUMN_KEY" = \'PRI\' AND
  47. "TABLE_NAME" = ? AND
  48. "TABLE_SCHEMA" = ?',
  49. 'reflect_belongs_to'=>'SELECT
  50. "TABLE_NAME","COLUMN_NAME",
  51. "REFERENCED_TABLE_NAME","REFERENCED_COLUMN_NAME"
  52. FROM
  53. "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE"
  54. WHERE
  55. "TABLE_NAME" COLLATE \'utf8_bin\' = ? AND
  56. "REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' IN ? AND
  57. "TABLE_SCHEMA" = ? AND
  58. "REFERENCED_TABLE_SCHEMA" = ?',
  59. 'reflect_has_many'=>'SELECT
  60. "TABLE_NAME","COLUMN_NAME",
  61. "REFERENCED_TABLE_NAME","REFERENCED_COLUMN_NAME"
  62. FROM
  63. "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE"
  64. WHERE
  65. "TABLE_NAME" COLLATE \'utf8_bin\' IN ? AND
  66. "REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' = ? AND
  67. "TABLE_SCHEMA" = ? AND
  68. "REFERENCED_TABLE_SCHEMA" = ?',
  69. 'reflect_habtm'=>'SELECT
  70. k1."TABLE_NAME", k1."COLUMN_NAME",
  71. k1."REFERENCED_TABLE_NAME", k1."REFERENCED_COLUMN_NAME",
  72. k2."TABLE_NAME", k2."COLUMN_NAME",
  73. k2."REFERENCED_TABLE_NAME", k2."REFERENCED_COLUMN_NAME"
  74. FROM
  75. "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" k1,
  76. "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" k2
  77. WHERE
  78. k1."TABLE_SCHEMA" = ? AND
  79. k2."TABLE_SCHEMA" = ? AND
  80. k1."REFERENCED_TABLE_SCHEMA" = ? AND
  81. k2."REFERENCED_TABLE_SCHEMA" = ? AND
  82. k1."TABLE_NAME" COLLATE \'utf8_bin\' = k2."TABLE_NAME" COLLATE \'utf8_bin\' AND
  83. k1."REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' = ? AND
  84. k2."REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' IN ?'
  85. );
  86. }
  87. public function getSql($name) {
  88. return isset($this->queries[$name])?$this->queries[$name]:false;
  89. }
  90. public function connect($hostname,$username,$password,$database,$port,$socket,$charset) {
  91. $db = mysqli_init();
  92. if (defined('MYSQLI_OPT_INT_AND_FLOAT_NATIVE')) {
  93. mysqli_options($db,MYSQLI_OPT_INT_AND_FLOAT_NATIVE,true);
  94. }
  95. $success = mysqli_real_connect($db,$hostname,$username,$password,$database,$port,$socket,MYSQLI_CLIENT_FOUND_ROWS);
  96. if (!$success) {
  97. throw new \Exception('Connect failed. '.mysqli_connect_error());
  98. }
  99. if (!mysqli_set_charset($db,$charset)) {
  100. throw new \Exception('Error setting charset. '.mysqli_error($db));
  101. }
  102. if (!mysqli_query($db,'SET SESSION sql_mode = \'ANSI_QUOTES\';')) {
  103. throw new \Exception('Error setting ANSI quotes. '.mysqli_error($db));
  104. }
  105. $this->db = $db;
  106. }
  107. public function query($sql,$params=array()) {
  108. $db = $this->db;
  109. $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params) {
  110. $param = array_shift($params);
  111. if ($matches[0]=='!') {
  112. $key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param);
  113. if (is_object($param) && $param->type=='base64') {
  114. return "TO_BASE64(\"$key\") as \"$key\"";
  115. }
  116. if (is_object($param) && $param->type=='wkt') {
  117. return "ST_AsText(\"$key\") as \"$key\"";
  118. }
  119. return '"'.$key.'"';
  120. } else {
  121. if (is_array($param)) return '('.implode(',',array_map(function($v) use (&$db) {
  122. return "'".mysqli_real_escape_string($db,$v)."'";
  123. },$param)).')';
  124. if (is_object($param) && $param->type=='base64') {
  125. return "x'".bin2hex(base64_decode($param->value))."'";
  126. }
  127. if (is_object($param) && $param->type=='wkt') {
  128. return "ST_GeomFromText('".mysqli_real_escape_string($db,$param->value)."')";
  129. }
  130. if ($param===null) return 'NULL';
  131. return "'".mysqli_real_escape_string($db,$param)."'";
  132. }
  133. }, $sql);
  134. //if (!strpos($sql,'INFORMATION_SCHEMA')) echo "\n$sql\n";
  135. //if (!strpos($sql,'INFORMATION_SCHEMA')) file_put_contents('log.txt',"\n$sql\n",FILE_APPEND);
  136. return mysqli_query($db,$sql);
  137. }
  138. protected function convertFloatAndInt($result,&$values,&$fields) {
  139. array_walk($values, function(&$v,$i) use ($result,$fields){
  140. if (is_string($v) && $this->isNumericType($fields[$i])) {
  141. $v+=0;
  142. }
  143. });
  144. }
  145. public function fetchAssoc($result,$fields=false) {
  146. $values = mysqli_fetch_assoc($result);
  147. if ($values && $fields && !defined('MYSQLI_OPT_INT_AND_FLOAT_NATIVE')) {
  148. $this->convertFloatAndInt($result,$values,$fields);
  149. }
  150. return $values;
  151. }
  152. public function fetchRow($result,$fields=false) {
  153. $values = mysqli_fetch_row($result);
  154. if ($values && $fields && !defined('MYSQLI_OPT_INT_AND_FLOAT_NATIVE')) {
  155. $fields = array_values($fields);
  156. $this->convertFloatAndInt($result,$values,$fields);
  157. }
  158. return $values;
  159. }
  160. public function insertId($result) {
  161. return mysqli_insert_id($this->db);
  162. }
  163. public function affectedRows($result) {
  164. return mysqli_affected_rows($this->db);
  165. }
  166. public function close($result) {
  167. return mysqli_free_result($result);
  168. }
  169. public function fetchFields($table) {
  170. $result = $this->query('SELECT * FROM ! WHERE 1=2;',array($table));
  171. return mysqli_fetch_fields($result);
  172. }
  173. public function addLimitToSql($sql,$limit,$offset) {
  174. return "$sql LIMIT $limit OFFSET $offset";
  175. }
  176. public function likeEscape($string) {
  177. return addcslashes($string,'%_');
  178. }
  179. public function convertFilter($field, $comparator, $value) {
  180. return false;
  181. }
  182. public function isNumericType($field) {
  183. return in_array($field->type,array(1,2,3,4,5,6,8,9));
  184. }
  185. public function isBinaryType($field) {
  186. //echo "$field->name: $field->type ($field->flags)\n";
  187. return (($field->flags & 128) && ($field->type>=249) && ($field->type<=252));
  188. }
  189. public function isGeometryType($field) {
  190. return ($field->type==255);
  191. }
  192. public function getDefaultCharset() {
  193. return 'utf8';
  194. }
  195. public function beginTransaction() {
  196. mysqli_query($this->db,'BEGIN');
  197. //return mysqli_begin_transaction($this->db);
  198. }
  199. public function commitTransaction() {
  200. mysqli_query($this->db,'COMMIT');
  201. //return mysqli_commit($this->db);
  202. }
  203. public function rollbackTransaction() {
  204. mysqli_query($this->db,'ROLLBACK');
  205. //return mysqli_rollback($this->db);
  206. }
  207. }
  208. class PostgreSQL implements DatabaseInterface {
  209. protected $db;
  210. protected $queries;
  211. public function __construct() {
  212. $this->queries = array(
  213. 'list_tables'=>'select
  214. "table_name","table_comment"
  215. from
  216. "information_schema"."tables"
  217. where
  218. "table_catalog" = ?',
  219. 'reflect_table'=>'select
  220. "table_name"
  221. from
  222. "information_schema"."tables"
  223. where
  224. "table_name" like ? and
  225. "table_catalog" = ?',
  226. 'reflect_pk'=>'select
  227. "column_name"
  228. from
  229. "information_schema"."table_constraints" tc,
  230. "information_schema"."key_column_usage" ku
  231. where
  232. tc."constraint_type" = \'PRIMARY KEY\' and
  233. tc."constraint_name" = ku."constraint_name" and
  234. ku."table_name" = ? and
  235. ku."table_catalog" = ?',
  236. 'reflect_belongs_to'=>'select
  237. cu1."table_name",cu1."column_name",
  238. cu2."table_name",cu2."column_name"
  239. from
  240. "information_schema".referential_constraints rc,
  241. "information_schema".key_column_usage cu1,
  242. "information_schema".key_column_usage cu2
  243. where
  244. cu1."constraint_name" = rc."constraint_name" and
  245. cu2."constraint_name" = rc."unique_constraint_name" and
  246. cu1."table_name" = ? and
  247. cu2."table_name" in ? and
  248. cu1."table_catalog" = ? and
  249. cu2."table_catalog" = ?',
  250. 'reflect_has_many'=>'select
  251. cu1."table_name",cu1."column_name",
  252. cu2."table_name",cu2."column_name"
  253. from
  254. "information_schema".referential_constraints rc,
  255. "information_schema".key_column_usage cu1,
  256. "information_schema".key_column_usage cu2
  257. where
  258. cu1."constraint_name" = rc."constraint_name" and
  259. cu2."constraint_name" = rc."unique_constraint_name" and
  260. cu1."table_name" in ? and
  261. cu2."table_name" = ? and
  262. cu1."table_catalog" = ? and
  263. cu2."table_catalog" = ?',
  264. 'reflect_habtm'=>'select
  265. cua1."table_name",cua1."column_name",
  266. cua2."table_name",cua2."column_name",
  267. cub1."table_name",cub1."column_name",
  268. cub2."table_name",cub2."column_name"
  269. from
  270. "information_schema".referential_constraints rca,
  271. "information_schema".referential_constraints rcb,
  272. "information_schema".key_column_usage cua1,
  273. "information_schema".key_column_usage cua2,
  274. "information_schema".key_column_usage cub1,
  275. "information_schema".key_column_usage cub2
  276. where
  277. cua1."constraint_name" = rca."constraint_name" and
  278. cua2."constraint_name" = rca."unique_constraint_name" and
  279. cub1."constraint_name" = rcb."constraint_name" and
  280. cub2."constraint_name" = rcb."unique_constraint_name" and
  281. cua1."table_catalog" = ? and
  282. cub1."table_catalog" = ? and
  283. cua2."table_catalog" = ? and
  284. cub2."table_catalog" = ? and
  285. cua1."table_name" = cub1."table_name" and
  286. cua2."table_name" = ? and
  287. cub2."table_name" in ?'
  288. );
  289. }
  290. public function getSql($name) {
  291. return isset($this->queries[$name])?$this->queries[$name]:false;
  292. }
  293. public function connect($hostname,$username,$password,$database,$port,$socket,$charset) {
  294. $e = function ($v) { return str_replace(array('\'','\\'),array('\\\'','\\\\'),$v); };
  295. $conn_string = '';
  296. if ($hostname || $socket) {
  297. if ($socket) $hostname = $e($socket);
  298. else $hostname = $e($hostname);
  299. $conn_string.= " host='$hostname'";
  300. }
  301. if ($port) {
  302. $port = ($port+0);
  303. $conn_string.= " port='$port'";
  304. }
  305. if ($database) {
  306. $database = $e($database);
  307. $conn_string.= " dbname='$database'";
  308. }
  309. if ($username) {
  310. $username = $e($username);
  311. $conn_string.= " user='$username'";
  312. }
  313. if ($password) {
  314. $password = $e($password);
  315. $conn_string.= " password='$password'";
  316. }
  317. if ($charset) {
  318. $charset = $e($charset);
  319. $conn_string.= " options='--client_encoding=$charset'";
  320. }
  321. $db = pg_connect($conn_string);
  322. $this->db = $db;
  323. }
  324. public function query($sql,$params=array()) {
  325. $db = $this->db;
  326. $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params) {
  327. $param = array_shift($params);
  328. if ($matches[0]=='!') {
  329. $key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param);
  330. if (is_object($param) && $param->type=='base64') {
  331. return "encode(\"$key\",'base64') as \"$key\"";
  332. }
  333. if (is_object($param) && $param->type=='wkt') {
  334. return "ST_AsText(\"$key\") as \"$key\"";
  335. }
  336. return '"'.$key.'"';
  337. } else {
  338. if (is_array($param)) return '('.implode(',',array_map(function($v) use (&$db) {
  339. return "'".pg_escape_string($db,$v)."'";
  340. },$param)).')';
  341. if (is_object($param) && $param->type=='base64') {
  342. return "'\x".bin2hex(base64_decode($param->value))."'";
  343. }
  344. if (is_object($param) && $param->type=='wkt') {
  345. return "ST_GeomFromText('".pg_escape_string($db,$param->value)."')";
  346. }
  347. if ($param===null) return 'NULL';
  348. return "'".pg_escape_string($db,$param)."'";
  349. }
  350. }, $sql);
  351. if (strtoupper(substr($sql,0,6))=='INSERT') {
  352. $sql .= ' RETURNING id;';
  353. }
  354. //echo "\n$sql\n";
  355. return @pg_query($db,$sql);
  356. }
  357. protected function convertFloatAndInt($result,&$values,&$fields) {
  358. array_walk($values, function(&$v,$i) use ($result,$fields){
  359. if (is_string($v) && $this->isNumericType($fields[$i])) {
  360. $v+=0;
  361. }
  362. });
  363. }
  364. public function fetchAssoc($result,$fields=false) {
  365. $values = pg_fetch_assoc($result);
  366. if ($values && $fields) {
  367. $this->convertFloatAndInt($result,$values,$fields);
  368. }
  369. return $values;
  370. }
  371. public function fetchRow($result,$fields=false) {
  372. $values = pg_fetch_row($result);
  373. if ($values && $fields) {
  374. $fields = array_values($fields);
  375. $this->convertFloatAndInt($result,$values,$fields);
  376. }
  377. return $values;
  378. }
  379. public function insertId($result) {
  380. list($id) = pg_fetch_row($result);
  381. return (int)$id;
  382. }
  383. public function affectedRows($result) {
  384. return pg_affected_rows($result);
  385. }
  386. public function close($result) {
  387. return pg_free_result($result);
  388. }
  389. public function fetchFields($table) {
  390. $result = $this->query('SELECT * FROM ! WHERE 1=2;',array($table));
  391. $keys = array();
  392. for($i=0;$i<pg_num_fields($result);$i++) {
  393. $field = array();
  394. $field['name'] = pg_field_name($result,$i);
  395. $field['type'] = pg_field_type($result,$i);
  396. $keys[$i] = (object)$field;
  397. }
  398. return $keys;
  399. }
  400. public function addLimitToSql($sql,$limit,$offset) {
  401. return "$sql LIMIT $limit OFFSET $offset";
  402. }
  403. public function likeEscape($string) {
  404. return addcslashes($string,'%_');
  405. }
  406. public function convertFilter($field, $comparator, $value) {
  407. return false;
  408. }
  409. public function isNumericType($field) {
  410. return in_array($field->type, array('int2', 'int4', 'int8', 'float4', 'float8'));
  411. }
  412. public function isBinaryType($field) {
  413. return $field->type == 'bytea';
  414. }
  415. public function isGeometryType($field) {
  416. return $field->type == 'geometry';
  417. }
  418. public function getDefaultCharset() {
  419. return 'UTF8';
  420. }
  421. public function beginTransaction() {
  422. return $this->query('BEGIN');
  423. }
  424. public function commitTransaction() {
  425. return $this->query('COMMIT');
  426. }
  427. public function rollbackTransaction() {
  428. return $this->query('ROLLBACK');
  429. }
  430. }
  431. class SQLServer implements DatabaseInterface {
  432. protected $db;
  433. protected $queries;
  434. public function __construct() {
  435. $this->queries = array(
  436. 'list_tables'=>'SELECT
  437. "TABLE_NAME",\'\' as "TABLE_COMMENT"
  438. FROM
  439. "INFORMATION_SCHEMA"."TABLES"
  440. WHERE
  441. "TABLE_CATALOG" = ?',
  442. 'reflect_table'=>'SELECT
  443. "TABLE_NAME"
  444. FROM
  445. "INFORMATION_SCHEMA"."TABLES"
  446. WHERE
  447. "TABLE_NAME" LIKE ? AND
  448. "TABLE_CATALOG" = ?',
  449. 'reflect_pk'=>'SELECT
  450. "COLUMN_NAME"
  451. FROM
  452. "INFORMATION_SCHEMA"."TABLE_CONSTRAINTS" tc,
  453. "INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" ku
  454. WHERE
  455. tc."CONSTRAINT_TYPE" = \'PRIMARY KEY\' AND
  456. tc."CONSTRAINT_NAME" = ku."CONSTRAINT_NAME" AND
  457. ku."TABLE_NAME" = ? AND
  458. ku."TABLE_CATALOG" = ?',
  459. 'reflect_belongs_to'=>'SELECT
  460. cu1."TABLE_NAME",cu1."COLUMN_NAME",
  461. cu2."TABLE_NAME",cu2."COLUMN_NAME"
  462. FROM
  463. "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rc,
  464. "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu1,
  465. "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu2
  466. WHERE
  467. cu1."CONSTRAINT_NAME" = rc."CONSTRAINT_NAME" AND
  468. cu2."CONSTRAINT_NAME" = rc."UNIQUE_CONSTRAINT_NAME" AND
  469. cu1."TABLE_NAME" = ? AND
  470. cu2."TABLE_NAME" IN ? AND
  471. cu1."TABLE_CATALOG" = ? AND
  472. cu2."TABLE_CATALOG" = ?',
  473. 'reflect_has_many'=>'SELECT
  474. cu1."TABLE_NAME",cu1."COLUMN_NAME",
  475. cu2."TABLE_NAME",cu2."COLUMN_NAME"
  476. FROM
  477. "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rc,
  478. "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu1,
  479. "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu2
  480. WHERE
  481. cu1."CONSTRAINT_NAME" = rc."CONSTRAINT_NAME" AND
  482. cu2."CONSTRAINT_NAME" = rc."UNIQUE_CONSTRAINT_NAME" AND
  483. cu1."TABLE_NAME" IN ? AND
  484. cu2."TABLE_NAME" = ? AND
  485. cu1."TABLE_CATALOG" = ? AND
  486. cu2."TABLE_CATALOG" = ?',
  487. 'reflect_habtm'=>'SELECT
  488. cua1."TABLE_NAME",cua1."COLUMN_NAME",
  489. cua2."TABLE_NAME",cua2."COLUMN_NAME",
  490. cub1."TABLE_NAME",cub1."COLUMN_NAME",
  491. cub2."TABLE_NAME",cub2."COLUMN_NAME"
  492. FROM
  493. "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rca,
  494. "INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rcb,
  495. "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cua1,
  496. "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cua2,
  497. "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cub1,
  498. "INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cub2
  499. WHERE
  500. cua1."CONSTRAINT_NAME" = rca."CONSTRAINT_NAME" AND
  501. cua2."CONSTRAINT_NAME" = rca."UNIQUE_CONSTRAINT_NAME" AND
  502. cub1."CONSTRAINT_NAME" = rcb."CONSTRAINT_NAME" AND
  503. cub2."CONSTRAINT_NAME" = rcb."UNIQUE_CONSTRAINT_NAME" AND
  504. cua1."TABLE_CATALOG" = ? AND
  505. cub1."TABLE_CATALOG" = ? AND
  506. cua2."TABLE_CATALOG" = ? AND
  507. cub2."TABLE_CATALOG" = ? AND
  508. cua1."TABLE_NAME" = cub1."TABLE_NAME" AND
  509. cua2."TABLE_NAME" = ? AND
  510. cub2."TABLE_NAME" IN ?'
  511. );
  512. }
  513. public function getSql($name) {
  514. return isset($this->queries[$name])?$this->queries[$name]:false;
  515. }
  516. public function connect($hostname,$username,$password,$database,$port,$socket,$charset) {
  517. $connectionInfo = array();
  518. if ($port) $hostname.=','.$port;
  519. if ($username) $connectionInfo['UID']=$username;
  520. if ($password) $connectionInfo['PWD']=$password;
  521. if ($database) $connectionInfo['Database']=$database;
  522. if ($charset) $connectionInfo['CharacterSet']=$charset;
  523. $connectionInfo['QuotedId']=1;
  524. $connectionInfo['ReturnDatesAsStrings']=1;
  525. $db = sqlsrv_connect($hostname, $connectionInfo);
  526. if (!$db) {
  527. throw new \Exception('Connect failed. '.print_r( sqlsrv_errors(), true));
  528. }
  529. if ($socket) {
  530. throw new \Exception('Socket connection is not supported.');
  531. }
  532. $this->db = $db;
  533. }
  534. public function query($sql,$params=array()) {
  535. $args = array();
  536. $db = $this->db;
  537. $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params,&$args) {
  538. static $i=-1;
  539. $i++;
  540. $param = $params[$i];
  541. if ($matches[0]=='!') {
  542. $key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param);
  543. if (is_object($param) && $param->type=='base64') {
  544. return "CAST(N'' AS XML).value('xs:base64Binary(xs:hexBinary(sql:column(\"$key\")))', 'VARCHAR(MAX)') as \"$key\"";
  545. }
  546. if (is_object($param) && $param->type=='wkt') {
  547. return "\"$key\".STAsText() as \"$key\"";
  548. }
  549. return '"'.$key.'"';
  550. } else {
  551. // This is workaround because SQLSRV cannot accept NULL in a param
  552. if ($matches[0]=='?' && is_null($param)) {
  553. return 'NULL';
  554. }
  555. if (is_array($param)) {
  556. $args = array_merge($args,$param);
  557. return '('.implode(',',str_split(str_repeat('?',count($param)))).')';
  558. }
  559. if (is_object($param) && $param->type=='base64') {
  560. $args[] = bin2hex(base64_decode($param->value));
  561. return 'CONVERT(VARBINARY(MAX),?,2)';
  562. }
  563. if (is_object($param) && $param->type=='wkt') {
  564. $args[] = $param->value;
  565. return 'geometry::STGeomFromText(?,0)';
  566. }
  567. $args[] = $param;
  568. return '?';
  569. }
  570. }, $sql);
  571. //var_dump($params);
  572. //echo "\n$sql\n";
  573. //var_dump($args);
  574. //file_put_contents('sql.txt',"\n$sql\n".var_export($args,true)."\n",FILE_APPEND);
  575. if (strtoupper(substr($sql,0,6))=='INSERT') {
  576. $sql .= ';SELECT SCOPE_IDENTITY()';
  577. }
  578. return sqlsrv_query($db,$sql,$args)?:null;
  579. }
  580. public function fetchAssoc($result,$fields=false) {
  581. return sqlsrv_fetch_array($result, SQLSRV_FETCH_ASSOC);
  582. }
  583. public function fetchRow($result,$fields=false) {
  584. return sqlsrv_fetch_array($result, SQLSRV_FETCH_NUMERIC);
  585. }
  586. public function insertId($result) {
  587. sqlsrv_next_result($result);
  588. sqlsrv_fetch($result);
  589. return (int)sqlsrv_get_field($result, 0);
  590. }
  591. public function affectedRows($result) {
  592. return sqlsrv_rows_affected($result);
  593. }
  594. public function close($result) {
  595. return sqlsrv_free_stmt($result);
  596. }
  597. public function fetchFields($table) {
  598. $result = $this->query('SELECT * FROM ! WHERE 1=2;',array($table));
  599. //var_dump(sqlsrv_field_metadata($result));
  600. return array_map(function($a){
  601. $p = array();
  602. foreach ($a as $k=>$v) {
  603. $p[strtolower($k)] = $v;
  604. }
  605. return (object)$p;
  606. },sqlsrv_field_metadata($result));
  607. }
  608. public function addLimitToSql($sql,$limit,$offset) {
  609. return "$sql OFFSET $offset ROWS FETCH NEXT $limit ROWS ONLY";
  610. }
  611. public function likeEscape($string) {
  612. return str_replace(array('%','_'),array('[%]','[_]'),$string);
  613. }
  614. public function convertFilter($field, $comparator, $value) {
  615. $comparator = strtolower($comparator);
  616. if ($comparator[0]!='n') {
  617. switch ($comparator) {
  618. case 'sco': return array('!.STContains(geometry::STGeomFromText(?,0))=1',$field,$value);
  619. case 'scr': return array('!.STCrosses(geometry::STGeomFromText(?,0))=1',$field,$value);
  620. case 'sdi': return array('!.STDisjoint(geometry::STGeomFromText(?,0))=1',$field,$value);
  621. case 'seq': return array('!.STEquals(geometry::STGeomFromText(?,0))=1',$field,$value);
  622. case 'sin': return array('!.STIntersects(geometry::STGeomFromText(?,0))=1',$field,$value);
  623. case 'sov': return array('!.STOverlaps(geometry::STGeomFromText(?,0))=1',$field,$value);
  624. case 'sto': return array('!.STTouches(geometry::STGeomFromText(?,0))=1',$field,$value);
  625. case 'swi': return array('!.STWithin(geometry::STGeomFromText(?,0))=1',$field,$value);
  626. case 'sic': return array('!.STIsClosed()=1',$field);
  627. case 'sis': return array('!.STIsSimple()=1',$field);
  628. case 'siv': return array('!.STIsValid()=1',$field);
  629. }
  630. } else {
  631. switch ($comparator) {
  632. case 'nsco': return array('!.STContains(geometry::STGeomFromText(?,0))=0',$field,$value);
  633. case 'nscr': return array('!.STCrosses(geometry::STGeomFromText(?,0))=0',$field,$value);
  634. case 'nsdi': return array('!.STDisjoint(geometry::STGeomFromText(?,0))=0',$field,$value);
  635. case 'nseq': return array('!.STEquals(geometry::STGeomFromText(?,0))=0',$field,$value);
  636. case 'nsin': return array('!.STIntersects(geometry::STGeomFromText(?,0))=0',$field,$value);
  637. case 'nsov': return array('!.STOverlaps(geometry::STGeomFromText(?,0))=0',$field,$value);
  638. case 'nsto': return array('!.STTouches(geometry::STGeomFromText(?,0))=0',$field,$value);
  639. case 'nswi': return array('!.STWithin(geometry::STGeomFromText(?,0))=0',$field,$value);
  640. case 'nsic': return array('!.STIsClosed()=0',$field);
  641. case 'nsis': return array('!.STIsSimple()=0',$field);
  642. case 'nsiv': return array('!.STIsValid()=0',$field);
  643. }
  644. }
  645. return false;
  646. }
  647. public function isNumericType($field) {
  648. return in_array($field->type,array(-6,-5,4,5,2,6,7));
  649. }
  650. public function isBinaryType($field) {
  651. return ($field->type>=-4 && $field->type<=-2);
  652. }
  653. public function isGeometryType($field) {
  654. return ($field->type==-151);
  655. }
  656. public function getDefaultCharset() {
  657. return 'UTF-8';
  658. }
  659. public function beginTransaction() {
  660. return sqlsrv_begin_transaction($this->db);
  661. }
  662. public function commitTransaction() {
  663. return sqlsrv_commit($this->db);
  664. }
  665. public function rollbackTransaction() {
  666. return sqlsrv_rollback($this->db);
  667. }
  668. }
  669. class SQLite implements DatabaseInterface {
  670. protected $db;
  671. protected $queries;
  672. public function __construct() {
  673. $this->queries = array(
  674. 'list_tables'=>'SELECT
  675. "name", ""
  676. FROM
  677. "sys/tables"',
  678. 'reflect_table'=>'SELECT
  679. "name"
  680. FROM
  681. "sys/tables"
  682. WHERE
  683. "name"=?',
  684. 'reflect_pk'=>'SELECT
  685. "name"
  686. FROM
  687. "sys/columns"
  688. WHERE
  689. "pk"=1 AND
  690. "self"=?',
  691. 'reflect_belongs_to'=>'SELECT
  692. "self", "from",
  693. "table", "to"
  694. FROM
  695. "sys/foreign_keys"
  696. WHERE
  697. "self" = ? AND
  698. "table" IN ? AND
  699. ? like "%" AND
  700. ? like "%"',
  701. 'reflect_has_many'=>'SELECT
  702. "self", "from",
  703. "table", "to"
  704. FROM
  705. "sys/foreign_keys"
  706. WHERE
  707. "self" IN ? AND
  708. "table" = ? AND
  709. ? like "%" AND
  710. ? like "%"',
  711. 'reflect_habtm'=>'SELECT
  712. k1."self", k1."from",
  713. k1."table", k1."to",
  714. k2."self", k2."from",
  715. k2."table", k2."to"
  716. FROM
  717. "sys/foreign_keys" k1,
  718. "sys/foreign_keys" k2
  719. WHERE
  720. ? like "%" AND
  721. ? like "%" AND
  722. ? like "%" AND
  723. ? like "%" AND
  724. k1."self" = k2."self" AND
  725. k1."table" = ? AND
  726. k2."table" IN ?'
  727. );
  728. }
  729. public function getSql($name) {
  730. return isset($this->queries[$name])?$this->queries[$name]:false;
  731. }
  732. public function connect($hostname,$username,$password,$database,$port,$socket,$charset) {
  733. $this->db = new SQLite3($database);
  734. // optimizations
  735. $this->db->querySingle('PRAGMA synchronous = NORMAL');
  736. $this->db->querySingle('PRAGMA foreign_keys = on');
  737. $reflection = $this->db->querySingle('SELECT name FROM sqlite_master WHERE type = "table" and name like "sys/%"');
  738. if (!$reflection) {
  739. //create reflection tables
  740. $this->query('CREATE table "sys/version" ("version" integer)');
  741. $this->query('CREATE table "sys/tables" ("name" text)');
  742. $this->query('CREATE table "sys/columns" ("self" text,"cid" integer,"name" text,"type" integer,"notnull" integer,"dflt_value" integer,"pk" integer)');
  743. $this->query('CREATE table "sys/foreign_keys" ("self" text,"id" integer,"seq" integer,"table" text,"from" text,"to" text,"on_update" text,"on_delete" text,"match" text)');
  744. }
  745. $version = $this->db->querySingle('pragma schema_version');
  746. if ($version != $this->db->querySingle('SELECT "version" from "sys/version"')) {
  747. // reflection may take a while
  748. set_time_limit(3600);
  749. // update version data
  750. $this->query('DELETE FROM "sys/version"');
  751. $this->query('INSERT into "sys/version" ("version") VALUES (?)',array($version));
  752. // update tables data
  753. $this->query('DELETE FROM "sys/tables"');
  754. $result = $this->query('SELECT * FROM sqlite_master WHERE (type = "table" or type = "view") and name not like "sys/%" and name<>"sqlite_sequence"');
  755. $tables = array();
  756. while ($row = $this->fetchAssoc($result)) {
  757. $tables[] = $row['name'];
  758. $this->query('INSERT into "sys/tables" ("name") VALUES (?)',array($row['name']));
  759. }
  760. // update columns and foreign_keys data
  761. $this->query('DELETE FROM "sys/columns"');
  762. $this->query('DELETE FROM "sys/foreign_keys"');
  763. foreach ($tables as $table) {
  764. $result = $this->query('pragma table_info(!)',array($table));
  765. while ($row = $this->fetchRow($result)) {
  766. array_unshift($row, $table);
  767. $this->query('INSERT into "sys/columns" ("self","cid","name","type","notnull","dflt_value","pk") VALUES (?,?,?,?,?,?,?)',$row);
  768. }
  769. $result = $this->query('pragma foreign_key_list(!)',array($table));
  770. while ($row = $this->fetchRow($result)) {
  771. array_unshift($row, $table);
  772. $this->query('INSERT into "sys/foreign_keys" ("self","id","seq","table","from","to","on_update","on_delete","match") VALUES (?,?,?,?,?,?,?,?,?)',$row);
  773. }
  774. }
  775. }
  776. }
  777. public function query($sql,$params=array()) {
  778. $db = $this->db;
  779. $sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params) {
  780. $param = array_shift($params);
  781. if ($matches[0]=='!') {
  782. $key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param);
  783. return '"'.$key.'"';
  784. } else {
  785. if (is_array($param)) return '('.implode(',',array_map(function($v) use (&$db) {
  786. return "'".$db->escapeString($v)."'";
  787. },$param)).')';
  788. if (is_object($param) && $param->type=='base64') {
  789. return "'".$db->escapeString($param->value)."'";
  790. }
  791. if ($param===null) return 'NULL';
  792. return "'".$db->escapeString($param)."'";
  793. }
  794. }, $sql);
  795. //echo "\n$sql\n";
  796. try { $result=$db->query($sql); } catch(\Exception $e) { $result=null; }
  797. return $result;
  798. }
  799. public function fetchAssoc($result,$fields=false) {
  800. return $result->fetchArray(SQLITE3_ASSOC);
  801. }
  802. public function fetchRow($result,$fields=false) {
  803. return $result->fetchArray(SQLITE3_NUM);
  804. }
  805. public function insertId($result) {
  806. return $this->db->lastInsertRowID();
  807. }
  808. public function affectedRows($result) {
  809. return $this->db->changes();
  810. }
  811. public function close($result) {
  812. return $result->finalize();
  813. }
  814. public function fetchFields($table) {
  815. $result = $this->query('SELECT * FROM "sys/columns" WHERE "self"=?;',array($table));
  816. $fields = array();
  817. while ($row = $this->fetchAssoc($result)){
  818. $fields[strtolower($row['name'])] = (object)$row;
  819. }
  820. return $fields;
  821. }
  822. public function addLimitToSql($sql,$limit,$offset) {
  823. return "$sql LIMIT $limit OFFSET $offset";
  824. }
  825. public function likeEscape($string) {
  826. return addcslashes($string,'%_');
  827. }
  828. public function convertFilter($field, $comparator, $value) {
  829. return false;
  830. }
  831. public function isNumericType($field) {
  832. return in_array($field->type,array('integer','real'));
  833. }
  834. public function isBinaryType($field) {
  835. return (substr($field->type,0,4)=='data');
  836. }
  837. public function isGeometryType($field) {
  838. return false;
  839. }
  840. public function getDefaultCharset() {
  841. return 'utf8';
  842. }
  843. public function beginTransaction() {
  844. return $this->query('BEGIN');
  845. }
  846. public function commitTransaction() {
  847. return $this->query('COMMIT');
  848. }
  849. public function rollbackTransaction() {
  850. return $this->query('ROLLBACK');
  851. }
  852. }
  853. class PHP_CRUD_API {
  854. protected $db;
  855. protected $settings;
  856. protected function mapMethodToAction($method,$key) {
  857. switch ($method) {
  858. case 'OPTIONS': return 'headers';
  859. case 'GET': return ($key===false)?'list':'read';
  860. case 'PUT': return 'update';
  861. case 'POST': return 'create';
  862. case 'DELETE': return 'delete';
  863. case 'PATCH': return 'increment';
  864. default: $this->exitWith404('method');
  865. }
  866. return false;
  867. }
  868. protected function parseRequestParameter(&$request,$characters) {
  869. if ($request==='') return false;
  870. $pos = strpos($request,'/');
  871. $value = $pos?substr($request,0,$pos):$request;
  872. $request = $pos?substr($request,$pos+1):'';
  873. if (!$characters) return $value;
  874. return preg_replace("/[^$characters]/",'',$value);
  875. }
  876. protected function parseGetParameter($get,$name,$characters) {
  877. $value = isset($get[$name])?$get[$name]:false;
  878. return $characters?preg_replace("/[^$characters]/",'',$value):$value;
  879. }
  880. protected function parseGetParameterArray($get,$name,$characters) {
  881. $values = isset($get[$name])?$get[$name]:false;
  882. if (!is_array($values)) $values = array($values);
  883. if ($characters) {
  884. foreach ($values as &$value) {
  885. $value = preg_replace("/[^$characters]/",'',$value);
  886. }
  887. }
  888. return $values;
  889. }
  890. protected function applyTableAuthorizer($callback,$action,$database,&$tables) {
  891. if (is_callable($callback,true)) foreach ($tables as $i=>$table) {
  892. if (!$callback($action,$database,$table)) {
  893. unset($tables[$i]);
  894. }
  895. }
  896. }
  897. protected function applyRecordFilter($callback,$action,$database,$tables,&$filters) {
  898. if (is_callable($callback,true)) foreach ($tables as $i=>$table) {
  899. $this->addFilters($filters,$table,array($table=>'and'),$callback($action,$database,$table));
  900. }
  901. }
  902. protected function applyTenancyFunction($callback,$action,$database,$fields,&$filters) {
  903. if (is_callable($callback,true)) foreach ($fields as $table=>$keys) {
  904. foreach ($keys as $field) {
  905. $v = $callback($action,$database,$table,$field->name);
  906. if ($v!==null) {
  907. if (is_array($v)) $this->addFilter($filters,$table,'and',$field->name,'in',implode(',',$v));
  908. else $this->addFilter($filters,$table,'and',$field->name,'eq',$v);
  909. }
  910. }
  911. }
  912. }
  913. protected function applyColumnAuthorizer($callback,$action,$database,&$fields) {
  914. if (is_callable($callback,true)) foreach ($fields as $table=>$keys) {
  915. foreach ($keys as $field) {
  916. if (!$callback($action,$database,$table,$field->name)) {
  917. unset($fields[$table][$field->name]);
  918. }
  919. }
  920. }
  921. }
  922. protected function applyInputTenancy($callback,$action,$database,$table,&$input,$keys) {
  923. if (is_callable($callback,true)) foreach ($keys as $key=>$field) {
  924. $v = $callback($action,$database,$table,$key);
  925. if ($v!==null && (isset($input->$key) || $action=='create')) {
  926. if (is_array($v)) {
  927. if (!count($v)) {
  928. $input->$key = null;
  929. } elseif (!isset($input->$key)) {
  930. $input->$key = $v[0];
  931. } elseif (!in_array($input->$key,$v)) {
  932. $input->$key = null;
  933. }
  934. } else {
  935. $input->$key = $v;
  936. }
  937. }
  938. }
  939. }
  940. protected function applyInputSanitizer($callback,$action,$database,$table,&$input,$keys) {
  941. if (is_callable($callback,true)) foreach ((array)$input as $key=>$value) {
  942. if (isset($keys[$key])) {
  943. $input->$key = $callback($action,$database,$table,$key,$keys[$key]->type,$value);
  944. }
  945. }
  946. }
  947. protected function applyInputValidator($callback,$action,$database,$table,$input,$keys,$context) {
  948. $errors = array();
  949. if (is_callable($callback,true)) foreach ((array)$input as $key=>$value) {
  950. if (isset($keys[$key])) {
  951. $error = $callback($action,$database,$table,$key,$keys[$key]->type,$value,$context);
  952. if ($error!==true && $error!==null) $errors[$key] = $error;
  953. }
  954. }
  955. if (!empty($errors)) $this->exitWith422($errors);
  956. }
  957. protected function processTableAndIncludeParameters($database,$table,$include,$action) {
  958. $blacklist = array('information_schema','mysql','sys','pg_catalog');
  959. if (in_array(strtolower($database), $blacklist)) return array();
  960. $table_list = array();
  961. if ($result = $this->db->query($this->db->getSql('reflect_table'),array($table,$database))) {
  962. while ($row = $this->db->fetchRow($result)) $table_list[] = $row[0];
  963. $this->db->close($result);
  964. }
  965. if (empty($table_list)) $this->exitWith404('entity');
  966. if ($action=='list') {
  967. foreach (explode(',',$include) as $table) {
  968. if ($result = $this->db->query($this->db->getSql('reflect_table'),array($table,$database))) {
  969. while ($row = $this->db->fetchRow($result)) $table_list[] = $row[0];
  970. $this->db->close($result);
  971. }
  972. }
  973. }
  974. return $table_list;
  975. }
  976. protected function exitWith404($type) {
  977. if (isset($_SERVER['REQUEST_METHOD'])) {
  978. header('Content-Type:',true,404);
  979. die("Not found ($type)");
  980. } else {
  981. throw new \Exception("Not found ($type)");
  982. }
  983. }
  984. protected function exitWith422($object) {
  985. if (isset($_SERVER['REQUEST_METHOD'])) {
  986. header('Content-Type:',true,422);
  987. die(json_encode($object));
  988. } else {
  989. throw new \Exception(json_encode($object));
  990. }
  991. }
  992. protected function headersCommand($parameters) {
  993. $headers = array();
  994. $headers[]='Access-Control-Allow-Headers: Content-Type';
  995. $headers[]='Access-Control-Allow-Methods: OPTIONS, GET, PUT, POST, DELETE, PATCH';
  996. $headers[]='Access-Control-Allow-Credentials: true';
  997. $headers[]='Access-Control-Max-Age: 1728000';
  998. if (isset($_SERVER['REQUEST_METHOD'])) {
  999. foreach ($headers as $header) header($header);
  1000. } else {
  1001. echo json_encode($headers);
  1002. }
  1003. }
  1004. protected function startOutput() {
  1005. if (isset($_SERVER['REQUEST_METHOD'])) {
  1006. header('Content-Type: application/json; charset=utf-8');
  1007. }
  1008. }
  1009. protected function findPrimaryKeys($table,$database) {
  1010. $fields = array();
  1011. if ($result = $this->db->query($this->db->getSql('reflect_pk'),array($table,$database))) {
  1012. while ($row = $this->db->fetchRow($result)) {
  1013. $fields[] = $row[0];
  1014. }
  1015. $this->db->close($result);
  1016. }
  1017. return $fields;
  1018. }
  1019. protected function processKeyParameter($key,$tables,$database) {
  1020. if ($key===false) return false;
  1021. $fields = $this->findPrimaryKeys($tables[0],$database);
  1022. if (count($fields)!=1) $this->exitWith404('1pk');
  1023. return array($key,$fields[0]);
  1024. }
  1025. protected function processOrderingsParameter($orderings) {
  1026. if (!$orderings) return false;
  1027. foreach ($orderings as &$order) {
  1028. $order = explode(',',$order,2);
  1029. if (count($order)<2) $order[1]='ASC';
  1030. if (!strlen($order[0])) return false;
  1031. $direction = strtoupper($order[1]);
  1032. if (in_array($direction,array('ASC','DESC'))) {
  1033. $order[1] = $direction;
  1034. }
  1035. }
  1036. return $orderings;
  1037. }
  1038. protected function convertFilter($field, $comparator, $value) {
  1039. $result = $this->db->convertFilter($field,$comparator,$value);
  1040. if ($result) return $result;
  1041. // default behavior
  1042. $comparator = strtolower($comparator);
  1043. if ($comparator[0]!='n') {
  1044. if (strlen($comparator)==2) {
  1045. switch ($comparator) {
  1046. case 'cs': return array('! LIKE ?',$field,'%'.$this->db->likeEscape($value).'%');
  1047. case 'sw': return array('! LIKE ?',$field,$this->db->likeEscape($value).'%');
  1048. case 'ew': return array('! LIKE ?',$field,'%'.$this->db->likeEscape($value));
  1049. case 'eq': return array('! = ?',$field,$value);
  1050. case 'lt': return array('! < ?',$field,$value);
  1051. case 'le': return array('! <= ?',$field,$value);
  1052. case 'ge': return array('! >= ?',$field,$value);
  1053. case 'gt': return array('! > ?',$field,$value);
  1054. case 'bt': $v = explode(',',$value); if (count($v)<2) return false;
  1055. return array('! BETWEEN ? AND ?',$field,$v[0],$v[1]);
  1056. case 'in': return array('! IN ?',$field,explode(',',$value));
  1057. case 'is': return array('! IS NULL',$field);
  1058. }
  1059. } else {
  1060. switch ($comparator) {
  1061. case 'sco': return array('ST_Contains(!,ST_GeomFromText(?))=TRUE',$field,$value);
  1062. case 'scr': return array('ST_Crosses(!,ST_GeomFromText(?))=TRUE',$field,$value);
  1063. case 'sdi': return array('ST_Disjoint(!,ST_GeomFromText(?))=TRUE',$field,$value);
  1064. case 'seq': return array('ST_Equals(!,ST_GeomFromText(?))=TRUE',$field,$value);
  1065. case 'sin': return array('ST_Intersects(!,ST_GeomFromText(?))=TRUE',$field,$value);
  1066. case 'sov': return array('ST_Overlaps(!,ST_GeomFromText(?))=TRUE',$field,$value);
  1067. case 'sto': return array('ST_Touches(!,ST_GeomFromText(?))=TRUE',$field,$value);
  1068. case 'swi': return array('ST_Within(!,ST_GeomFromText(?))=TRUE',$field,$value);
  1069. case 'sic': return array('ST_IsClosed(!)=TRUE',$field);
  1070. case 'sis': return array('ST_IsSimple(!)=TRUE',$field);
  1071. case 'siv': return array('ST_IsValid(!)=TRUE',$field);
  1072. }
  1073. }
  1074. } else {
  1075. if (strlen($comparator)==2) {
  1076. switch ($comparator) {
  1077. case 'ne': return $this->convertFilter($field, 'neq', $value); // deprecated
  1078. case 'ni': return $this->convertFilter($field, 'nin', $value); // deprecated
  1079. case 'no': return $this->convertFilter($field, 'nis', $value); // deprecated
  1080. }
  1081. } elseif (strlen($comparator)==3) {
  1082. switch ($comparator) {
  1083. case 'ncs': return array('! NOT LIKE ?',$field,'%'.$this->db->likeEscape($value).'%');
  1084. case 'nsw': return array('! NOT LIKE ?',$field,$this->db->likeEscape($value).'%');
  1085. case 'new': return array('! NOT LIKE ?',$field,'%'.$this->db->likeEscape($value));
  1086. case 'neq': return array('! <> ?',$field,$value);
  1087. case 'nlt': return array('! >= ?',$field,$value);
  1088. case 'nle': return array('! > ?',$field,$value);
  1089. case 'nge': return array('! < ?',$field,$value);
  1090. case 'ngt': return array('! <= ?',$field,$value);
  1091. case 'nbt': $v = explode(',',$value); if (count($v)<2) return false;
  1092. return array('! NOT BETWEEN ? AND ?',$field,$v[0],$v[1]);
  1093. case 'nin': return array('! NOT IN ?',$field,explode(',',$value));
  1094. case 'nis': return array('! IS NOT NULL',$field);
  1095. }
  1096. } else {
  1097. switch ($comparator) {
  1098. case 'nsco': return array('ST_Contains(!,ST_GeomFromText(?))=FALSE',$field,$value);
  1099. case 'nscr': return array('ST_Crosses(!,ST_GeomFromText(?))=FALSE',$field,$value);
  1100. case 'nsdi': return array('ST_Disjoint(!,ST_GeomFromText(?))=FALSE',$field,$value);
  1101. case 'nseq': return array('ST_Equals(!,ST_GeomFromText(?))=FALSE',$field,$value);
  1102. case 'nsin': return array('ST_Intersects(!,ST_GeomFromText(?))=FALSE',$field,$value);
  1103. case 'nsov': return array('ST_Overlaps(!,ST_GeomFromText(?))=FALSE',$field,$value);
  1104. case 'nsto': return array('ST_Touches(!,ST_GeomFromText(?))=FALSE',$field,$value);
  1105. case 'nswi': return array('ST_Within(!,ST_GeomFromText(?))=FALSE',$field,$value);
  1106. case 'nsic': return array('ST_IsClosed(!)=FALSE',$field);
  1107. case 'nsis': return array('ST_IsSimple(!)=FALSE',$field);
  1108. case 'nsiv': return array('ST_IsValid(!)=FALSE',$field);
  1109. }
  1110. }
  1111. }
  1112. return false;
  1113. }
  1114. public function addFilter(&$filters,$table,$and,$field,$comparator,$value) {
  1115. if (!isset($filters[$table])) $filters[$table] = array();
  1116. if (!isset($filters[$table][$and])) $filters[$table][$and] = array();
  1117. $filter = $this->convertFilter($field,$comparator,$value);
  1118. if ($filter) $filters[$table][$and][] = $filter;
  1119. }
  1120. public function addFilters(&$filters,$table,$satisfy,$filterStrings) {
  1121. if ($filterStrings) {
  1122. for ($i=0;$i<count($filterStrings);$i++) {
  1123. $parts = explode(',',$filterStrings[$i],3);
  1124. if (count($parts)>=2) {
  1125. if (strpos($parts[0],'.')) list($t,$f) = explode('.',$parts[0],2);
  1126. else list($t,$f) = array($table,$parts[0]);
  1127. $comparator = $parts[1];
  1128. $value = isset($parts[2])?$parts[2]:null;
  1129. $and = isset($satisfy[$t])?$satisfy[$t]:'and';
  1130. $this->addFilter($filters,$t,$and,$f,$comparator,$value);
  1131. }
  1132. }
  1133. }
  1134. }
  1135. protected function processSatisfyParameter($tables,$satisfyString) {
  1136. $satisfy = array();
  1137. foreach (explode(',',$satisfyString) as $str) {
  1138. if (strpos($str,'.')) list($t,$s) = explode('.',$str,2);
  1139. else list($t,$s) = array($tables[0],$str);
  1140. $and = ($s && strtolower($s)=='any')?'or':'and';
  1141. $satisfy[$t] = $and;
  1142. }
  1143. return $satisfy;
  1144. }
  1145. protected function processFiltersParameter($tables,$satisfy,$filterStrings) {
  1146. $filters = array();
  1147. $this->addFilters($filters,$tables[0],$satisfy,$filterStrings);
  1148. return $filters;
  1149. }
  1150. protected function processPageParameter($page) {
  1151. if (!$page) return false;
  1152. $page = explode(',',$page,2);
  1153. if (count($page)<2) $page[1]=20;
  1154. $page[0] = ($page[0]-1)*$page[1];
  1155. return $page;
  1156. }
  1157. protected function retrieveObject($key,$fields,$filters,$tables) {
  1158. if (!$key) return false;
  1159. $table = $tables[0];
  1160. $params = array();
  1161. $sql = 'SELECT ';
  1162. $this->convertOutputs($sql,$params,$fields[$table]);
  1163. $sql .= ' FROM !';
  1164. $params[] = $table;
  1165. $this->addFilter($filters,$table,'and',$key[1],'eq',$key[0]);
  1166. $this->addWhereFromFilters($filters[$table],$sql,$params);
  1167. $object = null;
  1168. if ($result = $this->db->query($sql,$params)) {
  1169. $object = $this->db->fetchAssoc($result,$fields[$table]);
  1170. $this->db->close($result);
  1171. }
  1172. return $object;
  1173. }
  1174. protected function retrieveObjects($key,$fields,$filters,$tables) {
  1175. $keyField = $key[1];
  1176. $keys = explode(',',$key[0]);
  1177. $rows = array();
  1178. foreach ($keys as $key) {
  1179. $result = $this->retrieveObject(array($key,$keyField),$fields,$filters,$tables);
  1180. if ($result===null) {
  1181. return null;
  1182. }
  1183. $rows[] = $result;
  1184. }
  1185. return $rows;
  1186. }
  1187. protected function createObject($input,$tables) {
  1188. if (!$input) return false;
  1189. $input = (array)$input;
  1190. $keys = implode(',',str_split(str_repeat('!', count($input))));
  1191. $values = implode(',',str_split(str_repeat('?', count($input))));
  1192. $params = array_merge(array_keys($input),array_values($input));
  1193. array_unshift($params, $tables[0]);
  1194. $result = $this->db->query('INSERT INTO ! ('.$keys.') VALUES ('.$values.')',$params);
  1195. if (!$result) return null;
  1196. return $this->db->insertId($result);
  1197. }
  1198. protected function createObjects($inputs,$tables) {
  1199. if (!$inputs) return false;
  1200. $ids = array();
  1201. $this->db->beginTransaction();
  1202. foreach ($inputs as $input) {
  1203. $result = $this->createObject($input,$tables);
  1204. if ($result===null) {
  1205. $this->db->rollbackTransaction();
  1206. return null;
  1207. }
  1208. $ids[] = $result;
  1209. }
  1210. $this->db->commitTransaction();
  1211. return $ids;
  1212. }
  1213. protected function updateObject($key,$input,$filters,$tables) {
  1214. if (!$input) return false;
  1215. $input = (array)$input;
  1216. $table = $tables[0];
  1217. $sql = 'UPDATE ! SET ';
  1218. $params = array($table);
  1219. foreach (array_keys($input) as $j=>$k) {
  1220. if ($j) $sql .= ',';
  1221. $v = $input[$k];
  1222. $sql .= '!=?';
  1223. $params[] = $k;
  1224. $params[] = $v;
  1225. }
  1226. $this->addFilter($filters,$table,'and',$key[1],'eq',$key[0]);
  1227. $this->addWhereFromFilters($filters[$table],$sql,$params);
  1228. $result = $this->db->query($sql,$params);
  1229. if (!$result) return null;
  1230. return $this->db->affectedRows($result);
  1231. }
  1232. protected function updateObjects($key,$inputs,$filters,$tables) {
  1233. if (!$inputs) return false;
  1234. $keyField = $key[1];
  1235. $keys = explode(',',$key[0]);
  1236. if (count($inputs)!=count($keys)) {
  1237. $this->exitWith404('subject');
  1238. }
  1239. $rows = array();
  1240. $this->db->beginTransaction();
  1241. foreach ($inputs as $i=>$input) {
  1242. $result = $this->updateObject(array($keys[$i],$keyField),$input,$filters,$tables);
  1243. if ($result===null) {
  1244. $this->db->rollbackTransaction();
  1245. return null;
  1246. }
  1247. $rows[] = $result;
  1248. }
  1249. $this->db->commitTransaction();
  1250. return $rows;
  1251. }
  1252. protected function deleteObject($key,$filters,$tables) {
  1253. $table = $tables[0];
  1254. $sql = 'DELETE FROM !';
  1255. $params = array($table);
  1256. $this->addFilter($filters,$table,'and',$key[1],'eq',$key[0]);
  1257. $this->addWhereFromFilters($filters[$table],$sql,$params);
  1258. $result = $this->db->query($sql,$params);
  1259. if (!$result) return null;
  1260. return $this->db->affectedRows($result);
  1261. }
  1262. protected function deleteObjects($key,$filters,$tables) {
  1263. $keyField = $key[1];
  1264. $keys = explode(',',$key[0]);
  1265. $rows = array();
  1266. $this->db->beginTransaction();
  1267. foreach ($keys as $key) {
  1268. $result = $this->deleteObject(array($key,$keyField),$filters,$tables);
  1269. if ($result===null) {
  1270. $this->db->rollbackTransaction();
  1271. return null;
  1272. }
  1273. $rows[] = $result;
  1274. }
  1275. $this->db->commitTransaction();
  1276. return $rows;
  1277. }
  1278. protected function incrementObject($key,$input,$filters,$tables,$fields) {
  1279. if (!$input) return false;
  1280. $input = (array)$input;
  1281. $table = $tables[0];
  1282. $sql = 'UPDATE ! SET ';
  1283. $params = array($table);
  1284. foreach (array_keys($input) as $j=>$k) {
  1285. if ($j) $sql .= ',';
  1286. $v = $input[$k];
  1287. if ($this->db->isNumericType($fields[$table][$k])) {
  1288. $sql .= '!=!+?';
  1289. $params[] = $k;
  1290. $params[] = $k;
  1291. $params[] = $v;
  1292. } else {
  1293. $sql .= '!=!';
  1294. $params[] = $k;
  1295. $params[] = $k;
  1296. }
  1297. }
  1298. $this->addFilter($filters,$table,'and',$key[1],'eq',$key[0]);
  1299. $this->addWhereFromFilters($filters[$table],$sql,$params);
  1300. $result = $this->db->query($sql,$params);
  1301. if (!$result) return null;
  1302. return $this->db->affectedRows($result);
  1303. }
  1304. protected function incrementObjects($key,$inputs,$filters,$tables,$fields) {
  1305. if (!$inputs) return false;
  1306. $keyField = $key[1];
  1307. $keys = explode(',',$key[0]);
  1308. if (count($inputs)!=count($keys)) {
  1309. $this->exitWith404('subject');
  1310. }
  1311. $rows = array();
  1312. $this->db->beginTransaction();
  1313. foreach ($inputs as $i=>$input) {
  1314. $result = $this->incrementObject(array($keys[$i],$keyField),$input,$filters,$tables,$fields);
  1315. if ($result===null) {
  1316. $this->db->rollbackTransaction();
  1317. return null;
  1318. }
  1319. $rows[] = $result;
  1320. }
  1321. $this->db->commitTransaction();
  1322. return $rows;
  1323. }
  1324. protected function findRelations($tables,$database,$auto_include) {
  1325. $tableset = array();
  1326. $collect = array();
  1327. $select = array();
  1328. while (count($tables)>1) {
  1329. $table0 = array_shift($tables);
  1330. $tableset[] = $table0;
  1331. $result = $this->db->query($this->db->getSql('reflect_belongs_to'),array($table0,$tables,$database,$database));
  1332. while ($row = $this->db->fetchRow($result)) {
  1333. if (!$auto_include && !in_array($row[0],array_merge($tables,$tableset))) continue;
  1334. $collect[$row[0]][$row[1]]=array();
  1335. $select[$row[2]][$row[3]]=array($row[0],$row[1]);
  1336. if (!in_array($row[0],$tableset)) $tableset[] = $row[0];
  1337. }
  1338. $result = $this->db->query($this->db->getSql('reflect_has_many'),array($tables,$table0,$database,$database));
  1339. while ($row = $this->db->fetchRow($result)) {
  1340. if (!$auto_include && !in_array($row[2],array_merge($tables,$tableset))) continue;
  1341. $collect[$row[2]][$row[3]]=array();
  1342. $select[$row[0]][$row[1]]=array($row[2],$row[3]);
  1343. if (!in_array($row[2],$tableset)) $tableset[] = $row[2];
  1344. }
  1345. $result = $this->db->query($this->db->getSql('reflect_habtm'),array($database,$database,$database,$database,$table0,$tables));
  1346. while ($row = $this->db->fetchRow($result)) {
  1347. if (!$auto_include && !in_array($row[2],array_merge($tables,$tableset))) continue;
  1348. if (!$auto_include && !in_array($row[4],array_merge($tables,$tableset))) continue;
  1349. $collect[$row[2]][$row[3]]=array();
  1350. $select[$row[0]][$row[1]]=array($row[2],$row[3]);
  1351. $collect[$row[4]][$row[5]]=array();
  1352. $select[$row[6]][$row[7]]=array($row[4],$row[5]);
  1353. if (!in_array($row[2],$tableset)) $tableset[] = $row[2];
  1354. if (!in_array($row[4],$tableset)) $tableset[] = $row[4];
  1355. }
  1356. }
  1357. $tableset[] = array_shift($tables);
  1358. $tableset = array_unique($tableset);
  1359. return array($tableset,$collect,$select);
  1360. }
  1361. protected function retrieveInputs($data) {
  1362. $input = (object)array();
  1363. if (strlen($data)>0) {
  1364. if ($data[0]=='{' || $data[0]=='[') {
  1365. $input = json_decode($data);
  1366. } else {
  1367. parse_str($data, $input);
  1368. foreach ($input as $key => $value) {
  1369. if (substr($key,-9)=='__is_null') {
  1370. $input[substr($key,0,-9)] = null;
  1371. unset($input[$key]);
  1372. }
  1373. }
  1374. $input = (object)$input;
  1375. }
  1376. }
  1377. return is_array($input)?$input:array($input);
  1378. }
  1379. protected function addRelationColumns($columns,$select) {
  1380. if ($columns) {
  1381. foreach ($select as $table=>$keys) {
  1382. foreach ($keys as $key=>$other) {
  1383. $columns.=",$table.$key,".implode('.',$other);
  1384. }
  1385. }
  1386. }
  1387. return $columns;
  1388. }
  1389. protected function findFields($tables,$columns,$database) {
  1390. $fields = array();
  1391. foreach ($tables as $i=>$table) {
  1392. $fields[$table] = $this->findTableFields($table,$database);
  1393. $fields[$table] = $this->filterFieldsByColumns($fields[$table],$columns,$i==0,$table);
  1394. }
  1395. return $fields;
  1396. }
  1397. protected function filterFieldsByColumns($fields,$columns,$first,$table) {
  1398. if ($columns) {
  1399. $columns = explode(',',$columns);
  1400. foreach (array_keys($fields) as $key) {
  1401. $delete = true;
  1402. foreach ($columns as $column) {
  1403. if (strpos($column,'.')) {
  1404. if ($column=="$table.$key" || $column=="$table.*") {
  1405. $delete = false;
  1406. }
  1407. } elseif ($first) {
  1408. if ($column==$key || $column=="*") {
  1409. $delete = false;
  1410. }
  1411. }
  1412. }
  1413. if ($delete) unset($fields[$key]);
  1414. }
  1415. }
  1416. return $fields;
  1417. }
  1418. protected function findTableFields($table,$database) {
  1419. $fields = array();
  1420. foreach ($this->db->fetchFields($table) as $field) {
  1421. $fields[$field->name] = $field;
  1422. }
  1423. return $fields;
  1424. }
  1425. protected function filterInputByFields($input,$fields) {
  1426. if ($fields) foreach (array_keys((array)$input) as $key) {
  1427. if (!isset($fields[$key])) {
  1428. unset($input->$key);
  1429. }
  1430. }
  1431. return $input;
  1432. }
  1433. protected function convertInputs(&$input,$fields) {
  1434. foreach ($fields as $key=>$field) {
  1435. if (isset($input->$key) && $input->$key && $this->db->isBinaryType($field)) {
  1436. $value = $input->$key;
  1437. $value = str_pad(strtr($value, '-_', '+/'), ceil(strlen($value) / 4) * 4, '=', STR_PAD_RIGHT);
  1438. $input->$key = (object)array('type'=>'base64','value'=>$value);
  1439. }
  1440. if (isset($input->$key) && $input->$key && $this->db->isGeometryType($field)) {
  1441. $input->$key = (object)array('type'=>'wkt','value'=>$input->$key);
  1442. }
  1443. }
  1444. }
  1445. protected function convertOutputs(&$sql, &$params, $fields) {
  1446. $sql .= implode(',',str_split(str_repeat('!',count($fields))));
  1447. foreach ($fields as $key=>$field) {
  1448. if ($this->db->isBinaryType($field)) {
  1449. $params[] = (object)array('type'=>'base64','key'=>$key);
  1450. }
  1451. else if ($this->db->isGeometryType($field)) {
  1452. $params[] = (object)array('type'=>'wkt','key'=>$key);
  1453. }
  1454. else {
  1455. $params[] = $key;
  1456. }
  1457. }
  1458. }
  1459. protected function getParameters($settings) {
  1460. extract($settings);
  1461. $table = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_');
  1462. $key = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_,'); // auto-increment or uuid
  1463. $action = $this->mapMethodToAction($method,$key);
  1464. $include = $this->parseGetParameter($get, 'include', 'a-zA-Z0-9\-_,');
  1465. $page = $this->parseGetParameter($get, 'page', '0-9,');
  1466. $filters = $this->parseGetParameterArray($get, 'filter', false);
  1467. $satisfy = $this->parseGetParameter($get, 'satisfy', 'a-zA-Z0-9\-_,.');
  1468. $columns = $this->parseGetParameter($get, 'columns', 'a-zA-Z0-9\-_,.*');
  1469. $orderings = $this->parseGetParameterArray($get, 'order', 'a-zA-Z0-9\-_,');
  1470. $transform = $this->parseGetParameter($get, 'transform', 't1');
  1471. $tables = $this->processTableAndIncludeParameters($database,$table,$include,$action);
  1472. $key = $this->processKeyParameter($key,$tables,$database);
  1473. $satisfy = $this->processSatisfyParameter($tables,$satisfy);
  1474. $filters = $this->processFiltersParameter($tables,$satisfy,$filters);
  1475. $page = $this->processPageParameter($page);
  1476. $orderings = $this->processOrderingsParameter($orderings);
  1477. // reflection
  1478. list($tables,$collect,$select) = $this->findRelations($tables,$database,$auto_include);
  1479. $columns = $this->addRelationColumns($columns,$select);
  1480. $fields = $this->findFields($tables,$columns,$database);
  1481. // permissions
  1482. if ($table_authorizer) $this->applyTableAuthorizer($table_authorizer,$action,$database,$tables);
  1483. if (!isset($tables[0])) $this->exitWith404('entity');
  1484. if ($record_filter) $this->applyRecordFilter($record_filter,$action,$database,$tables,$filters);
  1485. if ($tenancy_function) $this->applyTenancyFunction($tenancy_function,$action,$database,$fields,$filters);
  1486. if ($column_authorizer) $this->applyColumnAuthorizer($column_authorizer,$action,$database,$fields);
  1487. $multi = strpos($key[0],',')!==false;
  1488. if (strlen($post)) {
  1489. // input
  1490. $multi = $post[0]=='[';
  1491. $contexts = $this->retrieveInputs($post);
  1492. $inputs = array();
  1493. foreach ($contexts as $context) {
  1494. $input = $this->filterInputByFields($context,$fields[$tables[0]]);
  1495. if ($tenancy_function) $this->applyInputTenancy($tenancy_function,$action,$database,$tables[0],$input,$fields[$tables[0]]);
  1496. if ($input_sanitizer) $this->applyInputSanitizer($input_sanitizer,$action,$database,$tables[0],$input,$fields[$tables[0]]);
  1497. if ($input_validator) $this->applyInputValidator($input_validator,$action,$database,$tables[0],$input,$fields[$tables[0]],$context);
  1498. $this->convertInputs($input,$fields[$tables[0]]);
  1499. $inputs[] = $input;
  1500. }
  1501. }
  1502. return compact('action','database','tables','key','page','filters','fields','orderings','transform','multi','inputs','collect','select');
  1503. }
  1504. protected function addWhereFromFilters($filters,&$sql,&$params) {
  1505. $first = true;
  1506. if (isset($filters['or'])) {
  1507. $first = false;
  1508. $sql .= ' WHERE (';
  1509. foreach ($filters['or'] as $i=>$filter) {
  1510. $sql .= $i==0?'':' OR ';
  1511. $sql .= $filter[0];
  1512. for ($i=1;$i<count($filter);$i++) {
  1513. $params[] = $filter[$i];
  1514. }
  1515. }
  1516. $sql .= ')';
  1517. }
  1518. if (isset($filters['and'])) {
  1519. foreach ($filters['and'] as $i=>$filter) {
  1520. $sql .= $first?' WHERE ':' AND ';
  1521. $sql .= $filter[0];
  1522. for ($i=1;$i<count($filter);$i++) {
  1523. $params[] = $filter[$i];
  1524. }
  1525. $first = false;
  1526. }
  1527. }
  1528. }
  1529. protected function addOrderByFromOrderings($orderings,&$sql,&$params) {
  1530. foreach ($orderings as $i=>$ordering) {
  1531. $sql .= $i==0?' ORDER BY ':', ';
  1532. $sql .= '! '.$ordering[1];
  1533. $params[] = $ordering[0];
  1534. }
  1535. }
  1536. protected function listCommandInternal($parameters) {
  1537. extract($parameters);
  1538. echo '{';
  1539. $table = array_shift($tables);
  1540. // first table
  1541. $count = false;
  1542. echo '"'.$table.'":{';
  1543. if (is_array($orderings) && is_array($page)) {
  1544. $params = array();
  1545. $sql = 'SELECT COUNT(*) FROM !';
  1546. $params[] = $table;
  1547. if (isset($filters[$table])) {
  1548. $this->addWhereFromFilters($filters[$table],$sql,$params);
  1549. }
  1550. if ($result = $this->db->query($sql,$params)) {
  1551. while ($pages = $this->db->fetchRow($result)) {
  1552. $count = $pages[0];
  1553. }
  1554. }
  1555. }
  1556. $params = array();
  1557. $sql = 'SELECT ';
  1558. $this->convertOutputs($sql,$params,$fields[$table]);
  1559. $sql .= ' FROM !';
  1560. $params[] = $table;
  1561. if (isset($filters[$table])) {
  1562. $this->addWhereFromFilters($filters[$table],$sql,$params);
  1563. }
  1564. if (is_array($orderings)) {
  1565. $this->addOrderByFromOrderings($orderings,$sql,$params);
  1566. }
  1567. if (is_array($orderings) && is_array($page)) {
  1568. $sql = $this->db->addLimitToSql($sql,$page[1],$page[0]);
  1569. }
  1570. if ($result = $this->db->query($sql,$params)) {
  1571. echo '"columns":';
  1572. $keys = array_keys($fields[$table]);
  1573. echo json_encode($keys);
  1574. $keys = array_flip($keys);
  1575. echo ',"records":[';
  1576. $first_row = true;
  1577. while ($row = $this->db->fetchRow($result,$fields[$table])) {
  1578. if ($first_row) $first_row = false;
  1579. else echo ',';
  1580. if (isset($collect[$table])) {
  1581. foreach (array_keys($collect[$table]) as $field) {
  1582. $collect[$table][$field][] = $row[$keys[$field]];
  1583. }
  1584. }
  1585. echo json_encode($row);
  1586. }
  1587. $this->db->close($result);
  1588. echo ']';
  1589. if ($count) echo ',';
  1590. }
  1591. if ($count) echo '"results":'.$count;
  1592. echo '}';
  1593. // other tables
  1594. foreach ($tables as $t=>$table) {
  1595. echo ',';
  1596. echo '"'.$table.'":{';
  1597. $params = array();
  1598. $sql = 'SELECT ';
  1599. $this->convertOutputs($sql,$params,$fields[$table]);
  1600. $sql .= ' FROM !';
  1601. $params[] = $table;
  1602. if (isset($select[$table])) {
  1603. echo '"relations":{';
  1604. $first_row = true;
  1605. foreach ($select[$table] as $field => $path) {
  1606. $values = $collect[$path[0]][$path[1]];
  1607. if ($values) {
  1608. $this->addFilter($filters,$table,'and',$field,'in',implode(',',$values));
  1609. }
  1610. if ($first_row) $first_row = false;
  1611. else echo ',';
  1612. echo '"'.$field.'":"'.implode('.',$path).'"';
  1613. }
  1614. echo '}';
  1615. }
  1616. if (isset($filters[$table])) {
  1617. $this->addWhereFromFilters($filters[$table],$sql,$params);
  1618. }
  1619. if ($result = $this->db->query($sql,$params)) {
  1620. if (isset($select[$table])) echo ',';
  1621. echo '"columns":';
  1622. $keys = array_keys($fields[$table]);
  1623. echo json_encode($keys);
  1624. $keys = array_flip($keys);
  1625. echo ',"records":[';
  1626. $first_row = true;
  1627. while ($row = $this->db->fetchRow($result,$fields[$table])) {
  1628. if ($first_row) $first_row = false;
  1629. else echo ',';
  1630. if (isset($collect[$table])) {
  1631. foreach (array_keys($collect[$table]) as $field) {
  1632. $collect[$table][$field][]=$row[$keys[$field]];
  1633. }
  1634. }
  1635. echo json_encode($row);
  1636. }
  1637. $this->db->close($result);
  1638. echo ']';
  1639. }
  1640. echo '}';
  1641. }
  1642. echo '}';
  1643. }
  1644. protected function readCommand($parameters) {
  1645. extract($parameters);
  1646. if ($multi) $object = $this->retrieveObjects($key,$fields,$filters,$tables);
  1647. else $object = $this->retrieveObject($key,$fields,$filters,$tables);
  1648. if (!$object) $this->exitWith404('object');
  1649. $this->startOutput();
  1650. echo json_encode($object);
  1651. }
  1652. protected function createCommand($parameters) {
  1653. extract($parameters);
  1654. if (!$inputs || !$inputs[0]) $this->exitWith404('input');
  1655. $this->startOutput();
  1656. if ($multi) echo json_encode($this->createObjects($inputs,$tables));
  1657. else echo json_encode($this->createObject($inputs[0],$tables));
  1658. }
  1659. protected function updateCommand($parameters) {
  1660. extract($parameters);
  1661. if (!$inputs || !$inputs[0]) $this->exitWith404('subject');
  1662. $this->startOutput();
  1663. if ($multi) echo json_encode($this->updateObjects($key,$inputs,$filters,$tables));
  1664. else echo json_encode($this->updateObject($key,$inputs[0],$filters,$tables));
  1665. }
  1666. protected function deleteCommand($parameters) {
  1667. extract($parameters);
  1668. $this->startOutput();
  1669. if ($multi) echo json_encode($this->deleteObjects($key,$filters,$tables));
  1670. else echo json_encode($this->deleteObject($key,$filters,$tables));
  1671. }
  1672. protected function incrementCommand($parameters) {
  1673. extract($parameters);
  1674. if (!$inputs || !$inputs[0]) $this->exitWith404('subject');
  1675. $this->startOutput();
  1676. if ($multi) echo json_encode($this->incrementObjects($key,$inputs,$filters,$tables,$fields));
  1677. else echo json_encode($this->incrementObject($key,$inputs[0],$filters,$tables,$fields));
  1678. }
  1679. protected function listCommand($parameters) {
  1680. extract($parameters);
  1681. $this->startOutput();
  1682. if ($transform) {
  1683. ob_start();
  1684. }
  1685. $this->listCommandInternal($parameters);
  1686. if ($transform) {
  1687. $content = ob_get_contents();
  1688. ob_end_clean();
  1689. $data = json_decode($content,true);
  1690. echo json_encode(self::php_crud_api_transform($data));
  1691. }
  1692. }
  1693. protected function retrievePostData() {
  1694. if ($_FILES) {
  1695. $files = array();
  1696. foreach ($_FILES as $name => $file) {
  1697. foreach ($file as $key => $value) {
  1698. switch ($key) {
  1699. case 'tmp_name': $files[$name] = $value?base64_encode(file_get_contents($value)):''; break;
  1700. default: $files[$name.'_'.$key] = $value;
  1701. }
  1702. }
  1703. }
  1704. return http_build_query(array_merge($files,$_POST));
  1705. }
  1706. return file_get_contents('php://input');
  1707. }
  1708. public function __construct($config) {
  1709. extract($config);
  1710. // initialize
  1711. $dbengine = isset($dbengine)?$dbengine:null;
  1712. $hostname = isset($hostname)?$hostname:null;
  1713. $username = isset($username)?$username:null;
  1714. $password = isset($password)?$password:null;
  1715. $database = isset($database)?$database:null;
  1716. $port = isset($port)?$port:null;
  1717. $socket = isset($socket)?$socket:null;
  1718. $charset = isset($charset)?$charset:null;
  1719. $table_authorizer = isset($table_authorizer)?$table_authorizer:null;
  1720. $record_filter = isset($record_filter)?$record_filter:null;
  1721. $column_authorizer = isset($column_authorizer)?$column_authorizer:null;
  1722. $tenancy_function = isset($tenancy_function)?$tenancy_function:null;
  1723. $input_sanitizer = isset($input_sanitizer)?$input_sanitizer:null;
  1724. $input_validator = isset($input_validator)?$input_validator:null;
  1725. $extensions = isset($extensions)?$extensions:null;
  1726. $auto_include = isset($auto_include)?$auto_include:null;
  1727. $allow_origin = isset($allow_origin)?$allow_origin:null;
  1728. $db = isset($db)?$db:null;
  1729. $method = isset($method)?$method:null;
  1730. $request = isset($request)?$request:null;
  1731. $get = isset($get)?$get:null;
  1732. $post = isset($post)?$post:null;
  1733. $origin = isset($origin)?$origin:null;
  1734. // defaults
  1735. if (!$dbengine) {
  1736. $dbengine = 'MySQL';
  1737. }
  1738. if (!$method) {
  1739. $method = $_SERVER['REQUEST_METHOD'];
  1740. }
  1741. if (!$request) {
  1742. $request = isset($_SERVER['PATH_INFO'])?$_SERVER['PATH_INFO']:'';
  1743. if (!$request) {
  1744. $request = isset($_SERVER['ORIG_PATH_INFO'])?$_SERVER['ORIG_PATH_INFO']:'';
  1745. }
  1746. }
  1747. if (!$get) {
  1748. $get = $_GET;
  1749. }
  1750. if (!$post) {
  1751. $post = $this->retrievePostData();
  1752. }
  1753. if (!$origin) {
  1754. $origin = isset($_SERVER['HTTP_ORIGIN'])?$_SERVER['HTTP_ORIGIN']:'';
  1755. }
  1756. // connect
  1757. $request = trim($request,'/');
  1758. if (!$database) {
  1759. $database = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_');
  1760. }
  1761. if (!$db) {
  1762. $db = new $dbengine();
  1763. if (!$charset) {
  1764. $charset = $db->getDefaultCharset();
  1765. }
  1766. $db->connect($hostname,$username,$password,$database,$port,$socket,$charset);
  1767. }
  1768. if ($extensions===null) {
  1769. $extensions = true;
  1770. }
  1771. if ($auto_include===null) {
  1772. $auto_include = true;
  1773. }
  1774. if ($allow_origin===null) {
  1775. $allow_origin = '*';
  1776. }
  1777. $this->db = $db;
  1778. $this->settings = compact('method', 'request', 'get', 'post', 'origin', 'database', 'table_authorizer', 'record_filter', 'column_authorizer', 'tenancy_function', 'input_sanitizer', 'input_validator', 'extensions', 'auto_include', 'allow_origin');
  1779. }
  1780. public static function php_crud_api_transform(&$tables) {
  1781. $get_objects = function (&$tables,$table_name,$where_index=false,$match_value=false) use (&$get_objects) {
  1782. $objects = array();
  1783. if (isset($tables[$table_name]['records'])) {
  1784. foreach ($tables[$table_name]['records'] as $record) {
  1785. if ($where_index===false || $record[$where_index]==$match_value) {
  1786. $object = array();
  1787. foreach ($tables[$table_name]['columns'] as $index=>$column) {
  1788. $object[$column] = $record[$index];
  1789. foreach ($tables as $relation=>$reltable) {
  1790. if (isset($reltable['relations'])) {
  1791. foreach ($reltable['relations'] as $key=>$target) {
  1792. if ($target == "$table_name.$column") {
  1793. $column_indices = array_flip($reltable['columns']);
  1794. $object[$relation] = $get_objects($tables,$relation,$column_indices[$key],$record[$index]);
  1795. }
  1796. }
  1797. }
  1798. }
  1799. }
  1800. $objects[] = $object;
  1801. }
  1802. }
  1803. }
  1804. return $objects;
  1805. };
  1806. $tree = array();
  1807. foreach ($tables as $name=>$table) {
  1808. if (!isset($table['relations'])) {
  1809. $tree[$name] = $get_objects($tables,$name);
  1810. if (isset($table['results'])) {
  1811. $tree['_results'] = $table['results'];
  1812. }
  1813. }
  1814. }
  1815. return $tree;
  1816. }
  1817. protected function swagger($settings) {
  1818. extract($settings);
  1819. $tables = array();
  1820. if ($result = $this->db->query($this->db->getSql('list_tables'),array($database))) {
  1821. while ($row = $this->db->fetchRow($result)) {
  1822. $table = array(
  1823. 'name'=>$row[0],
  1824. 'comments'=>$row[1],
  1825. 'root_actions'=>array(
  1826. array('name'=>'list','method'=>'get'),
  1827. array('name'=>'create','method'=>'post'),
  1828. ),
  1829. 'id_actions'=>array(
  1830. array('name'=>'read','method'=>'get'),
  1831. array('name'=>'update','method'=>'put'),
  1832. array('name'=>'delete','method'=>'delete'),
  1833. array('name'=>'increment','method'=>'patch'),
  1834. ),
  1835. );
  1836. $tables[] = $table;
  1837. }
  1838. $this->db->close($result);
  1839. }
  1840. foreach ($tables as $t=>$table) {
  1841. $table_list = array($table['name']);
  1842. $table_fields = $this->findFields($table_list,false,$database);
  1843. $table_names = array_map(function($v){ return $v['name'];},$tables);
  1844. if ($extensions) {
  1845. $result = $this->db->query($this->db->getSql('reflect_belongs_to'),array($table_list[0],$table_names,$database,$database));
  1846. while ($row = $this->db->fetchRow($result)) {
  1847. $table_fields[$table['name']][$row[1]]->references=array($row[2],$row[3]);
  1848. }
  1849. $result = $this->db->query($this->db->getSql('reflect_has_many'),array($table_names,$table_list[0],$database,$database));
  1850. while ($row = $this->db->fetchRow($result)) {
  1851. $table_fields[$table['name']][$row[3]]->referenced[]=array($row[0],$row[1]);
  1852. }
  1853. $primaryKeys = $this->findPrimaryKeys($table_list[0],$database);
  1854. foreach ($primaryKeys as $primaryKey) {
  1855. $table_fields[$table['name']][$primaryKey]->primaryKey = true;
  1856. }
  1857. }
  1858. foreach (array('root_actions','id_actions') as $path) {
  1859. foreach ($table[$path] as $i=>$action) {
  1860. $table_list = array($table['name']);
  1861. $fields = $table_fields;
  1862. if ($table_authorizer) $this->applyTableAuthorizer($table_authorizer,$action['name'],$database,$table_list);
  1863. if ($column_authorizer) $this->applyColumnAuthorizer($column_authorizer,$action['name'],$database,$fields);
  1864. if (!$table_list || !$fields[$table['name']]) $tables[$t][$path][$i] = false;
  1865. else $tables[$t][$path][$i]['fields'] = $fields[$table['name']];
  1866. }
  1867. // remove unauthorized tables and tables without fields
  1868. $tables[$t][$path] = array_values(array_filter($tables[$t][$path]));
  1869. }
  1870. if (!$tables[$t]['root_actions']&&!$tables[$t]['id_actions']) $tables[$t] = false;
  1871. }
  1872. $tables = array_merge(array_filter($tables));
  1873. //var_dump($tables);die();
  1874. header('Content-Type: application/json; charset=utf-8');
  1875. echo '{"swagger":"2.0",';
  1876. echo '"info":{';
  1877. echo '"title":"'.$database.'",';
  1878. echo '"description":"API generated with [PHP-CRUD-API](https://github.com/mevdschee/php-crud-api)",';
  1879. echo '"version":"1.0.0"';
  1880. echo '},';
  1881. echo '"host":"'.$_SERVER['HTTP_HOST'].'",';
  1882. echo '"basePath":"'.$_SERVER['SCRIPT_NAME'].'",';
  1883. echo '"schemes":["http'.((!empty($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!=='off')?'s':'').'"],';
  1884. echo '"consumes":["application/json"],';
  1885. echo '"produces":["application/json"],';
  1886. echo '"tags":[';
  1887. foreach ($tables as $i=>$table) {
  1888. if ($i>0) echo ',';
  1889. echo '{';
  1890. echo '"name":"'.$table['name'].'",';
  1891. echo '"description":"'.$table['comments'].'"';
  1892. echo '}';
  1893. }
  1894. echo '],';
  1895. echo '"paths":{';
  1896. foreach ($tables as $i=>$table) {
  1897. if ($table['root_actions']) {
  1898. if ($i>0) echo ',';
  1899. echo '"/'.$table['name'].'":{';
  1900. foreach ($table['root_actions'] as $j=>$action) {
  1901. if ($j>0) echo ',';
  1902. echo '"'.$action['method'].'":{';
  1903. echo '"tags":["'.$table['name'].'"],';
  1904. echo '"summary":"'.ucfirst($action['name']).'",';
  1905. if ($action['name']=='list') {
  1906. echo '"parameters":[';
  1907. echo '{';
  1908. echo '"name":"include",';
  1909. echo '"in":"query",';
  1910. echo '"description":"One or more related entities (comma separated).",';
  1911. echo '"required":false,';
  1912. echo '"type":"string"';
  1913. echo '},';
  1914. echo '{';
  1915. echo '"name":"order",';
  1916. echo '"in":"query",';
  1917. echo '"description":"Column you want to sort on and the sort direction (comma separated). Example: id,desc",';
  1918. echo '"required":false,';
  1919. echo '"type":"string"';
  1920. echo '},';
  1921. echo '{';
  1922. echo '"name":"page",';
  1923. echo '"in":"query",';
  1924. echo '"description":"Page number and page size (comma separated). NB: You cannot use \"page\" without \"order\"! Example: 1,10",';
  1925. echo '"required":false,';
  1926. echo '"type":"string"';
  1927. echo '},';
  1928. echo '{';
  1929. echo '"name":"transform",';
  1930. echo '"in":"query",';
  1931. echo '"description":"Transform the records to object format. NB: This can also be done client-side in JavaScript!",';
  1932. echo '"required":false,';
  1933. echo '"type":"boolean"';
  1934. echo '},';
  1935. echo '{';
  1936. echo '"name":"columns",';
  1937. echo '"in":"query",';
  1938. echo '"description":"The table columns you want to retrieve (comma separated). Example: posts.*,categories.name",';
  1939. echo '"required":false,';
  1940. echo '"type":"string"';
  1941. echo '},';
  1942. echo '{';
  1943. echo '"name":"filter[]",';
  1944. echo '"in":"query",';
  1945. echo '"description":"Filters to be applied. Each filter consists of a column, an operator and a value (comma separated). Example: id,eq,1",';
  1946. echo '"required":false,';
  1947. echo '"type":"array",';
  1948. echo '"collectionFormat":"multi",';
  1949. echo '"items":{"type":"string"}';
  1950. echo '},';
  1951. echo '{';
  1952. echo '"name":"satisfy",';
  1953. echo '"in":"query",';
  1954. echo '"description":"Should all filters match (default)? Or any?",';
  1955. echo '"required":false,';
  1956. echo '"type":"string",';
  1957. echo '"enum":["any"]';
  1958. echo '}';
  1959. echo '],';
  1960. echo '"responses":{';
  1961. echo '"200":{';
  1962. echo '"description":"An array of '.$table['name'].'",';
  1963. echo '"schema":{';
  1964. echo '"type":"array",';
  1965. echo '"items":{';
  1966. echo '"type": "object",';
  1967. echo '"properties": {';
  1968. foreach (array_keys($action['fields']) as $k=>$field) {
  1969. if ($k>0) echo ',';
  1970. echo '"'.$field.'": {';
  1971. echo '"type": "string"';
  1972. if (isset($action['fields'][$field]->referenced)) {
  1973. echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced);
  1974. }
  1975. if (isset($action['fields'][$field]->references)) {
  1976. echo ',"x-references": '.json_encode($action['fields'][$field]->references);
  1977. }
  1978. if (isset($action['fields'][$field]->primaryKey)) {
  1979. echo ',"x-primary-key": true';
  1980. }
  1981. echo '}';
  1982. }
  1983. echo '}'; //properties
  1984. echo '}'; //items
  1985. echo '}'; //schema
  1986. echo '}'; //200
  1987. echo '}'; //responses
  1988. }
  1989. if ($action['name']=='create') {
  1990. echo '"parameters":[{';
  1991. echo '"name":"item",';
  1992. echo '"in":"body",';
  1993. echo '"description":"Item to create.",';
  1994. echo '"required":false,';
  1995. echo '"schema":{';
  1996. echo '"type": "object",';
  1997. echo '"properties": {';
  1998. foreach (array_keys($action['fields']) as $k=>$field) {
  1999. if ($k>0) echo ',';
  2000. echo '"'.$field.'": {';
  2001. echo '"type": "string"';
  2002. if (isset($action['fields'][$field]->referenced)) {
  2003. echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced);
  2004. }
  2005. if (isset($action['fields'][$field]->references)) {
  2006. echo ',"x-references": '.json_encode($action['fields'][$field]->references);
  2007. }
  2008. if (isset($action['fields'][$field]->primaryKey)) {
  2009. echo ',"x-primary-key": true';
  2010. }
  2011. echo '}';
  2012. }
  2013. echo '}'; //properties
  2014. echo '}'; //schema
  2015. echo '}],';
  2016. echo '"responses":{';
  2017. echo '"200":{';
  2018. echo '"description":"Identifier of created item.",';
  2019. echo '"schema":{';
  2020. echo '"type":"integer"';
  2021. echo '}';//schema
  2022. echo '}';//200
  2023. echo '}';//responses
  2024. }
  2025. echo '}';//method
  2026. }
  2027. echo '}';
  2028. }
  2029. if ($table['id_actions']) {
  2030. if ($i>0 || $table['root_actions']) echo ',';
  2031. echo '"/'.$table['name'].'/{id}":{';
  2032. foreach ($table['id_actions'] as $j=>$action) {
  2033. if ($j>0) echo ',';
  2034. echo '"'.$action['method'].'":{';
  2035. echo '"tags":["'.$table['name'].'"],';
  2036. echo '"summary":"'.ucfirst($action['name']).'",';
  2037. echo '"parameters":[';
  2038. echo '{';
  2039. echo '"name":"id",';
  2040. echo '"in":"path",';
  2041. echo '"description":"Identifier for item.",';
  2042. echo '"required":true,';
  2043. echo '"type":"string"';
  2044. echo '}';
  2045. if ($action['name']=='update') {
  2046. echo ',{';
  2047. echo '"name":"item",';
  2048. echo '"in":"body",';
  2049. echo '"description":"Properties of item to update.",';
  2050. echo '"required":false,';
  2051. echo '"schema":{';
  2052. echo '"type": "object",';
  2053. echo '"properties": {';
  2054. foreach (array_keys($action['fields']) as $k=>$field) {
  2055. if ($k>0) echo ',';
  2056. echo '"'.$field.'": {';
  2057. echo '"type": "string"';
  2058. if (isset($action['fields'][$field]->referenced)) {
  2059. echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced);
  2060. }
  2061. if (isset($action['fields'][$field]->references)) {
  2062. echo ',"x-references": '.json_encode($action['fields'][$field]->references);
  2063. }
  2064. if (isset($action['fields'][$field]->primaryKey)) {
  2065. echo ',"x-primary-key": true';
  2066. }
  2067. echo '}';
  2068. }
  2069. echo '}'; //properties
  2070. echo '}'; //schema
  2071. echo '}';
  2072. }
  2073. echo '],';
  2074. if ($action['name']=='read') {
  2075. echo '"responses":{';
  2076. echo '"200":{';
  2077. echo '"description":"The requested item.",';
  2078. echo '"schema":{';
  2079. echo '"type": "object",';
  2080. echo '"properties": {';
  2081. foreach (array_keys($action['fields']) as $k=>$field) {
  2082. if ($k>0) echo ',';
  2083. echo '"'.$field.'": {';
  2084. echo '"type": "string"';
  2085. if (isset($action['fields'][$field]->referenced)) {
  2086. echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced);
  2087. }
  2088. if (isset($action['fields'][$field]->references)) {
  2089. echo ',"x-references": '.json_encode($action['fields'][$field]->references);
  2090. }
  2091. if (isset($action['fields'][$field]->primaryKey)) {
  2092. echo ',"x-primary-key": true';
  2093. }
  2094. echo '}';
  2095. }
  2096. echo '}'; //properties
  2097. echo '}'; //schema
  2098. echo '}';
  2099. echo '}';
  2100. } else {
  2101. echo '"responses":{';
  2102. echo '"200":{';
  2103. echo '"description":"Number of affected rows.",';
  2104. echo '"schema":{';
  2105. echo '"type":"integer"';
  2106. echo '}';
  2107. echo '}';
  2108. echo '}';
  2109. }
  2110. echo '}';
  2111. }
  2112. echo '}';
  2113. }
  2114. }
  2115. echo '}';
  2116. echo '}';
  2117. }
  2118. protected function allowOrigin($origin,$allowOrigins) {
  2119. if ($allowOrigins=='*') {
  2120. header('Access-Control-Allow-Origin: *');
  2121. } else {
  2122. if ($origin) foreach (explode(',',$allowOrigins) as $o) {
  2123. if (preg_match('/^'.str_replace('\*','.*',preg_quote(strtolower(trim($o)))).'$/',$origin)) {
  2124. header('Access-Control-Allow-Origin: '.$origin);
  2125. break;
  2126. }
  2127. }
  2128. }
  2129. }
  2130. public function executeCommand() {
  2131. if (isset($_SERVER['REQUEST_METHOD'])) {
  2132. $this->allowOrigin($this->settings['origin'],$this->settings['allow_origin']);
  2133. }
  2134. if (!$this->settings['request']) {
  2135. $this->swagger($this->settings);
  2136. } else {
  2137. $parameters = $this->getParameters($this->settings);
  2138. switch($parameters['action']){
  2139. case 'list': $this->listCommand($parameters); break;
  2140. case 'read': $this->readCommand($parameters); break;
  2141. case 'create': $this->createCommand($parameters); break;
  2142. case 'update': $this->updateCommand($parameters); break;
  2143. case 'delete': $this->deleteCommand($parameters); break;
  2144. case 'increment': $this->incrementCommand($parameters); break;
  2145. case 'headers': $this->headersCommand($parameters); break;
  2146. }
  2147. }
  2148. }
  2149. }
  2150. // require 'auth.php'; // from the PHP-API-AUTH project, see: https://github.com/mevdschee/php-api-auth
  2151. // uncomment the lines below for token+session based authentication (see "login_token.html" + "login_token.php"):
  2152. // $auth = new PHP_API_AUTH(array(
  2153. // 'secret'=>'someVeryLongPassPhraseChangeMe',
  2154. // ));
  2155. // if ($auth->executeCommand()) exit(0);
  2156. // if (empty($_SESSION['user']) || $_GET['csrf']!=$_SESSION['csrf']) {
  2157. // header('HTTP/1.0 401 Unauthorized');
  2158. // exit(0);
  2159. // }
  2160. // uncomment the lines below for form+session based authentication (see "login.html"):
  2161. // $auth = new PHP_API_AUTH(array(
  2162. // 'authenticator'=>function($user,$pass){ $_SESSION['user']=($user=='admin' && $pass=='admin'); }
  2163. // ));
  2164. // if ($auth->executeCommand()) exit(0);
  2165. // if (empty($_SESSION['user']) || $_GET['csrf']!=$_SESSION['csrf']) {
  2166. // header('HTTP/1.0 401 Unauthorized');
  2167. // exit(0);
  2168. // }
  2169. // uncomment the lines below when running in stand-alone mode:
  2170. // $api = new PHP_CRUD_API(array(
  2171. // 'dbengine'=>'MySQL',
  2172. // 'hostname'=>'localhost',
  2173. // 'username'=>'',
  2174. // 'password'=>'',
  2175. // 'database'=>'',
  2176. // 'charset'=>'utf8'
  2177. // ));
  2178. // $api->executeCommand();
  2179. // For Microsoft SQL Server 2012 use:
  2180. // $api = new PHP_CRUD_API(array(
  2181. // 'dbengine'=>'SQLServer',
  2182. // 'hostname'=>'(local)',
  2183. // 'username'=>'',
  2184. // 'password'=>'',
  2185. // 'database'=>'xxx',
  2186. // 'charset'=>'UTF-8'
  2187. // ));
  2188. // $api->executeCommand();
  2189. // For PostgreSQL 9 use:
  2190. // $api = new PHP_CRUD_API(array(
  2191. // 'dbengine'=>'PostgreSQL',
  2192. // 'hostname'=>'localhost',
  2193. // 'username'=>'xxx',
  2194. // 'password'=>'xxx',
  2195. // 'database'=>'xxx',
  2196. // 'charset'=>'UTF8'
  2197. // ));
  2198. // $api->executeCommand();
  2199. // For SQLite 3 use:
  2200. // $api = new PHP_CRUD_API(array(
  2201. // 'dbengine'=>'SQLite',
  2202. // 'database'=>'data/blog.db',
  2203. // ));
  2204. // $api->executeCommand();