PageRenderTime 59ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/vendors/FX.php

https://github.com/nojimage/FileMaker-Todo-App
PHP | 2057 lines | 1694 code | 120 blank | 243 comment | 376 complexity | 5c74857a4e884cd5cb042b215bfb1590 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. #### FX.php #############################################################
  3. # #
  4. # By: Chris Hansen with Chris Adams, Gjermund Thorsen, and others #
  5. # Version: 4.5.1 #
  6. # Date: 28 Feb 2008 #
  7. # License: Artistic License and addendum (included with release) #
  8. # Web Site: www.iviking.org #
  9. # Details: FX is a free open-source PHP class for accessing FileMaker #
  10. # and other databases. For complete details about this class, #
  11. # please visit www.iviking.org. #
  12. # #
  13. #########################################################################
  14. define("FX_VERSION", '4.5.1'); // Current version information for FX.php. New constants as of version 4.0.
  15. define("FX_VERSION_FULL", "FX.php version 4.5.1 (28 Feb 2008) by Chris Hansen, Chris Adams, Gjermund Thorsen, and others.");
  16. require_once('FX_Error.php'); // This version of FX.php includes object based error handling. See
  17. // FX_Error.php for more information.
  18. if (! defined('DEBUG_FUZZY')) { // This version of FX.php includes the FX Fuzzy Debugger (turned off by default.)
  19. define('DEBUG_FUZZY', false);
  20. }
  21. require_once('FX_constants.php'); // The constants in this file are designed to be used with DoFXAction()
  22. define("EMAIL_ERROR_MESSAGES", FALSE); // Set this to TRUE to enable emailing of specific error messages.
  23. define("DISPLAY_ERROR_MESSAGES", TRUE); // Set this to FALSE to display the $altErrorMessage to the user.
  24. $webmasterEmailAddress = 'webmaster@yourdomain.com'; // If you set the above to TRUE, enter the appropriate email address on this line.
  25. $emailFromAddress = 'you@yourdomain.com'; // Sets who the error message will show as the sender.
  26. function EmailError ($errorText)
  27. {
  28. global $webmasterEmailAddress;
  29. global $emailFromAddress;
  30. if (EMAIL_ERROR_MESSAGES) {
  31. $emailSubject = "PHP Server Error";
  32. $emailMessage = "The following error just occured:\r\n\r\nMessage: {$errorText}\r\n\r\n**This is an automated message**";
  33. $emailStatus = mail($webmasterEmailAddress, $emailSubject, $emailMessage, "From: $emailFromAddress\r\n");
  34. }
  35. }
  36. function EmailErrorHandler ($FXErrorObj)
  37. {
  38. $altErrorMessage = 'The Server was unable to process your request.<br />The WebMaster has been emailed.<br /> Thank you for your patience.';
  39. EmailError($FXErrorObj->message);
  40. if (DISPLAY_ERROR_MESSAGES) {
  41. echo($FXErrorObj->message);
  42. } else {
  43. echo($altErrorMessage);
  44. }
  45. return true;
  46. }
  47. class FX
  48. {
  49. // These are the basic database variables.
  50. var $dataServer = "";
  51. var $dataServerType = 'FMPro7';
  52. var $dataPort;
  53. var $dataPortSuffix;
  54. var $urlScheme;
  55. var $useSSLProtocol = false;
  56. var $database = "";
  57. var $layout = ""; // the layout to be accessed for FM databases. For SQL, the table to be accessed.
  58. var $responseLayout = "";
  59. var $groupSize;
  60. var $currentSkip = 0;
  61. var $defaultOperator = 'bw';
  62. var $dataParams = array();
  63. var $sortParams = array();
  64. var $actionArray = array(
  65. // for backwards compatibility
  66. "-delete" =>"-delete",
  67. "-dup" =>"-dup",
  68. "-edit" =>"-edit",
  69. "-find" =>"-find",
  70. "-findall" =>"-findall",
  71. "-findany" =>"-findany",
  72. "-new" =>"-new",
  73. "-view" =>"-view",
  74. "-dbnames" =>"-dbnames",
  75. "-layoutnames" =>"-layoutnames",
  76. "-scriptnames" =>"-scriptnames",
  77. "-sqlquery" =>"-sqlquery",
  78. // new params for DoFXAction
  79. "delete" =>"-delete",
  80. "duplicate" =>"-dup",
  81. "update" =>"-edit",
  82. "perform_find" =>"-find",
  83. "show_all" =>"-findall",
  84. "show_any" =>"-findany",
  85. "new" =>"-new",
  86. "view_layout_objects" =>"-view",
  87. "view_database_names" =>"-dbnames",
  88. "view_layout_names" =>"-layoutnames",
  89. "view_script_names" =>"-scriptnames"
  90. );
  91. // Variables to help with SQL queries
  92. var $primaryKeyField = '';
  93. var $modifyDateField = '';
  94. var $dataKeySeparator = '';
  95. var $fuzzyKeyLogic = false;
  96. var $genericKeys = false;
  97. var $selectColsSet = false;
  98. var $selectColumns = '';
  99. // These are the variables to be used for storing the retrieved data.
  100. var $fieldInfo = array();
  101. var $currentData = array();
  102. var $valueLists = array();
  103. var $totalRecordCount = -1;
  104. var $foundCount = -1;
  105. var $dateFormat = "";
  106. var $timeFormat = "";
  107. var $dataURL = "";
  108. var $dataURLParams = "";
  109. var $dataQuery = "";
  110. // Variables used to track how data is moved in and out of FileMaker. Used when UTF-8 just doesn't cut it (as when working with Japanese characters.)
  111. // This and all related code were submitted by Masayuki Nii.
  112. // Note that if either of these variables are simply empty, UTF-8 is the default.
  113. var $charSet = ''; // Determines how outgoing data is encoded.
  114. var $dataParamsEncoding = ''; // Determines how incoming data is encoded.
  115. // Flags and Error Tracking
  116. var $currentFlag = '';
  117. var $currentRecord = '';
  118. var $currentField = '';
  119. var $currentValueList = '';
  120. var $fieldCount = 0;
  121. var $columnCount = -1; // columnCount is ++ed BEFORE looping
  122. var $fxError = 'No Action Taken';
  123. var $errorTracking = 0;
  124. var $useInnerArray = true; // Do NOT change this variable directly. Use FlattenInnerArray() or the appropriate param of action method.
  125. // These variables will be used if you need a password to access your data.
  126. var $DBUser = 'FX';
  127. var $DBPassword = ''; // This can be left blank, or replaced with a default or dummy password.
  128. var $userPass = '';
  129. // These variables are related to sending data to FileMaker via a Post.
  130. var $defaultPostPolicy = true;
  131. var $isPostQuery;
  132. var $useCURL = true;
  133. // When returning your data via the 'object' return type, these variables will contain the database meta data
  134. var $lastLinkPrevious = '';
  135. var $lastLinkNext = '';
  136. var $lastFoundCount = -2;
  137. var $lastFields = array();
  138. var $lastURL = '';
  139. var $lastQuery = '';
  140. var $lastQueryParams = array();
  141. var $lastErrorCode = -2;
  142. var $lastValueLists = array();
  143. var $lastDebugMessage = '';
  144. // Other variables
  145. var $invalidXMLChars = array("\x0B", "\x0C", "\x12");
  146. /*
  147. Translation arrays used with str_replace to handle special
  148. characters in UTF-8 data received from FileMaker. The two arrays
  149. should have matching numeric indexes such that $UTF8SpecialChars[0]
  150. contains the raw binary equivalent of $UTF8HTMLEntities[0].
  151. This would be a perfect use for strtr(), except that it only works
  152. with single-byte data. Instead, we use preg_replace, which means
  153. that we need to delimit our match strings
  154. Please note that in this latest release I've removed the need for
  155. the include files which contained long lists of characters. Gjermund
  156. was sure there was a better way and he was right. With the two six
  157. element arrays below, every unicode character is allowed for. Let
  158. me know how this works for you. A link to Gjermund's homepage can
  159. be found in the FX Links section of www.iViking.org.
  160. */
  161. var $UTF8SpecialChars = array(
  162. "|([\xC2-\xDF])([\x80-\xBF])|e",
  163. "|(\xE0)([\xA0-\xBF])([\x80-\xBF])|e",
  164. "|([\xE1-\xEF])([\x80-\xBF])([\x80-\xBF])|e",
  165. "|(\xF0)([\x90-\xBF])([\x80-\xBF])([\x80-\xBF])|e",
  166. "|([\xF1-\xF3])([\x80-\xBF])([\x80-\xBF])([\x80-\xBF])|e",
  167. "|(\xF4)([\x80-\x8F])([\x80-\xBF])([\x80-\xBF])|e"
  168. );
  169. var $UTF8HTMLEntities = array(
  170. "\$this->BuildExtendedChar('\\1','\\2')",
  171. "\$this->BuildExtendedChar('\\1','\\2','\\3')",
  172. "\$this->BuildExtendedChar('\\1','\\2','\\3')",
  173. "\$this->BuildExtendedChar('\\1','\\2','\\3','\\4')",
  174. "\$this->BuildExtendedChar('\\1','\\2','\\3','\\4')",
  175. "\$this->BuildExtendedChar('\\1','\\2','\\3','\\4')"
  176. );
  177. function BuildExtendedChar ($byteOne, $byteTwo="\x00", $byteThree="\x00", $byteFour="\x00")
  178. {
  179. if (ord($byteTwo) >= 128) {
  180. $tempChar = substr(decbin(ord($byteTwo)), -6);
  181. if (ord($byteThree) >= 128) {
  182. $tempChar .= substr(decbin(ord($byteThree)), -6);
  183. if (ord($byteFour) >= 128) {
  184. $tempChar .= substr(decbin(ord($byteFour)), -6);
  185. $tempChar = substr(decbin(ord($byteOne)), -3) . $tempChar;
  186. } else {
  187. $tempChar = substr(decbin(ord($byteOne)), -4) . $tempChar;
  188. }
  189. } else {
  190. $tempChar = substr(decbin(ord($byteOne)), -5) . $tempChar;
  191. }
  192. } else $tempChar = $byteOne;
  193. $tempChar = '&#' . bindec($tempChar) . ';';
  194. return $tempChar;
  195. }
  196. function ClearAllParams ()
  197. {
  198. $this->userPass = "";
  199. $this->dataURL = "";
  200. $this->dataURLParams = "";
  201. $this->dataQuery = "";
  202. $this->dataParams = array();
  203. $this->sortParams = array();
  204. $this->fieldInfo = array();
  205. $this->valueLists = array();
  206. $this->fieldCount = 0;
  207. $this->currentSkip = 0;
  208. $this->currentData = array();
  209. $this->columnCount = -1;
  210. $this->currentRecord = "";
  211. $this->currentField = "";
  212. $this->currentFlag = "";
  213. $this->isPostQuery = $this->defaultPostPolicy;
  214. $this->primaryKeyField = '';
  215. $this->modifyDateField = '';
  216. $this->dataKeySeparator = '';
  217. $this->fuzzyKeyLogic = false;
  218. $this->genericKeys = false;
  219. $this->useInnerArray = true;
  220. }
  221. function ErrorHandler ($errorText)
  222. {
  223. $this->fxError = $errorText;
  224. $this->errorTracking = 3300;
  225. return $errorText;
  226. }
  227. function FX ($dataServer, $dataPort=591, $dataType='', $dataURLType='')
  228. {
  229. $this->dataServer = $dataServer;
  230. $this->dataPort = $dataPort;
  231. $this->dataPortSuffix = ":" . $dataPort;
  232. if (strlen($dataType) > 0) {
  233. $this->dataServerType = $dataType;
  234. }
  235. if (strlen($dataURLType) > 0 && ($dataType == 'FMPro7' || $dataType == 'FMPro8' || $dataType == 'FMPro9') && strtolower($dataURLType) == 'https') {
  236. $this->useSSLProtocol = true;
  237. $this->urlScheme = 'https';
  238. } else {
  239. $this->useSSLProtocol = false;
  240. $this->urlScheme = 'http';
  241. }
  242. $this->ClearAllParams();
  243. $this->lastDebugMessage = '<p>Instantiating FX.php.</p>';
  244. }
  245. function CreateCurrentSort ()
  246. {
  247. $currentSort = "";
  248. foreach ($this->sortParams as $key1 => $value1) {
  249. foreach ($value1 as $key2 => $value2) {
  250. $$key2 = $value2;
  251. }
  252. $lowerCaseDataServerType = strtolower($this->dataServerType);
  253. if (substr($lowerCaseDataServerType, 0, 5) == 'fmpro' && substr($lowerCaseDataServerType, -1) > 6) {
  254. if ($sortOrder == "") {
  255. $currentSort .= "&-sortfield.{$key1}=" . str_replace ("%3A%3A", "::", rawurlencode($field));
  256. }
  257. else {
  258. $currentSort .= "&-sortfield.{$key1}=" . str_replace ("%3A%3A", "::", rawurlencode($field)) . "&-sortorder.{$key1}=" . $sortOrder;
  259. }
  260. } else {
  261. if ($sortOrder == "") {
  262. $currentSort .= "&-sortfield=" . str_replace ("%3A%3A", "::", rawurlencode($field));
  263. }
  264. else {
  265. $currentSort .= "&-sortfield=" . str_replace ("%3A%3A", "::", rawurlencode($field)) . "&-sortorder=" . $sortOrder;
  266. }
  267. }
  268. }
  269. return $currentSort;
  270. }
  271. function CreateCurrentSearch ()
  272. {
  273. $currentSearch = '';
  274. foreach ($this->dataParams as $key1 => $value1) {
  275. foreach ($value1 as $key2 => $value2) {
  276. $$key2 = $value2;
  277. }
  278. if ($op == "" && $this->defaultOperator == 'bw') {
  279. $currentSearch .= "&" . str_replace ("%3A%3A", "::", urlencode($name)) . "=" . urlencode($value);
  280. } else {
  281. if ($op == "") {
  282. $op = $this->defaultOperator;
  283. }
  284. switch (strtolower($this->dataServerType)) {
  285. case 'fmpro5':
  286. case 'fmpro6':
  287. case 'fmpro5/6':
  288. $currentSearch .= "&-op=" . $op . "&" . str_replace("%3A%3A", "::", urlencode($name)) . "=" . urlencode($value);
  289. break;
  290. case 'fmpro7':
  291. case 'fmpro8':
  292. case 'fmpro9':
  293. $tempFieldName = str_replace("%3A%3A", "::", urlencode($name));
  294. $currentSearch .= "&" . $tempFieldName . ".op=" . $op . "&" . $tempFieldName . "=" . urlencode($value);
  295. break;
  296. }
  297. }
  298. }
  299. return $currentSearch;
  300. }
  301. function AssembleCurrentSearch ($layRequest, $skipRequest, $currentSort, $currentSearch, $action, $FMV=6)
  302. {
  303. $tempSearch = '';
  304. $tempSearch = "-db=" . urlencode($this->database); // add the name of the database...
  305. $tempSearch .= $layRequest; // and any layout specified...
  306. if ($FMV < 7) {
  307. $tempSearch .= "&-format=-fmp_xml"; // then set the FileMaker XML format to use...
  308. }
  309. $tempSearch .= "&-max=$this->groupSize$skipRequest"; // add the set size and skip size data...
  310. $tempSearch .= $currentSort . $currentSearch . "&" . $action; // finally, add sorting, search parameters, and action data.
  311. return $tempSearch;
  312. }
  313. function StartElement($parser, $name, $attrs) // The functions to start XML parsing begin here
  314. {
  315. switch(strtolower($name)) {
  316. case "data":
  317. $this->currentFlag = "parseData";
  318. if (! $this->useInnerArray) {
  319. $this->currentData[$this->currentRecord][$this->currentField] = "";
  320. } else {
  321. $this->currentData[$this->currentRecord][$this->currentField][$this->currentFieldIndex] = "";
  322. }
  323. break;
  324. case "col":
  325. $this->currentFieldIndex = 0;
  326. ++$this->columnCount;
  327. $this->currentField = $this->fieldInfo[$this->columnCount]['name'];
  328. if ($this->useInnerArray) {
  329. $this->currentData[$this->currentRecord][$this->currentField] = array();
  330. }
  331. break;
  332. case "row":
  333. foreach ($attrs as $key => $value) {
  334. $key = strtolower($key);
  335. $$key = $value;
  336. }
  337. if (substr_count($this->dataURL, '-dbnames') > 0 || substr_count($this->dataURL, '-layoutnames') > 0) {
  338. $modid = count($this->currentData);
  339. }
  340. $this->currentRecord = $recordid . '.' . $modid;
  341. $this->currentData[$this->currentRecord] = array();
  342. break;
  343. case "field":
  344. if ($this->charSet != '' && defined('MB_OVERLOAD_STRING')) {
  345. foreach ($attrs as $key => $value) {
  346. $key = strtolower($key);
  347. $this->fieldInfo[$this->fieldCount][$key] = mb_convert_encoding($value, $this->charSet, 'UTF-8');
  348. }
  349. } else {
  350. foreach ($attrs as $key => $value) {
  351. $key = strtolower($key);
  352. $this->fieldInfo[$this->fieldCount][$key] = $value;
  353. }
  354. }
  355. $this->fieldInfo[$this->fieldCount]['extra'] = ''; // for compatibility w/ SQL databases
  356. if (substr_count($this->dataURL, '-view') < 1) {
  357. $this->fieldCount++;
  358. }
  359. break;
  360. case "style":
  361. foreach ($attrs as $key => $value) {
  362. $key = strtolower($key);
  363. $this->fieldInfo[$this->fieldCount][$key] = $value;
  364. }
  365. break;
  366. case "resultset":
  367. foreach ($attrs as $key => $value) {
  368. switch(strtolower($key)) {
  369. case "found":
  370. $this->foundCount = (int)$value;
  371. break;
  372. }
  373. }
  374. break;
  375. case "errorcode":
  376. $this->currentFlag = "fmError";
  377. break;
  378. case "valuelist":
  379. foreach ($attrs as $key => $value) {
  380. if (strtolower($key) == "name") {
  381. $this->currentValueList = $value;
  382. }
  383. }
  384. $this->valueLists[$this->currentValueList] = array();
  385. $this->currentFlag = "values";
  386. $this->currentValueListElement = -1;
  387. break;
  388. case "value":
  389. $this->currentValueListElement++;
  390. $this->valueLists[$this->currentValueList][$this->currentValueListElement] = "";
  391. break;
  392. case "database":
  393. foreach ($attrs as $key => $value) {
  394. switch(strtolower($key)) {
  395. case "dateformat":
  396. $this->dateFormat = $value;
  397. break;
  398. case "records":
  399. $this->totalRecordCount = $value;
  400. break;
  401. case "timeformat":
  402. $this->timeFormat = $value;
  403. break;
  404. }
  405. }
  406. break;
  407. default:
  408. break;
  409. }
  410. }
  411. function ElementContents($parser, $data)
  412. {
  413. switch($this->currentFlag) {
  414. case "parseData":
  415. if ($this->dataParamsEncoding != '' && defined('MB_OVERLOAD_STRING')) {
  416. if (! $this->useInnerArray) {
  417. $this->currentData[$this->currentRecord][$this->currentField] .= mb_convert_encoding($data, $this->charSet, 'UTF-8');
  418. } else {
  419. $this->currentData[$this->currentRecord][$this->currentField][$this->currentFieldIndex] .= mb_convert_encoding($data, $this->charSet, 'UTF-8');
  420. }
  421. } else {
  422. if (! $this->useInnerArray) {
  423. $this->currentData[$this->currentRecord][$this->currentField] .= preg_replace($this->UTF8SpecialChars, $this->UTF8HTMLEntities, $data);
  424. } else {
  425. $this->currentData[$this->currentRecord][$this->currentField][$this->currentFieldIndex] .= preg_replace($this->UTF8SpecialChars, $this->UTF8HTMLEntities, $data);
  426. }
  427. }
  428. break;
  429. case "fmError":
  430. $this->fxError = $data;
  431. break;
  432. case "values":
  433. $this->valueLists[$this->currentValueList][$this->currentValueListElement] .= preg_replace($this->UTF8SpecialChars, $this->UTF8HTMLEntities, $data);
  434. break;
  435. }
  436. }
  437. function EndElement($parser, $name)
  438. {
  439. switch(strtolower($name)) {
  440. case "data":
  441. $this->currentFieldIndex++;
  442. $this->currentFlag = "";
  443. break;
  444. case "col":
  445. break;
  446. case "row":
  447. $this->columnCount = -1;
  448. break;
  449. case "field":
  450. if (substr_count($this->dataURL, '-view') > 0) {
  451. $this->fieldCount++;
  452. }
  453. break;
  454. case "errorcode":
  455. case "valuelist":
  456. $this->currentFlag = "";
  457. break;
  458. }
  459. } // XML Parsing Functions End Here
  460. function RetrieveFMData ($action)
  461. {
  462. $data = '';
  463. if ($this->DBPassword != '') { // Assemble the Password Data
  464. $this->userPass = $this->DBUser . ':' . $this->DBPassword . '@';
  465. }
  466. if ($this->layout != "") { // Set up the layout portion of the query.
  467. $layRequest = "&-lay=" . urlencode($this->layout);
  468. }
  469. else {
  470. $layRequest = "";
  471. }
  472. if ($this->currentSkip > 0) { // Set up the skip size portion of the query.
  473. $skipRequest = "&-skip=$this->currentSkip";
  474. } else {
  475. $skipRequest = "";
  476. }
  477. $currentSort = $this->CreateCurrentSort();
  478. $currentSearch = $this->CreateCurrentSearch();
  479. $this->dataURL = "http://{$this->userPass}{$this->dataServer}{$this->dataPortSuffix}/FMPro"; // First add the server info to the URL...
  480. $this->dataURLParams = $this->AssembleCurrentSearch($layRequest, $skipRequest, $currentSort, $currentSearch, $action);
  481. $this->dataURL .= '?' . $this->dataURLParams;
  482. if ((defined("DEBUG") and DEBUG) or DEBUG_FUZZY) {
  483. $currentDebugString = "<p>Using FileMaker URL: <a href=\"{$this->dataURL}\">{$this->dataURL}</a></p>\n";
  484. $this->lastDebugMessage .= $currentDebugString;
  485. if (defined("DEBUG") and DEBUG) {
  486. echo $currentDebugString;
  487. }
  488. }
  489. if (defined("HAS_PHPCACHE") and defined("FX_USE_PHPCACHE") and strlen($this->dataURLParams) <= 510 and (substr_count($this->dataURLParams, '-find') > 0 || substr_count($this->dataURLParams, '-view') > 0 || substr_count($this->dataURLParams, '-dbnames') > 0 || substr_count($this->dataURLParams, '-layoutnames') > 0)) {
  490. $data = get_url_cached($this->dataURL);
  491. if (! $data) {
  492. return new FX_Error("Failed to retrieve cached URL in RetrieveFMData()");
  493. }
  494. $data = $data["Body"];
  495. } elseif ($this->isPostQuery) {
  496. if ($this->useCURL && defined("CURLOPT_TIMEVALUE")) {
  497. $curlHandle = curl_init(str_replace($this->dataURLParams, '', $this->dataURL));
  498. curl_setopt($curlHandle, CURLOPT_POST, 1);
  499. curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $this->dataURLParams);
  500. ob_start();
  501. if (! curl_exec($curlHandle)) {
  502. $this->lastDebugMessage .= "<p>Unable to connect to FileMaker. Use the DEBUG constant and try connecting with the resulting URL manually.<br />\n";
  503. $this->lastDebugMessage .= "You should also double check the user name and password used, the server address, and Web Companion configuration.</p>\n";
  504. return new FX_Error("cURL could not retrieve Post data in RetrieveFMData(). A bad URL is the most likely reason.");
  505. }
  506. curl_close($curlHandle);
  507. $data = trim(ob_get_contents());
  508. ob_end_clean();
  509. if (substr($data, -1) != '>') {
  510. $data = substr($data, 0, -1);
  511. }
  512. } else {
  513. $dataDelimiter = "\r\n";
  514. $socketData = "POST /FMPro HTTP/1.0{$dataDelimiter}";
  515. if (strlen(trim($this->userPass)) > 1) {
  516. $socketData .= "Authorization: Basic " . base64_encode($this->DBUser . ':' . $this->DBPassword) . $dataDelimiter;
  517. }
  518. $socketData .= "Host: {$this->dataServer}:{$this->dataPort}{$dataDelimiter}";
  519. $socketData .= "Pragma: no-cache{$dataDelimiter}";
  520. $socketData .= "Content-length: " . strlen($this->dataURLParams) . $dataDelimiter;
  521. $socketData .= "Content-type: application/x-www-form-urlencoded{$dataDelimiter}";
  522. // $socketData .= "Connection: close{$dataDelimiter}";
  523. $socketData .= $dataDelimiter . $this->dataURLParams;
  524. $fp = fsockopen ($this->dataServer, $this->dataPort, $this->errorTracking, $this->fxError, 30);
  525. if (! $fp) {
  526. $this->lastDebugMessage .= "<p>Unable to connect to FileMaker. Use the DEBUG constant and try connecting with the resulting URL manually.<br />\n";
  527. $this->lastDebugMessage .= "You should also double check the user name and password used, the server address, and Web Companion configuration.</p>\n";
  528. return new FX_Error( "Could not fsockopen the URL in retrieveFMData" );
  529. }
  530. fputs ($fp, $socketData);
  531. while (!feof($fp)) {
  532. $data .= fgets($fp, 128);
  533. }
  534. fclose($fp);
  535. $pos = strpos($data, chr(13) . chr(10) . chr(13) . chr(10)); // the separation code
  536. $data = substr($data, $pos + 4) . "\r\n";
  537. }
  538. } else {
  539. $fp = fopen($this->dataURL, "r");
  540. if (! $fp) {
  541. $this->lastDebugMessage .= "<p>Unable to connect to FileMaker. Use the DEBUG constant and try connecting with the resulting URL manually.<br />\n";
  542. $this->lastDebugMessage .= "You should also double check the user name and password used, the server address, and Web Companion configuration.</p>\n";
  543. return new FX_Error("Could not fopen URL in RetrieveFMData.");
  544. }
  545. while (!feof($fp)) {
  546. $data .= fread($fp, 4096);
  547. }
  548. fclose($fp);
  549. }
  550. $data = str_replace($this->invalidXMLChars, '', $data);
  551. return $data;
  552. }
  553. function RetrieveFM7Data ($action)
  554. {
  555. $data = '';
  556. if ($this->DBPassword != '' || $this->DBUser != 'FX') { // Assemble the Password Data
  557. $this->userPass = $this->DBUser . ':' . $this->DBPassword . '@';
  558. }
  559. if ($this->layout != "") { // Set up the layout portion of the query.
  560. $layRequest = "&-lay=" . urlencode($this->layout);
  561. if ($this->responseLayout != "") {
  562. $layRequest .= "&-lay.response=" . urlencode($this->responseLayout);
  563. }
  564. }
  565. else {
  566. $layRequest = "";
  567. }
  568. if ($this->currentSkip > 0) { // Set up the skip size portion of the query.
  569. $skipRequest = "&-skip={$this->currentSkip}";
  570. } else {
  571. $skipRequest = "";
  572. }
  573. $currentSort = $this->CreateCurrentSort();
  574. $currentSearch = $this->CreateCurrentSearch();
  575. if ($action == '-view') {
  576. $FMFile = 'FMPXMLLAYOUT.xml';
  577. } else {
  578. $FMFile = 'FMPXMLRESULT.xml';
  579. }
  580. $this->dataURL = "{$this->urlScheme}://{$this->userPass}{$this->dataServer}{$this->dataPortSuffix}/fmi/xml/{$FMFile}"; // First add the server info to the URL...
  581. $this->dataURLParams = $this->AssembleCurrentSearch($layRequest, $skipRequest, $currentSort, $currentSearch, $action, 7);
  582. $this->dataURL .= '?' . $this->dataURLParams;
  583. if ((defined("DEBUG") and DEBUG) or DEBUG_FUZZY) {
  584. $currentDebugString = "<p>Using FileMaker URL: <a href=\"{$this->dataURL}\">{$this->dataURL}</a></p>\n";
  585. $this->lastDebugMessage .= $currentDebugString;
  586. if (defined("DEBUG") and DEBUG) {
  587. echo $currentDebugString;
  588. }
  589. }
  590. if (defined("HAS_PHPCACHE") and defined("FX_USE_PHPCACHE") and strlen($this->dataURLParams) <= 510 and (substr_count($this->dataURLParams, '-find') > 0 || substr_count($this->dataURLParams, '-view') > 0 || substr_count($this->dataURLParams, '-dbnames') > 0 || substr_count($this->dataURLParams, '-layoutnames') > 0)) {
  591. $data = get_url_cached($this->dataURL);
  592. if (! $data) {
  593. return new FX_Error("Failed to retrieve cached URL in RetrieveFM7Data()");
  594. }
  595. $data = $data["Body"];
  596. } elseif ($this->isPostQuery) {
  597. if ($this->useCURL && defined("CURLOPT_TIMEVALUE")) {
  598. $curlHandle = curl_init(str_replace($this->dataURLParams, '', $this->dataURL));
  599. curl_setopt($curlHandle, CURLOPT_POST, 1);
  600. curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $this->dataURLParams);
  601. ob_start();
  602. if (! curl_exec($curlHandle)) {
  603. $this->lastDebugMessage .= "<p>Unable to connect to FileMaker. Use the DEBUG constant and try connecting with the resulting URL manually.<br />\n";
  604. $this->lastDebugMessage .= "You should also double check the user name and password used, the server address, and WPE configuration.</p>\n";
  605. return new FX_Error("cURL could not retrieve Post data in RetrieveFM7Data(). A bad URL is the most likely reason.");
  606. }
  607. curl_close($curlHandle);
  608. $data = trim(ob_get_contents());
  609. ob_end_clean();
  610. if (substr($data, -1) != '>') {
  611. $data = substr($data, 0, -1);
  612. }
  613. } else {
  614. $dataDelimiter = "\r\n";
  615. $socketData = "POST /fmi/xml/{$FMFile} HTTP/1.0{$dataDelimiter}";
  616. if ((defined("DEBUG") and DEBUG) or DEBUG_FUZZY) {
  617. $currentDebugString = "<p>Using socket [$socketData] - FileMaker URL: <a href=\"{$this->dataURL}\">{$this->dataURL}</a></p>\n";
  618. $this->lastDebugMessage .= $currentDebugString;
  619. if (defined("DEBUG") and DEBUG) {
  620. echo $currentDebugString;
  621. }
  622. }
  623. if (strlen(trim($this->userPass)) > 1) {
  624. $socketData .= "Authorization: Basic " . base64_encode($this->DBUser . ':' . $this->DBPassword) . $dataDelimiter;
  625. }
  626. $socketData .= "Host: {$this->dataServer}:{$this->dataPort}{$dataDelimiter}";
  627. $socketData .= "Pragma: no-cache{$dataDelimiter}";
  628. $socketData .= "Content-length: " . strlen($this->dataURLParams) . $dataDelimiter;
  629. $socketData .= "Content-type: application/x-www-form-urlencoded{$dataDelimiter}";
  630. $socketData .= $dataDelimiter . $this->dataURLParams;
  631. // Check if SSL is required
  632. if ($this->useSSLProtocol) {
  633. $protocol = "ssl://";
  634. } else {
  635. $protocol = "";
  636. }
  637. // debug to see what protocol is being used
  638. if ((defined("DEBUG") and DEBUG) or DEBUG_FUZZY) {
  639. $currentDebugString = "<p>Domain and Protocol are {$protocol}{$this->dataServer}</p>\n";
  640. $this->lastDebugMessage .= $currentDebugString;
  641. if (defined("DEBUG") and DEBUG) {
  642. echo $currentDebugString;
  643. }
  644. }
  645. $fp = fsockopen ($protocol . $this->dataServer, $this->dataPort, $this->errorTracking, $this->fxError, 30);
  646. if (! $fp) {
  647. $this->lastDebugMessage .= "<p>Unable to connect to FileMaker. Use the DEBUG constant and try connecting with the resulting URL manually.<br />\n";
  648. $this->lastDebugMessage .= "You should also double check the user name and password used, the server address, and WPE configuration.</p>\n";
  649. return new FX_Error( "Could not fsockopen the URL in retrieveFM7Data" );
  650. }
  651. fputs ($fp, $socketData);
  652. while (!feof($fp)) {
  653. $data .= fgets($fp, 128);
  654. }
  655. fclose($fp);
  656. $pos = strpos($data, chr(13) . chr(10) . chr(13) . chr(10)); // the separation code
  657. $data = substr($data, $pos + 4) . "\r\n";
  658. }
  659. } else {
  660. $fp = fopen($this->dataURL, "r");
  661. if (! $fp) {
  662. $this->lastDebugMessage .= "<p>Unable to connect to FileMaker. Use the DEBUG constant and try connecting with the resulting URL manually.<br />\n";
  663. $this->lastDebugMessage .= "You should also double check the user name and password used, the server address, and WPE configuration.</p>\n";
  664. return new FX_Error("Could not fopen URL in RetrieveFM7Data.");
  665. }
  666. while (!feof($fp)) {
  667. $data .= fread($fp, 4096);
  668. }
  669. fclose($fp);
  670. }
  671. $data = str_replace($this->invalidXMLChars, '', $data);
  672. return $data;
  673. }
  674. function BuildSQLSorts ()
  675. {
  676. $currentOrderBy = '';
  677. if (count($this->sortParams) > 0) {
  678. $counter = 0;
  679. $currentOrderBy .= ' ORDER BY ';
  680. foreach ($this->sortParams as $key1 => $value1) {
  681. foreach ($value1 as $key2 => $value2) {
  682. $$key2 = $value2;
  683. }
  684. if ($counter > 0) {
  685. $currentOrderBy .= ', ';
  686. }
  687. $currentOrderBy .= "{$field}";
  688. if (substr_count(strtolower($sortOrder), 'desc') > 0) {
  689. $currentOrderBy .= ' DESC';
  690. }
  691. ++$counter;
  692. }
  693. return $currentOrderBy;
  694. }
  695. }
  696. function BuildSQLQuery ($action)
  697. {
  698. $currentLOP = 'AND';
  699. $logicalOperators = array();
  700. $LOPCount = 0;
  701. $currentQuery = '';
  702. $counter = 0;
  703. $whereClause = '';
  704. switch ($action) {
  705. case '-find':
  706. foreach ($this->dataParams as $key1 => $value1) {
  707. foreach ($value1 as $key2 => $value2) {
  708. $$key2 = $value2;
  709. }
  710. switch ($name) {
  711. case '-lop':
  712. $LOPCount = array_push($logicalOperators, $currentLOP);
  713. $currentLOP = $value;
  714. $currentSearch .= "(";
  715. break;
  716. case '-lop_end':
  717. $currentLOP = array_pop($logicalOperators);
  718. --$LOPCount;
  719. $currentSearch .= ")";
  720. break;
  721. case '-recid':
  722. if ($counter > 0) {
  723. $currentSearch .= " {$currentLOP} ";
  724. }
  725. $currentSearch .= $this->primaryKeyField . " = '" . $value . "'";
  726. ++$counter;
  727. break;
  728. case '-script':
  729. case '-script.prefind':
  730. case '-script.presort':
  731. return new FX_Error("The '-script' parameter is not currently supported for SQL.");
  732. break;
  733. default:
  734. if ($op == "") {
  735. $op = $this->defaultOperator;
  736. }
  737. if ($counter > 0) {
  738. $currentSearch .= " {$currentLOP} ";
  739. }
  740. switch ($op) {
  741. case 'eq':
  742. $currentSearch .= $name . " = '" . $value . "'";
  743. break;
  744. case 'neq':
  745. $currentSearch .= $name . " != '" . $value . "'";
  746. break;
  747. case 'cn':
  748. $currentSearch .= $name . " LIKE '%" . $value . "%'";
  749. break;
  750. case 'bw':
  751. $currentSearch .= $name . " LIKE '" . $value . "%'";
  752. break;
  753. case 'ew':
  754. $currentSearch .= $name . " LIKE '%" . $value . "'";
  755. break;
  756. case 'gt':
  757. $currentSearch .= $name . " > '" . $value . "'";
  758. break;
  759. case 'gte':
  760. $currentSearch .= $name . " >= '" . $value . "'";
  761. break;
  762. case 'lt':
  763. $currentSearch .= $name . " < '" . $value . "'";
  764. break;
  765. case 'lte':
  766. $currentSearch .= $name . " <= '" . $value . "'";
  767. break;
  768. default: // default is a 'begins with' search for historical reasons (default in FM)
  769. $currentSearch .= $name . " LIKE '" . $value . "%'";
  770. break;
  771. }
  772. ++$counter;
  773. break;
  774. }
  775. }
  776. while ($LOPCount > 0) {
  777. --$LOPCount;
  778. $currentSearch .= ")";
  779. }
  780. $whereClause = ' WHERE ' . $currentSearch; // set the $whereClause variable here, to distinguish this from a "finall" request
  781. case '-findall': //
  782. if ($this->selectColsSet) {
  783. $currentQuery = "SELECT {$this->selectColumns} FROM {$this->layout}{$whereClause}" . $this->BuildSQLSorts();
  784. } else {
  785. $currentQuery = "SELECT * FROM {$this->layout}{$whereClause}" . $this->BuildSQLSorts();
  786. }
  787. break;
  788. case '-delete':
  789. foreach ($this->dataParams as $key1 => $value1) {
  790. foreach ($value1 as $key2 => $value2) {
  791. $$key2 = $value2;
  792. }
  793. if ($name == '-recid') {
  794. $currentQuery = "DELETE FROM {$this->layout} WHERE {$this->primaryKeyField} = '{$value}'";
  795. }
  796. }
  797. break;
  798. case '-edit':
  799. $whereClause = '';
  800. $currentQuery = "UPDATE {$this->layout} SET ";
  801. foreach ($this->dataParams as $key1 => $value1) {
  802. foreach ($value1 as $key2 => $value2) {
  803. $$key2 = $value2;
  804. }
  805. if ($name == '-recid') {
  806. $whereClause = " WHERE {$this->primaryKeyField} = '{$value}'";
  807. } else {
  808. if ($counter > 0) {
  809. $currentQuery .= ", ";
  810. }
  811. $currentQuery .= "{$name} = '{$value}'";
  812. ++$counter;
  813. }
  814. }
  815. $currentQuery .= $whereClause;
  816. break;
  817. case '-new':
  818. $tempColList = '(';
  819. $tempValueList = '(';
  820. foreach ($this->dataParams as $key1 => $value1) {
  821. foreach ($value1 as $key2 => $value2) {
  822. $$key2 = $value2;
  823. }
  824. if ($name == '-recid') {
  825. $currentQuery = "DELETE FROM {$this->layout} WHERE {$this->primaryKeyField} = '{$value}'";
  826. }
  827. if ($counter > 0) {
  828. $tempColList .= ", ";
  829. $tempValueList .= ", ";
  830. }
  831. $tempColList .= $name;
  832. $tempValueList .= "'{$value}'";
  833. ++$counter;
  834. }
  835. $tempColList .= ')';
  836. $tempValueList .= ')';
  837. $currentQuery = "INSERT INTO {$this->layout} {$tempColList} VALUES {$tempValueList}";
  838. break;
  839. }
  840. $currentQuery .= ';';
  841. return $currentQuery;
  842. }
  843. function RetrieveMySQLData ($action)
  844. {
  845. if (strlen(trim($this->dataServer)) < 1) {
  846. return new FX_Error('No MySQL server specified.');
  847. }
  848. if (strlen(trim($this->dataPort)) > 0) {
  849. $tempServer = $this->dataServer . ':' . $this->dataPort;
  850. } else {
  851. $tempServer = $this->dataServer;
  852. }
  853. $mysql_res = @mysql_connect($tempServer, $this->DBUser, $this->DBPassword); // although username and password are optional for this function, FX.php expects them to be set
  854. if ($mysql_res == false) {
  855. return new FX_Error('Unable to connect to MySQL server.');
  856. }
  857. if ($action != '-dbopen') {
  858. if (! mysql_select_db($this->database, $mysql_res)) {
  859. return new FX_Error('Unable to connect to specified MySQL database.');
  860. }
  861. }
  862. if (substr_count($action, '-db') == 0 && substr_count($action, 'names') == 0) {
  863. $theResult = mysql_query('SHOW COLUMNS FROM ' . $this->layout);
  864. if (! $theResult) {
  865. return new FX_Error('Unable to access MySQL column data: ' . mysql_error());
  866. }
  867. $counter = 0;
  868. $keyPrecedence = 0;
  869. while ($tempRow = mysql_fetch_assoc($theResult)) {
  870. $this->fieldInfo[$counter]['name'] = $tempRow['Field'];
  871. $this->fieldInfo[$counter]['type'] = $tempRow['Type'];
  872. $this->fieldInfo[$counter]['emptyok'] = $tempRow['Null'];
  873. $this->fieldInfo[$counter]['maxrepeat'] = 1;
  874. $this->fieldInfo[$counter]['extra'] = $tempRow['Key'] . ' ' . $tempRow['Extra'];
  875. if ($this->fuzzyKeyLogic) {
  876. if (strlen(trim($this->primaryKeyField)) < 1 || $keyPrecedence < 3) {
  877. if (substr_count($this->fieldInfo[$counter]['extra'], 'UNI ') > 0 && $keyPrecedence < 3) {
  878. $this->primaryKeyField = $this->fieldInfo[$counter]['name'];
  879. $keyPrecedence = 3;
  880. } elseif (substr_count($this->fieldInfo[$counter]['extra'], 'auto_increment') > 0 && $keyPrecedence < 2) {
  881. $this->primaryKeyField = $this->fieldInfo[$counter]['name'];
  882. $keyPrecedence = 2;
  883. } elseif (substr_count($this->fieldInfo[$counter]['extra'], 'PRI ') > 0 && $keyPrecedence < 1) {
  884. $this->primaryKeyField = $this->fieldInfo[$counter]['name'];
  885. $keyPrecedence = 1;
  886. }
  887. }
  888. }
  889. ++$counter;
  890. }
  891. }
  892. switch ($action) {
  893. case '-dbopen':
  894. case '-dbclose':
  895. return new FX_Error('Opening and closing MySQL databases not available.');
  896. break;
  897. case '-delete':
  898. case '-edit':
  899. case '-find':
  900. case '-findall':
  901. case '-new':
  902. $this->dataQuery = $this->BuildSQLQuery($action);
  903. if (FX::isError($this->dataQuery)) {
  904. return $this->dataQuery;
  905. }
  906. case '-sqlquery': // note that there is no preceding break, as we don't want to build a query
  907. $theResult = mysql_query($this->dataQuery);
  908. if ($theResult === false) {
  909. return new FX_Error('Invalid query: ' . mysql_error());
  910. } elseif ($theResult !== true) {
  911. if (substr_count($action, '-find') > 0 || substr_count($this->dataQuery, 'SELECT ') > 0) {
  912. $this->foundCount = mysql_num_rows($theResult);
  913. } else {
  914. $this->foundCount = mysql_affected_rows($theResult);
  915. }
  916. if ($action == '-dup' || $action == '-edit') {
  917. // pull in data on relevant record
  918. }
  919. $currentKey = '';
  920. while ($tempRow = mysql_fetch_assoc($theResult)) {
  921. foreach ($tempRow as $key => $value) {
  922. if ($this->useInnerArray) {
  923. $tempRow[$key] = array($value);
  924. }
  925. if ($key == $this->primaryKeyField) {
  926. $currentKey = $value;
  927. }
  928. }
  929. if ($this->genericKeys || $this->primaryKeyField == '') {
  930. $this->currentData[] = $tempRow;
  931. } else {
  932. $this->currentData[$currentKey] = $tempRow;
  933. }
  934. }
  935. } else {
  936. $this->currentData = array();
  937. }
  938. break;
  939. case '-findany':
  940. break;
  941. case '-dup':
  942. break;
  943. }
  944. $this->fxError = 0;
  945. return true;
  946. }
  947. function RetrievePostgreSQLData ($action)
  948. {
  949. $connectString = '';
  950. $unsupportedActions = array('-dbnames', '-layoutnames', '-scriptnames', '-dbopen', '-dbclose');
  951. if (in_array($action, $unsupportedActions)) {
  952. return new FX_Error("The requested Action ({$action}) is not supported in PostgreSQL via FX.php.");
  953. }
  954. if (strlen(trim($this->dataServer)) > 0) {
  955. $connectString .= " host={$this->dataServer}";
  956. }
  957. if (strlen(trim($this->dataPort)) > 0) {
  958. $connectString .= " port={$this->dataPort}";
  959. }
  960. if (strlen(trim($this->database)) > 0) {
  961. $connectString .= " dbname={$this->database}";
  962. }
  963. if (strlen(trim($this->DBUser)) > 0) {
  964. $connectString .= " user={$this->DBUser}";
  965. }
  966. if (strlen(trim($this->DBPassword)) > 0) {
  967. $connectString .= " password={$this->DBPassword}";
  968. }
  969. if (strlen(trim($this->urlScheme)) > 0 && $this->urlScheme == 'https') {
  970. $connectString .= " sslmode=require";
  971. }
  972. $postresql_res = @pg_connect($connectString);
  973. if ($postresql_res == false) {
  974. return new FX_Error("Unable to connect to PostgreSQL server. (" . pg_last_error($postresql_res) . ")");
  975. }
  976. $theResult = pg_query($postresql_res, "SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_name ='{$this->layout}'");
  977. if (! $theResult) {
  978. return new FX_Error('Unable to access PostgreSQL column data: ' . pg_last_error($postresql_res));
  979. }
  980. $counter = 0;
  981. $keyPrecedence = 0;
  982. while ($tempRow = @pg_fetch_array($theResult, $counter, PGSQL_ASSOC)) {
  983. $this->fieldInfo[$counter]['name'] = $tempRow['column_name'];
  984. $this->fieldInfo[$counter]['type'] = $tempRow['data_type'];
  985. $this->fieldInfo[$counter]['emptyok'] = $tempRow['is_nullable'];
  986. $this->fieldInfo[$counter]['maxrepeat'] = 1;
  987. ++$counter;
  988. }
  989. switch ($action) {
  990. case '-delete':
  991. case '-edit':
  992. case '-find':
  993. case '-findall':
  994. case '-new':
  995. $this->dataQuery = $this->BuildSQLQuery($action);
  996. if (FX::isError($this->dataQuery)) {
  997. return $this->dataQuery;
  998. }
  999. case '-sqlquery': // note that there is no preceding break, as we don't want to build a query
  1000. $theResult = pg_query($this->dataQuery);
  1001. if (! $theResult) {
  1002. return new FX_Error('Invalid query: ' . pg_last_error($postresql_res));
  1003. }
  1004. if (substr_count($action, '-find') > 0 || substr_count($this->dataQuery, 'SELECT ') > 0) {
  1005. $this->foundCount = pg_num_rows($theResult);
  1006. } else {
  1007. $this->foundCount = pg_a

Large files files are truncated, but you can click here to view the full file