tripal.entities.api.inc 59 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780
  1. <?php
  2. /**
  3. * @file
  4. * Provides an application programming interface (API) for working with
  5. * TripalEntity content types (bundles) and their entities.
  6. *
  7. */
  8. /**
  9. * @defgroup tripal_entities_api Tripal Entities
  10. * @ingroup tripal_api
  11. * @{
  12. * Provides an application programming interface (API) for working with
  13. * TripalEntity content types (bundles) and their entities.
  14. *
  15. * Bundles (Content Types): Bundles are types of content in a Drupal site.
  16. * By default, Drupal provides the Basic Page and Article content types,
  17. * and Drupal allows a site developer to create new content types on-the-fly
  18. * using the administrative interface--no programming required. Tripal also
  19. * provides several Content Type by default. During installation of Tripal the
  20. * Organism, Gene, Project, Analysis and other content types are created
  21. * automatically. The site developer can then create new content types for
  22. * different biological data--again, without any programming required.
  23. *
  24. * In order to to assist with data exchange and use of common data formats,
  25. * Tripal Bundles are defined using a controlled vocabulary term (cvterm).
  26. * For example, a "Gene" Bundle is defined using the Sequence Ontology term for
  27. * gene whose term accession is: SO:0000704. This mapping allows Tripal to
  28. * compare content across Tripal sites, and expose data to computational tools
  29. * that understand these vocabularies. By default, Tripal uses Chado as its
  30. * primary data storage back-end.
  31. *
  32. * Entity: An entity is a discrete data record. Entities are most commonly
  33. * seen as "pages" on a Drupal web site and are instances of a Bundle
  34. * (i.e content type). When data is published on a Tripal site such as
  35. * organisms, genes, germplasm, maps, etc., each record is represented by a
  36. * single entity with an entity ID as its only attribute. All other
  37. * information that the entity provides is made available via Fields.
  38. *
  39. * For more information please see:
  40. * http://tripal.info/tutorials/v3.x/developers-handbook/structure
  41. * @}
  42. *
  43. */
  44. /**
  45. * @section
  46. * Hooks.
  47. */
  48. /**
  49. * Allows a module to make changes to an entity object after creation.
  50. *
  51. * This function is added by Tripal to allow datastore backends to add
  52. * addition properties to the entity that they themselves will use later.
  53. *
  54. * @param $entity
  55. * @param $entity_type
  56. *
  57. * @ingroup tripal_entities_api
  58. */
  59. function hook_entity_create(&$entity, $entity_type) {
  60. }
  61. /**
  62. * Allows a module to perform tasks after a TripalBundle object is created.
  63. *
  64. * @param $bundle
  65. * The newly created TripalBundle object.
  66. * @param $storage_args
  67. * Arguments passed to the storage backend for this bundle. These arguments
  68. * typically provide details for how to associate this bundle with records
  69. * in the storage system. Each storage system will have its own set of
  70. * arguments that it will expect.
  71. *
  72. * @ingroup tripal_entities_api
  73. */
  74. function hook_bundle_create(&$bundle, $storage_args) {
  75. }
  76. /**
  77. * Allows a module to perform tasks after fields are added to a TripalBundle.
  78. *
  79. * @param $bundle
  80. * The newly created TripalBundle object.
  81. *
  82. * @ingroup tripal_entities_api
  83. */
  84. function hook_bundle_postcreate(&$bundle) {
  85. }
  86. /**
  87. * Allows a module to add admin notifications to the associated tripal table
  88. * during the cron run.
  89. *
  90. * @ingroup tripal_entities_api
  91. */
  92. function hook_tripal_cron_notification() {
  93. }
  94. /**
  95. * Allows a module to perform tasks before a TripalBundle object is deleted.
  96. *
  97. * @param $bundle
  98. * The newly created TripalBundle object.
  99. *
  100. * @ingroup tripal_entities_api
  101. */
  102. function hook_bundle_delete($bundle) {
  103. }
  104. /**
  105. * Implement this hook to define default formats for Tripal Content Types.
  106. *
  107. * @param TripalBundle $bundle
  108. * A tripal content type entity with information to be used for determining
  109. * the default title format.
  110. * @param array $available_tokens
  111. * An array of available tokens for this particular tripal content type.
  112. *
  113. * @return array
  114. * An array of potential formats. The lightest weighted format suggested by
  115. * all modules will be chosen.
  116. * Each array item should consist of a 'weight' and 'format'. See the hook
  117. * implementation below for examples.
  118. * - weight: an integer used to determine priority of suggestions.
  119. * The smaller/lighter the number the higher the priority.
  120. * Best practice is to use a weight less than 0 for extension modules.
  121. * specifically, -2 is a good weight for calculated formats and -5 is a
  122. * good weight for hard-coded formats specific to a given type.
  123. * - format: a string including approved tokens used to determine the title
  124. * on Tripal content pages.
  125. *
  126. * @ingroup tripal_entities_api
  127. */
  128. function hook_tripal_default_title_format($bundle, $available_tokens) {
  129. $format = [];
  130. // If you want to suggest a default format for a particular vocabulary term:
  131. //---------------------------------------------------------------------------
  132. // Load the term associated with this Tripal Content type.
  133. $term = entity_load('TripalTerm', ['id' => $bundle->term_id]);
  134. $term = reset($term);
  135. // If it's the term you are interested in then suggest a format.
  136. if ($term->name == 'organism') {
  137. // To suggest a format, add an element to the array with a format & weight
  138. // key.
  139. $format[] = [
  140. // This is the format/pattern you suggest be used to determine the title
  141. // of organism pages.
  142. 'format' => '[organism__genus] [organism__species]',
  143. // The weight/priority of your suggestion.
  144. 'weight' => -5,
  145. ];
  146. }
  147. // Say you know that in your particular site, all 'names' are required
  148. // and you want to only use the human-readable name:
  149. //---------------------------------------------------------------------------
  150. $name_field = preg_grep('/__name]$/', array_keys($available_tokens));
  151. $name_field = reset($name_field);
  152. if (is_string($name_field)) {
  153. $format[] = [
  154. 'format' => $name_field,
  155. 'weight' => -2,
  156. ];
  157. }
  158. return $format;
  159. }
  160. /**
  161. * @section
  162. * Entity.
  163. */
  164. /**
  165. * A replacement for the entity_load function of Drupal.
  166. *
  167. * This function should be used for loading of Tripal Entities. It provides
  168. * greater control to limit which fields are loaded with the entity. The
  169. * entity_load() function of Drupal will automatically attach all fields at
  170. * once but this may not be desired as some fields can be complex and large, and
  171. * the site developer may desire loading of fields via AJAX or the user of
  172. * web services may wish to specify the fields they want to include.
  173. *
  174. * @param $entity_type :
  175. * The entity type to load, e.g. node or user.
  176. * @param $ids
  177. * An array of entity IDs, or FALSE to load all entities.
  178. * @param $reset : Whether to reset the internal cache for the requested entity
  179. * type. Defaults to FALSE.
  180. * @param $field_ids
  181. * A list of numeric field IDs that should be loaded. The
  182. * TripalField named 'content_type' is always automatically added.
  183. * @param $cache
  184. * When loading of entities they can be cached with Drupal for later
  185. * faster loading. However, this can cause memory issues when running
  186. * Tripal jobs that load lots of entities. Caching of entities can
  187. * be disabled to improve memory performance by setting this to FALSE.
  188. *
  189. * @return
  190. * An array of entity objects indexed by their ids. When no results are
  191. * found, an empty array is returned.
  192. *
  193. * @ingroup tripal_entities_api
  194. */
  195. function tripal_load_entity($entity_type, $ids = FALSE, $reset = FALSE,
  196. $field_ids = [], $cache = TRUE) {
  197. // The $conditions is deprecated in the funtion arguments of entity_load
  198. // so it was removed from the parameters of this function as well. But
  199. // the load() function of the entity controller still expects it so set it
  200. // to an empty array.
  201. $conditions = [];
  202. // If this isn't a TripalEntity then just load it the old fashioned way
  203. // although caching will not be used if it not specifically set to FALSE.
  204. if ($entity_type != 'TripalEntity') {
  205. return entity_load($entity_type, $ids, $conditions, $reset);
  206. }
  207. // Get the entity controller and clear the cache if requested (default).
  208. $ec = entity_get_controller($entity_type);
  209. if ($reset) {
  210. $ec->resetCache();
  211. }
  212. return $ec->load($ids, $conditions, $field_ids, $cache);
  213. }
  214. /**
  215. * Retrieves a TripalTerm entity that matches the given arguments.
  216. *
  217. * @param $values
  218. * An associative array used to match a term.
  219. * Valid keys may be:
  220. * - vocabulary: Must always be used with accession to uniquely
  221. * identify a term.
  222. * - accession: Must always be used with vocabulary to uniquely
  223. * identify a term.
  224. * - term_id: Can be used alone to uniquely identify a term.
  225. *
  226. * @return
  227. * A TripalTerm entity object or NULL if not found.
  228. *
  229. * @ingroup tripal_entities_api
  230. */
  231. function tripal_load_term_entity($values) {
  232. $vocabulary = array_key_exists('vocabulary', $values) ? $values['vocabulary'] : '';
  233. $accession = array_key_exists('accession', $values) ? $values['accession'] : '';
  234. $term_id = array_key_exists('term_id', $values) ? $values['term_id'] : '';
  235. $term = NULL;
  236. if ($vocabulary and $accession) {
  237. $query = db_select('tripal_term', 'tt');
  238. $query->join('tripal_vocab', 'tv', 'tv.id = tt.vocab_id');
  239. $query->fields('tt', ['id'])
  240. ->fields('tv', ['vocabulary'])
  241. ->condition('tv.vocabulary', $vocabulary)
  242. ->condition('tt.accession', $accession);
  243. $term = $query->execute()->fetchObject();
  244. }
  245. else {
  246. if ($term_id) {
  247. $query = db_select('tripal_term', 'tt');
  248. $query->fields('tt', ['id'])
  249. ->condition('tt.id', $term_id);
  250. $term = $query->execute()->fetchObject();
  251. }
  252. }
  253. if ($term) {
  254. $entity = entity_load('TripalTerm', [$term->id]);
  255. return reset($entity);
  256. }
  257. return NULL;
  258. }
  259. /**
  260. * Retrieves a TripalVocab entity that maches the given arguments.
  261. *
  262. * @param $values
  263. * An associative array used to match a vocabulary.
  264. * The valid keys are:
  265. * - vocab_id: integer id of the vocabulary.
  266. * - vocabulary: string name of vocabulary.
  267. *
  268. * @return
  269. * A TripalVocab entity object or NULL if not found.
  270. *
  271. * @ingroup tripal_entities_api
  272. */
  273. function tripal_load_vocab_entity($values) {
  274. $vocabulary = array_key_exists('vocabulary', $values) ? $values['vocabulary'] : '';
  275. $vocab_id = array_key_exists('vocab_id', $values) ? $values['vocab_id'] : '';
  276. $vocab = NULL;
  277. $query = db_select('tripal_vocab', 'tv')
  278. ->fields('tv');
  279. if ($vocabulary) {
  280. $query->condition('tv.vocabulary', $vocabulary);
  281. }
  282. if ($vocab_id) {
  283. $query->condition('tv.id', $vocab_id);
  284. }
  285. $vocab = $query->execute()->fetchObject();
  286. if ($vocab) {
  287. $entity = entity_load('TripalVocab', [$vocab->id]);
  288. return reset($entity);
  289. }
  290. return NULL;
  291. }
  292. /**
  293. * Retrieves a TripalBundle entity that matches the given arguments.
  294. *
  295. * @param $values
  296. * An associative array used to match a bundle. Valid keys may:
  297. * - id: the numeric id of the bundle.
  298. * - name: the bundle name (e.g. 'bio_data_234')
  299. * - label: the bundle label (e.g. 'Organism')
  300. * - term_id: the term ID to which the bundle belongs
  301. * - accession: the full acccession for the bundle (e.g. OBI:0100026)
  302. *
  303. * @return
  304. * A TripalBundle entity object or NULL if not found.
  305. *
  306. * @ingroup tripal_entities_api
  307. */
  308. function tripal_load_bundle_entity($values) {
  309. $query = db_select('tripal_bundle', 'tb');
  310. $query->fields('tb');
  311. if (array_key_exists('id', $values)) {
  312. $query->condition('tb.id', $values['id']);
  313. }
  314. if (array_key_exists('name', $values)) {
  315. $query->condition('tb.name', $values['name']);
  316. }
  317. if (array_key_exists('label', $values)) {
  318. $query->condition('tb.label', $values['label']);
  319. }
  320. if (array_key_exists('term_id', $values)) {
  321. $query->condition('tb.term_id', $values['term_id']);
  322. }
  323. if (array_key_exists('accession', $values)) {
  324. list($vocab, $accession) = explode(':', $values['accession'], 2);
  325. $term = tripal_load_term_entity([
  326. 'vocabulary' => $vocab,
  327. 'accession' => $accession,
  328. ]);
  329. if (!$term) {
  330. return NULL;
  331. }
  332. $query->condition('tb.term_id', $term->id);
  333. }
  334. $bundle = $query->execute()->fetchObject();
  335. if ($bundle) {
  336. $entity = entity_load_unchanged('TripalBundle', $bundle->id);
  337. return $entity;
  338. }
  339. return NULL;
  340. }
  341. /**
  342. * Allows a module to write to the admin notification table.
  343. *
  344. * @param $title
  345. * A generic phrase indicating what the notification is for.
  346. * @param $details
  347. * A human-readable sentence or two describing the issue.
  348. * @param $type
  349. * A one word type indicating the type of notification. Tripal types include:
  350. * Jobs, Fields.
  351. * If no type is required please pass NULL.
  352. * @param $actions
  353. * A serialized PHP associative array containing the link and URL for each
  354. * action.
  355. * If not type is required please pass NULL.
  356. * @param $submitter_id
  357. * A unique ID provided by the submitter for checking to make sure that the
  358. * notification is not added more than once.
  359. *
  360. * @ingroup tripal_entities_api
  361. */
  362. function tripal_add_notification($title, $details, $type, $actions, $submitter_id) {
  363. $transaction = db_transaction();
  364. try {
  365. // Check the notification isn't already in the admin notification table.
  366. $dedup = db_select('tripal_admin_notfications', 'tan')
  367. ->fields('tan')
  368. ->condition('submitter_id', $submitter_id, '=')
  369. ->execute()->fetchAll();
  370. if (empty($dedup)) {
  371. $record = new stdClass;
  372. $record->details = $details;
  373. $record->title = $title;
  374. $record->submitter_id = $submitter_id;
  375. $record->actions = serialize($actions);
  376. $record->enabled = 1;
  377. $record->type = $type;
  378. $success = drupal_write_record('tripal_admin_notfications', $record);
  379. }
  380. } catch (Exception $e) {
  381. $transaction->rollback();
  382. watchdog('tripal_cron', 'Could not write notification to database.');
  383. }
  384. }
  385. /**
  386. * Creates a new Tripal Entity type (i.e. bundle).
  387. *
  388. * @param $args
  389. * An array of arguments that must include the following keys:
  390. * - vocabulary: The abbreviated vocabulary for the vocabulary
  391. * (e.g. RO, SO, PATO).
  392. * - accession: The unique term ID in the vocabulary $vocabulary
  393. * (i.e. an accession).
  394. * - term_name: A human-readable name for this term. This will became
  395. * the name that appears for the content type. In practice, this
  396. * should be the name of the term. (E.g. the name for SO:0000704 is gene).
  397. * - label: An alternative bundle label. This will be used instead of the
  398. * term name, for cases where the vocabulary term is not human readable.
  399. *
  400. * @param $job
  401. * The job ID if this is launched via a job.
  402. *
  403. * @return
  404. * The bundle object or FALSE if failure.
  405. *
  406. * @ingroup tripal_entities_api
  407. */
  408. function tripal_create_bundle($args, $job = NULL) {
  409. $vocabulary = $args['vocabulary'];
  410. $accession = $args['accession'];
  411. $term_name = $args['term_name'];
  412. $storage_args = $args['storage_args'];
  413. $label = $args['label'];
  414. $message_args = [
  415. 'job' => $job,
  416. 'print' => TRUE,
  417. 'watchdog' => TRUE,
  418. ];
  419. // tripal_report_error('tripal_entities', TRIPAL_INFO,
  420. // "Creation of a content type is performed using a database transaction. " .
  421. // "If it fails or is terminated prematurely then all insertions and " .
  422. // "updates are rolled back and will not be found in the database",
  423. // [], $message_args);
  424. $transaction = db_transaction();
  425. try {
  426. // First create the TripalVocab if it doesn't already exist.
  427. $vocab = tripal_load_vocab_entity(['vocabulary' => $vocabulary]);
  428. if (!$vocab) {
  429. $vocab = entity_get_controller('TripalVocab')->create(['vocabulary' => $vocabulary]);
  430. if ($vocab) {
  431. $vocab->save();
  432. }
  433. else {
  434. $transaction->rollback();
  435. tripal_report_error('tripal_entities', TRIPAL_ERROR,
  436. 'Unable to create TripalVocab :vocab', [':vocab' => $vocabulary], $message_args);
  437. return FALSE;
  438. }
  439. }
  440. // Next create the TripalTerm if it doesn't already exist.
  441. $term = tripal_load_term_entity([
  442. 'vocabulary' => $vocabulary,
  443. 'accession' => $accession,
  444. ]);
  445. if (!$term) {
  446. $targs = [
  447. 'vocab_id' => $vocab->id,
  448. 'accession' => $accession,
  449. 'name' => $term_name,
  450. ];
  451. $term = entity_get_controller('TripalTerm')->create($targs);
  452. if ($term) {
  453. $term = $term->save();
  454. }
  455. else {
  456. $transaction->rollback();
  457. tripal_report_error('tripal_entities', TRIPAL_ERROR,
  458. 'Unable to create TripalTerm :term', [':term' => $term_name], $message_args);
  459. return FALSE;
  460. }
  461. }
  462. // If the bundle doesn't already exist, then add it.
  463. $bundle_name = 'bio_data_' . $term->id;
  464. $einfo = entity_get_info('TripalEntity');
  465. if (!in_array($bundle_name, array_keys($einfo['bundles']))) {
  466. // Make the label for the content type have capitalized words. The
  467. // exception is 'mRNA' which we know should not be uppercased.
  468. if (!$label) {
  469. $label = ucwords(preg_replace('/_/', ' ', $term_name));
  470. if ($term_name == 'mRNA') {
  471. $label = $term_name;
  472. }
  473. }
  474. // Insert the bundle.
  475. db_insert('tripal_bundle')
  476. ->fields([
  477. 'label' => $label,
  478. 'type' => 'TripalEntity',
  479. 'name' => $bundle_name,
  480. 'term_id' => $term->id,
  481. ])
  482. ->execute();
  483. }
  484. $bundle = tripal_load_bundle_entity(['name' => $bundle_name]);
  485. if (!$bundle) {
  486. $transaction->rollback();
  487. tripal_report_error('tripal_entities', TRIPAL_ERROR,
  488. 'Unable to create Tripal Bundle :name.', [':name' => $bundle_name], $message_args);
  489. return FALSE;
  490. }
  491. $modules = module_implements('bundle_create');
  492. foreach ($modules as $module) {
  493. $function = $module . '_bundle_create';
  494. if (function_exists($function)) {
  495. $function($bundle, $storage_args);
  496. }
  497. }
  498. // Clear the entity cache so that Drupal will read our
  499. // hook_entity_info() implementation.
  500. global $language;
  501. $langcode = $language->language;
  502. cache_clear_all("entity_info:$langcode", 'cache');
  503. variable_set('menu_rebuild_needed', TRUE);
  504. // Get the bundle object.
  505. $bundle = tripal_load_bundle_entity(['name' => $bundle_name]);
  506. if (!$bundle) {
  507. $transaction->rollback();
  508. tripal_report_error('tripal_entities', TRIPAL_ERROR,
  509. 'Unable to load Tripal Bundle :name after cache clear.', [':name' => $bundle_name], $message_args);
  510. return FALSE;
  511. }
  512. // Set the bundle category
  513. $category = array_key_exists('category', $args) ? $args['category'] : 'Other';
  514. tripal_set_bundle_variable('bundle_category', $bundle->id, $category);
  515. // Attache the bundle fields.
  516. tripal_create_bundle_fields($bundle, $term);
  517. // Specifically commiting here since we have a fully featured bundle.
  518. // Post-create hook implementations assume we have a
  519. // created bundle so we don't want to rollback if a
  520. // custom implementation causes an exception.
  521. unset($transaction);
  522. } catch (Exception $e) {
  523. $transaction->rollback();
  524. $message_args['drupal_set_message'] = TRUE;
  525. tripal_report_error('tripal_entities', TRIPAL_ERROR,
  526. "Failed to create content type: %message.", ['%message' => $e->getMessage()], $message_args);
  527. return FALSE;
  528. }
  529. // Call any custom hook_bundle_postcreate() implementations.
  530. // This is done outside of the try/catch & transaction
  531. // since it occurs after creation and thus should not cause
  532. // a rollback of the creation on error.
  533. $modules = module_implements('bundle_postcreate');
  534. foreach ($modules as $module) {
  535. $function = $module . '_bundle_postcreate';
  536. if (function_exists($function)) {
  537. $function($bundle);
  538. }
  539. }
  540. // Set admin access for the new bundle.
  541. tripal_admin_access($bundle);
  542. // Report that we're done.
  543. tripal_report_error('tripal_entities', TRIPAL_INFO, "Done.", [], $message_args);
  544. return $bundle;
  545. }
  546. /**
  547. * Retrieves a list of the content types.
  548. *
  549. * @return
  550. * An array of bundles. Each bundle is an object containing information
  551. * about that bundle.
  552. *
  553. * @ingroup tripal_entities_api
  554. */
  555. function tripal_get_content_types() {
  556. return db_select('tripal_bundle', 'tb')
  557. ->fields('tb')
  558. ->execute()
  559. ->fetchAll();
  560. }
  561. /**
  562. * Refreshes the bundle such that new fields added by modules will be found
  563. * during cron.
  564. *
  565. * @param $bundle_name
  566. * The name of the bundle to refresh (e.g. bio_data_4).
  567. *
  568. * @ingroup tripal_entities_api
  569. */
  570. function tripal_tripal_cron_notification() {
  571. $num_created = 0;
  572. // Get all bundle names to cycle through.
  573. $all_bundles = db_select('tripal_bundle', 'tb')
  574. ->fields('tb', ['name'])
  575. ->execute()->fetchAll();
  576. foreach ($all_bundles as $bundle_name) {
  577. // Get the bundle object.
  578. $bundle = tripal_load_bundle_entity(['name' => $bundle_name->name]);
  579. if (!$bundle) {
  580. tripal_report_error('tripal', TRIPAL_ERROR, "Unrecognized bundle name '%bundle'.",
  581. ['%bundle' => $bundle_name]);
  582. return FALSE;
  583. }
  584. // Allow modules to add fields to the new bundle.
  585. $modules = module_implements('bundle_fields_info');
  586. foreach ($modules as $module) {
  587. $function = $module . '_bundle_fields_info';
  588. $info = $function('TripalEntity', $bundle);
  589. drupal_alter('bundle_fields_info', $info, 'TripalEntity', $bundle);
  590. foreach ($info as $field_name => $details) {
  591. // If the field already exists then skip it.
  592. $field = field_info_field($details['field_name']);
  593. if ($field) {
  594. continue;
  595. }
  596. // Create notification that new fields exist.
  597. $detail_info = ' Tripal has detected a new field ' . $details['field_name'] . ' for ' . $bundle->label . ' content type is available for import.';
  598. $title = 'New field available for import';
  599. $actions['Import'] = 'admin/import/field/' . $details['field_name'] . '/' . $bundle_name->name . '/' . $module . '/field';
  600. $type = 'Field';
  601. $submitter_id = $details['field_name'] . '-' . $bundle_name->name . '-' . $module;
  602. tripal_add_notification($title, $detail_info, $type, $actions, $submitter_id);
  603. $num_created++;
  604. }
  605. }
  606. // Allow modules to add instances to the new bundle.
  607. $modules = module_implements('bundle_instances_info');
  608. foreach ($modules as $module) {
  609. $function = $module . '_bundle_instances_info';
  610. $info = $function('TripalEntity', $bundle);
  611. drupal_alter('bundle_instances_info', $info, 'TripalEntity', $bundle);
  612. foreach ($info as $field_name => $details) {
  613. // If the field is already attached to this bundle then skip it.
  614. $field = field_info_field($details['field_name']);
  615. if ($field and array_key_exists('bundles', $field) and
  616. array_key_exists('TripalEntity', $field['bundles']) and
  617. in_array($bundle->name, $field['bundles']['TripalEntity'])) {
  618. continue;
  619. }
  620. // Create notification that new fields exist.
  621. $detail_info = ' Tripal has detected a new field ' . $details['field_name'] . ' for ' . $bundle->label . ' content type is available for import.';
  622. $title = 'New field available for import';
  623. $actions['Import'] = 'admin/import/field/' . $details['field_name'] . '/' . $bundle->name . '/' . $module . '/instance';
  624. $type = 'Field';
  625. $submitter_id = $details['field_name'] . '-' . $bundle_name->name . '-' . $module;
  626. tripal_add_notification($title, $detail_info, $type, $actions, $submitter_id);
  627. $num_created++;
  628. }
  629. }
  630. }
  631. }
  632. /**
  633. * Retrieves information about a given content type.
  634. *
  635. * @param $bundle_name
  636. * The name of a bundle.
  637. *
  638. * @return
  639. * An object containing information about the bundle.
  640. *
  641. * @ingroup tripal_entities_api
  642. */
  643. function tripal_get_content_type($bundle_name) {
  644. return db_select('tripal_bundle', 'tb')
  645. ->fields('tb')
  646. ->condition('tb.name', $bundle_name)
  647. ->execute()
  648. ->fetchAll();
  649. }
  650. /**
  651. * Refreshes the bundle such that new fields added by modules will be found.
  652. *
  653. * @param $bundle_name
  654. * The name of the bundle to refresh (e.g. bio_data_4).
  655. * @param $term
  656. * The term object for the bundle.
  657. *
  658. * @return
  659. * The array of field instance names that were added.
  660. *
  661. * @ingroup tripal_entities_api
  662. */
  663. function tripal_create_bundle_fields($bundle, $term) {
  664. $added = [];
  665. // Allow modules to add fields to the new bundle.
  666. $modules = module_implements('bundle_fields_info');
  667. $field_info = [];
  668. foreach ($modules as $module) {
  669. $function = $module . '_bundle_fields_info';
  670. $temp = $function('TripalEntity', $bundle);
  671. if (is_array($temp)) {
  672. // TODO: it would be good to check this array to make sure it follows
  673. // protocol. It would help identify potential errors.
  674. $field_info = array_merge($field_info, $temp);
  675. }
  676. }
  677. // Allow modules to alter which fields should be attached to content
  678. // types they create.
  679. drupal_alter('bundle_fields_info', $field_info, $bundle, $term);
  680. // Iterate through all of the fields and create them.
  681. foreach ($field_info as $field_name => $details) {
  682. $field_type = $details['type'];
  683. // If the field already exists then skip it.
  684. $field = field_info_field($details['field_name']);
  685. if ($field) {
  686. continue;
  687. }
  688. // Create the field.
  689. $field = field_create_field($details);
  690. if (!$field) {
  691. tripal_set_message(t("Could not create new field: %field.",
  692. ['%field' => $details['field_name']]), TRIPAL_ERROR);
  693. }
  694. }
  695. // Allow modules to add instances to the new bundle.
  696. $modules = module_implements('bundle_instances_info');
  697. $instance_info = [];
  698. foreach ($modules as $module) {
  699. $function = $module . '_bundle_instances_info';
  700. $temp = $function('TripalEntity', $bundle);
  701. if (is_array($temp)) {
  702. // TODO: it would be good to check this array to make sure it follows
  703. // protocol. It would help identify potential errors.
  704. $instance_info = array_merge($instance_info, $temp);
  705. }
  706. }
  707. // Allow modules to alter which fields should be attached to content
  708. // types they create.
  709. drupal_alter('bundle_instances_info', $instance_info, $bundle, $term);
  710. // Get the list of existing instances
  711. $existing_instances = field_info_instances('TripalEntity', $bundle->name);
  712. // Iterate through all of the field instances and create them.
  713. foreach ($instance_info as $instance_name => $details) {
  714. // Make sure the instance has a term. If not, report it and skip the field.
  715. if (!array_key_exists('term_vocabulary', $details['settings'])) {
  716. tripal_report_error('tripal_fields', TRIPAL_WARNING,
  717. 'The field instance, !field, is missing the "term_vocabulary" setting. The field instance cannot be added. Please check the field settings.',
  718. ['!field' => $instance_name], ['drupal_set_message' => TRUE]);
  719. continue;
  720. }
  721. if (!array_key_exists('term_accession', $details['settings'])) {
  722. tripal_report_error('tripal_fields', TRIPAL_WARNING,
  723. 'The field instance, !field, is missing the "term_accession" setting. The field instance cannot be added. Please check the field settings.',
  724. ['!field' => $instance_name], ['drupal_set_message' => TRUE]);
  725. continue;
  726. }
  727. // Make sure the term exists. If not, skip the field instance and
  728. // report an error.
  729. $field_term_id = $details['settings']['term_vocabulary'] . ':' . $details['settings']['term_accession'];
  730. $field_term = tripal_get_term_details($details['settings']['term_vocabulary'], $details['settings']['term_accession']);
  731. if (!$field_term) {
  732. tripal_report_error('tripal_fields', TRIPAL_WARNING,
  733. 'The term, !term, for the field, !field, does not exist in the database. The ' .
  734. 'field instance cannot be added. Please make sure the term is correct and add it if necessary.',
  735. [
  736. '!term' => $field_term_id,
  737. '!field' => $instance_name,
  738. ],
  739. ['drupal_set_message' => TRUE]);
  740. continue;
  741. }
  742. // Make sure the term is not used for any other existing field instance.
  743. $skip = FALSE;
  744. foreach ($existing_instances as $existing_name => $existing_instance) {
  745. // If this instance term is the same as this exsiting term and the
  746. // instance name is not the same then we have a problem.
  747. $existing_term_id = $existing_instance['settings']['term_vocabulary'] . ':' . $existing_instance['settings']['term_accession'];
  748. $existing_field = field_info_field($existing_name);
  749. if ($existing_term_id == $field_term_id and $instance_name != $existing_name) {
  750. tripal_report_error('tripal_fields', TRIPAL_WARNING,
  751. 'The field, !field, uses a term, !term, that is already in use on this content type. The ' .
  752. 'field instance cannot be added.',
  753. [
  754. '!term' => $existing_term_id,
  755. '!field' => $instance_name,
  756. ],
  757. ['drupal_set_message' => TRUE]);
  758. $skip = TRUE;
  759. }
  760. // If the instance term is the same as this exsting term but the storage
  761. // types are different then we have a problem.
  762. $existing_storage = $existing_field['storage']['type'];
  763. $this_field_storage = $field_info[$details['field_name']]['storage']['type'];
  764. if ($existing_term_id == $field_term_id and $existing_storage != $this_field_storage) {
  765. tripal_report_error('tripal_fields', TRIPAL_WARNING,
  766. 'The field, !field, provided by the storage type, !type, uses a term, !term, that is already in use on this content type and provided by another storage backend. The ' .
  767. 'field instance cannot be added. Perhaps, consider a different term and adjust the data in the database.',
  768. [
  769. '!term' => $existing_term_id,
  770. '!type' => $this_field_storage,
  771. '!field' => $instance_name,
  772. ],
  773. ['drupal_set_message' => TRUE]);
  774. $skip = TRUE;
  775. }
  776. }
  777. if ($skip) {
  778. continue;
  779. }
  780. // If the field is already attached to this bundle then skip it.
  781. if (array_key_exists($instance_name, $existing_instances)) {
  782. continue;
  783. }
  784. // Create the field instance.
  785. $instance = field_create_instance($details);
  786. $existing_instances[$instance_name] = $instance;
  787. $added[] = $instance_name;
  788. }
  789. return $added;
  790. }
  791. /**
  792. * Updates an existing field and its attached instance to a bundle.
  793. *
  794. *
  795. * @param $field_name
  796. * The name of the field.
  797. * @param $field_info
  798. * An associative array containing the field information. The following
  799. * key/value pairs are supported:
  800. * - field_type: a valid field type. May be any of the Drupal default
  801. * fields, one created by the tripal_chado module or another custom
  802. * module.
  803. * - widget_type: a valid widget type. May be any of the Drupal default
  804. * fields, one created by the tripal_chado module or another custom
  805. * module.
  806. * - field_settings: an array of settings that are appropriate for the
  807. * selected field type.
  808. * - widget_settings: an array of settings that are appropriate for the
  809. * selected widget type.
  810. * - description: a default description for this field.
  811. * - label: a label used as a header for this field.
  812. * - is_required: indicates if the field is required in the edit form.
  813. * - cardinality: indicates the number of values this field can support.
  814. * the default is 1 (meaning only one value). Use a value of
  815. * FIELD_CARDINALITY_UNLIMITED for unlimited number of values.
  816. * - default_value: A default value for the field.
  817. * - format: A string indicating the format for the field. Must be
  818. * specific to the field.
  819. * @param $entity_type_name
  820. * The entity type name.
  821. * @param $bundle_name
  822. * The bundle name.
  823. *
  824. * @return
  825. * FALSE if the field could not be updated
  826. *
  827. * TODO: this function really shouldn't try to create an instance and
  828. * attach to a bundle at the same time.
  829. *
  830. * @ingroup tripal_entities_api
  831. */
  832. function tripal_update_bundle_field($field_name, $field_info, $entity_type_name, $bundle_name) {
  833. $field = field_info_field($field_name);
  834. // If the field doesn't exists or is not attached to this bundle then
  835. // just return, there is nothing left to do.
  836. if (!$field or !array_key_exists('bundles', $field) or
  837. !array_key_exists($entity_type_name, $field['bundles']) or
  838. !in_array($bundle_name, $field['bundles'][$entity_type_name])) {
  839. return FALSE;
  840. }
  841. $field['field_name'] = $field_name;
  842. if (array_key_exists('field_type', $field_info)) {
  843. $field['cardinality'] = $field_info['cardinality'];
  844. }
  845. if (array_key_exists('locked', $field_info)) {
  846. $field['locked'] = $field_info['locked'];
  847. }
  848. if (array_key_exists('storage', $field_info)) {
  849. $field['storage']['type'] = $field_info['storage'];
  850. }
  851. if (array_key_exists('field_settings', $field_info)) {
  852. $field['settings'] = $field_info['field_settings'];
  853. }
  854. field_update_field($field);
  855. $field_instance['field_name'] = $field_name;
  856. $field_instance['entity_type'] = $entity_type_name;
  857. $field_instance['bundle'] = $bundle_name;
  858. if (array_key_exists('label', $field_info)) {
  859. $field['label'] = $field_info['label'];
  860. }
  861. if (array_key_exists('description', $field_info)) {
  862. $field['description'] = $field_info['description'];
  863. }
  864. if (array_key_exists('widget', $field_info)) {
  865. if (array_key_exists('widget_type', $field_info['widget'])) {
  866. $field['widget']['type'] = $field_info['widget_type'];
  867. }
  868. if (array_key_exists('widget_settings', $field_info['widget'])) {
  869. $field['widget']['settings'] = $field_info['widget_settings'];
  870. }
  871. }
  872. if (array_key_exists('required', $field_info)) {
  873. $field['required'] = $field_info['is_required'];
  874. }
  875. if (array_key_exists('settings', $field_info)) {
  876. $field['settings'] = $field_info['field_settings'];
  877. }
  878. if (array_key_exists('default_value', $field_info)) {
  879. $field['default_value'] = $field_info['default_value'];
  880. }
  881. if (array_key_exists('format', $field_info)) {
  882. $field['format'] = $field_info['format'];
  883. }
  884. field_update_instance($field_instance);
  885. }
  886. /**
  887. * @section
  888. * Bundle Variables.
  889. */
  890. /**
  891. * Fetch the value for a given tripal variable.
  892. *
  893. * @param string $variable_name
  894. * The name of the variable as in tripal_variables.name.
  895. * @param int $bundle_id
  896. * The unique identfier for the bundle you want the value for.
  897. *
  898. * @return text
  899. * The value of the specified variable for the specified bundle.
  900. *
  901. * @ingroup tripal_entities_api
  902. */
  903. function tripal_get_bundle_variable($variable_name, $bundle_id, $default = FALSE) {
  904. $variable = tripal_get_variable($variable_name);
  905. // Warn if we can't find the tripal_variable.
  906. if (!$variable) {
  907. return $default;
  908. }
  909. // Select the value for this variable.
  910. $value = db_select('tripal_bundle_variables', 'var')
  911. ->fields('var', ['value'])
  912. ->condition('var.bundle_id', $bundle_id)
  913. ->condition('var.variable_id', $variable->variable_id)
  914. ->execute()
  915. ->fetchField();
  916. // Warn if the value appears to be empty.
  917. if (!$value) {
  918. return $default;
  919. }
  920. return $value;
  921. }
  922. /**
  923. * Save the value of a tripal variable for a given bundle.
  924. *
  925. * @param string $variable_name
  926. * The name of the variable as in tripal_variables.name.
  927. * @param int $bundle_id
  928. * The unique identfier for the bundle you want the value for.
  929. * @param $text $value
  930. * The value of the variable for the given bundle.
  931. *
  932. * @ingroup tripal_entities_api
  933. */
  934. function tripal_set_bundle_variable($variable_name, $bundle_id, $value) {
  935. $variable = tripal_get_variable($variable_name);
  936. // And then we need to write the new format to the tripal_bundle_variables
  937. // table.
  938. $record = [
  939. 'bundle_id' => $bundle_id,
  940. 'variable_id' => $variable->variable_id,
  941. 'value' => $value,
  942. ];
  943. // Check whether there is already a format saved.
  944. $bundle_variable_id = db_select('tripal_bundle_variables', 'var')
  945. ->fields('var', ['bundle_variable_id'])
  946. ->condition('var.bundle_id', $record['bundle_id'])
  947. ->condition('var.variable_id', $record['variable_id'])
  948. ->execute()
  949. ->fetchField();
  950. if ($bundle_variable_id) {
  951. $record['bundle_variable_id'] = $bundle_variable_id;
  952. return drupal_write_record('tripal_bundle_variables', $record, 'bundle_variable_id');
  953. }
  954. else {
  955. return drupal_write_record('tripal_bundle_variables', $record);
  956. }
  957. }
  958. /**
  959. * @section
  960. * Title & URL Formats.
  961. */
  962. /**
  963. * Get Page Title Format for a given Tripal Entity Type.
  964. *
  965. * @param TripalBundle $bundle
  966. * The Entity object for the Tripal Bundle the title format is for.
  967. *
  968. * @ingroup tripal_entities_api
  969. */
  970. function tripal_get_title_format($bundle) {
  971. // Get the existing title format if it exists.
  972. $title_format = tripal_get_bundle_variable('title_format', $bundle->id);
  973. // If there isn't yet a title format for this bundle/type then we should
  974. // determine the default.
  975. if (!$title_format) {
  976. $title_format = tripal_get_default_title_format($bundle);
  977. tripal_save_title_format($bundle, $title_format);
  978. }
  979. return $title_format;
  980. }
  981. /**
  982. * Save Page Title Format for a given Tripal Entity Type.
  983. *
  984. * @param TripalBundle $entity
  985. * The Entity object for the Tripal Bundle the title format is for.
  986. * @param string $format
  987. * The pattern to be used when generating entity titles for the above type.
  988. *
  989. * @ingroup tripal_entities_api
  990. */
  991. function tripal_save_title_format($entity, $format) {
  992. return tripal_set_bundle_variable('title_format', $entity->id, $format);
  993. }
  994. /**
  995. * Determine the default title format to use for an entity.
  996. *
  997. * @param TripalBundle $bundle
  998. * The Entity object for the Tripal Bundle that the title format is for.
  999. *
  1000. * @return string
  1001. * A default title format.
  1002. *
  1003. * @ingroup tripal_entities_api
  1004. */
  1005. function tripal_get_default_title_format($bundle) {
  1006. $format = '';
  1007. // Retrieve all available tokens.
  1008. $tokens = tripal_get_entity_tokens($bundle);
  1009. // A) Check to see if more informed modules have suggested a title for this
  1010. // type. Invoke hook_tripal_default_title_format() to get all suggestions
  1011. // from other modules.
  1012. $suggestions = module_invoke_all('tripal_default_title_format', $bundle, $tokens);
  1013. if ($suggestions) {
  1014. // Use the suggestion with the lightest weight.
  1015. $lightest_key = NULL;
  1016. foreach ($suggestions as $k => $s) {
  1017. if ($lightest_key === NULL) {
  1018. $lightest_key = $k;
  1019. }
  1020. if ($s['weight'] < $lightest_key) {
  1021. $lightest_key = $k;
  1022. }
  1023. }
  1024. $format = $suggestions[$lightest_key]['format'];
  1025. return $format;
  1026. }
  1027. // B) Generate our own ugly title by simply comma-separating all the
  1028. // required fields.
  1029. if (!$format) {
  1030. $tmp = [];
  1031. // Check which tokens are required fields and join them into a default
  1032. // format.
  1033. foreach ($tokens as $token) {
  1034. if ($token['required']) {
  1035. $tmp[] = $token['token'];
  1036. }
  1037. }
  1038. $format = implode(', ', $tmp);
  1039. return $format;
  1040. }
  1041. return $format;
  1042. }
  1043. /**
  1044. * Returns an array of tokens based on Tripal Entity Fields.
  1045. *
  1046. * @param TripalBundle $bundle
  1047. * The bundle entity for which you want tokens.
  1048. *
  1049. * @return
  1050. * An array of tokens where the key is the machine_name of the token.
  1051. *
  1052. * @ingroup tripal_entities_api
  1053. */
  1054. function tripal_get_entity_tokens($bundle, $options = []) {
  1055. $tokens = [];
  1056. // Set default options.
  1057. $options['required only'] = (isset($options['required only'])) ? $options['required only'] : FALSE;
  1058. $options['include id'] = (isset($options['include id'])) ? $options['include id'] : TRUE;
  1059. if ($options['include id']) {
  1060. $token = '[TripalBundle__bundle_id]';
  1061. $tokens[$token] = [
  1062. 'label' => 'Bundle ID',
  1063. 'description' => 'The unique identifier for this Tripal Content Type.',
  1064. 'token' => $token,
  1065. 'field_name' => NULL,
  1066. 'required' => TRUE,
  1067. ];
  1068. $token = '[TripalEntity__entity_id]';
  1069. $tokens[$token] = [
  1070. 'label' => 'Content/Entity ID',
  1071. 'description' => 'The unique identifier for an individual piece of Tripal Content.',
  1072. 'token' => $token,
  1073. 'field_name' => NULL,
  1074. 'required' => TRUE,
  1075. ];
  1076. }
  1077. $instances = field_info_instances('TripalEntity', $bundle->name);
  1078. foreach ($instances as $instance_name => $instance) {
  1079. if (!$instance['required'] and $options['required only']) {
  1080. continue;
  1081. }
  1082. $use_field = FALSE;
  1083. // Iterate through the TripalEntity fields and see if they have
  1084. // sub-elements, if so, add those as tokens too.
  1085. $field_name = $instance['field_name'];
  1086. if ($instance['entity_type'] == 'TripalEntity') {
  1087. if (tripal_load_include_field_class($field_name)) {
  1088. $field = field_info_field($field_name);
  1089. $field_obj = new $field_name($field, $instance);
  1090. $element_info = $field_obj->elementInfo();
  1091. $term_id = $instance['settings']['term_vocabulary'] . ':' . $instance['settings']['term_accession'];
  1092. if ($element_info and
  1093. array_key_exists($term_id, $element_info) and
  1094. array_key_exists('elements', $element_info[$term_id]) and count($element_info[$term_id]['elements']) > 0) {
  1095. $elements = $element_info[$term_id]['elements'];
  1096. _tripal_get_entity_tokens_for_elements($instance, $field_name, $elements, $tokens, $options);
  1097. }
  1098. else {
  1099. $use_field = TRUE;
  1100. }
  1101. }
  1102. else {
  1103. $use_field = TRUE;
  1104. }
  1105. }
  1106. else {
  1107. $use_field = TRUE;
  1108. }
  1109. // If we have no elements to add then just add the field as is.
  1110. if ($use_field) {
  1111. // Build the token from the field information.
  1112. $token = '[' . $instance['field_name'] . ']';
  1113. $tokens[$token] = [
  1114. 'label' => $instance['label'],
  1115. 'description' => $instance['description'],
  1116. 'token' => $token,
  1117. 'field_name' => $instance['field_name'],
  1118. 'required' => $instance['required'],
  1119. ];
  1120. }
  1121. }
  1122. return $tokens;
  1123. }
  1124. /**
  1125. * A recursive helper function to get tokens for element sub fields.
  1126. *
  1127. * @param $instance
  1128. * A original field instance object.
  1129. * @param $parent
  1130. * The name of the parent. The first time this is called outside of
  1131. * recursion this should be the field name.
  1132. * @param $elements
  1133. * The array of elements to process.
  1134. * @param $tokens
  1135. * The array of tokens to be added to.
  1136. */
  1137. function _tripal_get_entity_tokens_for_elements($instance, $parent, $elements, &$tokens, $options) {
  1138. // Iterate through all of the elements and add tokens for each one.
  1139. foreach ($elements as $child_term_id => $details) {
  1140. // We don't need to add the entity element.
  1141. if ($child_term_id == 'entity') {
  1142. continue;
  1143. }
  1144. // Skip elements that aren't required.
  1145. $required = array_key_exists('required', $details) ? $details['required'] : FALSE;
  1146. if (!$required and $options['required only']) {
  1147. continue;
  1148. }
  1149. $token = '[' . $parent . ',' . $child_term_id . ']';
  1150. $label = $child_term_id;
  1151. if (array_key_exists('name', $details)) {
  1152. $label = $details['name'];
  1153. }
  1154. elseif (preg_match('/:/', $child_term_id)) {
  1155. list($vocabulary, $accession) = explode(':', $child_term_id);
  1156. $term = tripal_get_term_details($vocabulary, $accession);
  1157. $label = $term['name'];
  1158. }
  1159. // Add the token!
  1160. $tokens[$token] = [
  1161. 'label' => $label,
  1162. 'description' => array_key_exists('description', $details) ? $details['description'] : '',
  1163. 'token' => $token,
  1164. 'field_name' => $instance['field_name'],
  1165. 'required' => $required,
  1166. ];
  1167. // Recurse to include sub elements
  1168. if (array_key_exists('elements', $details)) {
  1169. _tripal_get_entity_tokens_for_elements($instance, $parent . ',' . $child_term_id,
  1170. $details['elements'], $tokens, $options);
  1171. }
  1172. }
  1173. }
  1174. /**
  1175. * Replace all Tripal Tokens in a given string.
  1176. *
  1177. * NOTE: If there is no value for a token then the token is removed.
  1178. *
  1179. * @param string $string
  1180. * The string containing tokens.
  1181. * @param TripalEntity $entity
  1182. * The entity with field values used to find values of tokens.
  1183. * @param TripalBundle $bundle_entity
  1184. * The bundle enitity containing special values sometimes needed for token
  1185. * replacement.
  1186. *
  1187. * @return
  1188. * The string will all tokens replaced with values.
  1189. *
  1190. * @ingroup tripal_entities_api
  1191. */
  1192. function tripal_replace_entity_tokens($string, &$entity, $bundle_entity = NULL) {
  1193. // Determine which tokens were used in the format string
  1194. $used_tokens = [];
  1195. if (preg_match_all('/\[.*?\]/', $string, $matches)) {
  1196. $used_tokens = $matches[0];
  1197. }
  1198. // If there are no tokens then just return the string.
  1199. if (count($used_tokens) == 0) {
  1200. return $string;
  1201. }
  1202. // If the fields are not loaded for the entity then we want to load them
  1203. // but we won't do a field_attach_load() as that will load all of the
  1204. // fields. For syncing (publishing) of content loading all fields for
  1205. // all synced entities causes extreme slowness, so we'll only attach
  1206. // the necessary fields for replacing tokens.
  1207. $attach_fields = [];
  1208. foreach ($used_tokens as $token) {
  1209. $token = preg_replace('/[\[\]]/', '', $token);
  1210. $elements = explode(',', $token);
  1211. $field_name = array_shift($elements);
  1212. //$field_name = str_replace(array('.','[',']'), array('__','',''), $field_name);
  1213. if (!property_exists($entity, $field_name) or empty($entity->{$field_name})) {
  1214. $field = field_info_field($field_name);
  1215. $storage = $field['storage'];
  1216. $attach_fields[$storage['type']]['storage'] = $storage;
  1217. $attach_fields[$storage['type']]['fields'][] = $field;
  1218. }
  1219. }
  1220. // If we have any fields that need attaching, then do so now.
  1221. if (count(array_keys($attach_fields)) > 0) {
  1222. foreach ($attach_fields as $storage_type => $details) {
  1223. $field_ids = [];
  1224. $storage = $details['storage'];
  1225. $fields = $details['fields'];
  1226. foreach ($fields as $field) {
  1227. $field_ids[$field['id']] = [$entity->id];
  1228. }
  1229. $entities = [$entity->id => $entity];
  1230. module_invoke($storage['module'], 'field_storage_load', 'TripalEntity',
  1231. $entities, FIELD_LOAD_CURRENT, $field_ids, []);
  1232. }
  1233. }
  1234. // Now that all necessary fields are attached process the tokens.
  1235. foreach ($used_tokens as $token) {
  1236. $token = preg_replace('/[\[\]]/', '', $token);
  1237. $elements = explode(',', $token);
  1238. $field_name = array_shift($elements);
  1239. $value = '';
  1240. if (property_exists($entity, $field_name)) {
  1241. $value = '';
  1242. // Note: there is a memory leak in field_get_items() so we can't use it
  1243. // here or bulk publishing will slowly erode memory.
  1244. // $field_value = field_get_items('TripalEntity', $entity, $field_name);
  1245. if (array_key_exists('und', $entity->{$field_name}) and
  1246. array_key_exists(0, $entity->{$field_name}['und'])) {
  1247. $value = $entity->{$field_name}['und'][0]['value'];
  1248. // If the value is an array it means we have sub elements and we can
  1249. // descend through the array to look for matching value.
  1250. if (is_array($value) and count($elements) > 0) {
  1251. $value = _tripal_replace_entity_tokens_for_elements($elements, $value);
  1252. }
  1253. }
  1254. }
  1255. // The TripalBundle__bundle_id is a special token for substituting the
  1256. // bundle id.
  1257. elseif ($field_name === 'TripalBundle__bundle_id') {
  1258. // Load the bundle entity if we weren't given it.
  1259. if (!$bundle_entity) {
  1260. $bundle_entity = tripal_load_bundle_entity(['name' => $entity->bundle]);
  1261. }
  1262. // This token should be the id of the TripalBundle.
  1263. $value = $bundle_entity->id;
  1264. }
  1265. // The TripalBundle__bundle_id is a special token for substituting the
  1266. // entty id.
  1267. elseif ($field_name === 'TripalEntity__entity_id') {
  1268. // This token should be the id of the TripalEntity.
  1269. $value = $entity->id;
  1270. }
  1271. // We can't support tokens that have multiple elements (i.e. in an array).
  1272. if (is_array($value)) {
  1273. $string = str_replace('[' . $token . ']', '', $string);
  1274. }
  1275. else {
  1276. $string = str_replace('[' . $token . ']', $value, $string);
  1277. }
  1278. }
  1279. return $string;
  1280. }
  1281. /**
  1282. * A helper function for tripal_replace_entity_tokens to get token values.
  1283. *
  1284. * This helper function is used when the tokens are from subelements.
  1285. *
  1286. * @param $entity
  1287. */
  1288. function _tripal_replace_entity_tokens_for_elements($elements, $values) {
  1289. $term_id = array_shift($elements);
  1290. $value = $values[$term_id];
  1291. if (count($elements) == 0) {
  1292. return $value;
  1293. }
  1294. else {
  1295. _tripal_replace_entity_tokens_for_elements($elements, $value);
  1296. }
  1297. }
  1298. /**
  1299. * Formats the tokens for display.
  1300. *
  1301. * @param array $tokens
  1302. * A list of tokens generated via tripal_get_entity_tokens().
  1303. *
  1304. * @return
  1305. * Rendered output describing the available tokens.
  1306. *
  1307. * @ingroup tripal_entities_api
  1308. */
  1309. function theme_token_list($tokens) {
  1310. $header = ['Token', 'Name', 'Description'];
  1311. $rows = [];
  1312. foreach ($tokens as $details) {
  1313. $rows[] = [
  1314. $details['token'],
  1315. $details['label'],
  1316. $details['description'],
  1317. ];
  1318. }
  1319. return theme('table', ['header' => $header, 'rows' => $rows]);
  1320. }
  1321. /**
  1322. * Define the entity label callback. This will return the title.
  1323. *
  1324. * @param $entity
  1325. *
  1326. * @return mixed
  1327. *
  1328. * @ingroup tripal_entities_api
  1329. */
  1330. function tripal_entity_label($entity) {
  1331. if (property_exists($entity, 'title')) {
  1332. return $entity->title;
  1333. }
  1334. return NULL;
  1335. }
  1336. /**
  1337. * Retrieves details, including attached fields, for a given bundle.
  1338. *
  1339. * @param $bundle_name
  1340. * The name of the bundle (e.g. bio_data_xx)
  1341. *
  1342. * @return
  1343. * An array containing the name, label, controlled vocabulary details
  1344. * and a list of fields attached to the bundle. Returns FALSE
  1345. * if the bundle does not exist.
  1346. *
  1347. * @ingroup tripal_entities_api
  1348. */
  1349. function tripal_get_bundle_details($bundle_name) {
  1350. global $user;
  1351. $bundle = tripal_load_bundle_entity(['name' => $bundle_name]);
  1352. if (!$bundle) {
  1353. return FALSE;
  1354. }
  1355. $term = tripal_load_term_entity(['term_id' => $bundle->term_id]);
  1356. $vocab = $term->vocab;
  1357. $instances = field_info_instances('TripalEntity', $bundle->name);
  1358. $details = [
  1359. 'name' => $bundle->name,
  1360. 'label' => $bundle->label,
  1361. 'term' => [
  1362. 'accession' => $vocab->vocabulary . ':' . $term->accession,
  1363. 'name' => $term->name,
  1364. 'definition' => $term->definition,
  1365. 'url' => $term->url,
  1366. ],
  1367. 'fields' => [],
  1368. ];
  1369. // Iterate through each feild and provide a discription of it and
  1370. // it's sub elements.
  1371. foreach ($instances as $instance) {
  1372. // Skip deleted fields.
  1373. if ($instance['deleted']) {
  1374. continue;
  1375. }
  1376. $field_name = $instance['field_name'];
  1377. $field = field_info_field($field_name);
  1378. $field_class = $field['type'];
  1379. $term_vocab = $instance['settings']['term_vocabulary'];
  1380. $term_accession = $instance['settings']['term_accession'];
  1381. $field_term = tripal_get_term_details($term_vocab, $term_accession);
  1382. $field_details = [
  1383. 'name' => $field_name,
  1384. 'label' => $instance['label'],
  1385. 'term' => [
  1386. 'accession' => $term_vocab . ":" . $term_accession,
  1387. 'name' => $field_term['name'],
  1388. 'definition' => $field_term['definition'],
  1389. 'url' => $field_term['url'],
  1390. ],
  1391. // These items can be overridden by the element_info array that
  1392. // is present in a TripalField instance. Here we set defaults.
  1393. 'required' => $instance['required'] ? TRUE : FALSE,
  1394. 'type' => 'xs:string',
  1395. 'readonly' => TRUE,
  1396. // The cardinatlity value always comes from the field.
  1397. 'cardinality' => $field['cardinality'],
  1398. ];
  1399. if (tripal_load_include_field_class($field_class)) {
  1400. $field_obj = new $field_class($field, $instance);
  1401. $element_info = $field_obj->elementInfo();
  1402. $element_info = $element_info[$term_vocab . ':' . $term_accession];
  1403. // If the element info for this field sets required, type and readonly
  1404. // attributes then set those.
  1405. $field_details['required'] = array_key_exists('required', $element_info) ? $element_info['required'] : FALSE;
  1406. $field_details['type'] = array_key_exists('type', $element_info) ? $element_info['type'] : 'xs:string';
  1407. $field_details['readonly'] = array_key_exists('readonly', $element_info) ? $element_info['readonly'] : TRUE;
  1408. $field_details['label'] = array_key_exists('label', $element_info) ? $element_info['label'] : $field_details['label'];
  1409. $field_details['help'] = array_key_exists('help', $element_info) ? $element_info['help'] : '';
  1410. // If this field is an 'xs:complexType' then it will have sub elements.
  1411. // we need to add those as well.
  1412. if (array_key_exists('elements', $element_info) and is_array($element_info['elements'])) {
  1413. _tripal_get_bundle_field_element_details($element_info['elements'], $field_details);
  1414. }
  1415. $details['fields'][] = $field_details;
  1416. }
  1417. }
  1418. return $details;
  1419. }
  1420. /**
  1421. * A recursive helper function for the tripal_get_bundle_details.
  1422. *
  1423. * @param $elementInfo
  1424. *
  1425. * @ingroup tripal_entities_api
  1426. */
  1427. function _tripal_get_bundle_field_element_details($elements, &$field_details) {
  1428. $field_details['elements'] = [];
  1429. foreach ($elements as $element_key => $element_info) {
  1430. // Handle the entity element differnetly.
  1431. if ($element_key == 'entity') {
  1432. continue;
  1433. }
  1434. list($term_vocab, $term_accession) = explode(':', $element_key);
  1435. $term = tripal_get_term_details($term_vocab, $term_accession);
  1436. $element_details = [
  1437. 'name' => $element_info['name'],
  1438. 'label' => array_key_exists('label', $element_info) ? $element_info['label'] : ucfirst(preg_replace('/_/', ' ', $term['name'])),
  1439. 'help' => array_key_exists('help', $element_info) ? $element_info['help'] : '',
  1440. 'term' => [
  1441. 'accession' => $term_vocab . ':' . $term_accession,
  1442. 'name' => $term['name'],
  1443. 'definition' => $term['definition'],
  1444. 'url' => $term['url'],
  1445. ],
  1446. 'required' => array_key_exists('required', $element_info) ? $element_info['required'] : FALSE,
  1447. 'type' => array_key_exists('type', $element_info) ? $element_info['type'] : 'xs:string',
  1448. 'readonly' => array_key_exists('readonly', $element_info) ? $element_info['readonly'] : TRUE,
  1449. ];
  1450. if (array_key_exists('elements', $element_info) and is_array($element_info['elements'])) {
  1451. _tripal_get_bundle_field_element_details($element_info['elements'], $element_details);
  1452. }
  1453. $field_details['elements'][] = $element_details;
  1454. }
  1455. }
  1456. /**
  1457. * Is this completed? It doesn't look right and I can't find it used anywhere
  1458. * in the existing code.
  1459. *
  1460. * @param $bundle_name
  1461. * The name of the bundle (e.g. bio_data_xx)
  1462. * @param unknown $values
  1463. *
  1464. * @throws Exception
  1465. *
  1466. *
  1467. */
  1468. function tripal_insert_entity($bundle_name, $values) {
  1469. global $user;
  1470. $bundle = tripal_load_bundle_entity(['name' => $bundle_name]);
  1471. // Get the fields associated with this content type.
  1472. $instances = field_info_instances('TripalEntity', $bundle->name);
  1473. foreach ($instances as $instance) {
  1474. $field_name = $instance['field_name'];
  1475. $field = field_info_field($field_name);
  1476. $field_type = $field['type'];
  1477. $field_settings = $field['settings'];
  1478. $instance_settings = $instance['settings'];
  1479. $field_name = $field['field_name'];
  1480. $vocabulary = $instance['settings']['term_vocabulary'];
  1481. $accession = $instance['settings']['term_accession'];
  1482. $field_accession = $vocabulary . ':' . $accession;
  1483. $field_term = tripal_get_term_details($vocabulary, $accession);
  1484. $field_key = $field_term['name'];
  1485. $field_key = strtolower(preg_replace('/ /', '_', $key));
  1486. // There are three ways that a field value can be specified. Those
  1487. // are as the controlled vocabulary accession (e.g. GO:0000134), sa
  1488. // the field name or as the field key which is the term name with
  1489. // spaces replaced with underscores.
  1490. // First make sure that required fields are present.
  1491. if ($instance['required'] == TRUE) {
  1492. if (!array_key_exists($field_key, $values) and
  1493. !array_key_exists($field_accession, $values) and
  1494. !array_key_exists($field_name, $values)) {
  1495. throw new Exception(t('Cannot insert the record. Missing the required field "%missing".',
  1496. ['%missing' => $field_name]));
  1497. }
  1498. }
  1499. }
  1500. // Make sure that all required fields are present.
  1501. // TODO: make sure the user has permission to do this.
  1502. $ec = entity_get_controller('TripalEntity');
  1503. $entity = $ec->create([
  1504. 'bundle' => $bundle_name,
  1505. 'term_id' => $bundle->term_id,
  1506. ]);
  1507. $entity = $entity->save();
  1508. }
  1509. /**
  1510. * Are we keeping this?
  1511. *
  1512. * @param $bundle_name
  1513. * @param $values
  1514. *
  1515. *
  1516. */
  1517. function tripal_update_entity($bundle_name, $values) {
  1518. }
  1519. /**
  1520. * Removes orphaned entities.
  1521. *
  1522. * An orphaned entity can occur if the module that created the entity
  1523. * unknowingly lost its underlying record in its data store. Such a case
  1524. * could happen if someone directly removed the record from the data store
  1525. * outside of the module's control. This function allows each module
  1526. * to report if any orphans are missing for a given bundle type.
  1527. *
  1528. * @param integer $bundle_id
  1529. * The numeric ID of the bundle.
  1530. * @param TripalJob $job
  1531. * (Optional). If this function is executed via the Tripal Jobs system then
  1532. * this argument is provided.
  1533. */
  1534. function tripal_unpublish_orphans(int $bundle_id, TripalJob $job = NULL) {
  1535. $bundlec = entity_get_controller('TripalBundle');
  1536. $ids = $bundlec->deleteOrphans($bundle_id, $job);
  1537. }
  1538. /**
  1539. * A hook for modules to delete details for orphaned entities.
  1540. *
  1541. * This hook is called by the TripalBundleController. Modules that create
  1542. * entities should use this hook to clean up entities that are orphaned. The
  1543. * list of $ids passed should be entities who are already known to
  1544. * be orphaned. These IDs are found by the TripalBundleController using the
  1545. * results from the hook_bundle_find_orphans() function.
  1546. *
  1547. * An implementation of this hook should not try to clean up the entity itself,
  1548. * but rather it should only clean up its own records used to manage the
  1549. * relationship between the entity and the underlying data that the
  1550. * module provides.
  1551. *
  1552. * An orphaned entity can occur if the module that created the entity
  1553. * unknowingly lost its underlying record in its data store. Such a case
  1554. * could happen if someone directly removed the record from the data store
  1555. * outside of the module's control. This function allows each module
  1556. * to report if any orphans are missing for a given bundle type.
  1557. *
  1558. * @param TripalBundle $bundle
  1559. * A TripalBundle object for the bundle whose entities are orphaned.
  1560. * @param array $ids
  1561. * A list of entity IDs known to be orphaned.
  1562. * @param TripalJob $job
  1563. * An optional Tripal Job object. This is provided when this function is
  1564. * called using the Tripal Jobs system. Implementors of this hook can
  1565. * use the addItemsHandled() function to indicate how many entities were
  1566. * cleaned up.
  1567. *
  1568. * @return integer
  1569. * The number of entitites that were cleaned up.
  1570. */
  1571. function hook_bundle_delete_orphans(TripalBundle $bundle, array $ids, TripalJob $job = NULL) {
  1572. // See the tripal_chado_bundle_delete_orphans() function for an example.
  1573. }
  1574. /**
  1575. * A hook for modules to report on oprhaned entities.
  1576. *
  1577. * An orphaned entity can occur if the module that created the entity
  1578. * unknowingly lost its underlying record in its data store. Such a case
  1579. * could happen if someone directly removed the record from the data store
  1580. * outside of the module's control. This function allows each module
  1581. * to report if any orphans are missing for a given bundle type.
  1582. *
  1583. * @param TripalBunldle $bundle
  1584. * A TripalBundle object for the bundle that should be checked for
  1585. * orphaned entities.
  1586. * @param bool $count
  1587. * TRUE if the function should return only the number of orphaned entities.
  1588. * FALSE if the function should return the list of orphned entities.
  1589. * @param integer $offset
  1590. * For paging of entities set this to the offset within the total count.
  1591. * @param integer $limit
  1592. * For paging of entities set this to the total number to return.
  1593. *
  1594. * @return array|bool
  1595. * If $count == FALSE then an array of all entity IDs that are orphaned. If
  1596. * $count == TRUE then a single integer count value is returned.
  1597. */
  1598. function hook_bundle_find_orphans(TripalBundle $bundle, $count = FALSE,
  1599. $offset = 0, $limit = 10) {
  1600. // See the tripal_chado_bundle_find_orphans() function for an example.
  1601. }