tripal_core.chado_nodes.title_and_path.api.inc

Contains API functions to set titles and paths for all chado nodes

TITLES ==================================== There are three steps to implement the ability to set custom titles for the node type: 1) Add the 'chado_node_api' elements to the hook_node_info function(). These values define the name of the base table and how to refer to nodes in singular and plural. There are additional paramaters that can be added to the 'chado_node_api' for syncing nodes (see documentation for the chado_node_sync_form() function for additional options.

 function modulename_node_info() {
   return array(
     'chado_example' => array(
       'name' => t('example'),
       'base' => 'chado_example',
       'description' => t('A Chado example is a collection of material that can be sampled and have experiments performed on it.'),
       'has_title' => TRUE,
       'locked' => TRUE,

       // this is what differs from the regular Drupal-documented hook_node_info()
       'chado_node_api' => array(
         'base_table' => 'example',            // the name of the chado base table
         'hook_prefix' => 'chado_example',     // usually the name of the node type
         'record_type_title' => array(
           'singular' => t('Example'),         // Singular human-readable title
           'plural' => t('Examples')           // Plural human-readable title
         ),
       )
     ),
   );
 }

2) Add the "Set Page Titles" Form to the admin settings form

   // If the module is using the "Chado Node: Title & Path API" to allow custom titles
   // for your node type then you need to add the configuration form for this functionality.
   $details = array(
     'module' => 'tripal_example',        // the name of the MODULE implementing the content type
     'content_type' => 'chado_example',   // the name of the content type
       // An array of options to use under "Page Titles"
       // the key should be the token and the value should be the human-readable option
     'options' => array(
       '[example.name]' => 'Germplasm Name Only',
       '[example.uniquename]' => 'Germplasm Unique Name Only',
         // there should always be one options matching the unique constraint.
         // If you have a more human-readable constraint, then that is preferrable.
         // See the tripal feature module for a good example of this.
       '[example.example_id]' => 'Unique Contraint: The Chado ID for Examples'
     ),
     // the token indicating the unique constraint in the options array
     'unique_option' => '[example.example_id]'
   );
   // This call adds the configuration form to your current form
   // This sub-form handles it's own validation & submit
   chado_add_admin_form_set_title($form, $form_state, $details);

3) Use chado_get_node_title($node) where ever you want the title for your node. This should be done in hook_load(), hook_node_insert(), hook_node_update(). The reason you set the title in the node_action hooks, which act on all nodes, is because at that point you have the generic loaded node.

 function tripal_example_load($nodes) {

   foreach ($nodes as $nid => $node) {

     // Add all of the custom content for your node type.
     // See tripal_example.chado_node.api: chado_example_load()

     // Now get the title
     $node->title = chado_get_node_title($node);

     $nodes[$nid] = $node;
   }
 }

Optionally define a default for a specific content type by implementing a function of the name [content type]_chado_node_default_title_format() that returns a string describing the default format.

 function chado_example_chado_node_default_title_format() {
   return '[example.example_id]';
 }

If you don't implement this then a default format based on the unique constraint for the base table of the content type will be generated.

NODE URL/PATHS ==================================== There are three steps to implement the ability to set custom URLs for the node type: 1) Add the 'chado_node_api' elements to the hook_node_info function(). These values define the name of the base table and how to refer to nodes in singular and plural. There are additional paramaters that can be added to the 'chado_node_api' for syncing nodes (see documentation for the chado_node_sync_form() function for additional options.

 function modulename_node_info() {
   return array(
     'chado_example' => array(
       'name' => t('example'),
       'base' => 'chado_example',
       'description' => t('A Chado example is a collection of material that can be sampled and have experiments performed on it.'),
       'has_title' => TRUE,
       'locked' => TRUE,

       // this is what differs from the regular Drupal-documented hook_node_info()
       'chado_node_api' => array(
         'base_table' => 'example',            // the name of the chado base table
         'hook_prefix' => 'chado_example',     // usually the name of the node type
         'record_type_title' => array(
           'singular' => t('Example'),         // Singular human-readable title
           'plural' => t('Examples')           // Plural human-readable title
         ),
       )
     ),
   );
 }

2) Add the "Set Page URLs" Form to the admin settings form

   // If the module is using the "Chado Node: Title & Path API" to allow custom URLs
   // for your node type then you need to add the configuration form for this functionality.
   $details = array(
     'module' => 'tripal_example',        // the name of the MODULE implementing the content type
     'content_type' => 'chado_example',   // the name of the content type
       // An array of options to use under "Page URLs"
       // the key should be the token and the value should be the human-readable option
     'options' => array(
       '/example/[example.type_id>cvterm.name]/[example.example_id]' => 'Examples separated by Type',
         // there should always be one options matching the unique constraint.
         // If you have a more human-readable constraint, then that is preferrable.
         // See the tripal feature module for a good example of this.
       '/example/[example.example_id]' => 'Unique Contraint: The Chado ID for Examples'
     ),
   );
   // This call adds the configuration form to your current form
   // This sub-form handles it's own validation & submit
   chado_add_admin_form_set_url($form, $form_state, $details);

3) Use chado_set_node_url($node) where ever you want to reset the URL of the node. This should be done in hook_node_insert(), hook_node_update(). The reason you set the title in the node_action hooks, which act on all nodes, is because at that point you have the generic loaded node.

 function tripal_example_node_insert($node) {

   // Set the URL path after inserting.
   switch ($node->type) {
     case 'chado_example':

       // Now use the API to set the path.
       chado_set_node_url($node);

       break;
   }
 }

Optionally define a default for a specific content type by implementing a function of the name [content type]_chado_node_default_url_format() that returns a string describing the default format.

 function chado_example_chado_node_default_url_format() {
   return '/example/[example.example_id]';
 }

If you don't implement this then a default format based on the unique constraint for the base table of the content type will be generated.

File

tripal_core/api/tripal_core.chado_nodes.title_and_path.api.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * Contains API functions to set titles and paths for all chado nodes
  5. *
  6. * TITLES
  7. * ====================================
  8. * There are three steps to implement the ability to set custom titles for the node type:
  9. * 1) Add the 'chado_node_api' elements to the hook_node_info function(). These values
  10. * define the name of the base table and how to refer to nodes in singular and plural.
  11. * There are additional paramaters that can be added to the 'chado_node_api' for
  12. * syncing nodes (see documentation for the chado_node_sync_form() function for additional
  13. * options.
  14. * @code
  15. function modulename_node_info() {
  16. return array(
  17. 'chado_example' => array(
  18. 'name' => t('example'),
  19. 'base' => 'chado_example',
  20. 'description' => t('A Chado example is a collection of material that can be sampled and have experiments performed on it.'),
  21. 'has_title' => TRUE,
  22. 'locked' => TRUE,
  23. // this is what differs from the regular Drupal-documented hook_node_info()
  24. 'chado_node_api' => array(
  25. 'base_table' => 'example', // the name of the chado base table
  26. 'hook_prefix' => 'chado_example', // usually the name of the node type
  27. 'record_type_title' => array(
  28. 'singular' => t('Example'), // Singular human-readable title
  29. 'plural' => t('Examples') // Plural human-readable title
  30. ),
  31. )
  32. ),
  33. );
  34. }
  35. * @endcode
  36. *
  37. * 2) Add the "Set Page Titles" Form to the admin settings form
  38. * @code
  39. // If the module is using the "Chado Node: Title & Path API" to allow custom titles
  40. // for your node type then you need to add the configuration form for this functionality.
  41. $details = array(
  42. 'module' => 'tripal_example', // the name of the MODULE implementing the content type
  43. 'content_type' => 'chado_example', // the name of the content type
  44. // An array of options to use under "Page Titles"
  45. // the key should be the token and the value should be the human-readable option
  46. 'options' => array(
  47. '[example.name]' => 'Germplasm Name Only',
  48. '[example.uniquename]' => 'Germplasm Unique Name Only',
  49. // there should always be one options matching the unique constraint.
  50. // If you have a more human-readable constraint, then that is preferrable.
  51. // See the tripal feature module for a good example of this.
  52. '[example.example_id]' => 'Unique Contraint: The Chado ID for Examples'
  53. ),
  54. // the token indicating the unique constraint in the options array
  55. 'unique_option' => '[example.example_id]'
  56. );
  57. // This call adds the configuration form to your current form
  58. // This sub-form handles it's own validation & submit
  59. chado_add_admin_form_set_title($form, $form_state, $details);
  60. * @endcode
  61. *
  62. * 3) Use chado_get_node_title($node) where ever you want the title for your node. This
  63. * should be done in hook_load(), hook_node_insert(), hook_node_update(). The reason you
  64. * set the title in the node_action hooks, which act on all nodes, is because at that
  65. * point you have the generic loaded node.
  66. * @code
  67. function tripal_example_load($nodes) {
  68. foreach ($nodes as $nid => $node) {
  69. // Add all of the custom content for your node type.
  70. // See tripal_example.chado_node.api: chado_example_load()
  71. // Now get the title
  72. $node->title = chado_get_node_title($node);
  73. $nodes[$nid] = $node;
  74. }
  75. }
  76. * @endcode
  77. *
  78. * Optionally define a default for a specific content type by implementing a function of the name
  79. * [content type]_chado_node_default_title_format() that returns a string describing the
  80. * default format.
  81. * @code
  82. function chado_example_chado_node_default_title_format() {
  83. return '[example.example_id]';
  84. }
  85. * @endcode
  86. * If you don't implement this then a default format based on the unique constraint for
  87. * the base table of the content type will be generated.
  88. *
  89. * NODE URL/PATHS
  90. * ====================================
  91. * There are three steps to implement the ability to set custom URLs for the node type:
  92. * 1) Add the 'chado_node_api' elements to the hook_node_info function(). These values
  93. * define the name of the base table and how to refer to nodes in singular and plural.
  94. * There are additional paramaters that can be added to the 'chado_node_api' for
  95. * syncing nodes (see documentation for the chado_node_sync_form() function for additional
  96. * options.
  97. * @code
  98. function modulename_node_info() {
  99. return array(
  100. 'chado_example' => array(
  101. 'name' => t('example'),
  102. 'base' => 'chado_example',
  103. 'description' => t('A Chado example is a collection of material that can be sampled and have experiments performed on it.'),
  104. 'has_title' => TRUE,
  105. 'locked' => TRUE,
  106. // this is what differs from the regular Drupal-documented hook_node_info()
  107. 'chado_node_api' => array(
  108. 'base_table' => 'example', // the name of the chado base table
  109. 'hook_prefix' => 'chado_example', // usually the name of the node type
  110. 'record_type_title' => array(
  111. 'singular' => t('Example'), // Singular human-readable title
  112. 'plural' => t('Examples') // Plural human-readable title
  113. ),
  114. )
  115. ),
  116. );
  117. }
  118. * @endcode
  119. *
  120. * 2) Add the "Set Page URLs" Form to the admin settings form
  121. * @code
  122. // If the module is using the "Chado Node: Title & Path API" to allow custom URLs
  123. // for your node type then you need to add the configuration form for this functionality.
  124. $details = array(
  125. 'module' => 'tripal_example', // the name of the MODULE implementing the content type
  126. 'content_type' => 'chado_example', // the name of the content type
  127. // An array of options to use under "Page URLs"
  128. // the key should be the token and the value should be the human-readable option
  129. 'options' => array(
  130. '/example/[example.type_id>cvterm.name]/[example.example_id]' => 'Examples separated by Type',
  131. // there should always be one options matching the unique constraint.
  132. // If you have a more human-readable constraint, then that is preferrable.
  133. // See the tripal feature module for a good example of this.
  134. '/example/[example.example_id]' => 'Unique Contraint: The Chado ID for Examples'
  135. ),
  136. );
  137. // This call adds the configuration form to your current form
  138. // This sub-form handles it's own validation & submit
  139. chado_add_admin_form_set_url($form, $form_state, $details);
  140. * @endcode
  141. *
  142. * 3) Use chado_set_node_url($node) where ever you want to reset the URL of the
  143. * node. This should be done in hook_node_insert(), hook_node_update(). The reason you
  144. * set the title in the node_action hooks, which act on all nodes, is because at that
  145. * point you have the generic loaded node.
  146. * @code
  147. function tripal_example_node_insert($node) {
  148. // Set the URL path after inserting.
  149. switch ($node->type) {
  150. case 'chado_example':
  151. // Now use the API to set the path.
  152. chado_set_node_url($node);
  153. break;
  154. }
  155. }
  156. * @endcode
  157. *
  158. * Optionally define a default for a specific content type by implementing a function of the name
  159. * [content type]_chado_node_default_url_format() that returns a string describing the
  160. * default format.
  161. * @code
  162. function chado_example_chado_node_default_url_format() {
  163. return '/example/[example.example_id]';
  164. }
  165. * @endcode
  166. * If you don't implement this then a default format based on the unique constraint for
  167. * the base table of the content type will be generated.
  168. */
  169. /**
  170. * @section
  171. * Set Titles
  172. */
  173. /**
  174. * Get the title of a node based on the Title Format set in the admin
  175. * section of the module. If the format has not yet been set than
  176. * the the unique constrain and name fields will be used to generate
  177. * a default format
  178. *
  179. * @param $node
  180. * The node object
  181. *
  182. * @ingroup tripal_chado_node_api
  183. */
  184. function chado_get_node_title($node) {
  185. $content_type = $node->type;
  186. // Get the tokens and format
  187. $tokens = array(); // this will be set by chado_node_get_title_format
  188. $title = chado_node_get_title_format($content_type, $tokens);
  189. // Determine which tokens were used in the format string
  190. if (preg_match_all('/\[[^]]+\]/', $title, $used_tokens)) {
  191. // Get the value for each token used
  192. foreach ($used_tokens[0] as $token) {
  193. $token_info = $tokens[$token];
  194. if (!empty($token_info)) {
  195. $value = chado_get_token_value($token_info, $node);
  196. $title = str_replace($token, $value, $title);
  197. }
  198. }
  199. }
  200. else {
  201. return $title;
  202. }
  203. return $title;
  204. }
  205. /**
  206. * Generic "Set Node Title" sub-form for setting the title of any chado node
  207. *
  208. * @param $form
  209. * The Drupal form array into which the property form elements will be added
  210. * @param $form_state
  211. * The corresponding form_state array for the form
  212. * @param $details
  213. * An array defining details used by this form.
  214. * Required keys that are always required:
  215. * -module: the name of the module implementing the node. For example, for features
  216. * the module is tripal_feature.
  217. * -options: an array of quick-choice options to supply to the user. The key should be
  218. * the token and the value should be a human-readable description of the option
  219. * -content_type: the name of the content type. Defaults to module name.
  220. * Optional keys include:
  221. * -fieldset_title: the title to use for the fieldset. Defaults to "Set Page Title".
  222. * -default_option: the default format to use which matches one of those in $details['options']
  223. * -custom_tokens: an array of custom tokens that follow the same format as those
  224. * generated by chado_node_generate_tokens().
  225. *
  226. * @ingroup tripal_chado_node_api
  227. */
  228. function chado_add_admin_form_set_title(&$form, &$form_state, $details) {
  229. // Get Node Info
  230. if (isset($details['module'])) {
  231. $node_info = call_user_func($details['module'] . '_node_info');
  232. $chado_node_api = $node_info[ $details['content_type'] ]['chado_node_api'];
  233. }
  234. else {
  235. tripal_report_error(
  236. 'chado_node_api',
  237. TRIPAL_ERROR,
  238. "Set Titles API: When calling chado_add_admin_form_set_title, you \$details array must include 'module' => [name of your module] in order to pull out all the information provided in your implementation of hook_node_info"
  239. );
  240. }
  241. // Defaults
  242. $details['fieldset_title'] = (isset($details['fieldset_title'])) ? $details['fieldset_title'] : 'Set Page Titles';
  243. $details['additional_instructions'] = (isset($details['additional_instructions'])) ? $details['additional_instructions'] : '';
  244. $details['custom_tokens'] = (isset($details['custom_tokens'])) ? $details['custom_tokens'] : array();
  245. $details['content_type'] = (isset($details['content_type'])) ? $details['content_type'] : $details['module'];
  246. $tokens = array();
  247. if (empty($tokens)) {
  248. $tokens = chado_node_generate_tokens($chado_node_api['base_table']);
  249. }
  250. $tokens = array_merge($tokens, $details['custom_tokens']);
  251. $token_list = chado_node_format_tokens($tokens);
  252. $details['default_option'] = (isset($details['default_option'])) ? $details['default_option'] : chado_node_get_title_format($details['content_type'], $tokens);
  253. // FORM PROPER
  254. $msg = t(
  255. 'Each synced %singular must have a unique page title, however, %plural may have the
  256. same name if they are of different types or from different organisms. Therefore,
  257. we must be sure that the page titles can uniquely identify the %singular being viewed.
  258. Select an option below that will uniquely identify all %plural on your site.'
  259. . $details['additional_instructions'],
  260. array('%singular' => $chado_node_api['record_type_title']['singular'],
  261. '%plural' => $chado_node_api['record_type_title']['plural'])
  262. );
  263. $form['set_titles'] = array(
  264. '#type' => 'fieldset',
  265. '#title' => t($details['fieldset_title']),
  266. '#description' => $msg,
  267. '#collapsible' => TRUE,
  268. '#collapsed' => FALSE,
  269. '#prefix' => "<div id='set_titles-fieldset'>",
  270. '#suffix' => '</div>',
  271. );
  272. $form['set_titles']['content_type'] = array(
  273. '#type' => 'hidden',
  274. '#value' => $node_info[ $details['content_type'] ]['base'],
  275. );
  276. $details['options']['custom'] = 'Custom: See the text field below.';
  277. $form['set_titles']['title_option'] = array(
  278. '#title' => t('%singular Page Titles', array('%singular' => $chado_node_api['record_type_title']['singular'])),
  279. '#type' => 'radios',
  280. '#description' => t("Choose a title type from the list above that is
  281. guaranteed to be unique for all %plural. If in doubt it is safest to choose
  282. the 'Unique Constaint' option as that guarantees uniqueness.",
  283. array('%plural' => $chado_node_api['record_type_title']['plural'])),
  284. '#required' => FALSE,
  285. '#options' => $details['options'],
  286. '#default_value' => (isset($details['options'][$details['default_option']])) ? $details['default_option'] : 'custom',
  287. );
  288. $form['set_titles']['title_format_variable'] = array(
  289. '#type' => 'hidden',
  290. '#value' => $details['module'] . '_title_format'
  291. );
  292. $form['set_titles']['custom_title'] = array(
  293. '#type' => 'textarea',
  294. '#title' => 'Custom Page Title',
  295. '#description' => 'You may rearrange elements in this text box to customize the page
  296. titles. The available tokens are listed below. You can separate or include any text
  297. between the tokens. <strong>Important: be sure that whatever you choose
  298. will always be unique even considering future data that may be added. If in doubt,
  299. please select the unique constraint title option above.</strong>',
  300. '#default_value' => $details['default_option'],
  301. '#rows' => 1
  302. );
  303. $form['set_titles']['token_display'] = array(
  304. '#type' => 'fieldset',
  305. '#title' => 'Available Tokens',
  306. '#description' => 'Copy the token and paste it into the "Custom Page Title" text field above.',
  307. '#collapsible' => TRUE,
  308. '#collapsed' => TRUE
  309. );
  310. $form['set_titles']['token_display']['content'] = array(
  311. '#type' => 'item',
  312. '#markup' => $token_list
  313. );
  314. $form['set_titles']['tokens'] = array(
  315. '#type' => 'hidden',
  316. '#value' => serialize($tokens)
  317. );
  318. $form['set_titles']['submit'] = array(
  319. '#type' => 'submit',
  320. '#value' => 'Set Titles',
  321. '#validate' => array('chado_add_admin_form_set_title_form_validate'),
  322. '#submit' => array('chado_add_admin_form_set_title_form_submit')
  323. );
  324. }
  325. /**
  326. * Implements hook_form_validate().
  327. * VALIDATE: validate the format.
  328. */
  329. function chado_add_admin_form_set_title_form_validate($form, $form_state) {
  330. // Ensure that all tokens used in the format are in the tokens list
  331. if (preg_match_all('/\[[^]]+\]/',$form_state['values']['custom_title'],$used_tokens)) {
  332. $token_list = unserialize($form_state['values']['tokens']);
  333. foreach ($used_tokens[0] as $token) {
  334. if (!array_key_exists($token,$token_list)) {
  335. form_set_error('custom_title', 'All tokens used must be in the "Available Tokens" list. Please make sure not to use [ or ] unless it\'s denoting a token');
  336. }
  337. }
  338. }
  339. }
  340. /**
  341. * Implements hook_form_submit().
  342. * SUBMIT: Actually add the format specified by chado_add_admin_form_set_title()
  343. */
  344. function chado_add_admin_form_set_title_form_submit($form, $form_state) {
  345. if ($form_state['values']['title_option'] == 'custom') {
  346. $format = $form_state['values']['custom_title'];
  347. }
  348. else {
  349. $format = $form_state['values']['title_option'];
  350. }
  351. chado_node_add_token_format('title', $form_state['values']['content_type'], $format, $form_state['values']['tokens']);
  352. }
  353. /**
  354. * Get the title format for a specific content type
  355. *
  356. * If the title format has not yet been set then the following will be done
  357. * 1) Check to see if there is a legacy title format set (features & stocks)
  358. * 2) Check if there is a defined default for this content type
  359. * 3) Create a format using any name fields and the unique constraint for the
  360. * base table associated with this content type
  361. *
  362. * Define a default for a specific content type by implementing a function of the name
  363. * [content type]_chado_node_default_title_format() that returns a string describing the
  364. * default format.
  365. *
  366. * @param $content_type
  367. * The name of the content (node) type you are interested in (ie: chado_feature)
  368. * @param $tokens
  369. * An array, passed by reference that is filled to include the tokens for this
  370. * node type. Each token is an array with the following keys:
  371. * -table: the name of the chado table
  372. * -field: the name of the field in the above table
  373. * -token: the token string (ie: [stock.stock_id])
  374. * -description: a very short description of the token (displayed when tokens are listed)
  375. * -location: the location of the value in a chado node variable with each level
  376. * separated by an arrow (->) symbol. For example, the location for $node->feature->type_id->name
  377. * is feature>type_id>name
  378. *
  379. * @return
  380. * A string containing tokens describing the default format for the title of nodes
  381. * of the specified content type.
  382. */
  383. function chado_node_get_title_format($content_type, &$tokens, $base_table = NULL) {
  384. $format_record_format = $format = '';
  385. $format_record_tokens = '';
  386. // Is there a title format set?
  387. $format_record = chado_node_get_token_format('title', $content_type, array('return_record' => TRUE));
  388. if (!empty($format_record)) {
  389. $format_record_format = $format = $format_record->format;
  390. $format_record_tokens = $tokens = $format_record->tokens;
  391. }
  392. // All three options below need the tokens to be generated so do that now
  393. if (empty($format)) {
  394. if (empty($base_table)) {
  395. $base_table = chado_node_get_base_table($content_type);
  396. }
  397. $tokens = chado_node_generate_tokens($base_table);
  398. }
  399. // 1) Check for legacy format
  400. if (empty($format)) {
  401. $format = chado_node_get_legacy_title_default($content_type);
  402. }
  403. // 2) Module-defined default format
  404. if (empty($format)) {
  405. $hook = $content_type . '_chado_node_default_title_format';
  406. if (function_exists($hook)) {
  407. $format = call_user_func($hook);
  408. }
  409. }
  410. // 3) Create unique constraint format
  411. if (empty($format)) {
  412. if (empty($base_table)) {
  413. $base_table = chado_node_get_base_table($content_type);
  414. }
  415. $format = chado_node_get_unique_constraint_format($base_table);
  416. }
  417. // Add the format to table so we can use it later
  418. // (optimization: to speed up bulk updates, don't update the record if
  419. // the format and tokens are unchanged)
  420. if ($format != $format_record_format || $tokens != $format_record_tokens) {
  421. chado_node_add_token_format('title', $content_type, $format, $tokens);
  422. }
  423. return $format;
  424. }
  425. /**
  426. * Handles legacy title options
  427. *
  428. * Features & Stocks already had custom functionality to handle title
  429. * setting before this API was created. That has since been removed but
  430. * but to remain backwards compatible this function checks for those
  431. * old settings and translates them into new defaults.
  432. */
  433. function chado_node_get_legacy_title_default($content_type) {
  434. if ($content_type == 'chado_feature') {
  435. $legacy_option = variable_get('chado_feature_title', 'unique_constraint');
  436. switch ($legacy_option) {
  437. case 'feature_unique_name':
  438. $default_title_format = '[feature.uniquename]';
  439. break;
  440. case 'feature_name':
  441. $default_title_format = '[feature.name]';
  442. break;
  443. case 'unique_constraint':
  444. $default_title_format = '[feature.name], [feature.uniquename] ([feature.type_id>cvterm.name]) [feature.organism_id>organism.genus] [feature.organism_id>organism.species]';
  445. break;
  446. }
  447. return $default_title_format;
  448. }
  449. elseif ($content_type == 'chado_stock') {
  450. $legacy_option = variable_get('chado_stock_title', 'unique_constraint');
  451. switch ($legacy_option) {
  452. case 'stock_unique_name':
  453. $default_title_format = '[stock.uniquename]';
  454. break;
  455. case 'stock_name':
  456. $default_title_format = '[stock.name]';
  457. break;
  458. case 'unique_constraint':
  459. $default_title_format = '[stock.name], [stock.uniquename] ([stock.type_id>cvterm.name]) [stock.organism_id>organism.genus] [stock.organism_id>organism.species]';
  460. break;
  461. }
  462. return $default_title_format;
  463. }
  464. else {
  465. return FALSE;
  466. }
  467. }
  468. /**
  469. * @section
  470. * Set Paths
  471. */
  472. /**
  473. * Get the url of a node based on the url Format set in the admin
  474. * section of the module. If the format has not yet been set than
  475. * the the unique constrain and name fields will be used to generate
  476. * a default format
  477. *
  478. * @param $node
  479. * The node object
  480. *
  481. * @ingroup tripal_chado_node_api
  482. */
  483. function chado_get_node_url($node) {
  484. $content_type = $node->type;
  485. // Get the tokens and format
  486. $tokens = array(); // this will be set by chado_node_get_url_format
  487. $url = chado_node_get_url_format($content_type, $tokens);
  488. // Determine which tokens were used in the format string
  489. if (preg_match_all('/\[[^]]+\]/', $url, $used_tokens)) {
  490. // Get the value for each token used
  491. foreach ($used_tokens[0] as $token) {
  492. $token_info = $tokens[$token];
  493. if (!empty($token_info)) {
  494. $value = chado_get_token_value($token_info, $node);
  495. if (is_string($value)) {
  496. $url = str_replace($token, $value, $url);
  497. }
  498. else {
  499. tripal_report_error('chado_node_api',TRIPAL_ERROR,
  500. 'Unable to replace %token. The value in the node should be a string but is instead: \'%value\'',
  501. array('%token' => $token, '%value' => print_r($value, TRUE))
  502. );
  503. }
  504. }
  505. }
  506. }
  507. else {
  508. return $url;
  509. }
  510. return $url;
  511. }
  512. /**
  513. * Set the URL for a given node.
  514. *
  515. * Note: This makes the old URL completely invalid which breaks bookmarks.
  516. * Furthermore, if someone attempts to go to an old URL they will get a white
  517. * screen PDO error which is not very user friendly ;-)
  518. * @todo handle re-directing for old URLs or at least ensure a page not found
  519. * error is displayed.
  520. *
  521. * @param $node
  522. * The node to set the URL for. The node object must have at a
  523. * minimum the 'nid' and 'type' properties, as well as the
  524. * chado object (e.g. 'organism' for chado_organism node type, and
  525. * 'feature' for chado_feature node type, etc.).
  526. *
  527. * @return
  528. * The URL alias that was set.
  529. */
  530. function chado_set_node_url($node) {
  531. // Get the global variable determining the language of the current content.
  532. global $language_content;
  533. // First we need to get the URL alias.
  534. $url_alias = chado_get_node_url($node);
  535. // And remove any forward slashes since those wreak havok.
  536. $url_alias = preg_replace('/^\//', '', $url_alias);
  537. // Use the node to determine the source/original URL.
  538. $source_url = 'node/' . $node->nid;
  539. // Ensure that there are no spaces and other non-friendly characters are
  540. // sanitized.
  541. $url_alias = preg_replace('/\s+/','-',$url_alias);
  542. // Only bother setting the alias if there is one.
  543. if (!empty($url_alias) AND $source_url != $url_alias) {
  544. // First we need to check if the alias already exists.
  545. $path = path_load(array('source' => $source_url, 'alias' => $url_alias));
  546. // Etierh there isn't an alias yet so we just create one.
  547. if (empty($path)) {
  548. $path = array(
  549. 'source' => $source_url,
  550. 'alias' => $url_alias
  551. );
  552. path_save($path);
  553. }
  554. // Or an Alias already exists but we would like to add a new one.
  555. elseif ($path['alias'] != $url_alias) {
  556. $path = array(
  557. 'source' => $source_url,
  558. 'alias' => $url_alias
  559. );
  560. path_save($path);
  561. }
  562. // If the Alias already exists we want to double check that it's for the
  563. // current node. Otherwise we should warn the administrator that the path
  564. // format they chose isn't unique.
  565. else {
  566. $num_aliases = db_query('SELECT count(*) as num_alias FROM {url_alias} WHERE alias=:alias AND source!=:source',
  567. array(':alias' => $url_alias, ':source' => $source_url))->fetchField();
  568. // If there is an alias with a different source than warn the admin.
  569. if ($num_aliases > 0) {
  570. tripal_report_error('chado_node_api', TRIPAL_WARNING,
  571. 'URL Alias: The URL format for %content-type is not unique. Specifically, %alias was almost added multiple times.',
  572. array(
  573. '%content-type' => $node->type,
  574. '%alias' => $url_alias,
  575. )
  576. );
  577. }
  578. }
  579. }
  580. return $url_alias;
  581. }
  582. /**
  583. * Tripal Job for updating all node URLs for a particular node type.
  584. *
  585. * @param $content_type
  586. * The machine name of the node type to update URLs for.
  587. * @param $job_id
  588. * The ID of the tripal job calling this function.
  589. */
  590. function chado_update_existing_node_urls($content_type, $job_id = 0) {
  591. $transaction = db_transaction();
  592. print "\nNOTE: Setting of URLs is performed using a database transaction. \n" .
  593. "If the load fails or is terminated prematurely then the entire set of \n" .
  594. "new URLs will be rolled back and no changes will be made.\n\n";
  595. try {
  596. // Get the number of records we need to set URLs for.
  597. $num_nodes = db_query('SELECT count(*) FROM {' . $content_type . '}')->fetchField();
  598. // Calculate the interval at which we will print an update on the screen.
  599. $num_set = 0;
  600. $num_per_interval = 100;
  601. if ($num_nodes > 0) {
  602. print "There are $num_nodes nodes to update URLs for. You will be \n" .
  603. "updated on the progress every 100 nodes.\n\n";
  604. // Get the list of nodes of this particular content type.
  605. $query = new EntityFieldQuery();
  606. $result = $query->entityCondition('entity_type', 'node')
  607. ->entityCondition('bundle', $content_type)
  608. ->execute();
  609. if (isset($result['node'])) {
  610. $nids = array_keys($result['node']);
  611. foreach ($nids as $nid) {
  612. // Load the current node. Normally here we would use the node_load()
  613. // function that is part of the Drupal API. However, this seems
  614. // to have memory leaks that have not yet been identified, so
  615. // we'll create the object by hand:
  616. $node = new stdClass();
  617. $node->nid = $nid;
  618. $node->type = $content_type;
  619. $table = preg_replace('/chado_/', '', $content_type);
  620. $id = chado_get_id_from_nid($table, $nid);
  621. $node->$table = chado_generate_var($table, array($table . "_id" => $id));
  622. // Now set the URL for the current node.
  623. $alias = chado_set_node_url($node);
  624. // update the job status every 1% nodes
  625. if ($num_set % $num_per_interval == 0) {
  626. $percent = ($num_set / $num_nodes) * 100;
  627. // Update the user on progress.
  628. $percent = sprintf("%.2f", $percent);
  629. print "Setting URLs (" . $percent . "%). Memory: " . number_format(memory_get_usage()) . " bytes.\r";
  630. // Update the tripal job.
  631. if ($job_id) {
  632. tripal_set_job_progress($job_id, intval($percent));
  633. }
  634. }
  635. $num_set++;
  636. }
  637. }
  638. $percent = ($num_set / $num_nodes) * 100;
  639. tripal_set_job_progress($job_id, intval($percent));
  640. $percent = sprintf("%.2f", $percent);
  641. print "Setting URLs (" . $percent . "%). Memory: " . number_format(memory_get_usage()) . " bytes.\r";
  642. print "\nDone. Set " . number_format($num_set) . " URLs\n";
  643. }
  644. else {
  645. print "\nThere are no $content_type nodes to update. If you know there\n"
  646. . "are records of this type in chado, try sync'ing them to Drupal.\n";
  647. }
  648. }
  649. catch (Exception $e) {
  650. $transaction->rollback();
  651. print "\n"; // make sure we start errors on new line
  652. watchdog_exception('chado_node_api', $e);
  653. tripal_report_error(
  654. 'chado_node_api',
  655. TRIPAL_WARNING,
  656. 'Unable to update URLs for the :content-type Node Type. Specifically, the last URL attempted was NID=:nid and Alias=:alias',
  657. array(':content-type' => $content_type, ':nid' => $node->nid, ':alias' => $alias)
  658. );
  659. }
  660. }
  661. /**
  662. * Generic "Set Node URL" sub-form for setting the url of any chado node
  663. *
  664. * @param $form
  665. * The Drupal form array into which the property form elements will be added
  666. * @param $form_state
  667. * The corresponding form_state array for the form
  668. * @param $details
  669. * An array defining details used by this form.
  670. * Required keys that are always required:
  671. * -module: the name of the module implementing the node. For example, for features
  672. * the module is tripal_feature.
  673. * -options: an array of quick-choice options to supply to the user. The key should be
  674. * the token and the value should be a human-readable description of the option
  675. * Optional keys include:
  676. * -content_type: the name of the content type. Defaults to module name.
  677. * -fieldset_title: the title to use for the fieldset. Defaults to "Set Page url".
  678. * -default_option: the default format to use which matches one of those in $details['options']
  679. * -custom_tokens: an array of custom tokens that follow the same format as those
  680. * generated by chado_node_generate_tokens().
  681. *
  682. * @ingroup tripal_chado_node_api
  683. */
  684. function chado_add_admin_form_set_url(&$form, &$form_state, $details) {
  685. // Get Node Info
  686. if (isset($details['module'])) {
  687. $node_info = call_user_func($details['module'] . '_node_info');
  688. $chado_node_api = $node_info[ $details['content_type'] ]['chado_node_api'];
  689. }
  690. else {
  691. tripal_report_error(
  692. 'chado_node_api',
  693. TRIPAL_ERROR,
  694. "Set URL API: When calling chado_add_admin_form_set_url, you \$details array must include 'module' => [name of your module] in order to pull out all the information provided in your implementation of hook_node_info"
  695. );
  696. }
  697. // Defaults
  698. $details['fieldset_title'] = (isset($details['fieldset_title'])) ? $details['fieldset_title'] : 'Set Page URLs';
  699. $details['additional_instructions'] = (isset($details['additional_instructions'])) ? $details['additional_instructions'] : '';
  700. $details['custom_tokens'] = (isset($details['custom_tokens'])) ? $details['custom_tokens'] : array();
  701. $details['content_type'] = (isset($details['content_type'])) ? $details['content_type'] : $details['module'];
  702. $tokens = array();
  703. if (empty($tokens)) {
  704. $tokens = chado_node_generate_tokens($chado_node_api['base_table']);
  705. }
  706. $tokens = array_merge($tokens, $details['custom_tokens']);
  707. $token_list = chado_node_format_tokens($tokens);
  708. $current_format = chado_node_get_url_format($details['content_type'], $tokens);
  709. $details['default_option'] = (isset($details['default_option'])) ? $details['default_option'] : $current_format;
  710. // FORM PROPER
  711. $msg = t(
  712. 'Each synced %singular must have a unique page URL, however, %plural may have the
  713. same name if they are of different types or from different organisms. Therefore,
  714. we must be sure that the page URLs can uniquely identify the %singular being viewed.
  715. Select an option below that will uniquely identify all %plural on your site.'
  716. . $details['additional_instructions'],
  717. array('%singular' => $chado_node_api['record_type_title']['singular'],
  718. '%plural' => $chado_node_api['record_type_title']['plural'])
  719. );
  720. $form['set_url'] = array(
  721. '#type' => 'fieldset',
  722. '#title' => t($details['fieldset_title']),
  723. '#description' => $msg,
  724. '#collapsible' => TRUE,
  725. '#collapsed' => FALSE,
  726. '#prefix' => "<div id='set_url-fieldset'>",
  727. '#suffix' => '</div>',
  728. );
  729. $form['set_url']['content_type'] = array(
  730. '#type' => 'hidden',
  731. '#value' => $node_info[ $details['content_type'] ]['base'],
  732. );
  733. $details['options']['custom'] = 'Custom: See the text field below.';
  734. $form['set_url']['url_option'] = array(
  735. '#title' => t('%singular Page URL', array('%singular' => $chado_node_api['record_type_title']['singular'])),
  736. '#type' => 'radios',
  737. '#description' => t("Choose a URL type from the list above that is
  738. guaranteed to be unique for all %plural. If in doubt it is safest to choose
  739. the 'Unique Constaint' option as that guarantees uniqueness.",
  740. array('%plural' => $chado_node_api['record_type_title']['plural'])),
  741. '#required' => FALSE,
  742. '#options' => $details['options'],
  743. '#default_value' => (isset($details['options'][$details['default_option']])) ? $details['default_option'] : 'custom',
  744. );
  745. $form['set_url']['url_format_variable'] = array(
  746. '#type' => 'hidden',
  747. '#value' => $details['module'] . '_url_format'
  748. );
  749. $form['set_url']['custom_url'] = array(
  750. '#type' => 'textarea',
  751. '#title' => 'Custom Page URL',
  752. '#description' => 'You may rearrange elements in this text box to customize the page
  753. URLs. The available tokens are listed below. You can separate or include any text
  754. between the tokens. <strong>Important: be sure that whatever you choose
  755. will always be unique even considering future data that may be added. If in doubt,
  756. please select the unique constraint title option above.</strong>',
  757. '#default_value' => $current_format,
  758. '#rows' => 1
  759. );
  760. $form['set_url']['token_display'] = array(
  761. '#type' => 'fieldset',
  762. '#title' => 'Available Tokens',
  763. '#description' => 'Copy the token and paste it into the "Custom Page URL" text field above.',
  764. '#collapsible' => TRUE,
  765. '#collapsed' => TRUE
  766. );
  767. $form['set_url']['token_display']['content'] = array(
  768. '#type' => 'item',
  769. '#markup' => $token_list
  770. );
  771. $form['set_url']['tokens'] = array(
  772. '#type' => 'hidden',
  773. '#value' => serialize($tokens)
  774. );
  775. $form['set_url']['submit'] = array(
  776. '#type' => 'submit',
  777. '#value' => 'Set URLs',
  778. '#validate' => array('chado_add_admin_form_set_url_form_validate'),
  779. '#submit' => array('chado_add_admin_form_set_url_form_submit')
  780. );
  781. }
  782. /**
  783. * Implements hook_form_validate().
  784. * VALIDATE: validate the format.
  785. */
  786. function chado_add_admin_form_set_url_form_validate($form, $form_state) {
  787. // Ensure that all tokens used in the format are in the tokens list
  788. if (preg_match_all('/\[[^]]+\]/',$form_state['values']['custom_url'],$used_tokens)) {
  789. $token_list = unserialize($form_state['values']['tokens']);
  790. foreach ($used_tokens[0] as $token) {
  791. if (!array_key_exists($token,$token_list)) {
  792. form_set_error('custom_url', 'All tokens used must be in the "Available Tokens" list. Please make sure not to use [ or ] unless it\'s denoting a token');
  793. }
  794. }
  795. }
  796. }
  797. /**
  798. * Implements hook_form_submit().
  799. * SUBMIT: Actually add the format specified by chado_add_admin_form_set_title()
  800. */
  801. function chado_add_admin_form_set_url_form_submit($form, $form_state) {
  802. if ($form_state['values']['url_option'] == 'custom') {
  803. $format = $form_state['values']['custom_url'];
  804. }
  805. else {
  806. $format = $form_state['values']['url_option'];
  807. }
  808. // Add the format to the table for all new nodes.
  809. chado_node_add_token_format(
  810. 'url',
  811. $form_state['values']['content_type'],
  812. $format,
  813. $form_state['values']['tokens']
  814. );
  815. // Add a job to Update all existing nodes.
  816. global $user;
  817. tripal_add_job(
  818. "Set " . $form_state['values']['content_type'] . " URLs for all existing nodes.",
  819. $form_state['values']['content_type'],
  820. 'chado_update_existing_node_urls',
  821. array('content_type' => $form_state['values']['content_type']),
  822. $user->uid
  823. );
  824. }
  825. /**
  826. * Get the url format for a specific content type
  827. *
  828. * If the url format has not yet been set then the following will be done
  829. * 1) Check to see if there is a legacy url format set (features & stocks)
  830. * 2) Check if there is a defined default for this content type
  831. * 3) Create a format using any name fields and the unique constraint for the
  832. * base table associated with this content type
  833. *
  834. * Define a default for a specific content type by implementing a function of the name
  835. * [content type]_chado_node_default_url_format() that returns a string describing the
  836. * default format.
  837. *
  838. * @param $content_type
  839. * The name of the content (node) type you are interested in (ie: chado_feature)
  840. * @param $tokens
  841. * An array, passed by reference that is filled to include the tokens for this
  842. * node type. Each token is an array with the following keys:
  843. * -table: the name of the chado table
  844. * -field: the name of the field in the above table
  845. * -token: the token string (ie: [stock.stock_id])
  846. * -description: a very short description of the token (displayed when tokens are listed)
  847. * -location: the location of the value in a chado node variable with each level
  848. * separated by an arrow (->) symbol. For example, the location for $node->feature->type_id->name
  849. * is feature>type_id>name
  850. *
  851. * @return
  852. * A string containing tokens describing the default format for the url of nodes
  853. * of the specified content type.
  854. */
  855. function chado_node_get_url_format($content_type, &$tokens, $base_table = NULL) {
  856. $format_record_format = $format = '';
  857. $format_record_tokens = '';
  858. // Is there a url format set?
  859. $format_record = chado_node_get_token_format('url', $content_type, array('return_record' => TRUE));
  860. if (!empty($format_record)) {
  861. $format_record_format = $format = $format_record->format;
  862. $format_record_tokens = $tokens = $format_record->tokens;
  863. }
  864. // All three options below need the tokens to be generated so do that now
  865. if (empty($format)) {
  866. if (empty($base_table)) {
  867. $base_table = chado_node_get_base_table($content_type);
  868. }
  869. if (empty($tokens)) {
  870. $tokens = chado_node_generate_tokens($base_table);
  871. }
  872. }
  873. // 1) Check for legacy format
  874. if (empty($format)) {
  875. $format = chado_node_get_legacy_url_default($content_type);
  876. }
  877. // 2) Module-defined default format
  878. if (empty($format)) {
  879. $hook = $content_type . '_chado_node_default_url_format';
  880. if (function_exists($hook)) {
  881. $format = call_user_func($hook);
  882. }
  883. }
  884. // 3) Create unique constraint format
  885. if (empty($format)) {
  886. if (empty($base_table)) {
  887. $base_table = chado_node_get_base_table($content_type);
  888. }
  889. $format = chado_node_get_unique_constraint_format($base_table, 'url');
  890. }
  891. // Add the format to table so we can use it later
  892. // (optimization: to speed up bulk updates, don't update the record if
  893. // the format and tokens are unchanged)
  894. if ($format != $format_record_format || $tokens != $format_record_tokens) {
  895. chado_node_add_token_format('url', $content_type, $format, $tokens);
  896. }
  897. return $format;
  898. }
  899. /**
  900. * Handles legacy URL options
  901. *
  902. * Features, Projects & Stocks already had custom functionality to handle URL
  903. * setting before this API was created. That has since been removed but
  904. * to remain backwards compatible this function checks for those
  905. * old settings and translates them into new defaults.
  906. */
  907. function chado_node_get_legacy_url_default($content_type) {
  908. if ($content_type == 'chado_feature') {
  909. $legacy_format = variable_get('chado_feature_url_string', NULL);
  910. $legacy_tokens = array(
  911. '[id]' => '[feature.feature_id]',
  912. '[genus]' => '[feature.organism_id>organism.genus]',
  913. '[species]' => '[feature.organism_id>organism.species]',
  914. '[type]' => '[feature.type_id>cvterm.name]',
  915. '[uniquename]' => '[feature.uniquename]',
  916. '[name]' => '[feature.name]',
  917. );
  918. if ($legacy_format) {
  919. return str_replace(array_keys($legacy_tokens),$legacy_tokens, $legacy_format);
  920. }
  921. }
  922. elseif ($content_type == 'chado_stock') {
  923. $legacy_format = variable_get('chado_stock_url_string', NULL);
  924. $legacy_tokens = array(
  925. '[id]' => '[stock.stock_id]',
  926. '[genus]' => '[stock.organism_id>organism.genus]',
  927. '[species]' => '[stock.organism_id>organism.species]',
  928. '[type]' => '[stock.type_id>cvterm.name]',
  929. '[uniquename]' => '[stock.uniquename]',
  930. '[name]' => '[stock.name]',
  931. );
  932. if ($legacy_format) {
  933. return str_replace(array_keys($legacy_tokens),$legacy_tokens, $legacy_format);
  934. }
  935. }
  936. elseif ($content_type == 'chado_project') {
  937. $legacy_format = variable_get('chado_project_url_string', NULL);
  938. $legacy_tokens = array(
  939. '[id]' => '[project.project_id]',
  940. '[name]' => '[project.name]',
  941. );
  942. if ($legacy_format) {
  943. return str_replace(array_keys($legacy_tokens),$legacy_tokens, $legacy_format);
  944. }
  945. }
  946. return FALSE;
  947. }
  948. /**
  949. * @section
  950. * Tokens
  951. */
  952. /**
  953. * Save a format to be used by chado_get_node_title() or chado_get_node_url()
  954. *
  955. * @param $application
  956. * What the format is to be applied to. For example 'title' for generating node titles
  957. * and 'path' for generating node paths
  958. * @param $content_type
  959. * The name of the content type this format applies to (ie: $node->type)
  960. * @param $format
  961. * A string including tokens used to generate the title/path (which is based on $application)
  962. * @param $tokens
  963. * An array of tokens generated by chado_node_generate_tokens(). This is saved to ensure the
  964. * tokens that are available when the format is created are still available when it's used
  965. */
  966. function chado_node_add_token_format($application, $content_type, $format, $tokens) {
  967. if (is_array($tokens)) {
  968. $tokens = serialize($tokens);
  969. }
  970. $record = array(
  971. 'content_type' => $content_type,
  972. 'application' => $application,
  973. 'format' => $format,
  974. 'tokens' => $tokens
  975. );
  976. // Check if it already exists
  977. $id = db_query('SELECT tripal_format_id FROM {tripal_token_formats} WHERE content_type=:type AND application=:application', array(':type'=>$record['content_type'], ':application'=>$record['application']))->fetchField();
  978. if ($id) {
  979. drupal_write_record('tripal_token_formats',$record,array('content_type','application'));
  980. }
  981. else {
  982. drupal_write_record('tripal_token_formats',$record);
  983. }
  984. }
  985. /**
  986. * Get the format for the given application of a given content type (ie: the feature title)
  987. *
  988. * @param $application
  989. * What the format is to be applied to. For example 'title' for generating node titles
  990. * and 'path' for generating node paths
  991. * @param $content_type
  992. * The name of the content type this format applies to (ie: $node->type)
  993. * @param $options
  994. * An array of any of the following options:
  995. * - return_record: if TRUE this will return the entire record rather
  996. * than just the format string
  997. * @return
  998. * A string specifying the format
  999. */
  1000. function chado_node_get_token_format($application, $content_type, $options = array()) {
  1001. $format_record = db_select('tripal_token_formats','t')
  1002. ->fields('t')
  1003. ->condition('content_type', $content_type,'=')
  1004. ->condition('application', $application,'=')
  1005. ->execute()
  1006. ->fetchObject();
  1007. if (is_object($format_record)) {
  1008. if (isset($options['return_record'])) {
  1009. $format_record->tokens = unserialize($format_record->tokens);
  1010. return $format_record;
  1011. }
  1012. else {
  1013. return $format_record->format;
  1014. }
  1015. }
  1016. else {
  1017. return FALSE;
  1018. }
  1019. }
  1020. /**
  1021. * Generate a Readable but not necessarily unique format based on a given primary
  1022. * key token.
  1023. *
  1024. * For example, given the token [feature.type_id>cvterm.cvterm_id] you don't
  1025. * want the actual id indexed but instead would want the term name, [feature.type_id>cvterm.name]
  1026. */
  1027. function chado_node_get_readable_format($token) {
  1028. // First, lets break down the token into it's parts.
  1029. // 1. Remove containing brackets.
  1030. $parts = str_replace(array('[',']'),'',$token);
  1031. // 2. Break into table clauses.
  1032. $parts = explode('>',$parts);
  1033. // 3. Break each table clause into table & field.
  1034. foreach ($parts as $k => $v) {
  1035. $parts[$k] = explode('.', $v);
  1036. if (sizeof($parts[$k]) == 1) {
  1037. $parts[$k] = explode(':', $v);
  1038. }
  1039. }
  1040. $last_k = $k;
  1041. // Now, we want to find readable fields for the last table specified in the token.
  1042. // (ie: for cvterm in [feature.type_id>cvterm.cvterm_id])
  1043. $table = $parts[$last_k][0];
  1044. $format = array();
  1045. if ($table == 'organism') {
  1046. $format[] = preg_replace('/(\w+)\]$/', 'genus]', $token);
  1047. $format[] = preg_replace('/(\w+)\]$/', 'species]', $token);
  1048. $format[] = preg_replace('/(\w+)\]$/', 'common_name]', $token);
  1049. $format = $format[0] . ' ' . $format[1] . ' (' . $format[2] . ')';
  1050. }
  1051. elseif ($table == 'dbxref') {
  1052. $format[] = preg_replace('/(\w+)\]$/', 'accession]', $token);
  1053. $format[] = preg_replace('/(\w+)\]$/', 'db_id>db.name]', $token);
  1054. $format = $format[0] . ' (' . $format[1] . ')';
  1055. }
  1056. else {
  1057. $schema = chado_get_schema($table);
  1058. foreach ($schema['fields'] as $field_name => $details) {
  1059. if (preg_match('/name/',$field_name)) {
  1060. $format[] = preg_replace('/(\w+)\]$/', $field_name.']', $token);
  1061. }
  1062. }
  1063. $format = implode(', ',$format);
  1064. }
  1065. if (empty($format)) {
  1066. return FALSE;
  1067. }
  1068. return $format;
  1069. }
  1070. /**
  1071. * Generate the unique constraint for a given base table using the
  1072. * Chado Schema API definition
  1073. *
  1074. * @param $base_table
  1075. * The base table to generate the unique constraint format for
  1076. * @param $format_type
  1077. * The type of format to return. This should be one of 'title' or 'url'.
  1078. * @return
  1079. * A format string including tokens describing the unique constraint
  1080. * including all name fields
  1081. */
  1082. function chado_node_get_unique_constraint_format($base_table, $format_type = 'title') {
  1083. $table_descrip = chado_get_schema($base_table);
  1084. // Find the name/uniquename from the base table
  1085. $names = array();
  1086. foreach($table_descrip['fields'] as $field_name => $field) {
  1087. if (preg_match('/name/',$field_name)) {
  1088. $names[$field_name] = "[$base_table.$field_name]";
  1089. }
  1090. }
  1091. uksort($names, 'tripal_sort_key_length_asc');
  1092. // Get tokens to match the unique key
  1093. $tokens = array();
  1094. foreach ($table_descrip['unique keys'] as $keyset) {
  1095. foreach ($keyset as $key) {
  1096. if (isset($names[$key])) {
  1097. // Do not add it into the tokens if it's already in the names
  1098. // since we don't want it repeated 2X
  1099. }
  1100. elseif ($key == 'type_id') {
  1101. $tokens[$key] = "[$base_table.type_id>cvterm.name]";
  1102. }
  1103. elseif ($key == 'organism_id') {
  1104. $tokens[$key] = "[$base_table.organism_id>organism.abbreviation]";
  1105. }
  1106. else {
  1107. $tokens[$key] = "[$base_table.$key]";
  1108. }
  1109. }
  1110. }
  1111. if ($format_type == 'title') {
  1112. $format = implode(', ',$names) . ' (' . implode(', ',$tokens) . ')';
  1113. }
  1114. elseif ($format_type == 'url') {
  1115. // We don't want to put more than one name in the URL. Thus we are
  1116. // arbitrarily grabbing the longest name token since it it likely the
  1117. // uniquename.
  1118. $format = implode('/',$tokens) . '/' . array_pop($names);
  1119. }
  1120. else {
  1121. $format = FALSE;
  1122. tripal_report_error(
  1123. 'tripal_node_api',
  1124. TRIPAL_ERROR,
  1125. 'Unable to determine the format for the unique contraint since the format type (%format-type) is not supported (only "title" and "url" are at this time).',
  1126. array('%format-type' => $format_type)
  1127. );
  1128. }
  1129. return $format;
  1130. }
  1131. /**
  1132. * Generate tokens for a particular base table
  1133. *
  1134. * @param $base_table
  1135. * The name of the chado table you would like to generate tokens for
  1136. * @param $token_prefix
  1137. * RECURSIVE ARG: Used to determine the generic token based on previous interations.
  1138. * For example, when adding cvterm fields to a feature token, the token_prefix is "feature.type_id"
  1139. * so that resulting tokens can be "feature.type_id>cvterm.*" (ie: [feature.type_id>cvterm.name] )
  1140. * @param $location_prefix
  1141. * RECURSIVE ARG: Used to keep track of the location of the value based on previous interations.
  1142. * For example, when adding cvterm fields to a feature token, the location_prefix is "feature > type_id"
  1143. * so that resulting tokens can be "feature > type_id > *" (ie: feature > type_id > name)
  1144. * @return
  1145. * An array of available tokens where the key is the table.field and the value is an array
  1146. * with the following keys:
  1147. * -table: the name of the chado table
  1148. * -field: the name of the field in the above table
  1149. * -token: the token string (ie: [stock.stock_id])
  1150. * -description: a very short description of the token (displayed when tokens are listed)
  1151. * -location: the location of the value in a chado node variable with each level
  1152. * separated by an arrow (->) symbol. For example, the location for $node->feature->type_id->name
  1153. * is feature>type_id>name
  1154. */
  1155. function chado_node_generate_tokens($base_table, $token_prefix = FALSE, $location_prefix = FALSE) {
  1156. $tokens = array();
  1157. $table_descrip = chado_get_schema($base_table);
  1158. foreach ($table_descrip['fields'] as $field_name => $field_details) {
  1159. if (empty($token_prefix)) {
  1160. $token = '[' . $base_table . '.' . $field_name . ']';
  1161. $location = implode(' > ',array($base_table, $field_name));
  1162. }
  1163. else {
  1164. $token = '[' . $token_prefix . '>' . $base_table . '.' . $field_name . ']';
  1165. $location = $location_prefix . ' > ' . $field_name;
  1166. }
  1167. $tokens[$token] = array(
  1168. 'name' => ucwords(str_replace('_',' ',$base_table)) . ': ' . ucwords(str_replace('_',' ',$field_name)),
  1169. 'table' => $base_table,
  1170. 'field' => $field_name,
  1171. 'token' => $token,
  1172. 'description' => array_key_exists('description', $field_details) ? $field_details['description'] : '',
  1173. 'location' => $location
  1174. );
  1175. if (!array_key_exists('description', $field_details) or preg_match('/TODO/',$field_details['description'])) {
  1176. $tokens[$token]['description'] = 'The '.$field_name.' field of the '.$base_table.' table.';
  1177. }
  1178. }
  1179. // RECURSION:
  1180. // Follow the foreign key relationships recursively
  1181. if (array_key_exists('foreign keys', $table_descrip)) {
  1182. foreach ($table_descrip['foreign keys'] as $table => $details) {
  1183. foreach ($details['columns'] as $left_field => $right_field) {
  1184. if (empty($token_prefix)) {
  1185. $sub_token_prefix = $base_table . '.' . $left_field;
  1186. $sub_location_prefix = implode(' > ',array($base_table, $left_field));
  1187. }
  1188. else {
  1189. $sub_token_prefix = $token_prefix . '>' . $base_table . ':' . $left_field;
  1190. $sub_location_prefix = $location_prefix . ' > ' . implode(' > ',array($base_table, $left_field));
  1191. }
  1192. $sub_tokens = chado_node_generate_tokens($table, $sub_token_prefix, $sub_location_prefix);
  1193. if (is_array($sub_tokens)) {
  1194. $tokens = array_merge($tokens, $sub_tokens);
  1195. }
  1196. }
  1197. }
  1198. }
  1199. return $tokens;
  1200. }
  1201. /**
  1202. * Retrieve the value of the token from the node based on the $token_info['location']
  1203. *
  1204. * @param $token_info
  1205. * An array of information about the token including:
  1206. * -table: the name of the chado table
  1207. * -field: the name of the field in the above table
  1208. * -token: the token string (ie: [stock.stock_id])
  1209. * -description: a very short description of the token (displayed when tokens are listed)
  1210. * -location: the location of the value in a chado node variable with each level
  1211. * separated by an arrow (>) symbol. For example, the location for $node->feature->type_id->name
  1212. * is feature>type_id>name
  1213. * @param $node
  1214. * The node to get the value of the token
  1215. *
  1216. * @return
  1217. * The value of the token
  1218. */
  1219. function chado_get_token_value($token_info, $node, $options = array()) {
  1220. $token = $token_info['token'];
  1221. $table = $token_info['table'];
  1222. $var = $node;
  1223. $supress_errors = (isset($options['supress_errors'])) ? $options['supress_errors'] : FALSE;
  1224. // Iterate through each portion of the location string. An example string
  1225. // might be: stock > type_id > name.
  1226. $location = explode('>', $token_info['location']);
  1227. foreach ($location as $index) {
  1228. $index = trim($index);
  1229. // if $var is an object then it is the $node object or a table
  1230. // that has been expanded.
  1231. if (is_object($var)) {
  1232. // check to see if the index is a member of the object. If so,
  1233. // then reset the $var to this value.
  1234. if (property_exists($var, $index)) {
  1235. $var = $var->$index;
  1236. }
  1237. else {
  1238. if (!$supress_errors) {
  1239. tripal_report_error('chado_node_api', TRIPAL_WARNING,
  1240. 'Tokens: Unable to determine the value of %token. Things went awry when trying ' .
  1241. 'to access \'%index\' for the following: \'%var\'.',
  1242. array('%token' => $token, '%index' => $index, '%var' => print_r($var,TRUE))
  1243. );
  1244. }
  1245. return '';
  1246. }
  1247. }
  1248. // if the $var is an array then there are multiple instances of the same
  1249. // table in a FK relationship (e.g. relationship tables)
  1250. elseif (is_array($var)) {
  1251. $var = $var[$index];
  1252. }
  1253. else {
  1254. if (!$supress_errors) {
  1255. tripal_report_error('chado_node_api', TRIPAL_WARNING,
  1256. 'Tokens: Unable to determine the value of %token. Things went awry when trying ' .
  1257. 'to access \'%index\' for the following: \'%var\'.',
  1258. array('%token' => $token, '%index' => $index, '%var' => print_r($var,TRUE))
  1259. );
  1260. }
  1261. return '';
  1262. }
  1263. }
  1264. return $var;
  1265. }
  1266. /**
  1267. * Format a set of tokens for consistent display
  1268. *
  1269. * @param $tokens
  1270. * An array of tokens from chado_node_generate_tokens()
  1271. *
  1272. * @return
  1273. * HTML displaying the token list
  1274. */
  1275. function chado_node_format_tokens($tokens) {
  1276. $header = array('name' => 'Name','token' => 'Token','description' => 'Description');
  1277. $rows = array();
  1278. usort($tokens, 'chado_sort_tokens_by_location');
  1279. foreach ($tokens as $token) {
  1280. $rows[] = array(
  1281. 'name' => $token['name'],
  1282. 'token' => $token['token'],
  1283. 'description' => $token['description']
  1284. );
  1285. }
  1286. $table = array(
  1287. 'header' => $header,
  1288. 'rows' => $rows,
  1289. 'attributes' => array(
  1290. 'id' => 'tripal_tokens',
  1291. 'class' => 'tripal-data-table'
  1292. ),
  1293. 'sticky' => FALSE,
  1294. 'caption' => '',
  1295. 'colgroups' => array(),
  1296. 'empty' => '',
  1297. );
  1298. return theme_table($table);
  1299. }
  1300. /**
  1301. * Returns the "location" as specified in the token information based on the token.
  1302. */
  1303. function chado_node_get_location_from_token($token) {
  1304. if (is_array($token) and isset($token['location'])) {
  1305. return $token['location'];
  1306. }
  1307. // If we have been given the token as a string, we can still determine the location
  1308. // but it takes more work...
  1309. // First, lets clarify what the location is: the location shows which keys in which
  1310. // order need to be travelled in order to access the value. For example, the token
  1311. // [feature.organism_id>organism.genus] would have a location of
  1312. // feature > organism_id > genus to show that the value is at
  1313. // $node->feature->organism->genus.
  1314. elseif (is_string($token)) {
  1315. // First, lets break down the token into it's parts.
  1316. // 1. Remove containing brackets.
  1317. $parts = str_replace(array('[',']'),'',$token);
  1318. // 2. Break into table clauses.
  1319. $parts = explode('>',$parts);
  1320. // 3. Break each table clause into table & field.
  1321. foreach ($parts as $k => $v) {
  1322. $parts[$k] = explode('.', $v);
  1323. if (sizeof($parts[$k]) == 1) {
  1324. $parts[$k] = explode(':', $v);
  1325. }
  1326. }
  1327. // This is a base level field that is not a foreign key.
  1328. if (sizeof($parts) == 1 AND sizeof($parts[0]) == 2) {
  1329. return $parts[0][0] . ' > ' . $parts[0][1];
  1330. }
  1331. // Darn, we have at least one foreign key...
  1332. elseif (sizeof($parts) > 1 AND sizeof($parts[0]) == 2) {
  1333. $location = $parts[0][0] . ' > ' . $parts[0][1];
  1334. foreach ($parts as $k => $p) {
  1335. if ($k != 0 AND isset($p[1])) {
  1336. $location .= ' > ' . $p[1];
  1337. }
  1338. }
  1339. return $location;
  1340. }
  1341. else {
  1342. return FALSE;
  1343. }
  1344. }
  1345. }
  1346. /**
  1347. * This sorts tokens first by depth (ie: stock.* is before stock.*>subtable.*) and
  1348. * then alphabetically within a level (ie: stock.name comes before stock.type_id)
  1349. *
  1350. * This is a usort callback and shouldn't be called directly. To use:
  1351. * usort($tokens, 'chado_sort_tokens_by_location');
  1352. */
  1353. function chado_sort_tokens_by_location($tokenA, $tokenB) {
  1354. // First check if they're the same
  1355. if ($tokenA['location'] == $tokenB['location']) {
  1356. return 0;
  1357. }
  1358. // Then check if there's a difference in depth
  1359. // For example, "stock > type_id" comes before "stock > type_id > name"
  1360. $tokenA_depth = substr_count($tokenA['location'],'>');
  1361. $tokenB_depth = substr_count($tokenB['location'],'>');
  1362. if ($tokenA_depth != $tokenB_depth) {
  1363. return ($tokenA_depth < $tokenB_depth) ? -1 : 1;
  1364. }
  1365. // If the depth is equal then just use alphabetical basic string compare
  1366. return ($tokenA['location'] < $tokenB['location']) ? -1 : 1;
  1367. }
  1368. /**
  1369. * Sorts an associative array by key length where sorter keys will be first
  1370. *
  1371. * This is a uksort callback and shouldn't be called directly. To use;
  1372. * uksort($arr, 'tripal_sort_key_length_asc');
  1373. */
  1374. function tripal_sort_key_length_asc($a, $b) {
  1375. if (strlen($a) == strlen($b)) {
  1376. return 0;
  1377. }
  1378. elseif (strlen($a) > strlen($b)) {
  1379. return 1;
  1380. }
  1381. else {
  1382. return -1;
  1383. }
  1384. }