/query.php

http://forceworkbench.googlecode.com/ · PHP · 995 lines · 789 code · 178 blank · 28 comment · 304 complexity · 9a3394069275ec52be65185dc62e95df MD5 · raw file

  1. <?php
  2. require_once 'soxl/QueryObjects.php';
  3. require_once 'session.php';
  4. require_once 'shared.php';
  5. $defaultSettings['numFilters'] = 1;
  6. //clear the form if the user changes the object
  7. if (isset($_POST['justUpdate']) && $_POST['justUpdate'] == true) {
  8. $queryRequest = new QueryRequest($defaultSettings);
  9. $queryRequest->setObject($_POST['QB_object_sel']);
  10. } else {
  11. //create a new QueryRequest object to save named and/or last query
  12. $lastQr = new QueryRequest($_REQUEST);
  13. //save last query. always do this even if named.
  14. if ((isset($_POST['querySubmit']) && $_POST['querySubmit']=='Query') || (isset($_POST['doSaveQr']) && $_POST['doSaveQr'] == 'Save' )) {
  15. $_SESSION['lastQueryRequest'] = $lastQr;
  16. }
  17. $persistedSavedQueryRequestsKey = "PSQR@";
  18. if (getConfig("savedQueriesAndSearchesPersistanceLevel") == 'USER') {
  19. $persistedSavedQueryRequestsKey .= WorkbenchContext::get()->getUserInfo()->userId . "@" . WorkbenchContext::get()->getUserInfo()->organizationId;
  20. } else if (getConfig("savedQueriesAndSearchesPersistanceLevel") == "ORG") {
  21. $persistedSavedQueryRequestsKey .= WorkbenchContext::get()->getUserInfo()->organizationId;
  22. } else if (getConfig("savedQueriesAndSearchesPersistanceLevel") == 'ALL') {
  23. $persistedSavedQueryRequestsKey .= "ALL";
  24. }
  25. //populate queryRequest for this page view. first see if user wants to retreive a saved query,
  26. //then see if there was a last query, else just show a null query with default object.
  27. if (isset($_REQUEST['getQr']) && $_REQUEST['getQr'] != "" && isset($_SESSION['savedQueryRequests'][$_REQUEST['getQr']])) {
  28. $queryRequest = $_SESSION['savedQueryRequests'][$_REQUEST['getQr']];
  29. $_POST['querySubmit'] = 'Query'; //simulate the user clicking 'Query' to run immediately
  30. } else if (isset($_SESSION['lastQueryRequest'])) {
  31. $queryRequest = $_SESSION['lastQueryRequest'];
  32. } else {
  33. $queryRequest = new QueryRequest($defaultSettings);
  34. $queryRequest->setObject(WorkbenchContext::get()->getDefaultObject());
  35. if (getConfig("savedQueriesAndSearchesPersistanceLevel") != 'NONE' && !isset($_SESSION['savedQueryRequests']) && isset($_COOKIE[$persistedSavedQueryRequestsKey])) {
  36. $_SESSION['savedQueryRequests'] = unserialize($_COOKIE[$persistedSavedQueryRequestsKey]);
  37. }
  38. }
  39. //clear all saved queries in scope if user requests
  40. if (isset($_POST['clearAllQr']) && $_POST['clearAllQr'] == 'Clear All') {
  41. $_SESSION['savedQueryRequests'] = null;
  42. if (getConfig("savedQueriesAndSearchesPersistanceLevel") != 'NONE') {
  43. setcookie($persistedSavedQueryRequestsKey,null,time()-3600);
  44. }
  45. }
  46. //save as named query
  47. if (isset($_POST['doSaveQr']) && $_POST['doSaveQr'] == 'Save' && isset($_REQUEST['saveQr']) && strlen($_REQUEST['saveQr']) > 0) {
  48. $_SESSION['savedQueryRequests'][htmlspecialchars($_REQUEST['saveQr'],ENT_QUOTES)] = $lastQr;
  49. if (getConfig("savedQueriesAndSearchesPersistanceLevel") != 'NONE') {
  50. setcookie($persistedSavedQueryRequestsKey,serialize($_SESSION['savedQueryRequests']),time()+60*60*24*7);
  51. }
  52. }
  53. }
  54. //Main form logic: When the user first enters the page, display form defaulted to
  55. //show the query results with default object selected on a previous page, otherwise
  56. // just display the blank form. When the user selects the SCREEN or CSV options, the
  57. //query is processed by the correct function
  58. if (isset($_POST['queryMore']) && isset($_SESSION['queryLocator'])) {
  59. require_once 'header.php';
  60. // $queryRequest->setExportTo('screen');
  61. displayQueryForm($queryRequest);
  62. $queryTimeStart = microtime(true);
  63. $records = query(null,'QueryMore',$_SESSION['queryLocator']);
  64. $queryTimeEnd = microtime(true);
  65. $queryTimeElapsed = $queryTimeEnd - $queryTimeStart;
  66. displayQueryResults($records,$queryTimeElapsed,$queryRequest);
  67. include_once 'footer.php';
  68. } else if (isset($_POST['querySubmit']) && $_POST['querySubmit']=='Query' && $queryRequest->getSoqlQuery() != null && ($queryRequest->getExportTo() == 'screen' || $queryRequest->getExportTo() == 'matrix')) {
  69. require_once 'header.php';
  70. displayQueryForm($queryRequest);
  71. if ($queryRequest->getExportTo() == 'matrix' && ($queryRequest->getMatrixCols() == "" || $queryRequest->getMatrixRows() == "")) {
  72. displayWarning("Both column and row must be specified for Matrix view.", false, true);
  73. return;
  74. }
  75. $queryTimeStart = microtime(true);
  76. $records = query($queryRequest->getSoqlQuery(),$queryRequest->getQueryAction());
  77. $queryTimeEnd = microtime(true);
  78. $queryTimeElapsed = $queryTimeEnd - $queryTimeStart;
  79. displayQueryResults($records,$queryTimeElapsed,$queryRequest);
  80. include_once 'footer.php';
  81. } else if (isset($_POST['querySubmit']) && $_POST['querySubmit']=='Query' && $queryRequest->getSoqlQuery() != null && strpos($queryRequest->getExportTo(), 'async_') === 0) {
  82. queryAsync($queryRequest);
  83. } else if (isset($_POST['querySubmit']) && $_POST['querySubmit']=='Query' && $queryRequest->getSoqlQuery() != null && $queryRequest->getExportTo() == 'csv') {
  84. if (!substr_count($_POST['soql_query'],"count()")) {
  85. $records = query($queryRequest->getSoqlQuery(),$queryRequest->getQueryAction(),null,true);
  86. exportQueryAsCsv($records,$queryRequest->getExportTo());
  87. } else {
  88. require_once 'header.php';
  89. displayQueryForm($queryRequest);
  90. print "</form>"; //could include inside because if IE page loading bug
  91. print "<p>&nbsp;</p>";
  92. displayError("count() is not supported for CSV. View as List or choose fields and try again.");
  93. include_once 'footer.php';
  94. }
  95. } else {
  96. require_once 'header.php';
  97. if ($queryRequest->getExportTo() == null) $queryRequest->setExportTo('screen');
  98. $queryRequest->setQueryAction('Query');
  99. displayQueryForm($queryRequest);
  100. print "</form>"; //could include inside because if IE page loading bug
  101. include_once 'footer.php';
  102. }
  103. //Show the main SOQL query form with default query or last submitted query and export action (screen or CSV)
  104. function displayQueryForm($queryRequest) {
  105. registerShortcut("Ctrl+Alt+W",
  106. "addFilterRow(document.getElementById('numFilters').value++);".
  107. "toggleFieldDisabled();");
  108. if ($queryRequest->getObject()) {;
  109. $describeSObjectResult = WorkbenchContext::get()->describeSObjects($queryRequest->getObject());
  110. $fieldValuesToLabels = array();
  111. foreach ($describeSObjectResult->fields as $field) {
  112. $fieldValuesToLabels[$field->name] = $field->name;
  113. }
  114. } else {
  115. displayInfo('First choose an object to use the SOQL builder wizard.');
  116. }
  117. print "<script>\n";
  118. print "var field_type_array = new Array();\n";
  119. if (isset($describeSObjectResult)) {
  120. foreach ($describeSObjectResult->fields as $fields => $field) {
  121. print " field_type_array[\"$field->name\"]=[\"$field->type\"];\n";
  122. }
  123. }
  124. $ops = array(
  125. '=' => '=',
  126. '!=' => '&ne;',
  127. '<' => '&lt;',
  128. '<=' => '&le;',
  129. '>' => '&gt;',
  130. '>=' => '&ge;',
  131. 'starts' => 'starts with',
  132. 'ends' => 'ends with',
  133. 'contains' => 'contains',
  134. 'IN' => 'in',
  135. 'NOT IN' => 'not in',
  136. 'INCLUDES' => 'includes',
  137. 'EXCLUDES' => 'excludes'
  138. );
  139. print "var compOper_array = new Array();\n";
  140. foreach ($ops as $opValue => $opLabel) {
  141. print " compOper_array[\"$opValue\"]=[\"$opLabel\"];\n";
  142. }
  143. print <<<QUERY_BUILDER_SCRIPT
  144. function parentChildRelationshipQueryBlocker() {
  145. var soql = document.getElementById('soql_query_textarea').value.toUpperCase();
  146. if (soql.indexOf('(SELECT') != -1 && soql.indexOf('IN (SELECT') == -1 && document.getElementById('export_action_csv').checked) {
  147. return confirm ("Export of parent-to-child relationship queries to CSV are not yet supported by Workbench and may give unexpected results. Are you sure you wish to continue?");
  148. }
  149. }
  150. function doesQueryHaveName() {
  151. var saveQr = document.getElementById('saveQr');
  152. if (saveQr.value == null || saveQr.value.length == 0) {
  153. alert('Query must have a name to save.');
  154. return false;
  155. }
  156. return true;
  157. }
  158. function toggleFieldDisabled() {
  159. var QB_field_sel = document.getElementById('QB_field_sel');
  160. if (document.getElementById('QB_object_sel').value) {
  161. QB_field_sel.disabled = false;
  162. } else {
  163. QB_field_sel.disabled = true;
  164. }
  165. var isFieldSelected = false;
  166. for (var i = 0; i < QB_field_sel.options.length; i++)
  167. if (QB_field_sel.options[i].selected)
  168. isFieldSelected = true;
  169. if (isFieldSelected || (document.getElementById('matrix_rows').value != '' && document.getElementById('matrix_cols').value != '')) {
  170. document.getElementById('QB_orderby_field').disabled = false;
  171. document.getElementById('QB_orderby_sort').disabled = false;
  172. document.getElementById('QB_nulls').disabled = false;
  173. document.getElementById('QB_limit_txt').disabled = false;
  174. document.getElementById('QB_filter_field_0').disabled = false;
  175. if (document.getElementById('QB_filter_field_0').value) {
  176. document.getElementById('QB_filter_value_0').disabled = false;
  177. document.getElementById('QB_filter_compOper_0').disabled = false;
  178. } else {
  179. document.getElementById('QB_filter_value_0').disabled = true;
  180. document.getElementById('QB_filter_compOper_0').disabled = true;
  181. }
  182. } else {
  183. document.getElementById('QB_filter_field_0').disabled = true;
  184. document.getElementById('QB_filter_compOper_0').disabled = true;
  185. document.getElementById('QB_filter_value_0').disabled = true;
  186. document.getElementById('QB_orderby_field').disabled = true;
  187. document.getElementById('QB_orderby_sort').disabled = true;
  188. document.getElementById('QB_nulls').disabled = true;
  189. document.getElementById('QB_limit_txt').disabled = true;
  190. }
  191. var allPreviousRowsUsed = true;
  192. for (var r = 1; r < document.getElementById('numFilters').value; r++) {
  193. var lastRow = r-1;
  194. var thisRow = r;
  195. if (isFieldSelected && allPreviousRowsUsed && document.getElementById('QB_filter_field_' + lastRow).value && document.getElementById('QB_filter_compOper_' + lastRow).value && document.getElementById('QB_filter_value_' + lastRow).value) {
  196. document.getElementById('QB_filter_field_' + thisRow).disabled = false;
  197. if (document.getElementById('QB_filter_field_' + thisRow).value) {
  198. document.getElementById('QB_filter_value_' + thisRow).disabled = false;
  199. document.getElementById('QB_filter_compOper_' + thisRow).disabled = false;
  200. } else {
  201. document.getElementById('QB_filter_value_' + thisRow).disabled = true;
  202. document.getElementById('QB_filter_compOper_' + thisRow).disabled = true;
  203. }
  204. } else {
  205. allPreviousRowsUsed = false;
  206. document.getElementById('QB_filter_field_' + thisRow).disabled = true;
  207. document.getElementById('QB_filter_compOper_' + thisRow).disabled = true;
  208. document.getElementById('QB_filter_value_' + thisRow).disabled = true;
  209. }
  210. }
  211. }
  212. function updateObject() {
  213. document.query_form.justUpdate.value = 1;
  214. document.query_form.submit();
  215. }
  216. function exportActionIs(type) {
  217. var exportActions = document.getElementById('query_form')['export_action'];
  218. for (var i = 0; i < exportActions.length; i++) {
  219. if (exportActions[i].checked && exportActions[i].value == type) {
  220. return true;
  221. }
  222. }
  223. return false;
  224. }
  225. function arrayContains(haystack, needle) {
  226. for (i in haystack) {
  227. if (haystack[i] == needle) {
  228. return true;
  229. }
  230. }
  231. return false;
  232. }
  233. function buildQuery() {
  234. toggleFieldDisabled();
  235. var QB_object_sel = document.getElementById('QB_object_sel').value;
  236. var QB_field_sel = document.getElementById('QB_field_sel');
  237. QB_fields_selected = new Array();
  238. for (var i = 0; i < QB_field_sel.options.length; i++) {
  239. if (QB_field_sel.options[i].selected) {
  240. QB_fields_selected.push(QB_field_sel.options[i].value);
  241. }
  242. }
  243. if (exportActionIs('matrix')) {
  244. var matrix_cols = document.getElementById('matrix_cols');
  245. var matrix_rows = document.getElementById('matrix_rows');
  246. if (matrix_cols.value != '' && matrix_rows.value != '') {
  247. if (!arrayContains(QB_fields_selected, matrix_cols.value)) QB_fields_selected.push(matrix_cols.value);
  248. if (!arrayContains(QB_fields_selected, matrix_rows.value)) QB_fields_selected.push(matrix_rows.value);
  249. }
  250. }
  251. var soql_select = '';
  252. if (QB_fields_selected.toString().indexOf('count()') != -1 && QB_fields_selected.length > 1) {
  253. alert('Warning: Choosing count() with other fields will result in a malformed query. Unselect either count() or the other fields to continue.');
  254. } else if (QB_fields_selected.length > 0) {
  255. var soql_select = 'SELECT ' + QB_fields_selected + ' FROM ' + QB_object_sel;
  256. }
  257. soql_where = '';
  258. for (var f = 0; f < document.getElementById('numFilters').value; f++) {
  259. var QB_filter_field = document.getElementById('QB_filter_field_' + f).value;
  260. var QB_filter_compOper = document.getElementById('QB_filter_compOper_' + f).value;
  261. var QB_filter_value = document.getElementById('QB_filter_value_' + f).value;
  262. var soql_where_logicOper = '';
  263. if (f > 0) {
  264. soql_where_logicOper = ' AND ';
  265. }
  266. if (QB_filter_field && QB_filter_compOper && QB_filter_value) {
  267. if (QB_filter_compOper == 'starts') {
  268. QB_filter_compOper = 'LIKE'
  269. QB_filter_value = QB_filter_value + '%';
  270. } else if (QB_filter_compOper == 'ends') {
  271. QB_filter_compOper = 'LIKE'
  272. QB_filter_value = '%' + QB_filter_value;
  273. } else if (QB_filter_compOper == 'contains') {
  274. QB_filter_compOper = 'LIKE'
  275. QB_filter_value = '%' + QB_filter_value + '%';
  276. }
  277. if (QB_filter_compOper == 'IN' ||
  278. QB_filter_compOper == 'NOT IN' ||
  279. QB_filter_compOper == 'INCLUDES' ||
  280. QB_filter_compOper == 'EXCLUDES') {
  281. QB_filter_value_q = '(' + QB_filter_value + ')';
  282. } else if ((QB_filter_value == 'null') ||
  283. (field_type_array[QB_filter_field] == "datetime") ||
  284. (field_type_array[QB_filter_field] == "date") ||
  285. (field_type_array[QB_filter_field] == "currency") ||
  286. (field_type_array[QB_filter_field] == "percent") ||
  287. (field_type_array[QB_filter_field] == "double") ||
  288. (field_type_array[QB_filter_field] == "int") ||
  289. (field_type_array[QB_filter_field] == "boolean")) {
  290. QB_filter_value_q = QB_filter_value;
  291. } else {
  292. QB_filter_value_q = '\'' + QB_filter_value + '\'';
  293. }
  294. soql_where += soql_where_logicOper + QB_filter_field + ' ' + QB_filter_compOper + ' ' + QB_filter_value_q;
  295. } else {
  296. break;
  297. }
  298. }
  299. soql_where = soql_where != '' ? ' WHERE ' + soql_where : '';
  300. var QB_orderby_field = document.getElementById('QB_orderby_field').value;
  301. var QB_orderby_sort = document.getElementById('QB_orderby_sort').value;
  302. var QB_nulls = document.getElementById('QB_nulls').value;
  303. if (QB_orderby_field) {
  304. var soql_orderby = ' ORDER BY ' + QB_orderby_field + ' ' + QB_orderby_sort;
  305. if (QB_nulls)
  306. soql_orderby = soql_orderby + ' NULLS ' + QB_nulls;
  307. } else
  308. var soql_orderby = '';
  309. var QB_limit_txt = document.getElementById('QB_limit_txt').value;
  310. if (QB_limit_txt)
  311. var soql_limit = ' LIMIT ' + QB_limit_txt;
  312. else
  313. var soql_limit = '';
  314. if (soql_select)
  315. document.getElementById('soql_query_textarea').value = soql_select + soql_where + soql_orderby + soql_limit ;
  316. }
  317. function addFilterRow(filterRowNum, defaultField, defaultCompOper, defaultValue) {
  318. //build the row inner html
  319. var row = filterRowNum == 0 ? "<br/>Filter results by:<br/>" : "" ;
  320. row += "<select id='QB_filter_field_" + filterRowNum + "' name='QB_filter_field_" + filterRowNum + "' style='width: 16em;' onChange='buildQuery();' onkeyup='buildQuery();'>" +
  321. "<option value=''></option>";
  322. for (var field in field_type_array) {
  323. row += "<option value='" + field + "'";
  324. if (defaultField == field) row += " selected='selected' ";
  325. row += "'>" + field + "</option>";
  326. }
  327. row += "</select>&nbsp;" +
  328. "" +
  329. "<select id='QB_filter_compOper_" + filterRowNum + "' name='QB_filter_compOper_" + filterRowNum + "' style='width: 6em;' onChange='buildQuery();' onkeyup='buildQuery();'>";
  330. for (var opKey in compOper_array) {
  331. row += "<option value='" + opKey + "'";
  332. if (defaultCompOper == opKey) row += " selected='selected' ";
  333. row += ">" + compOper_array[opKey] + "</option>";
  334. }
  335. defaultValue = defaultValue != null ? defaultValue : "";
  336. row += "</select>&nbsp;" +
  337. "<input type='text' id='QB_filter_value_" + filterRowNum + "' size='31' name='QB_filter_value_" + filterRowNum + "' value='" + defaultValue + "' onkeyup='buildQuery();' />";
  338. //add to the DOM
  339. var newFilterCell = document.createElement('td');
  340. newFilterCell.setAttribute('colSpan','4');
  341. newFilterCell.setAttribute('vAlign','top');
  342. newFilterCell.setAttribute('nowrap','true');
  343. newFilterCell.innerHTML = row;
  344. var newPlusCell = document.createElement('td');
  345. newPlusCell.setAttribute('id','filter_plus_cell_' + filterRowNum);
  346. newPlusCell.setAttribute('vAlign','bottom');
  347. newPlusCell.innerHTML = "<img id='filter_plus_button' src='" + WORKBENCH_STATIC_RESOURCES_PATH + "/images/plus_icon.jpg' onclick='addFilterRow(document.getElementById(\"numFilters\").value++);toggleFieldDisabled();' onmouseover='this.style.cursor=\"pointer\";' style='padding-top: 4px;'/>";
  348. var newFilterRow = document.createElement('tr');
  349. newFilterRow.setAttribute('id','filter_row_' + filterRowNum);
  350. newFilterRow.appendChild(newFilterCell);
  351. newFilterRow.appendChild(newPlusCell);
  352. document.getElementById('QB_right_sub_table').getElementsByTagName("TBODY").item(0).appendChild(newFilterRow);
  353. if (filterRowNum > 0) {
  354. var filter_plus_button = document.getElementById('filter_plus_button');
  355. filter_plus_button.parentNode.removeChild(filter_plus_button);
  356. }
  357. //expand the field list so it looks right
  358. document.getElementById('QB_field_sel').size += 2;
  359. }
  360. function toggleMatrixSortSelectors(hasChanged) {
  361. if (exportActionIs('matrix')) {
  362. document.getElementById('matrix_selection_headers').style.display = '';
  363. document.getElementById('matrix_selection_row').style.display = '';
  364. document.getElementById('QB_field_sel').size += 4;
  365. if(hasChanged) buildQuery();
  366. } else if (document.getElementById('matrix_selection_headers').style.display == '') {
  367. document.getElementById('matrix_selection_headers').style.display = 'none';
  368. document.getElementById('matrix_selection_row').style.display = 'none';
  369. document.getElementById('QB_field_sel').size -= 4;
  370. if(hasChanged) buildQuery();
  371. }
  372. //don't do anything if moving from screen to csv
  373. }
  374. </script>
  375. QUERY_BUILDER_SCRIPT;
  376. if (getConfig("autoJumpToResults")) {
  377. print "<form method='POST' id='query_form' name='query_form' action='#qr'>\n";
  378. } else {
  379. print "<form method='POST' id='query_form' name='query_form' action=''>\n";
  380. }
  381. print "<input type='hidden' name='justUpdate' value='0' />";
  382. print "<input type='hidden' id='numFilters' name='numFilters' value='" . count($queryRequest->getFilters()) ."' />";
  383. print "<p class='instructions'>Choose the object, fields, and critera to build a SOQL query below:</p>\n";
  384. print "<table border='0' style='width: 100%;'>\n";
  385. print "<tr><td valign='top' width='1'>Object:";
  386. printObjectSelection($queryRequest->getObject(), 'QB_object_sel', "16", "onChange='updateObject();'", "queryable");
  387. print "<p/>Fields:<select id='QB_field_sel' name='QB_field_sel[]' multiple='mutliple' size='4' style='width: 16em;' onChange='buildQuery();'>\n";
  388. if (isset($describeSObjectResult)) {
  389. print " <option value='count()'";
  390. if ($queryRequest->getFields() != null) { //check to make sure something is selected; otherwise warnings will display
  391. foreach ($queryRequest->getFields() as $selectedField) {
  392. if ('count()' == $selectedField) print " selected='selected' ";
  393. }
  394. }
  395. print ">count()</option>\n";
  396. //print ">$field->name</option>\n";
  397. foreach ($describeSObjectResult->fields as $fields => $field) {
  398. print " <option value='$field->name'";
  399. if ($queryRequest->getFields() != null) { //check to make sure something is selected; otherwise warnings will display
  400. foreach ($queryRequest->getFields() as $selectedField) {
  401. if ($field->name == $selectedField) print " selected='selected' ";
  402. }
  403. }
  404. print ">$field->name</option>\n";
  405. }
  406. }
  407. print "</select></td>\n";
  408. print "<td valign='top'>";
  409. print "<table border='0' align='right' style='width:100%'>\n";
  410. print "<tr><td valign='top' colspan=2>View as:<br/>" .
  411. "<label><input type='radio' id='export_action_screen' name='export_action' value='screen' ";
  412. if ($queryRequest->getExportTo() == 'screen') print "checked='true'";
  413. print " onClick='toggleMatrixSortSelectors(true);'>List</label>&nbsp;";
  414. print "<label><input type='radio' id='export_action_matrix' name='export_action' value='matrix' ";
  415. if ($queryRequest->getExportTo() == 'matrix') print "checked='true'";
  416. print " onClick='toggleMatrixSortSelectors(true);'>Matrix</label>";
  417. if (getConfig("allowQueryCsvExport")) {
  418. print "<label><input type='radio' id='export_action_csv' name='export_action' value='csv' ";
  419. if ($queryRequest->getExportTo() == 'csv') print "checked='true'";
  420. print " onClick='toggleMatrixSortSelectors(true);'>CSV</label>&nbsp;";
  421. }
  422. print "<label><input type='radio' id='export_action_async_csv' name='export_action' value='async_CSV' ";
  423. if ($queryRequest->getExportTo() == 'async_CSV') print "checked='true'";
  424. print " onClick='toggleMatrixSortSelectors(true);'>Bulk CSV</label>&nbsp;";
  425. print "<label><input type='radio' id='export_action_async_xml' name='export_action' value='async_XML' ";
  426. if ($queryRequest->getExportTo() == 'async_XML') print "checked='true'";
  427. print " onClick='toggleMatrixSortSelectors(true);'>Bulk XML</label>&nbsp;";
  428. print "<td valign='top' colspan=2>Deleted and archived records:<br/>" .
  429. "<label><input type='radio' name='query_action' value='Query' ";
  430. if ($queryRequest->getQueryAction() == 'Query') print "checked='true'";
  431. print " >Exclude</label>&nbsp;";
  432. print "<label><input type='radio' name='query_action' value='QueryAll' ";
  433. if ($queryRequest->getQueryAction() == 'QueryAll') print "checked='true'";
  434. print " >Include</label></td></tr></table>\n";
  435. print "<table id='QB_right_sub_table' border='0' align='right' style='width:100%'>\n";
  436. print "<tr id='matrix_selection_headers' style='display: none;'><td><br/>Columns:</td> <td><br/>Rows:</td> <td>&nbsp;</td></tr>\n";
  437. print "<tr id='matrix_selection_row' style='display: none;'><td><select id='matrix_cols' name='matrix_cols' style='width: 15em;' onChange='toggleFieldDisabled();buildQuery();' onkeyup='toggleFieldDisabled();buildQuery();'>";
  438. if(isset($fieldValuesToLabels)) printSelectOptions(array_merge(array(""=>""),$fieldValuesToLabels), $queryRequest->getMatrixCols());
  439. print "</select></td> <td><select id='matrix_rows' name='matrix_rows' style='width: 15em;' onChange='toggleFieldDisabled();buildQuery();' onkeyup='toggleFieldDisabled();buildQuery();'>";
  440. if(isset($fieldValuesToLabels)) printSelectOptions(array_merge(array(""=>""),$fieldValuesToLabels), $queryRequest->getMatrixRows());
  441. print "</select></td> <td><img onmouseover=\"Tip('Matrix view groups records into columns and rows of common field values.')\" align='absmiddle' src='" . getStaticResourcesPath() ."/images/help16.png'/></td></tr>\n";
  442. print "<tr id='sort_selection_headers'><td colspan='2'><br/>Sort results by:</td> <td><br/>Max Records:</td></tr>\n";
  443. print "<tr id='sort_selection_row'>";
  444. print "<td colspan='2'><select id='QB_orderby_field' name='QB_orderby_field' style='width: 16em;' onChange='buildQuery();'>\n";
  445. print "<option value=''></option>\n";
  446. if (isset($describeSObjectResult)) {
  447. foreach ($describeSObjectResult->fields as $fields => $field) {
  448. print " <option value='$field->name'";
  449. if ($queryRequest->getOrderByField() != null && $field->name == $queryRequest->getOrderByField()) print " selected='selected' ";
  450. print ">$field->name</option>\n";
  451. }
  452. }
  453. print "</select>\n";
  454. $qBOrderbySortOptions = array(
  455. 'ASC' => 'A to Z',
  456. 'DESC' => 'Z to A'
  457. );
  458. print "<select id='QB_orderby_sort' name='QB_orderby_sort' style='width: 6em;' onChange='buildQuery();' onkeyup='buildQuery();'>\n";
  459. foreach ($qBOrderbySortOptions as $opKey => $op) {
  460. print "<option value='$opKey'";
  461. if (isset($_POST['QB_orderby_sort']) && $opKey == $_POST['QB_orderby_sort']) print " selected='selected' ";
  462. print ">$op</option>\n";
  463. }
  464. print "</select>\n";
  465. $qBNullsOptions = array(
  466. 'FIRST' => 'Nulls First',
  467. 'LAST' => 'Nulls Last'
  468. );
  469. print "<select id='QB_nulls' name='QB_nulls' style='width: 10em;' onChange='buildQuery();' onkeyup='buildQuery();'>\n";
  470. foreach ($qBNullsOptions as $opKey => $op) {
  471. print "<option value='$opKey'";
  472. if ($queryRequest->getOrderByNulls() != null && $opKey == $queryRequest->getOrderByNulls()) print " selected='selected' ";
  473. print ">$op</option>\n";
  474. }
  475. print "</select></td>\n";
  476. print "<td><input type='text' id='QB_limit_txt' size='10' name='QB_limit_txt' value='" . htmlspecialchars($queryRequest->getLimit() != null ? $queryRequest->getLimit() : null,ENT_QUOTES) . "' onkeyup='buildQuery();' /></td>\n";
  477. print "</tr>\n";
  478. print "</table>\n";
  479. print "</td></tr>\n";
  480. $filterRowNum = 0;
  481. foreach ($queryRequest->getFilters() as $filter) {
  482. print "<script>addFilterRow(" .
  483. $filterRowNum++ . ", " .
  484. "\"" . $filter->getField() . "\", " .
  485. "\"" . $filter->getCompOper() . "\", " .
  486. "\"" . htmlspecialchars($filter->getValue(), ENT_QUOTES) . "\"" .
  487. ");</script>";
  488. }
  489. print "<tr><td valign='top' colspan=5><br/>Enter or modify a SOQL query below:\n" .
  490. "<br/><textarea id='soql_query_textarea' type='text' name='soql_query' rows='" . getConfig("textareaRows") . "' style='width: 99%; overflow: auto; font-family: monospace, courier;'>" . htmlspecialchars($queryRequest->getSoqlQuery(),ENT_QUOTES) . "</textarea>\n" .
  491. "</td></tr>\n";
  492. print "<tr><td colspan=1><input type='submit' name='querySubmit' value='Query' onclick='return parentChildRelationshipQueryBlocker();' />\n" .
  493. "<input type='reset' value='Reset' />\n" .
  494. "</td>";
  495. //save and retrieve named queries
  496. print "<td colspan=4 align='right'>";
  497. print "&nbsp;Run: " .
  498. "<select name='getQr' style='width: 10em;' onChange='document.query_form.submit();'>" .
  499. "<option value='' selected='selected'></option>";
  500. if (isset($_SESSION['savedQueryRequests'])) {
  501. foreach ($_SESSION['savedQueryRequests'] as $qrName => $qr) {
  502. if($qrName != null) print "<option value='$qrName'>$qrName</option>";
  503. }
  504. }
  505. print "</select>";
  506. print "&nbsp;&nbsp;Save as: <input type='text' id='saveQr' name='saveQr' value='" . htmlspecialchars($queryRequest->getName(),ENT_QUOTES) . "' style='width: 10em;'/>\n";
  507. print "<input type='submit' name='doSaveQr' value='Save' onclick='return doesQueryHaveName();' />\n";
  508. print "<input type='submit' name='clearAllQr' value='Clear All'/>\n";
  509. print "&nbsp;&nbsp;" .
  510. "<img onmouseover=\"Tip('Save a query with a name and run it at a later time during your session. Note, if a query is already saved with the same name, the previous one will be overwritten.')\" align='absmiddle' src='" . getStaticResourcesPath() ."/images/help16.png'/>";
  511. print "</td></tr></table><p/>\n";
  512. print "<script>toggleFieldDisabled();toggleMatrixSortSelectors(false);</script>";
  513. }
  514. function query($soqlQuery,$queryAction,$queryLocator = null,$suppressScreenOutput=false) {
  515. try {
  516. if (!getConfig("allowParentRelationshipQueries") && preg_match("/SELECT.*?(\w+\.\w+).*FROM/i", $soqlQuery, $matches)) {
  517. throw new WorkbenchHandledException("Parent relationship queries are not allowed: " . $matches[1]);
  518. }
  519. if ($queryAction == 'Query') $queryResponse = WorkbenchContext::get()->getPartnerConnection()->query($soqlQuery);
  520. if ($queryAction == 'QueryAll') $queryResponse = WorkbenchContext::get()->getPartnerConnection()->queryAll($soqlQuery);
  521. if ($queryAction == 'QueryMore' && isset($queryLocator)) $queryResponse = WorkbenchContext::get()->getPartnerConnection()->queryMore($queryLocator);
  522. if (substr_count($soqlQuery,"count()") && $suppressScreenOutput == false) {
  523. $countString = "Query would return " . $queryResponse->size . " record";
  524. $countString .= ($queryResponse->size == 1) ? "." : "s.";
  525. displayInfo($countString);
  526. $records = $queryResponse->size;
  527. include_once 'footer.php';
  528. exit;
  529. }
  530. if (isset($queryResponse->records)) {
  531. $records = $queryResponse->records;
  532. } else {
  533. $records = null;
  534. }
  535. $_SESSION['totalQuerySize'] = $queryResponse->size;
  536. if (!$queryResponse->done) {
  537. $_SESSION['queryLocator'] = $queryResponse->queryLocator;
  538. } else {
  539. $_SESSION['queryLocator'] = null;
  540. }
  541. //correction for documents and attachments with body. issue #176
  542. if ($queryResponse->size > 0 && !is_array($records)) {
  543. $records = array($records);
  544. }
  545. $memLimitBytes = toBytes(ini_get("memory_limit"));
  546. $memWarningThreshold = getConfig("memoryUsageWarningThreshold") / 100;
  547. while(($suppressScreenOutput || getConfig("autoRunQueryMore")) && !$queryResponse->done) {
  548. if ($memLimitBytes != 0 && (memory_get_usage() / $memLimitBytes > $memWarningThreshold)) {
  549. displayError("Workbench almost exhausted all its memory after only processing " . count($records) . " rows of data.
  550. When performing a large queries, it is recommended to export as Bulk CSV or Bulk XML.",
  551. $suppressScreenOutput, true);
  552. return; // bail out
  553. }
  554. $queryResponse = WorkbenchContext::get()->getPartnerConnection()->queryMore($queryResponse->queryLocator);
  555. if (!is_array($queryResponse->records)) {
  556. $queryResponse->records = array($queryResponse->records);
  557. }
  558. $records = array_merge($records, $queryResponse->records); //todo: do memory check here
  559. }
  560. return $records;
  561. } catch (Exception $e) {
  562. print "<p><a name='qr'>&nbsp;</a></p>";
  563. displayError($e->getMessage(),true,true);
  564. }
  565. }
  566. function getQueryResultHeaders($sobject, $tail="") {
  567. if (!isset($headerBufferArray)) {
  568. $headerBufferArray = array();
  569. }
  570. if (isset($sobject->Id) && !isset($sobject->fields->Id)) {
  571. $headerBufferArray[] = $tail . "Id";
  572. }
  573. if (isset($sobject->fields)) {
  574. foreach ($sobject->fields->children() as $field) {
  575. $headerBufferArray[] = $tail . htmlspecialchars($field->getName(),ENT_QUOTES);
  576. }
  577. }
  578. if (isset($sobject->sobjects)) {
  579. foreach ($sobject->sobjects as $sobjects) {
  580. $recurse = getQueryResultHeaders($sobjects, $tail . htmlspecialchars($sobjects->type,ENT_QUOTES) . ".");
  581. $headerBufferArray = array_merge($headerBufferArray, $recurse);
  582. }
  583. }
  584. if (isset($sobject->queryResult)) {
  585. if(!is_array($sobject->queryResult)) $sobject->queryResult = array($sobject->queryResult);
  586. foreach ($sobject->queryResult as $qr) {
  587. $headerBufferArray[] = $qr->records[0]->type;
  588. }
  589. }
  590. return $headerBufferArray;
  591. }
  592. function getQueryResultRow($sobject, $escapeHtmlChars=true) {
  593. if (!isset($rowBuffer)) {
  594. $rowBuffer = array();
  595. }
  596. if (isset($sobject->Id) && !isset($sobject->fields->Id)) {
  597. $rowBuffer[] = $sobject->Id;
  598. }
  599. if (isset($sobject->fields)) {
  600. foreach ($sobject->fields as $datum) {
  601. $rowBuffer[] = ($escapeHtmlChars ? htmlspecialchars($datum,ENT_QUOTES) : $datum);
  602. }
  603. }
  604. if (isset($sobject->sobjects)) {
  605. foreach ($sobject->sobjects as $sobjects) {
  606. $rowBuffer = array_merge($rowBuffer, getQueryResultRow($sobjects,$escapeHtmlChars));
  607. }
  608. }
  609. if (isset($sobject->queryResult)) {
  610. $rowBuffer[] = $sobject->queryResult;
  611. }
  612. return localizeDateTimes($rowBuffer);
  613. }
  614. function createQueryResultsMatrix($records, $matrixCols, $matrixRows) {
  615. $matrix;
  616. $allColNames = array();
  617. $allRowNames = array();
  618. foreach ($records as $rawRecord) {
  619. $record = new SObject($rawRecord);
  620. $data = "";
  621. if (isset($record->Id)) $record->fields->Id = $record->Id;
  622. foreach ($record->fields as $fieldName => $fieldValue) {
  623. if ($fieldName == $matrixCols || $fieldName == $matrixRows) {
  624. continue;
  625. }
  626. $data .= "<em>" . htmlspecialchars($fieldName) . ":</em> " . htmlspecialchars($fieldValue,ENT_QUOTES) . "<br/>";
  627. }
  628. foreach ($record->fields as $rowName => $rowValue) {
  629. if ($rowName != $matrixRows) continue;
  630. foreach ($record->fields as $colName => $colValue) {
  631. if ($colName != $matrixCols) continue;
  632. $allColNames["$colValue"] = $colValue;
  633. $allRowNames["$rowValue"] = $rowValue;
  634. $matrix["$rowValue"]["$colValue"][] = $data;
  635. }
  636. }
  637. }
  638. if (count($allColNames) == 0 || count($allRowNames) == 0) {
  639. displayWarning("No records match matrix column and row selections.", false, true);
  640. return;
  641. }
  642. $table = "<table id='query_results_matrix' border='1' class='" . getTableClass() . "'>";
  643. $hw = false;
  644. foreach ($allRowNames as $rowName) {
  645. if (!$hw) {
  646. $table .= "<tr><td></td>";
  647. foreach ($allColNames as $colName) {
  648. $table .= "<th>" . htmlspecialchars($colName) . "</th>";
  649. }
  650. $table .= "</tr>";
  651. $hw = true;
  652. }
  653. $table .= "<tr>";
  654. $table .= "<th>" . htmlspecialchars($rowName) . "</th>";
  655. foreach ($allColNames as $colName) {
  656. $table .= "<td>";
  657. if (isset($matrix["$rowName"]["$colName"])) {
  658. foreach ($matrix["$rowName"]["$colName"] as $data) {
  659. $table .= "<div class='matrixItem'" . ($data == "" ? "style='width: 0px;'" : "") . ">$data</div>";
  660. }
  661. }
  662. $table .= "</td>";
  663. }
  664. $table .= "</tr>";
  665. }
  666. $table .= "</table>";
  667. return localizeDateTimes($table);
  668. }
  669. function createQueryResultTable($records, $rowNum) {
  670. $table = "<table id='query_results' class='" . getTableClass() . "'>\n";
  671. //call shared recusive function above for header printing
  672. $table .= "<tr><th></th><th>";
  673. if ($records[0] instanceof SObject) {
  674. $table .= implode("</th><th>", getQueryResultHeaders($records[0]));
  675. } else {
  676. $table .= implode("</th><th>", getQueryResultHeaders(new SObject($records[0])));
  677. }
  678. $table .= "</th></tr>\n";
  679. //Print the remaining rows in the body
  680. foreach ($records as $record) {
  681. //call shared recusive function above for row printing
  682. $table .= "<tr><td>" . $rowNum++ . "</td><td>";
  683. if ($record instanceof SObject) {
  684. $row = getQueryResultRow($record);
  685. } else {
  686. $row = getQueryResultRow(new SObject($record));
  687. }
  688. for ($i = 0; $i < count($row); $i++) {
  689. if($row[$i] instanceof QueryResult && !is_array($row[$i])) $row[$i] = array($row[$i]);
  690. if (isset($row[$i][0]) && $row[$i][0] instanceof QueryResult) {
  691. foreach ($row[$i] as $qr) {
  692. $table .= createQueryResultTable($qr->records, 1);
  693. if($qr != end($row[$i])) $table .= "</td><td>";
  694. }
  695. } else {
  696. $table .= $row[$i];
  697. }
  698. if ($i+1 != count($row)) {
  699. $table .= "</td><td>";
  700. }
  701. }
  702. $table .= "</td></tr>\n";
  703. }
  704. $table .= "</table>";
  705. return $table;
  706. }
  707. //If the user selects to display the form on screen, they are routed to this function
  708. function displayQueryResults($records, $queryTimeElapsed, QueryRequest $queryRequest) {
  709. //Check if records were returned
  710. if ($records) {
  711. if (getConfig("areTablesSortable")) {
  712. addFooterScript("<script type='text/javascript' src='" . getStaticResourcesPath() . "/script/sortable.js'></script>");
  713. }
  714. try {
  715. $rowNum = 0;
  716. print "<a name='qr'></a><div style='clear: both;'><br/><h2>Query Results</h2>\n";
  717. if (isset($_SESSION['queryLocator']) && !getConfig("autoRunQueryMore")) {
  718. preg_match("/-(\d+)/",$_SESSION['queryLocator'],$lastRecord);
  719. $rowNum = ($lastRecord[1] - count($records) + 1);
  720. print "<p>Returned records $rowNum - " . $lastRecord[1] . " of ";
  721. } else if (!getConfig("autoRunQueryMore")) {
  722. $rowNum = ($_SESSION['totalQuerySize'] - count($records) + 1);
  723. print "<p>Returned records $rowNum - " . $_SESSION['totalQuerySize'] . " of ";
  724. } else {
  725. $rowNum = 1;
  726. print "<p>Returned ";
  727. }
  728. print $_SESSION['totalQuerySize'] . " total record";
  729. if ($_SESSION['totalQuerySize'] !== 1) print 's';
  730. print " in ";
  731. printf ("%01.3f", $queryTimeElapsed);
  732. print " seconds:</p>\n";
  733. if (!getConfig("autoRunQueryMore") && $_SESSION['queryLocator']) {
  734. print "<p><input type='submit' name='queryMore' id='queryMoreButtonTop' value='More...' /></p>\n";
  735. }
  736. print addLinksToIds($queryRequest->getExportTo() == 'matrix' ?
  737. createQueryResultsMatrix($records, $queryRequest->getMatrixCols(), $queryRequest->getMatrixRows()) :
  738. createQueryResultTable($records, $rowNum));
  739. if (!getConfig("autoRunQueryMore") && $_SESSION['queryLocator']) {
  740. print "<p><input type='submit' name='queryMore' id='queryMoreButtonBottom' value='More...' /></p>";
  741. }
  742. print "</form></div>\n";
  743. } catch (Exception $e) {
  744. print "<p />";
  745. displayError($e->getMessage(), false, true);
  746. }
  747. } else {
  748. print "<p><a name='qr'>&nbsp;</a></p>";
  749. displayWarning("Sorry, no records returned.");
  750. }
  751. include_once 'footer.php';
  752. }
  753. //Export the above query to a CSV file
  754. function exportQueryAsCsv($records,$queryAction) {
  755. if (!getConfig("allowQueryCsvExport")) {
  756. throw new Exception("Export to CSV not allowed");
  757. }
  758. if ($records) {
  759. try {
  760. $csvFile = fopen('php://output','w') or die("Error opening php://output");
  761. $csvFilename = "export" . date('YmdHis') . ".csv";
  762. header("Content-Type: application/csv");
  763. header("Content-Disposition: attachment; filename=$csvFilename");
  764. //Write first row to CSV and unset variable
  765. fputcsv($csvFile,getQueryResultHeaders(new SObject($records[0])));
  766. //Export remaining rows and write to CSV line-by-line
  767. foreach ($records as $record) {
  768. fputcsv($csvFile, getQueryResultRow(new SObject($record),false));
  769. }
  770. fclose($csvFile) or die("Error closing php://output");
  771. } catch (Exception $e) {
  772. require_once("header.php");
  773. displayQueryForm(new QueryRequest($_POST),'csv',$queryAction);
  774. print "<p />";
  775. displayError($e->getMessage(),false,true);
  776. }
  777. } else {
  778. require_once("header.php");
  779. displayQueryForm(new QueryRequest($_POST),'csv',$queryAction);
  780. print "<p />";
  781. displayWarning("No records returned for CSV output.",false,true);
  782. }
  783. }
  784. function queryAsync($queryRequest) {
  785. if ($queryRequest->getQueryAction() == "QueryAll") {
  786. throw new WorkbenchHandledException("Including deleted and archived records not supported by Bulk Queries.");
  787. }
  788. $asyncConnection = WorkbenchContext::get()->getAsyncBulkConnection();
  789. $job = new JobInfo();
  790. // try to find actual object in FROM clause in case it is different from object set in form
  791. preg_match("/FROM\s(\w+)/i", $queryRequest->getSoqlQuery(), $fromMatches);
  792. // if we can't find it, go ahead and use the object from the form.
  793. // it's probably a malformed query anyway, but let SFDC error on it instead of Workbench
  794. $job->setObject(isset($fromMatches[1]) ? $fromMatches[1] : $queryRequest->getObject());
  795. $job->setOpertion("query");
  796. $job->setContentType(substr($queryRequest->getExportTo(), strlen("async_")));
  797. $job->setConcurrencyMode(getConfig("asyncConcurrencyMode"));
  798. $job = $asyncConnection->createJob($job);
  799. $asyncConnection->createBatch($job, $queryRequest->getSoqlQuery());
  800. $job = $asyncConnection->updateJobState($job->getId(), "Closed");
  801. header("Location: asyncStatus.php?jobId=" . $job->getId());
  802. }
  803. ?>