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

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