tripal_analysis.form.inc 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. <?php
  2. /**
  3. * When editing or creating a new node of type 'chado_analysis' we need
  4. * a form. This function creates the form that will be used for this.
  5. *
  6. * @ingroup tripal_analysis
  7. */
  8. function chado_analysis_form($node) {
  9. tripal_core_ahah_init_form();
  10. $form = array();
  11. $analysis = $node->analysis;
  12. // add in the description column. It is a text field and may not be included
  13. // if the text is too big.
  14. $analysis = tripal_core_expand_chado_vars($analysis, 'field', 'analysis.description');
  15. // get form defaults
  16. $analysis_id = $node->analysis_id;
  17. if (!$analysis_id) {
  18. $analysis_id = $analysis->analysis_id;
  19. }
  20. $analysisname = $node->analysisname;
  21. if (!$analysisname) {
  22. $analysisname = $analysis->name;
  23. }
  24. $program = $node->program;
  25. if (!$program) {
  26. $program = $analysis->program;
  27. }
  28. $programversion = $node->programversion;
  29. if (!$programversion) {
  30. $programversion = $analysis->programversion;
  31. }
  32. $algorithm = $node->algorithm;
  33. if (!$algorithm) {
  34. $algorithm = $analysis->algorithm;
  35. }
  36. $sourcename = $node->sourcename;
  37. if (!$sourcename) {
  38. $sourcename = $analysis->sourcename;
  39. }
  40. $sourceversion = $node->sourceversion;
  41. if (!$sourceversion) {
  42. $sourceversion = $analysis->sourceversion;
  43. }
  44. $sourceuri = $node->sourceuri;
  45. if (!$sourceuri) {
  46. $sourceuri = $analysis->sourceuri;
  47. }
  48. $timeexecuted = $node->timeexecuted;
  49. if (!$timeexecuted) {
  50. $timeexecuted = $analysis->timeexecuted;
  51. }
  52. $description = $node->description;
  53. if (!$description) {
  54. $description = $analysis->description;
  55. }
  56. // on AHAH callbacks we want to keep a list of all the properties that have been removed
  57. // we'll store this info in a hidden field and retrieve it here
  58. $d_removed = $form_state['values']['removed'];
  59. // get the number of new fields that have been aded via AHAH callbacks
  60. $num_new = $form_state['values']['num_new'] ? $form_state['values']['num_new'] : 0;
  61. // initialze default properties array. This is where we store the property defaults
  62. $d_properties = array();
  63. $form['title']= array(
  64. '#type' => 'hidden',
  65. '#default_value' => $node->title,
  66. );
  67. $form['analysis_id']= array(
  68. '#type' => 'hidden',
  69. '#default_value' => $analysis_id,
  70. );
  71. $form['analysisname']= array(
  72. '#type' => 'textfield',
  73. '#title' => t('Analysis Name'),
  74. '#required' => TRUE,
  75. '#default_value' => $analysisname,
  76. '#description' => t("This should be a brief name that
  77. describes the analysis succintly. This name will helps the user find analyses."),
  78. );
  79. $form['program']= array(
  80. '#type' => 'textfield',
  81. '#title' => t('Program'),
  82. '#required' => TRUE,
  83. '#default_value' => $program,
  84. '#description' => t("Program name, e.g. blastx, blastp, sim4, genscan."),
  85. );
  86. $form['programversion']= array(
  87. '#type' => 'textfield',
  88. '#title' => t('Program Version'),
  89. '#required' => TRUE,
  90. '#default_value' => $programversion,
  91. '#description' => t("Version description, e.g. TBLASTX 2.0MP-WashU [09-Nov-2000]. Enter 'n/a' if no version is available."),
  92. );
  93. $form['algorithm']= array(
  94. '#type' => 'textfield',
  95. '#title' => t('Algorithm'),
  96. '#required' => FALSE,
  97. '#default_value' => $algorithm,
  98. '#description' => t("Algorithm name, e.g. blast."),
  99. );
  100. $form['sourcename']= array(
  101. '#type' => 'textfield',
  102. '#title' => t('Source Name'),
  103. '#required' => TRUE,
  104. '#default_value' => $sourcename,
  105. '#description' => t('The name of the source data. This could be a file name, data set name or a
  106. small description for how the data was collected. For long descriptions use the description field below'),
  107. );
  108. $form['sourceversion']= array(
  109. '#type' => 'textfield',
  110. '#title' => t('Source Version'),
  111. '#required' => FALSE,
  112. '#default_value' => $sourceversion,
  113. '#description' => t('If the source dataset has a version, include it here'),
  114. );
  115. $form['sourceuri']= array(
  116. '#type' => 'textfield',
  117. '#title' => t('Source URI'),
  118. '#required' => FALSE,
  119. '#default_value' => $sourceuri,
  120. '#description' => t("This is a permanent URL or URI for the source of the analysis.
  121. Someone could recreate the analysis directly by going to this URI and
  122. fetching the source data (e.g. the blast database, or the training model)."),
  123. );
  124. // Get time saved in chado
  125. $default_time = $timeexecuted;
  126. $year = preg_replace("/^(\d+)-\d+-\d+ .*/", "$1", $default_time);
  127. $month = preg_replace("/^\d+-0?(\d+)-\d+ .*/", "$1", $default_time);
  128. $day = preg_replace("/^\d+-\d+-0?(\d+) .*/", "$1", $default_time);
  129. // If the time is not set, use current time
  130. if (!$default_time) {
  131. $default_time = time();
  132. $year = format_date($default_time, 'custom', 'Y');
  133. $month = format_date($default_time, 'custom', 'n');
  134. $day = format_date($default_time, 'custom', 'j');
  135. }
  136. $form['timeexecuted']= array(
  137. '#type' => 'date',
  138. '#title' => t('Time Executed'),
  139. '#required' => TRUE,
  140. '#default_value' => array(
  141. 'year' => $year,
  142. 'month' => $month,
  143. 'day' => $day,
  144. ),
  145. );
  146. $form['description']= array(
  147. '#type' => 'textarea',
  148. '#rows' => 15,
  149. '#title' => t('Materials & Methods (Description and/or Program Settings)'),
  150. '#required' => FALSE,
  151. '#default_value' => $description,
  152. '#description' => t('Please provide all necessary information to allow
  153. someone to recreate the analysis, including materials and methods
  154. for collection of the source data and performing the analysis'),
  155. );
  156. return $form;
  157. }
  158. /**
  159. * Validates the user input before creating an analysis node
  160. *
  161. * @ingroup tripal_analysis
  162. */
  163. function chado_analysis_validate($node, &$form) {
  164. // use the analysis parent to validate the node
  165. tripal_analysis_validate($node, $form);
  166. }
  167. /**
  168. * This validation is being used for three activities:
  169. * CASE A: Update a node that exists in both drupal and chado
  170. * CASE B: Synchronizing a node from chado to drupal
  171. * CASE C: Inserting a new node that exists in niether drupal nor chado
  172. *
  173. * @ingroup tripal_analysis
  174. */
  175. function tripal_analysis_validate($node, &$form) {
  176. // Only nodes being updated will have an nid already
  177. if (!is_null($node->nid)) {
  178. // CASE A: We are validating a form for updating an existing node
  179. // get the existing node
  180. $values = array('analysis_id' => $node->analysis_id);
  181. $result = tripal_core_chado_select('analysis', array('*'), $values);
  182. $analysis = $result[0];
  183. // if the name has changed make sure it doesn't conflict with an existing name
  184. if($analysis->name != $node->analysisname) {
  185. $values = array('name' => $node->analysisname);
  186. $result = tripal_core_chado_select('analysis', array('analysis_id'), $values);
  187. if($result and count($result) > 0) {
  188. form_set_error('analysisname', 'Cannot update the analysis with this analysis name. An analysis with this name already exists.');
  189. return;
  190. }
  191. }
  192. // if the unique constraint has changed check to make sure it doesn't conflict with an
  193. // existing record
  194. if($analysis->program != $node->program or $analysis->programversion != $node->programversion or
  195. $analysis->sourcename != $node->sourcename) {
  196. $values = array(
  197. 'program' => $node->program,
  198. 'programversion' => $node->programversion,
  199. 'sourcename' => $node->sourcename,
  200. );
  201. $result = tripal_core_chado_select('analysis', array('analysis_id'), $values);
  202. if ($result and count($result) > 0) {
  203. if ($analysis->program != $node->program) {
  204. $field = 'program';
  205. }
  206. if ($analysis->programversion != $node->programversion) {
  207. $field = 'programversion';
  208. }
  209. if ($analysis->sourcename != $node->sourcename) {
  210. $field = 'sourcename';
  211. }
  212. form_set_error($field, 'Cannot update the analysis with this program,
  213. program version and source name. An analysis with these values already exists.');
  214. return;
  215. }
  216. }
  217. }
  218. else{
  219. // To differentiate if we are syncing or creating a new analysis altogther, see if an
  220. // analysis_id already exists
  221. if ($node->analysis_id and $node->analysis_id != 0) {
  222. // CASE B: Synchronizing a node from chado to drupal
  223. // we don't need to do anything.
  224. }
  225. else {
  226. // CASE C: We are validating a form for inserting a new node
  227. // The unique constraint for the chado analysis table is: program, programversion, sourcename
  228. $values = array(
  229. 'program' => $node->program,
  230. 'programversion' => $node->programversion,
  231. 'sourcename' => $node->sourcename,
  232. );
  233. $analysis = tripal_core_chado_select('analysis', array('analysis_id'), $values);
  234. if ($analysis and count($analysis) > 0) {
  235. form_set_error('program', 'Cannot add the analysis with this program,
  236. program version and source name. An analysis with these values already exists.');
  237. return;
  238. }
  239. // make sure we have a unique analysis name. This is not a requirement
  240. // for the analysis table but we use the analysis name for the Drupal node
  241. // title, so it should be unique
  242. $values = array('name' => $node->analysisname);
  243. $result = tripal_core_chado_select('analysis', array('analysis_id'), $values);
  244. if($result and count($result) > 0) {
  245. form_set_error('analysisname', 'Cannot add the analysis with this analysis name. An analysis with this name already exists.');
  246. return;
  247. }
  248. }
  249. }
  250. }
  251. /*
  252. *
  253. */
  254. function chado_analysis_node_form_add_new_empty_props(&$form, $properties_select) {
  255. // add one more blank set of property fields
  256. $form['properties']['new']["new_id"] = array(
  257. '#type' => 'select',
  258. '#options' => $properties_select,
  259. '#ahah' => array(
  260. 'path' => "tripal_analysis/properties/description",
  261. 'wrapper' => 'tripal-analysis-new_value-desc',
  262. 'event' => 'change',
  263. 'method' => 'replace',
  264. ),
  265. );
  266. $form['properties']['new']["new_value"] = array(
  267. '#type' => 'textarea',
  268. '#default_value' => '',
  269. '#cols' => 5,
  270. '#rows' => $rows,
  271. '#description' => '<div id="tripal-analysis-new_value-desc"></div>'
  272. );
  273. $form['properties']['new']["add"] = array(
  274. '#type' => 'image_button',
  275. '#value' => t('Add'),
  276. '#src' => drupal_get_path('theme', 'tripal') . '/images/add.png',
  277. '#ahah' => array(
  278. 'path' => "tripal_analysis/properties/add",
  279. 'wrapper' => 'tripal-analysis-edit-properties-table',
  280. 'event' => 'click',
  281. 'method' => 'replace',
  282. ),
  283. '#attributes' => array('onClick' => 'return false;'),
  284. );
  285. }
  286. /*
  287. *
  288. */
  289. function chado_analysis_node_form_add_new_props(&$form, $form_state, &$d_properties, &$d_removed) {
  290. // first, add in all of the new properties that were added through a previous AHAH callback
  291. $j = 0;
  292. $num_properties++;
  293. // we need to find the
  294. if ($form_state['values']) {
  295. foreach ($form_state['values'] as $element_name => $value) {
  296. if (preg_match('/new_value-(\d+)-(\d+)/', $element_name, $matches)) {
  297. $new_id = $matches[1];
  298. $rank = $matches[2];
  299. // skip any properties that the user requested to delete through a previous
  300. // AHAH callback or through the current AHAH callback
  301. if($d_removed["$new_id-$rank"]) {
  302. continue;
  303. }
  304. if($form_state['post']['remove-' . $new_id . '-' . $rank]) {
  305. $d_removed["$new_id-$rank"] = 1;
  306. continue;
  307. }
  308. // get this new_id information
  309. $cvterm = tripal_core_chado_select('cvterm', array('name', 'definition'), array('cvterm_id' => $new_id));
  310. // add it to the $d_properties array
  311. $d_properties[$new_id][$rank]['name'] = $cvterm->name;
  312. $d_properties[$new_id][$rank]['id'] = $new_id;
  313. $d_properties[$new_id][$rank]['value'] = $value;
  314. $d_properties[$new_id][$rank]['definition'] = $cvterm->definition;
  315. $num_properties++;
  316. // determine how many rows we need in the textarea
  317. $rows = 1;
  318. // add the new fields
  319. $form['properties']['new'][$new_id][$rank]["new_id-$new_id-$rank"] = array(
  320. '#type' => 'item',
  321. '#value' => $cvterm[0]->name
  322. );
  323. $form['properties']['new'][$new_id][$rank]["new_value-$new_id-$rank"] = array(
  324. '#type' => 'textarea',
  325. '#default_value' => $value,
  326. '#cols' => 50,
  327. '#rows' => $rows,
  328. '#description' => $cvterm->definition,
  329. );
  330. $form['properties']['new'][$new_id][$rank]["remove-$new_id-$rank"] = array(
  331. '#type' => 'image_button',
  332. '#value' => t('Remove'),
  333. '#src' => drupal_get_path('theme', 'tripal') . '/images/minus.png',
  334. '#ahah' => array(
  335. 'path' => "tripal_analysis/properties/minus/$new_id/$rank",
  336. 'wrapper' => 'tripal-analysis-edit-properties-table',
  337. 'event' => 'click',
  338. 'method' => 'replace',
  339. ),
  340. '#attributes' => array('onClick' => 'return false;'),
  341. );
  342. }
  343. }
  344. }
  345. // second add in any new properties added during this callback
  346. if($form_state['post']['add']) {
  347. $new_id = $form_state['values']['new_id'];
  348. $new_value = $form_state['values']['new_value'];
  349. // get the rank by counting the number of entries
  350. $rank = count($d_properties[$new_id]);
  351. // get this new_id information
  352. $cvterm = tripal_core_chado_select('cvterm', array('name', 'definition'), array('cvterm_id' => $new_id));
  353. // add it to the $d_properties array
  354. $d_properties[$new_id][$rank]['name'] = $cvterm->name;
  355. $d_properties[$new_id][$rank]['id'] = $new_id;
  356. $d_properties[$new_id][$rank]['value'] = $value;
  357. $d_properties[$new_id][$rank]['definition'] = $cvterm->definition;
  358. $num_properties++;
  359. // determine how many rows we need in the textarea
  360. $rows = 1;
  361. if (preg_match('/Abstract/', $cvterm[0]->name)) {
  362. $rows = 10;
  363. }
  364. if ($cvterm[0]->name == 'Authors') {
  365. $rows = 2;
  366. }
  367. // add the new fields
  368. $form['properties']['new'][$new_id][$rank]["new_id-$new_id-$rank"] = array(
  369. '#type' => 'item',
  370. '#value' => $cvterm[0]->name
  371. );
  372. $form['properties']['new'][$new_id][$rank]["new_value-$new_id-$rank"] = array(
  373. '#type' => 'textarea',
  374. '#default_value' => $new_value,
  375. '#cols' => 50,
  376. '#rows' => $rows,
  377. '#description' => $cvterm->definition,
  378. );
  379. $form['properties']['new'][$new_id][$rank]["remove-$new_id-$rank"] = array(
  380. '#type' => 'image_button',
  381. '#value' => t('Remove'),
  382. '#src' => drupal_get_path('theme', 'tripal') . '/images/minus.png',
  383. '#ahah' => array(
  384. 'path' => "tripal_analysis/properties/minus/$new_id/$rank",
  385. 'wrapper' => 'tripal-analysis-edit-properties-table',
  386. 'event' => 'click',
  387. 'method' => 'replace',
  388. ),
  389. '#attributes' => array('onClick' => 'return false;'),
  390. );
  391. }
  392. return $num_properties;
  393. }
  394. /*
  395. *
  396. */
  397. function chado_analysis_node_form_add_analysisprop_table_props(&$form, $form_state, $analysis_id, &$d_properties, &$d_removed) {
  398. // get the properties for this analysis
  399. $num_properties = 0;
  400. if(!$analysis_id) {
  401. return $num_properties;
  402. }
  403. $sql = "
  404. SELECT CVT.cvterm_id, CVT.name, CVT.definition, PP.value, PP.rank
  405. FROM {analysisprop} PP
  406. INNER JOIN {cvterm} CVT on CVT.cvterm_id = PP.type_id
  407. WHERE PP.analysis_id = %d
  408. ORDER BY CVT.name, PP.rank
  409. ";
  410. $analysis_props = chado_query($sql, $analysis_id);
  411. while ($prop = db_fetch_object($analysis_props)) {
  412. $type_id = $prop->cvterm_id;
  413. $rank = count($d_properties[$type_id]);
  414. // skip any properties that the user requested to delete through a previous
  415. // AHAH callback or through the current AHAH callback
  416. if($d_removed["$type_id-$rank"]) {
  417. continue;
  418. }
  419. if($form_state['post']['remove-' . $type_id . '-' . $rank]) {
  420. $d_removed["$type_id-$rank"] = 1;
  421. continue;
  422. }
  423. $d_properties[$type_id][$rank]['name'] = $prop->name;
  424. $d_properties[$type_id][$rank]['id'] = $type_id;
  425. $d_properties[$type_id][$rank]['value'] = $prop->value;
  426. $d_properties[$type_id][$rank]['definition'] = $prop->definition;
  427. $num_properties++;
  428. $form['properties'][$type_id][$rank]["prop_id-$type_id-$rank"] = array(
  429. '#type' => 'item',
  430. '#value' => $prop->name,
  431. );
  432. $form['properties'][$type_id][$rank]["prop_value-$type_id-$rank"] = array(
  433. '#type' => 'textarea',
  434. '#default_value' => $prop->value,
  435. '#cols' => 50,
  436. '#rows' => $rows,
  437. '#description' => $prop->definition,
  438. );
  439. $form['properties'][$type_id][$rank]["remove-$type_id-$rank"] = array(
  440. '#type' => 'image_button',
  441. '#value' => t('Remove'),
  442. '#src' => drupal_get_path('theme', 'tripal') . '/images/minus.png',
  443. '#ahah' => array(
  444. 'path' => "tripal_analysis/properties/minus/$type_id/$rank",
  445. 'wrapper' => 'tripal-analysis-edit-properties-table',
  446. 'event' => 'click',
  447. 'method' => 'replace',
  448. ),
  449. '#attributes' => array('onClick' => 'return false;'),
  450. );
  451. }
  452. return $num_properties;
  453. }
  454. /*
  455. *
  456. */
  457. function tripal_analysis_theme_node_form_properties($form) {
  458. $rows = array();
  459. if ($form['properties']) {
  460. // first add in the properties derived from the analysisprop table
  461. // the array tree for these properties looks like this:
  462. // $form['properties'][$type_id][$rank]["prop_id-$type_id-$rank"]
  463. foreach ($form['properties'] as $type_id => $elements) {
  464. // there are other fields in the properties array so we only
  465. // want the numeric ones those are our type_id
  466. if (is_numeric($type_id)) {
  467. foreach ($elements as $rank => $element) {
  468. if (is_numeric($rank)) {
  469. $rows[] = array(
  470. drupal_render($element["prop_id-$type_id-$rank"]),
  471. drupal_render($element["prop_value-$type_id-$rank"]),
  472. drupal_render($element["remove-$type_id-$rank"]),
  473. );
  474. }
  475. }
  476. }
  477. }
  478. // second, add in any new properties added by the user through AHAH callbacks
  479. // the array tree for these properties looks like this:
  480. // $form['properties']['new'][$type_id][$rank]["new_id-$new_id-$rank"]
  481. foreach ($form['properties']['new'] as $type_id => $elements) {
  482. if (is_numeric($type_id)) {
  483. foreach ($elements as $rank => $element) {
  484. if (is_numeric($rank)) {
  485. $rows[] = array(
  486. drupal_render($element["new_id-$type_id-$rank"]),
  487. drupal_render($element["new_value-$type_id-$rank"]),
  488. drupal_render($element["remove-$type_id-$rank"]),
  489. );
  490. }
  491. }
  492. }
  493. }
  494. // finally add in a set of blank field for adding a new property
  495. $rows[] = array(
  496. drupal_render($form['properties']['new']['new_id']),
  497. drupal_render($form['properties']['new']['new_value']),
  498. drupal_render($form['properties']['new']['add']),
  499. );
  500. }
  501. $headers = array('Property Type','Value', '');
  502. return theme('table', $headers, $rows, array('id'=> "tripal-analysis-edit-properties-table"));
  503. }
  504. /*
  505. *
  506. */
  507. function tripal_analysis_property_add() {
  508. $status = TRUE;
  509. // prepare and render the form
  510. $form = tripal_core_ahah_prepare_form();
  511. // we only want to return the properties as that's all we'll replace with this AHAh callback
  512. $data = tripal_analysis_theme_node_form_properties($form);
  513. // bind javascript events to the new objects that will be returned
  514. // so that AHAH enabled elements will work.
  515. $settings = tripal_core_ahah_bind_events();
  516. // return the updated JSON
  517. drupal_json(
  518. array(
  519. 'status' => $status,
  520. 'data' => $data,
  521. 'settings' => $settings,
  522. )
  523. );
  524. }
  525. /*
  526. *
  527. */
  528. function tripal_analysis_property_delete() {
  529. $status = TRUE;
  530. // prepare and render the form
  531. $form = tripal_core_ahah_prepare_form();
  532. // we only want to return the properties as that's all we'll replace with this AHAh callback
  533. $data = tripal_analysis_theme_node_form_properties($form);
  534. // bind javascript events to the new objects that will be returned
  535. // so that AHAH enabled elements will work.
  536. $settings = tripal_core_ahah_bind_events();
  537. // return the updated JSON
  538. drupal_json(
  539. array(
  540. 'status' => $status,
  541. 'data' => $data,
  542. 'settings' => $settings,
  543. )
  544. );
  545. }
  546. /*
  547. *
  548. */
  549. function tripal_analysis_property_get_description() {
  550. $new_id = $_POST['new_id'];
  551. $values = array('cvterm_id' => $new_id);
  552. $cvterm = tripal_core_chado_select('cvterm', array('definition'), $values);
  553. $description = '&nbsp;';
  554. if ($cvterm[0]->definition) {
  555. $description = $cvterm[0]->definition;
  556. }
  557. drupal_json(
  558. array(
  559. 'status' => TRUE,
  560. 'data' => '<div id="tripal-analysis-new_value-desc">' . $description . '</div>',
  561. )
  562. );
  563. }
  564. /*
  565. *
  566. */
  567. function theme_chado_analysis_node_form($form) {
  568. $properties_table = tripal_analysis_theme_node_form_properties($form);
  569. $markup = drupal_render($form['analysis_id']);
  570. $markup .= drupal_render($form['title']);
  571. $markup .= drupal_render($form['type_id']);
  572. $markup .= drupal_render($form['description']);
  573. $markup .= "<b>Include Additional Details</b><br>You may add additional properties to this analysis by scrolling to the bottom of this table, selecting a property type from the dropdown and adding text. You may add as many properties as desired by clicking the plus button on the right. To remove a property, click the minus button";
  574. $markup .= $properties_table;
  575. $form['properties'] = array(
  576. '#type' => 'markup',
  577. '#value' => $markup,
  578. );
  579. return drupal_render($form);
  580. }