PageRenderTime 70ms CodeModel.GetById 26ms 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
  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_affected_rows($theResult);
  1008. }
  1009. if ($action == '-dup' || $action == '-edit') {
  1010. // pull in data on relevant record
  1011. }
  1012. $counter = 0;
  1013. $currentKey = '';
  1014. while ($tempRow = @pg_fetch_array($theResult, $counter, PGSQL_ASSOC)) {
  1015. foreach ($tempRow as $key => $value) {
  1016. if ($this->useInnerArray) {
  1017. $tempRow[$key] = array($value);
  1018. }
  1019. if ($key == $this->primaryKeyField) {
  1020. $currentKey = $value;
  1021. }
  1022. }
  1023. if ($this->genericKeys || $this->primaryKeyField == '') {
  1024. $this->currentData[] = $tempRow;
  1025. } else {
  1026. $this->currentData[$currentKey] = $tempRow;
  1027. }
  1028. ++$counter;
  1029. }
  1030. break;
  1031. case '-findany':
  1032. break;
  1033. case '-dup':
  1034. break;
  1035. }
  1036. $this->fxError = 0;
  1037. return true;
  1038. }
  1039. function RetrieveOpenBaseData ($action)
  1040. {
  1041. $availableActions = array('-delete', '-edit', '-find', '-findall', '-new', '-sqlquery');
  1042. $columnTypes = array( 1 => 'char', 2 => 'integer', 3 => 'float', 4 => 'long', 5 => 'money', 6 => 'date', 7 => 'time', 8 => 'object', 9 => 'datetime', 10 => 'longlong', 11 => 'boolean', 12 => 'binary', 13 => 'text', 14 => 'timestamp');
  1043. if (! in_array(strtolower($action), $availableActions)) { // first off, toss out any requests for actions NOT supported under OpenBase
  1044. return new FX_Error("The action requested ({$action}) is not supported by OpenBase via FX.php.");
  1045. }
  1046. // although username and password are optional for this function, FX.php expects them to be set
  1047. $openBase_res = ob_connect($this->database, $this->dataServer, $this->DBUser, $this->DBPassword);
  1048. if (substr(trim($openBase_res), 0, 13) != 'Resource id #') {
  1049. return new FX_Error("Error {$theResult}. Unable to connect to OpenBase database.");
  1050. }
  1051. switch ($action) {
  1052. case '-delete':
  1053. case '-edit':
  1054. case '-find':
  1055. case '-findall':
  1056. case '-new':
  1057. $this->dataQuery = $this->BuildSQLQuery($action);
  1058. if (FX::isError($this->dataQuery)) {
  1059. return $this->dataQuery;
  1060. }
  1061. case '-sqlquery': // note that there is no preceding break, as we don't want to build a query
  1062. ob_makeCommand($openBase_res, $this->dataQuery);
  1063. $theResult = ob_executeCommand($openBase_res);
  1064. if (! $theResult) {
  1065. $tempErrorText = ob_servermessage($openBase_res);
  1066. ob_disconnect($openBase_res); // ob_disconnect() is not in the documentation
  1067. return new FX_Error("Unsuccessful query: $this->dataQuery ({$tempErrorText})");
  1068. }
  1069. $fieldCount = ob_resultColumnCount($openBase_res);
  1070. for ($i = 0; $i < $fieldCount; ++$i) {
  1071. $this->fieldInfo[$i]['name'] = ob_resultColumnName($openBase_res, $i);
  1072. $this->fieldInfo[$i]['type'] = ob_resultColumnType($openBase_res, $i);
  1073. $this->fieldInfo[$i]['emptyok'] = 'NO DATA';
  1074. $this->fieldInfo[$i]['maxrepeat'] = 1;
  1075. $this->fieldInfo[$i]['extra'] = '';
  1076. }
  1077. $this->foundCount = ob_rowsAffected($openBase_res);
  1078. $retrieveRow = array();
  1079. $currentKey = '';
  1080. while (ob_resultReturned($openBase_res) && ob_nextRowWithArray($openBase_res, $retrieveRow)) {
  1081. $tempRow = array();
  1082. foreach ($retrieveRow as $key => $value) {
  1083. if (! $this->useInnerArray) {
  1084. $tempRow[$this->fieldInfo[$key]['name']] = $value;
  1085. } else {
  1086. $tempRow[$this->fieldInfo[$key]['name']] = array($value);
  1087. }
  1088. if ($key == $this->primaryKeyField) {
  1089. $currentKey = $value;
  1090. } elseif ($this->primaryKeyField == '' && $this->fieldInfo[$key]['name'] == '_rowid') {
  1091. $currentKey = $value;
  1092. }
  1093. }
  1094. if (($this->genericKeys || $this->primaryKeyField == '') && strlen(trim($currentKey)) < 1) {
  1095. $this->currentData[] = $tempRow;
  1096. } else {
  1097. $this->currentData[$currentKey] = $tempRow;
  1098. }
  1099. }
  1100. break;
  1101. default:
  1102. return new FX_Error("The action requested ({$action}) is not supported by OpenBase via FX.php.");
  1103. break;
  1104. }
  1105. $this->fxError = 0;
  1106. return true;
  1107. }
  1108. function RetrieveODBCData ($action)
  1109. {
  1110. $availableActions = array('-delete', '-edit', '-find', '-findall', '-new', '-sqlquery');
  1111. if (! in_array(strtolower($action), $availableActions)) { // first off, toss out any requests for actions NOT supported under ODBC
  1112. return new FX_Error("The action requested ({$action}) is not supported under ODBC via FX.php.");
  1113. }
  1114. $odbc_res = odbc_connect($this->database, $this->DBUser, $this->DBPassword); // although username and password are optional for this function, FX.php expects them to be set
  1115. if ($odbc_res == false) {
  1116. return new FX_Error('Unable to connect to ODBC data source.');
  1117. }
  1118. switch ($action) {
  1119. case '-delete':
  1120. case '-edit':
  1121. case '-find':
  1122. case '-findall':
  1123. case '-new':
  1124. $this->dataQuery = $this->BuildSQLQuery($action);
  1125. if (FX::isError($this->dataQuery)) {
  1126. return $this->dataQuery;
  1127. }
  1128. case '-sqlquery': // note that there is no preceding break, as we don't want to build a query
  1129. $odbc_result = odbc_exec($odbc_res, $this->dataQuery);
  1130. if (! $odbc_result) {
  1131. $tempErrorText = odbc_errormsg($odbc_res);
  1132. odbc_close($odbc_res);
  1133. return new FX_Error("Unsuccessful query: $this->dataQuery ({$tempErrorText})");
  1134. }
  1135. $this->foundCount = odbc_num_rows($odbc_result);
  1136. $fieldCount = odbc_num_fields($odbc_result);
  1137. if ($theResult < 0) {
  1138. $tempErrorText = odbc_errormsg($odbc_res);
  1139. odbc_close($odbc_res);
  1140. return new FX_Error("Unable to access field count for current ODBC query. ({$tempErrorText})");
  1141. }
  1142. $odbc_columns = odbc_columns($odbc_res);
  1143. if (! $odbc_columns) {
  1144. $tempErrorText = odbc_errormsg($odbc_res);
  1145. odbc_close($odbc_res);
  1146. return new FX_Error("Unable to retrieve column data via ODBC. ({$tempErrorText})");
  1147. }
  1148. while (odbc_fetch_row($odbc_columns)) {
  1149. $fieldNumber = odbc_result($odbc_columns, 'ORDINAL_POSITION');
  1150. $this->fieldInfo[$fieldNumber]['name'] = odbc_result($odbc_columns, 'COLUMN_NAME');
  1151. $this->fieldInfo[$fieldNumber]['type'] = odbc_result($odbc_columns, 'TYPE_NAME');
  1152. $this->fieldInfo[$fieldNumber]['emptyok'] = odbc_result($odbc_columns, 'IS_NULLABLE');
  1153. $this->fieldInfo[$fieldNumber]['maxrepeat'] = 1;
  1154. $this->fieldInfo[$fieldNumber]['extra'] = 'COLUMN_SIZE:' . odbc_result($odbc_columns, 'COLUMN_SIZE') . '|BUFFER_LENGTH:' . odbc_result($odbc_columns, 'BUFFER_LENGTH') . '|NUM_PREC_RADIX:' . odbc_result($odbc_columns, 'NUM_PREC_RADIX');
  1155. }
  1156. while (odbc_fetch_row($odbc_result)) {
  1157. $tempRow = array();
  1158. for ($i = 1; $i <= $fieldCount; ++$i) {
  1159. $theResult = odbc_result($odbc_result, $i);
  1160. if (! $this->useInnerArray) {
  1161. $tempRow[$this->fieldInfo[$i]['name']] = $theResult;
  1162. } else {
  1163. $tempRow[$this->fieldInfo[$i]['name']] = array($theResult);
  1164. }
  1165. if ($this->fieldInfo[$i]['name'] == $this->primaryKeyField) {
  1166. $currentKey = $theResult;
  1167. }
  1168. }
  1169. if ($this->genericKeys || $this->primaryKeyField == '') {
  1170. $this->currentData[] = $tempRow;
  1171. } else {
  1172. $this->currentData[$currentKey] = $tempRow;
  1173. }
  1174. }
  1175. break;
  1176. default:
  1177. return new FX_Error("The action requested ({$action}) is not supported by FileMaker under ODBC via FX.php.");
  1178. break;
  1179. }
  1180. $this->fxError = 0;
  1181. return true;
  1182. }
  1183. function RetrieveCAFEphp4PCData ($action) // uncomment this section ONLY on Windows, or the COM object will cause the PHP parser to die
  1184. {
  1185. /*
  1186. // Note that because of the way in which CAFEphp and FileMaker are implemented, CAFEphp must be running on the same
  1187. // machine that is serving as the web server. (You'll note that PHP creates a COM object which looks for a locally
  1188. // running application.) For this same reason, the server IP and port are irrelevant.
  1189. $availableActions = array('-delete', '-edit', '-find', '-findall', '-new', '-sqlquery');
  1190. if (! in_array(strtolower($action), $availableActions)) { // first off, toss out any requests for actions NOT supported under CAFEphp
  1191. return new FX_Error("The action requested ({$action}) is not supported in CAFEphp.");
  1192. }
  1193. $CAFEphp_res = new COM('CAFEphp.Application'); // although username and password are optional for this function, FX.php expects them to be set
  1194. if ($CAFEphp_res == false) {
  1195. return new FX_Error('Unable to load to CAFEphp.');
  1196. }
  1197. if ((defined("DEBUG") and DEBUG) or DEBUG_FUZZY) {
  1198. $currentDebugString = "<p>CAFEphp version: " . $CAFEphp_res->Version() . "</p>\n";
  1199. $this->lastDebugMessage .= $currentDebugString;
  1200. if (defined("DEBUG") and DEBUG) {
  1201. echo $currentDebugString;
  1202. }
  1203. }
  1204. $theResult = $CAFEphp_res->Connect($this->database, $this->DBUser, $this->DBPassword);
  1205. if ($theResult != 0) {
  1206. $CAFEphp_res->EndConnection();
  1207. switch ($theResult) {
  1208. case -1:
  1209. return new FX_Error('Unable to connect. Be sure the FileMaker database and CAFEphp are running.');
  1210. break;
  1211. case -2:
  1212. return new FX_Error('Certificate not present. You MUST have a certificate.');
  1213. break;
  1214. case -3:
  1215. return new FX_Error('Certificate is corrupt.');
  1216. break;
  1217. case -4:
  1218. return new FX_Error('CAFEphp is not running or the demo version has expired.');
  1219. break;
  1220. case -5:
  1221. return new FX_Error('The current demo of CAFEphp has expired.');
  1222. break;
  1223. default:
  1224. return new FX_Error('An unknown error has occured while attempting to create the COM object.');
  1225. break;
  1226. }
  1227. }
  1228. switch ($action) {
  1229. case '-delete':
  1230. case '-edit':
  1231. case '-find':
  1232. case '-findall':
  1233. case '-new':
  1234. $this->dataQuery = $this->BuildSQLQuery($action);
  1235. if (FX::isError($this->dataQuery)) {
  1236. return $this->dataQuery;
  1237. }
  1238. case '-sqlquery': // note that there is no preceding break, as we don't want to build a query
  1239. if (substr(trim($this->dataQuery), 0, 6) == 'SELECT') {
  1240. $currentSelect = true;
  1241. $theResult = $CAFEphp_res->Query($this->dataQuery, $this->groupSize);
  1242. } else {
  1243. $currentSelect = false;
  1244. $theResult = $CAFEphp_res->Execute($this->dataQuery);
  1245. }
  1246. if ($theResult < 0) {
  1247. $CAFEphp_res->EndConnection();
  1248. switch ($theResult) {
  1249. case -1:
  1250. return new FX_Error('No CAFEphp connection for the query.');
  1251. break;
  1252. default:
  1253. return new FX_Error('An unknown error occured during the query.');
  1254. break;
  1255. }
  1256. }
  1257. $this->foundCount = $theResult;
  1258. $theResult = $CAFEphp_res->FieldCount();
  1259. if ($theResult < 0) {
  1260. $CAFEphp_res->EndConnection();
  1261. switch ($theResult) {
  1262. case -1:
  1263. return new FX_Error('No CAFEphp connection for the field count.');
  1264. break;
  1265. case -2:
  1266. return new FX_Error('No query was performed for a field count.');
  1267. break;
  1268. default:
  1269. return new FX_Error('An unknown error occured during the query.');
  1270. break;
  1271. }
  1272. } else {
  1273. $currentFieldCount = $theResult;
  1274. }
  1275. for ($i = 0; $i < $currentFieldCount; ++$i) {
  1276. $theResult = $CAFEphp_res->FieldName($i);
  1277. if ($theResult == '$-CAFEphpNOCONNECTION') {
  1278. $CAFEphp_res->EndConnection();
  1279. return new FX_Error("No CAFEphp connection while retieving the name of field {$i}.");
  1280. } elseif ($theResult == '$-CAFEphpNOQUERY') {
  1281. $CAFEphp_res->EndConnection();
  1282. return new FX_Error("CAFEphp returned a \"No Query\" error while retieving the name of field {$i}.");
  1283. } elseif ($theResult == '$-CAFEphpUNKNOWNERROR') {
  1284. $CAFEphp_res->EndConnection();
  1285. return new FX_Error("CAFEphp returned an unknown error while retieving the name of field {$i}.");
  1286. }
  1287. $this->fieldInfo[$i]['name'] = $theResult;
  1288. $this->fieldInfo[$i]['type'] = 'NO DATA';
  1289. $this->fieldInfo[$i]['emptyok'] = 'NO DATA';
  1290. $this->fieldInfo[$i]['maxrepeat'] = 'NO DATA';
  1291. $this->fieldInfo[$i]['extra'] = '';
  1292. }
  1293. if ($currentSelect) {
  1294. $tempRow = array();
  1295. for ($i = 0; $i < $this->foundCount; ++$i) {
  1296. for ($j = 0; $j < $currentFieldCount; ++$j) {
  1297. $theResult = $CAFEphp_res->FieldValue($j);
  1298. if ($theResult == '$-CAFEphpNOCONNECTION') {
  1299. $CAFEphp_res->EndConnection();
  1300. return new FX_Error("No CAFEphp connection while retieving the value of field {$i} for record {$j}.");
  1301. } elseif ($theResult == '$-CAFEphpNOQUERY') {
  1302. $CAFEphp_res->EndConnection();
  1303. return new FX_Error("CAFEphp returned a \"No Query\" error while retieving the value of field {$i} for record {$j}.");
  1304. } elseif ($theResult == '$-CAFEphpUNKNOWNERROR') {
  1305. $CAFEphp_res->EndConnection();
  1306. return new FX_Error("CAFEphp returned an unknown error while retieving the value of field {$i} for record {$j}.");
  1307. }
  1308. if (! $this->useInnerArray) {
  1309. $tempRow[$this->fieldInfo[$j]['name']] = $theResult;
  1310. } else {
  1311. $tempRow[$this->fieldInfo[$j]['name']] = array($theResult);
  1312. }
  1313. if ($this->fieldInfo[$j]['name'] == $this->primaryKeyField) {
  1314. $currentKey = $value;
  1315. }
  1316. }
  1317. if ($this->genericKeys || $this->primaryKeyField == '') {
  1318. $this->currentData[] = $tempRow;
  1319. } else {
  1320. $this->currentData[$currentKey] = $tempRow;
  1321. }
  1322. $theResult = $CAFEphp_res->MoveNext();
  1323. if ($theResult < 0) {
  1324. $CAFEphp_res->EndConnection();
  1325. $next = $i + 1;
  1326. switch ($theResult) {
  1327. case -1:
  1328. return new FX_Error('No CAFEphp connection while moving from record {$i} to {$next}.');
  1329. break;
  1330. case -2:
  1331. return new FX_Error('There was no current query while moving from record {$i} to {$next}.');
  1332. break;
  1333. default:
  1334. return new FX_Error('An unknown error occured while moving from record {$i} to {$next}.');
  1335. break;
  1336. }
  1337. }
  1338. }
  1339. }
  1340. break;
  1341. default:
  1342. return new FX_Error("The action requested ({$action}) is not supported in CAFEphp.");
  1343. break;
  1344. }
  1345. $this->fxError = 0;
  1346. return true;
  1347. */
  1348. }
  1349. function ExecuteQuery ($action)
  1350. {
  1351. switch (strtolower($this->dataServerType)) {
  1352. case 'fmpro5':
  1353. case 'fmpro6':
  1354. case 'fmpro5/6':
  1355. if ((defined("DEBUG") and DEBUG) or DEBUG_FUZZY) {
  1356. $currentDebugString = "<p>Accessing FileMaker Pro 5/6 data.</p>\n";
  1357. $this->lastDebugMessage .= $currentDebugString;
  1358. if (defined("DEBUG") and DEBUG) {
  1359. echo $currentDebugString;
  1360. }
  1361. }
  1362. $data = $this->RetrieveFMData($action);
  1363. if (FX::isError($data)) {
  1364. return $data;
  1365. }
  1366. $xml_parser = xml_parser_create("UTF-8");
  1367. xml_set_object($xml_parser, $this);
  1368. xml_set_element_handler($xml_parser, "StartElement", "EndElement");
  1369. xml_set_character_data_handler($xml_parser, "ElementContents");
  1370. $xmlParseResult = xml_parse($xml_parser, $data, true);
  1371. if (! $xmlParseResult) {
  1372. $theMessage = sprintf("ExecuteQuery XML error: %s at line %d",
  1373. xml_error_string(xml_get_error_code($xml_parser)),
  1374. xml_get_current_line_number($xml_parser));
  1375. xml_parser_free($xml_parser);
  1376. $this->lastDebugMessage .= "<p>Unable to parse FileMaker XML. Use the DEBUG constant and try connecting with the resulting URL manually.<br />\n";
  1377. $this->lastDebugMessage .= "You should also double check the <strong>user name</strong> and <strong>password</strong> used, the <strong>server address and port</strong>, and <strong>Web Companion configuration</strong>.<br />\n";
  1378. $this->lastDebugMessage .= "Finally, be sure that you have specified the correct <strong>data type</strong> (e.g. FileMaker 5 or 6 versus 7 or 8.)</p>\n";
  1379. return new FX_Error($theMessage);
  1380. }
  1381. xml_parser_free($xml_parser);
  1382. break;
  1383. case 'fmpro7':
  1384. case 'fmpro8':
  1385. case 'fmpro9':
  1386. if ((defined("DEBUG") and DEBUG) or DEBUG_FUZZY) {
  1387. $currentDebugString = "<p>Accessing FileMaker Pro 7/8/9 data.</p>\n";
  1388. $this->lastDebugMessage .= $currentDebugString;
  1389. if (defined("DEBUG") and DEBUG) {
  1390. echo $currentDebugString;
  1391. }
  1392. }
  1393. $data = $this->RetrieveFM7Data($action);
  1394. if (FX::isError($data)) {
  1395. return $data;
  1396. }
  1397. $xml_parser = xml_parser_create("UTF-8");
  1398. xml_set_object($xml_parser, $this);
  1399. xml_set_element_handler($xml_parser, "StartElement", "EndElement");
  1400. xml_set_character_data_handler($xml_parser, "ElementContents");
  1401. $xmlParseResult = xml_parse($xml_parser, $data, true);
  1402. if (! $xmlParseResult) {
  1403. /* Masayuki Nii added at Oct 9, 2009 */
  1404. $this->columnCount = -1;
  1405. xml_parser_free($xml_parser);
  1406. $xml_parser = xml_parser_create("UTF-8");
  1407. xml_set_object($xml_parser, $this);
  1408. xml_set_element_handler($xml_parser, "StartElement", "EndElement");
  1409. xml_set_character_data_handler($xml_parser, "ElementContents");
  1410. $xmlParseResult = xml_parse($xml_parser, ConvertSarrogatePair( $data ), true);
  1411. if (! $xmlParseResult) {
  1412. /* ==============End of the addition */
  1413. $theMessage = sprintf("ExecuteQuery XML error: %s at line %d",
  1414. xml_error_string(xml_get_error_code($xml_parser)),
  1415. xml_get_current_line_number($xml_parser));
  1416. xml_parser_free($xml_parser);
  1417. $this->lastDebugMessage .= "<p>Unable to parse FileMaker XML. Use the DEBUG constant and try connecting with the resulting URL manually.<br />\n";
  1418. $this->lastDebugMessage .= "You should also double check the <strong>user name</strong> and <strong>password</strong> used, the <strong>server address and port</strong>, and <strong>WPE configuration</strong>.<br />\n";
  1419. $this->lastDebugMessage .= "Finally, be sure that you have specified the correct <strong>data type</strong> (e.g. FileMaker 5 or 6 versus 7 or 8.)</p>\n";
  1420. return new FX_Error($theMessage);
  1421. /* Masayuki Nii added at Oct 9, 2009 */
  1422. }
  1423. /* ==============End of the addition */
  1424. }
  1425. xml_parser_free($xml_parser);
  1426. break;
  1427. case 'openbase':
  1428. if ((defined("DEBUG") and DEBUG) or DEBUG_FUZZY) {
  1429. $currentDebugString = "<p>Accessing OpenBase data.</p>\n";
  1430. $this->lastDebugMessage .= $currentDebugString;
  1431. if (defined("DEBUG") and DEBUG) {
  1432. echo $currentDebugString;
  1433. }
  1434. }
  1435. $openBaseResult = $this->RetrieveOpenBaseData($action);
  1436. if (FX::isError($openBaseResult)) {
  1437. return $openBaseResult;
  1438. }
  1439. break;
  1440. case 'mysql':
  1441. if ((defined("DEBUG") and DEBUG) or DEBUG_FUZZY) {
  1442. $currentDebugString = "<p>Accessing MySQL data.</p>\n";
  1443. $this->lastDebugMessage .= $currentDebugString;
  1444. if (defined("DEBUG") and DEBUG) {
  1445. echo $currentDebugString;
  1446. }
  1447. }
  1448. $mySQLResult = $this->RetrieveMySQLData($action);
  1449. if (FX::isError($mySQLResult)) {
  1450. return $mySQLResult;
  1451. }
  1452. break;
  1453. case 'postgres':
  1454. if ((defined("DEBUG") and DEBUG) or DEBUG_FUZZY) {
  1455. $currentDebugString = "<p>Accessing PostgreSQL data.</p>\n";
  1456. if ($this->fuzzyKeyLogic) {
  1457. $currentDebugString .= "<p>WARNING: Fuzzy key logic is not supported for PostgreSQL.</p>\n";
  1458. }
  1459. $this->lastDebugMessage .= $currentDebugString;
  1460. if (defined("DEBUG") and DEBUG) {
  1461. echo $currentDebugString;
  1462. }
  1463. }
  1464. $postgreSQLResult = $this->RetrievePostgreSQLData($action);
  1465. if (FX::isError($postgreSQLResult)) {
  1466. return $postgreSQLResult;
  1467. }
  1468. break;
  1469. case 'odbc':
  1470. if ((defined("DEBUG") and DEBUG) or DEBUG_FUZZY) {
  1471. $currentDebugString = "<p>Accessing data via ODBC.</p>\n";
  1472. $this->lastDebugMessage .= $currentDebugString;
  1473. if (defined("DEBUG") and DEBUG) {
  1474. echo $currentDebugString;
  1475. }
  1476. }
  1477. $odbcResult = $this->RetrieveODBCData($action);
  1478. if (FX::isError($odbcResult)) {
  1479. return $odbcResult;
  1480. }
  1481. break;
  1482. case 'cafephp4pc':
  1483. if ((defined("DEBUG") and DEBUG) or DEBUG_FUZZY) {
  1484. $currentDebugString = "<p>Accessing CAFEphp data.</p>\n";
  1485. $this->lastDebugMessage .= $currentDebugString;
  1486. if (defined("DEBUG") and DEBUG) {
  1487. echo $currentDebugString;
  1488. }
  1489. }
  1490. $CAFEphpResult = $this->RetrieveCAFEphp4PCData($action);
  1491. if (FX::isError($CAFEphpResult)) {
  1492. return $CAFEphpResult;
  1493. }
  1494. break;
  1495. }
  1496. }
  1497. function BuildLinkQueryString ()
  1498. {
  1499. $tempQueryString = '';
  1500. if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'POST') {
  1501. $paramSetCount = 0;
  1502. $appendFlag = true;
  1503. foreach ($_POST as $key => $value) {
  1504. if ($appendFlag && strcasecmp($key, '-foundSetParams_begin') != 0 && strcasecmp($key, '-foundSetParams_end') != 0) {
  1505. $tempQueryString .= urlencode($key) . '=' . urlencode($value) . '&';
  1506. } elseif (strcasecmp($key, '-foundSetParams_begin') == 0) {
  1507. $appendFlag = true;
  1508. if ($paramSetCount < 1) {
  1509. $tempQueryString = '';
  1510. ++$paramSetCount;
  1511. }
  1512. } elseif (strcasecmp($key, '-foundSetParams_end') == 0) {
  1513. $appendFlag = false;
  1514. }
  1515. }
  1516. } else {
  1517. $beginTagLower = strtolower('-foundSetParams_begin');
  1518. $endTagLower = strtolower('-foundSetParams_end');
  1519. if (! isset($_SERVER['QUERY_STRING'])) {
  1520. $_SERVER['QUERY_STRING'] = '';
  1521. }
  1522. $queryStringLower = strtolower($_SERVER['QUERY_STRING']);
  1523. if (substr_count($queryStringLower, $beginTagLower) > 0 && substr_count($queryStringLower, $beginTagLower) == substr_count($queryStringLower, $endTagLower)) {
  1524. $tempOffset = 0;
  1525. for ($i = 0; $i < substr_count($queryStringLower, $beginTagLower); ++$i) {
  1526. $tempBeginFoundSetParams = strpos($queryStringLower, $beginTagLower, $tempOffset);
  1527. $tempEndFoundSetParams = strpos($queryStringLower, $endTagLower, $tempOffset) + (strlen($endTagLower) - 1);
  1528. $tempFoundSetParams = substr($_SERVER['QUERY_STRING'], $tempBeginFoundSetParams, ($tempEndFoundSetParams - $tempBeginFoundSetParams) + 1);
  1529. $tempQueryString .= preg_replace("/(?i)$beginTagLower=[^&]*&(.*)&$endTagLower/", "\$1", $tempFoundSetParams);
  1530. $tempOffset = $tempEndFoundSetParams;
  1531. }
  1532. } else {
  1533. $tempQueryString = $_SERVER['QUERY_STRING'];
  1534. }
  1535. $tempQueryString = preg_replace("/skip=[\d]*[&]?/", "", $tempQueryString);
  1536. }
  1537. return $tempQueryString;
  1538. }
  1539. function AssembleDataSet ($returnData)
  1540. {
  1541. $dataSet = array();
  1542. $FMNext = $this->currentSkip + $this->groupSize;
  1543. $FMPrevious = $this->currentSkip - $this->groupSize;
  1544. switch ($returnData) {
  1545. case 'object':
  1546. $dataSet = $this->currentData;
  1547. if ($FMNext < $this->foundCount || $FMPrevious >= 0) {
  1548. $tempQueryString = $this->BuildLinkQueryString();
  1549. } else {
  1550. $tempQueryString = '';
  1551. }
  1552. if ($FMNext >= $this->foundCount) {
  1553. $this->lastLinkNext = "";
  1554. } else {
  1555. $this->lastLinkNext = $_SERVER['SCRIPT_NAME'] . "?skip=$FMNext&{$tempQueryString}";
  1556. }
  1557. if ($FMPrevious < 0) {
  1558. $this->lastLinkPrevious = "";
  1559. } else {
  1560. $this->lastLinkPrevious = $_SERVER['SCRIPT_NAME'] . "?skip=$FMPrevious&{$tempQueryString}";
  1561. }
  1562. $this->lastFoundCount = $this->foundCount;
  1563. $this->lastFields = $this->fieldInfo;
  1564. $this->lastURL = $this->dataURL;
  1565. $this->lastQuery = $this->dataQuery;
  1566. $this->lastQueryParams = $this->dataParams;
  1567. $this->lastErrorCode = $this->fxError;
  1568. $this->lastValueLists = $this->valueLists;
  1569. if (DEBUG_FUZZY && $this->lastErrorCode != 0) {
  1570. require_once('FX_Fuzzy_Debugger.php');
  1571. $fuzzyErrorData = new FX_Fuzzy_Debugger($this);
  1572. if ($fuzzyErrorData->fuzzyOut !== false) {
  1573. $this->lastDebugMessage .= $fuzzyErrorData->fuzzyOut;
  1574. }
  1575. }
  1576. break;
  1577. case 'full':
  1578. $dataSet['data'] = $this->currentData;
  1579. case 'basic':
  1580. if ($FMNext < $this->foundCount || $FMPrevious >= 0) {
  1581. $tempQueryString = $this->BuildLinkQueryString();
  1582. } else {
  1583. $tempQueryString = '';
  1584. }
  1585. if ($FMNext >= $this->foundCount) {
  1586. $dataSet['linkNext'] = "";
  1587. } else {
  1588. $dataSet['linkNext'] = $_SERVER['SCRIPT_NAME'] . "?skip=$FMNext&{$tempQueryString}";
  1589. }
  1590. if ($FMPrevious < 0) {
  1591. $dataSet['linkPrevious'] = "";
  1592. } else {
  1593. $dataSet['linkPrevious'] = $_SERVER['SCRIPT_NAME'] . "?skip=$FMPrevious&{$tempQueryString}";
  1594. }
  1595. $dataSet['foundCount'] = $this->foundCount;
  1596. $dataSet['fields'] = $this->fieldInfo;
  1597. $dataSet['URL'] = $this->dataURL;
  1598. $dataSet['query'] = $this->dataQuery;
  1599. $dataSet['errorCode'] = $this->fxError;
  1600. $dataSet['valueLists'] = $this->valueLists;
  1601. $this->lastFoundCount = $this->foundCount;
  1602. $this->lastFields = $this->fieldInfo;
  1603. $this->lastURL = $this->dataURL;
  1604. $this->lastQuery = $this->dataQuery;
  1605. $this->lastQueryParams = $this->dataParams;
  1606. $this->lastErrorCode = $this->fxError;
  1607. $this->lastValueLists = $this->valueLists;
  1608. if (DEBUG_FUZZY && $this->lastErrorCode != 0) {
  1609. require_once('FX_Fuzzy_Debugger.php');
  1610. $fuzzyErrorData = new FX_Fuzzy_Debugger($this);
  1611. if ($fuzzyErrorData !== false) {
  1612. $this->lastDebugMessage .= $fuzzyErrorData;
  1613. }
  1614. }
  1615. break;
  1616. }
  1617. $this->ClearAllParams();
  1618. return $dataSet;
  1619. }
  1620. function FMAction ($Action, $returnDataSet, $returnData, $useInnerArray)
  1621. {
  1622. $this->useInnerArray = $useInnerArray;
  1623. $queryResult = $this->ExecuteQuery($this->actionArray[strtolower($Action)]);
  1624. if (FX::isError($queryResult)){
  1625. if (EMAIL_ERROR_MESSAGES) {
  1626. EmailErrorHandler($queryResult);
  1627. }
  1628. return $queryResult;
  1629. }
  1630. if ($returnDataSet) {
  1631. $dataSet = $this->AssembleDataSet($returnData);
  1632. return $dataSet;
  1633. } else {
  1634. $this->ClearAllParams();
  1635. return true;
  1636. }
  1637. }
  1638. // The functions above (with the exception of the FX constructor) are intened to be called from other functions within FX.php (i.e. private functions).
  1639. // The functions below are those which are intended for general use by developers (i.e. public functions).
  1640. // Once I'm quite sure that most people are using PHP5, I'll release a version using the improved object model of PHP5.
  1641. function isError($data) {
  1642. return (bool)(is_object($data) &&
  1643. (strtolower(get_class($data)) == 'fx_error' ||
  1644. is_subclass_of($data, 'fx_error')));
  1645. }
  1646. function SetCharacterEncoding ($encoding) { // This is the more general of the encoding functions (see notes below, and the functions documentation.)
  1647. $this->charSet = $encoding;
  1648. $this->dataParamsEncoding = $encoding;
  1649. // When using a different type of encoding downstream than upstream, you must call this function -- SetCharacterEncoding() --
  1650. // to set downstream encoding (the way data FROM the database is encoded) BEFORE calling SetDataParamsEncoding().
  1651. // When this function is called alone, both instance valiables are set to the same value.
  1652. // *IMPORTANT*: Using either this function or the next one is moot unless you have multi-byte support compliled into PHP (e.g. Complete PHP).
  1653. }
  1654. function SetDataParamsEncoding ($encoding) { // SetDataParamsEncoding() is used to specify the encoding of parameters sent to the database (upstream encoding.)
  1655. $this->dataParamsEncoding = $encoding;
  1656. }
  1657. function SetDBData ($database, $layout="", $groupSize=50, $responseLayout="") // the layout parameter is equivalent to the table to be used in SQL queries
  1658. {
  1659. $this->database = $database;
  1660. $this->layout = $layout;
  1661. $this->groupSize = $groupSize;
  1662. $this->responseLayout = $responseLayout;
  1663. $this->ClearAllParams();
  1664. $this->lastDebugMessage .= '<p>Configuring database connection...</p>';
  1665. }
  1666. function SetDBPassword ($DBPassword, $DBUser='FX') // Note that for historical reasons, password is the FIRST parameter for this function
  1667. {
  1668. if ($DBUser == '') {
  1669. $DBUser = 'FX';
  1670. }
  1671. $this->DBPassword = $DBPassword;
  1672. $this->DBUser = $DBUser;
  1673. $this->lastDebugMessage .= '<p>Setting user name and password...</p>';
  1674. }
  1675. function SetDBUserPass ($DBUser, $DBPassword='') // Same as above function, but paramters are in the opposite order
  1676. {
  1677. $this->SetDBPassword($DBPassword, $DBUser);
  1678. }
  1679. function SetDefaultOperator ($op)
  1680. {
  1681. $this->defaultOperator = $op;
  1682. return true;
  1683. }
  1684. function AddDBParam ($name, $value, $op="") // Add a search parameter. An operator is usually not necessary.
  1685. {
  1686. if ($this->dataParamsEncoding != '' && defined('MB_OVERLOAD_STRING')) {
  1687. $this->dataParams[]["name"] = mb_convert_encoding($name, $this->dataParamsEncoding, $this->charSet);
  1688. end($this->dataParams);
  1689. $convedValue = mb_convert_encoding($value, $this->dataParamsEncoding, $this->charSet);
  1690. /* Masayuki Nii added at Oct 10, 2009 */
  1691. if ( ! defined('SURROGATE_INPUT_PATCH_DISABLED') && $this->charSet == 'UTF-8') {
  1692. $count = 0;
  1693. for ($i=0; $i< strlen($value); $i++) {
  1694. $c = ord(substr( $value, $i, 1 ));
  1695. if ( ( $c == 0xF0 )&&( (ord(substr( $value, $i+1, 1 )) & 0xF0) == 0xA0 )) {
  1696. $i += 4; $count++;
  1697. }
  1698. }
  1699. $convedValue .= str_repeat( mb_convert_encoding(chr(0xE3).chr(0x80).chr(0x80), $this->dataParamsEncoding, 'UTF-8'), $count );
  1700. }
  1701. $this->dataParams[key($this->dataParams)]["value"] = $convedValue;
  1702. // =======================
  1703. } else {
  1704. $this->dataParams[]["name"] = $name;
  1705. end($this->dataParams);
  1706. $this->dataParams[key($this->dataParams)]["value"] = $value;
  1707. }
  1708. $this->dataParams[key($this->dataParams)]["op"] = $op;
  1709. }
  1710. function AddDBParamArray ($paramsArray, $paramOperatorsArray=array()) // Add an array of search parameters. An operator is usually not necessary.
  1711. {
  1712. foreach ($paramsArray as $key => $value) {
  1713. if (isset($paramOperatorsArray[$key]) && strlen(trim($paramOperatorsArray[$key])) > 0) {
  1714. $this->AddDBParam($key, $value, $paramOperatorsArray[$key]);
  1715. } else {
  1716. $this->AddDBParam($key, $value);
  1717. }
  1718. }
  1719. }
  1720. function SetPortalRow ($fieldsArray, $portalRowID=0, $relationshipName='')
  1721. {
  1722. foreach ($fieldsArray as $fieldName => $fieldValue) {
  1723. if (strlen(trim($relationshipName)) > 0 && substr_count($fieldName, '::') < 1) {
  1724. $this->AddDBParam("{$relationshipName}::{$fieldName}.{$portalRowID}", $fieldValue);
  1725. } else {
  1726. $this->AddDBParam("{$fieldName}.{$portalRowID}", $fieldValue);
  1727. }
  1728. }
  1729. }
  1730. function SetRecordID ($recordID)
  1731. {
  1732. if (! is_numeric($recordID) || (intval($recordID) != $recordID)) {
  1733. if ((defined("DEBUG") and DEBUG) or DEBUG_FUZZY) {
  1734. $currentDebugString = "<p>RecordIDs must be integers. Value passed was &quot;{$recordID}&quot;.</p>\n";
  1735. $this->lastDebugMessage .= $currentDebugString;
  1736. if (defined("DEBUG") and DEBUG) {
  1737. echo $currentDebugString;
  1738. }
  1739. }
  1740. }
  1741. $this->AddDBParam('-recid', $recordID);
  1742. }
  1743. function SetModID ($modID)
  1744. {
  1745. if (! is_numeric($modID) || (intval($modID) != $modID)) {
  1746. if ((defined("DEBUG") and DEBUG) or DEBUG_FUZZY) {
  1747. $currentDebugString = "<p>ModIDs must be integers. Value passed was &quot;{$modID}&quot;.</p>\n";
  1748. $this->lastDebugMessage .= $currentDebugString;
  1749. if (defined("DEBUG") and DEBUG) {
  1750. echo $currentDebugString;
  1751. }
  1752. }
  1753. }
  1754. $this->AddDBParam('-modid', $modID);
  1755. }
  1756. function SetLogicalOR ()
  1757. {
  1758. $this->AddDBParam('-lop', 'or');
  1759. }
  1760. // FileMaker 7 only
  1761. function SetFMGlobal ($globalFieldName, $globalFieldValue)
  1762. {
  1763. $this->AddDBParam("{$globalFieldName}.global", $globalFieldValue);
  1764. }
  1765. function PerformFMScript ($scriptName) // This function is only meaningful when working with FileMaker data sources
  1766. {
  1767. $this->AddDBParam('-script', $scriptName);
  1768. }
  1769. function PerformFMScriptPrefind ($scriptName) // This function is only meaningful when working with FileMaker data sources
  1770. {
  1771. $this->AddDBParam('-script.prefind', $scriptName);
  1772. }
  1773. function PerformFMScriptPresort ($scriptName) // This function is only meaningful when working with FileMaker data sources
  1774. {
  1775. $this->AddDBParam('-script.presort', $scriptName);
  1776. }
  1777. function AddSortParam ($field, $sortOrder="", $performOrder=0) // Add a sort parameter. An operator is usually not necessary.
  1778. {
  1779. if ($performOrder > 0) {
  1780. $this->sortParams[$performOrder]["field"] = $field;
  1781. $this->sortParams[$performOrder]["sortOrder"] = $sortOrder;
  1782. } else {
  1783. if (count($this->sortParams) == 0) {
  1784. $this->sortParams[1]["field"] = $field;
  1785. } else {
  1786. $this->sortParams[]["field"] = $field;
  1787. }
  1788. end($this->sortParams);
  1789. $this->sortParams[key($this->sortParams)]["sortOrder"] = $sortOrder;
  1790. }
  1791. }
  1792. function FMSkipRecords ($skipSize)
  1793. {
  1794. $this->currentSkip = $skipSize;
  1795. }
  1796. function FMPostQuery ($isPostQuery = true)
  1797. {
  1798. $this->isPostQuery = $isPostQuery;
  1799. }
  1800. function FMUseCURL ($useCURL = true)
  1801. {
  1802. $this->useCURL = $useCURL;
  1803. }
  1804. // By default, FX.php adds an extra layer to the returned array to allow for repeating fields and portals.
  1805. // When these are not present, or when accessing SQL data, this may not be desirable. FlattenInnerArray() removes this extra layer.
  1806. function FlattenInnerArray ()
  1807. {
  1808. $this->useInnerArray = false;
  1809. }
  1810. /* The actions that you can send to FileMaker start here */
  1811. function FMDBOpen ()
  1812. {
  1813. $queryResult = $this->ExecuteQuery("-dbopen");
  1814. if (FX::isError($queryResult)){
  1815. return $queryResult;
  1816. }
  1817. }
  1818. function FMDBClose ()
  1819. {
  1820. $queryResult = $this->ExecuteQuery("-dbclose");
  1821. if (FX::isError($queryResult)){
  1822. return $queryResult;
  1823. }
  1824. }
  1825. function FMDelete ($returnDataSet = false, $returnData = 'basic', $useInnerArray = true)
  1826. {
  1827. return $this->FMAction("-delete", $returnDataSet, $returnData, $useInnerArray);
  1828. }
  1829. function FMDup ($returnDataSet = true, $returnData = 'full', $useInnerArray = true)
  1830. {
  1831. return $this->FMAction("-dup", $returnDataSet, $returnData, $useInnerArray);
  1832. }
  1833. function FMEdit ($returnDataSet = true, $returnData = 'full', $useInnerArray = true)
  1834. {
  1835. return $this->FMAction("-edit", $returnDataSet, $returnData, $useInnerArray);
  1836. }
  1837. function FMFind ($returnDataSet = true, $returnData = 'full', $useInnerArray = true)
  1838. {
  1839. return $this->FMAction("-find", $returnDataSet, $returnData, $useInnerArray);
  1840. }
  1841. function FMFindAll ($returnDataSet = true, $returnData = 'full', $useInnerArray = true)
  1842. {
  1843. return $this->FMAction("-findall", $returnDataSet, $returnData, $useInnerArray);
  1844. }
  1845. function FMFindAny ($returnDataSet = true, $returnData = 'full', $useInnerArray = true)
  1846. {
  1847. return $this->FMAction("-findany", $returnDataSet, $returnData, $useInnerArray);
  1848. }
  1849. function FMNew ($returnDataSet = true, $returnData = 'full', $useInnerArray = true)
  1850. {
  1851. return $this->FMAction("-new", $returnDataSet, $returnData, $useInnerArray);
  1852. }
  1853. function FMView ($returnDataSet = true, $returnData = 'full', $useInnerArray = true)
  1854. {
  1855. return $this->FMAction("-view", $returnDataSet, $returnData, $useInnerArray);
  1856. }
  1857. function FMDBNames ($returnDataSet = true, $returnData = 'full', $useInnerArray = true)
  1858. {
  1859. return $this->FMAction("-dbnames", $returnDataSet, $returnData, $useInnerArray);
  1860. }
  1861. function FMLayoutNames ($returnDataSet = true, $returnData = 'full', $useInnerArray = true)
  1862. {
  1863. return $this->FMAction("-layoutnames", $returnDataSet, $returnData, $useInnerArray);
  1864. }
  1865. function FMScriptNames ($returnDataSet = true, $returnData = 'full', $useInnerArray = true)
  1866. {
  1867. return $this->FMAction("-scriptnames", $returnDataSet, $returnData, $useInnerArray);
  1868. }
  1869. // DoFXAction() is a general purpose action function designed to streamline FX.php code
  1870. function DoFXAction ($currentAction, $returnDataSet = true, $useInnerArray = false, $returnType = 'object')
  1871. {
  1872. return $this->FMAction($currentAction, $returnDataSet, $returnType, $useInnerArray);
  1873. }
  1874. /* The actions that you can send to FileMaker end here */
  1875. // PerformSQLQuery() is akin to the FileMaker actions above with two differences:
  1876. // 1) It is SQL specific
  1877. // 2) The SQL query passed is the sole determinant of the query performed (AddDBParam, etc. will be ignored)
  1878. function PerformSQLQuery ($SQLQuery, $returnDataSet = true, $useInnerArray = false, $returnData = 'object')
  1879. {
  1880. $this->dataQuery = $SQLQuery;
  1881. return $this->FMAction("-sqlquery", $returnDataSet, $returnData, $useInnerArray);
  1882. }
  1883. // SetDataKey() is used for SQL queries as a way to provide parity with the RecordID/ModID combo provided by FileMaker Pro
  1884. function SetDataKey ($keyField, $modifyField = '', $separator = '.')
  1885. {
  1886. $this->primaryKeyField = $keyField;
  1887. $this->modifyDateField = $modifyField;
  1888. $this->dataKeySeparator = $separator;
  1889. return true;
  1890. }
  1891. // SetSelectColumns() allows users to specify which columns should be returned by an SQL SELECT statement
  1892. function SetSelectColumns ($columnList)
  1893. {
  1894. $this->selectColsSet = true;
  1895. $this->selectColumns = $columnList;
  1896. return true;
  1897. }
  1898. // SQLFuzzyKeyLogicOn() can be used to have FX.php make it's best guess as to a viable key in an SQL DB
  1899. function SQLFuzzyKeyLogicOn ($logicSwitch = false)
  1900. {
  1901. $this->fuzzyKeyLogic = $logicSwitch;
  1902. return true;
  1903. }
  1904. // By default, FX.php uses records' keys as the indices for the returned array. UseGenericKeys() is used to change this behavior.
  1905. function UseGenericKeys ($genericKeys=true)
  1906. {
  1907. $this->genericKeys = $genericKeys;
  1908. return true;
  1909. }
  1910. }
  1911. /* Convert wrong sarrogated-pair character to light code sequence in UTF-8
  1912. * Masayuki Nii (msyk@msyk.net) Oct 9, 2009
  1913. * Refered http://www.nii.ac.jp/CAT-ILL/about/system/vista.html
  1914. */
  1915. function ConvertSarrogatePair($data) {
  1916. $altData = '';
  1917. for ($i=0; $i<strlen($data); $i++) {
  1918. $c = substr( $data, $i, 1 );
  1919. if (( ord($c) == 0xed )&&( (ord(substr( $data, $i+1, 1 )) & 0xF0) == 0xA0 )) {
  1920. for ( $j = 0; $j < 6 ; $j++ )
  1921. $utfSeq[] = ord(substr($data, $i+$j,1));
  1922. $convSeq[3] = $utfSeq[5];
  1923. $convSeq[2] = $utfSeq[4] & 0x0F | (($utfSeq[2] & 0x03) << 4) | 0x80;
  1924. $topDigit = ($utfSeq[1] & 0x0F) + 1;
  1925. $convSeq[1] = (($utfSeq[2] >> 2) & 0x0F) | (($topDigit & 0x03) << 4) | 0x80;
  1926. $convSeq[0] = (($topDigit >> 2) & 0x07) | 0xF0;
  1927. $c = chr( $convSeq[0] ).chr( $convSeq[1] ).chr( $convSeq[2] ).chr( $convSeq[3] );
  1928. $i += 5;
  1929. }
  1930. $altData .= $c;
  1931. }
  1932. return $altData;
  1933. }
  1934. ?>