TripalEntityCollection.inc

File

tripal/includes/TripalEntityCollection.inc
View source
  1. <?php
  2. class TripalEntityCollection {
  3. /**
  4. * The name of the bundles (i.e. content type) to which the entities belong.
  5. */
  6. protected $bundles = array();
  7. /**
  8. * The collection ID
  9. */
  10. protected $collection_id = NULL;
  11. /**
  12. * The name of this collection.
  13. */
  14. protected $collection_name = '';
  15. /**
  16. * An array of numeric entities IDs.
  17. */
  18. protected $ids = array();
  19. /**
  20. * An array of field IDs.
  21. */
  22. protected $fields = array();
  23. /**
  24. * The user object of the user that owns the collection.
  25. */
  26. protected $user = array();
  27. /**
  28. * The date that the collection was created.
  29. */
  30. protected $create_date = '';
  31. /**
  32. * The list of downloaders available for this bundle.
  33. */
  34. protected $formatters = array();
  35. /**
  36. * The description for this collection.
  37. */
  38. protected $description = '';
  39. /**
  40. * Constructs a new instance of the TripalEntityCollection class.
  41. */
  42. public function __construct() {
  43. }
  44. /**
  45. * Deletes the current collection
  46. */
  47. public function delete() {
  48. if (!$this->collection_id) {
  49. throw new Exception('This data collection object has not yet been loaded. Cannot delete.');
  50. }
  51. try {
  52. // Remove any files that may have been created
  53. foreach ($this->formatters as $class_name => $label) {
  54. tripal_load_include_downloader_class($class_name);
  55. $outfile = $this->getOutfile($class_name);
  56. $downloader = new $class_name($this->collection_id, $outfile);
  57. $downloader->delete();
  58. }
  59. // Delete from the tripal collection table.
  60. db_delete('tripal_collection')
  61. ->condition('collection_id', $this->collection_id)
  62. ->execute();
  63. // Delete the field groups from the tripal_bundle_collection table.
  64. db_delete('tripal_collection_bundle')
  65. ->condition('collection_id', $this->collection_id)
  66. ->execute();
  67. // Reset the class to defaults.
  68. $this->collection_id = NULL;
  69. $this->collection_name = '';
  70. $this->create_date = '';
  71. $this->description = '';
  72. }
  73. catch (Exception $e) {
  74. throw new Exception('Cannot delete collection: ' . $e->getMessage());
  75. }
  76. }
  77. /**
  78. * Loads an existing collection using a collection ID.
  79. *
  80. * @param $collection_id
  81. * The ID of the collection to load.
  82. *
  83. * @throws Exception
  84. */
  85. public function load($collection_id) {
  86. // Make sure we have a numeric job_id.
  87. if (!$collection_id or !is_numeric($collection_id)) {
  88. throw new Exception("You must provide the collection_id to load the collection.");
  89. }
  90. $collection = db_select('tripal_collection', 'tc')
  91. ->fields('tc')
  92. ->condition('collection_id', $collection_id)
  93. ->execute()
  94. ->fetchObject();
  95. if (!$collection) {
  96. throw new Exception("Cannot find a collection with the ID provided.");
  97. }
  98. // Fix the date/time fields.
  99. $this->collection_name = $collection->collection_name;
  100. $this->create_date = $collection->create_date;
  101. $this->user = user_load($collection->uid);
  102. $this->description = $collection->description;
  103. $this->collection_id = $collection->collection_id;
  104. // Now get the bundles in this collection.
  105. $bundles = db_select('tripal_collection_bundle', 'tcb')
  106. ->fields('tcb')
  107. ->condition('collection_id', $collection->collection_id)
  108. ->execute();
  109. // If more than one bundle plop into associative array.
  110. while ($bundle = $bundles->fetchObject()) {
  111. $bundle_name = $bundle->bundle_name;
  112. $this->bundles[$bundle_name] = $bundle;
  113. $this->ids[$bundle_name] = unserialize($bundle->ids);
  114. $this->fields[$bundle_name] = unserialize($bundle->fields);
  115. }
  116. // Iterate through the fields and find out what download formats are
  117. // supported for this basket.
  118. $this->formatters = $this->setFormatters();
  119. }
  120. /**
  121. * Creates a new data collection.
  122. *
  123. * To add bundles with entities and fields to a collection, use the
  124. * addBundle() function after the collection is created.
  125. *
  126. * @param $details
  127. * An association array containing the details for a collection. The
  128. * details must include the following key/value pairs:
  129. * - uid: The ID of the user that owns the collection
  130. * - collection_name: The name of the collection
  131. * - description: A user supplied description for the collection.
  132. *
  133. * @throws Exception
  134. */
  135. public function create($details) {
  136. if (!$details['uid']) {
  137. throw new Exception("Must provide a 'uid' key to TripalEntityCollection::create().");
  138. }
  139. if (!$details['collection_name']) {
  140. throw new Exception("Must provide a 'collection_name' key to TripalEntityCollection::create().");
  141. }
  142. // Before inserting the new collection make sure we don't violate the unique
  143. // constraint that a user can only have one collection of the give name.
  144. $has_match = db_select('tripal_collection', 'tc')
  145. ->fields('tc', array('collection_id'))
  146. ->condition('uid', $details['uid'])
  147. ->condition('collection_name', $details['collection_name'])
  148. ->execute()
  149. ->fetchField();
  150. if ($has_match) {
  151. throw new Exception('Cannot create the collection. One with this name already exists');
  152. }
  153. try {
  154. $collection_id = db_insert('tripal_collection')
  155. ->fields(array(
  156. 'collection_name' => $details['collection_name'],
  157. 'create_date' => time(),
  158. 'uid' => $details['uid'],
  159. 'description' => array_key_exists('description', $details) ? $details['description'] : '',
  160. ))
  161. ->execute();
  162. // Now load the job into this object.
  163. $this->load($collection_id);
  164. }
  165. catch (Exception $e) {
  166. throw new Exception('Cannot create collection: ' . $e->getMessage());
  167. }
  168. }
  169. /**
  170. * Creates a new tripal_collection_bundle entry.
  171. *
  172. * @param $details
  173. * An association array containing the details for a collection. The
  174. * details must include the following key/value pairs:
  175. * - bundle_name: The name of the TripalEntity content type.
  176. * - ids: An array of the entity IDs that form the collection.
  177. * - fields: An array of the field IDs that the collection is limited to.
  178. *
  179. * @throws Exception
  180. */
  181. public function addBundle($details) {
  182. if (!$details['bundle_name']) {
  183. throw new Exception("Must provide a 'bundle_name' to TripalEntityCollection::addFields().");
  184. }
  185. if (!$details['ids']) {
  186. throw new Exception("Must provide a 'ids' to TripalEntityCollection::addFields().");
  187. }
  188. if (!$details['fields']) {
  189. throw new Exception("Must provide a 'fields' to TripalEntityCollection::addFields().");
  190. }
  191. try {
  192. $collection_bundle_id = db_insert('tripal_collection_bundle')
  193. ->fields(array(
  194. 'bundle_name' => $details['bundle_name'],
  195. 'ids' => serialize($details['ids']),
  196. 'fields' => serialize($details['fields']),
  197. 'collection_id' => $this->collection_id,
  198. ))
  199. ->execute();
  200. // Now load the job into this object.
  201. $this->load($this->collection_id);
  202. }
  203. catch (Exception $e) {
  204. throw new Exception('Cannot create collection: ' . $e->getMessage());
  205. }
  206. }
  207. /**
  208. * Retrieves the list of bundles associated with the collection.
  209. *
  210. * @return
  211. * An array of bundles.
  212. */
  213. public function getBundles() {
  214. return $this->bundles;
  215. }
  216. /**
  217. * Retrieves the site id for this specific bundle fo the collection.
  218. *
  219. * @return
  220. * A remote site ID, or an empty string if the bundle is local.
  221. */
  222. public function getBundleSiteId($bundle_name) {
  223. return $this->bundles[$bundle_name]->site_id;
  224. }
  225. /**
  226. * Retrieves the list of appropriate download formatters for the basket.
  227. *
  228. * @return
  229. * An associative array where the key is the TripalFieldDownloader class
  230. * name and the value is the human-readable lable for the formatter.
  231. */
  232. private function setFormatters() {
  233. $downloaders = array();
  234. // Iterate through the fields and find out what download formats are
  235. // supported for this basket.
  236. foreach ($this->fields as $bundle_name => $field_ids) {
  237. // Need the site ID from the tripal_collection_bundle table.
  238. $site_id = $this->getBundleSiteId($bundle_name);
  239. foreach ($field_ids as $field_id) {
  240. // If this is a field from a remote site then get it's formatters
  241. if ($site_id and module_exists('tripal_ws')) {
  242. $formatters = tripal_get_remote_field_formatters($site_id, $bundle_name, $field_id);
  243. $this->formatters += $formatters;
  244. }
  245. else {
  246. $field_info = field_info_field_by_id($field_id);
  247. if (!$field_info) {
  248. continue;
  249. }
  250. $field_name = $field_info['field_name'];
  251. $instance = field_info_instance('TripalEntity', $field_name, $bundle_name);
  252. $formatters = tripal_get_field_field_formatters($field_info, $instance);
  253. $this->formatters += $formatters;
  254. }
  255. }
  256. }
  257. $this->formatters = array_unique($this->formatters);
  258. return $this->formatters;
  259. }
  260. /**
  261. * Retrieves the list of appropriate download formatters for the basket.
  262. *
  263. * @return
  264. * An associative array where the key is the TripalFieldDownloader class
  265. * name and the value is the human-readable lable for the formatter.
  266. */
  267. public function getFormatters() {
  268. return $this->formatters;
  269. }
  270. /**
  271. * Retrieves the list of entity IDs.
  272. *
  273. * @return
  274. * An array of numeric entity IDs.
  275. */
  276. public function getEntityIDs($bundle_name) {
  277. return $this->ids[$bundle_name];
  278. }
  279. /**
  280. * Retrieves the list of fields in the basket.
  281. *
  282. * @return
  283. * An array of numeric field IDs.
  284. */
  285. public function getFieldIDs($bundle_name) {
  286. return $this->fields[$bundle_name];
  287. }
  288. /**
  289. * Retrieves the date that the basket was created.
  290. *
  291. * @param $formatted
  292. * If TRUE then the date time will be formatted for human readability.
  293. * @return
  294. * A UNIX time stamp string containing the date or a human-readable
  295. * string if $formatted = TRUE.
  296. */
  297. public function getCreateDate($formatted = TRUE) {
  298. if ($formatted) {
  299. return format_date($this->create_date);
  300. }
  301. return $this->create_date;
  302. }
  303. /**
  304. * Retrieves the name of the collection.
  305. *
  306. * @return
  307. * A string containing the name of the collection.
  308. */
  309. public function getName() {
  310. return $this->collection_name;
  311. }
  312. /**
  313. * Retrieves the collection ID.
  314. *
  315. * @return
  316. * A numeric ID for this collection.
  317. */
  318. public function getCollectionID(){
  319. return $this->collection_id;
  320. }
  321. /**
  322. * Retrieves the collection description
  323. *
  324. * @return
  325. * A string containing the description of the collection.
  326. */
  327. public function getDescription() {
  328. return $this->description;
  329. }
  330. /**
  331. * Retrieves the user object of the user that owns the collection
  332. *
  333. * @return
  334. * A Drupal user object.
  335. */
  336. public function getUser() {
  337. return $this->user;
  338. }
  339. /**
  340. * Retrieves the ID of the user that owns the collection
  341. *
  342. * @return
  343. * The numeric User ID.
  344. */
  345. public function getUserID() {
  346. if ($this->user) {
  347. return $this->user->uid;
  348. }
  349. return NULL;
  350. }
  351. /**
  352. * Retrieves the output filename for the desired formatter.
  353. *
  354. * @param $formatter
  355. * The class name of the formatter to use. The formatter must
  356. * be compatible with the data collection.
  357. *
  358. * @throws Exception
  359. */
  360. public function getOutfile($formatter) {
  361. if(!$this->isFormatterCompatible($formatter)) {
  362. throw new Exception(t('The formatter, "%formatter", is not compatible with this data collection.', array('%formatter' => $formatter)));
  363. }
  364. if (!tripal_load_include_downloader_class($formatter)) {
  365. throw new Exception(t('Cannot find the formatter named "@formatter".', array('@formatter', $formatter)));
  366. }
  367. $extension = $formatter::$default_extension;
  368. $create_date = $this->getCreateDate(FALSE);
  369. $outfile = preg_replace('/[^\w]/', '_', ucwords($this->collection_name)) . '_collection' . '_' . $create_date . '.' . $extension;
  370. return $outfile;
  371. }
  372. /**
  373. * Indicates if the given formatter is compatible with the data collection.
  374. *
  375. * @param $formatter
  376. * The class name of the formatter to check.
  377. * @return boolean
  378. * TRUE if the formatter is compatible, FALSE otherwise.
  379. */
  380. protected function isFormatterCompatible($formatter) {
  381. foreach ($this->formatters as $class_name => $label) {
  382. if ($class_name == $formatter) {
  383. return TRUE;
  384. }
  385. }
  386. return FALSE;
  387. }
  388. /**
  389. * Retrieves the URL for the downloadable file.
  390. *
  391. * @param $formatter
  392. * The name of the class
  393. */
  394. public function getOutfileURL($formatter) {
  395. $outfile = $this->getOutfilePath($formatter);
  396. }
  397. /**
  398. * Retrieves the path for the downloadable file.
  399. *
  400. * The path is in the Drupal URI format.
  401. *
  402. * @param $formatter
  403. * The name of the class
  404. */
  405. public function getOutfilePath($formatter) {
  406. if (!$this->isFormatterCompatible($formatter)) {
  407. throw new Exception(t('The formatter, "@formatter", is not compatible with this data collection.', array('@formatter' => $formatter)));
  408. }
  409. if (!tripal_load_include_downloader_class($formatter)) {
  410. throw new Exception(t('Cannot find the formatter named "@formatter".', array('@formatter', $formatter)));
  411. }
  412. $outfile = $this->getOutfile($formatter);
  413. // Make sure the user directory exists
  414. $user_dir = 'public://tripal/users/' . $this->user->uid;
  415. $outfilePath = $user_dir. '/' . $outfile;
  416. return $outfilePath;
  417. }
  418. /**
  419. * Writes the collection to a file using a given formatter.
  420. *
  421. * @param formatter
  422. * The name of the formatter class to use (e.g. TripalTabDownloader). The
  423. * formatter must be compatible with the data collection. If no
  424. * formatter is supplied then all file formats supported by this
  425. * data collection will be created.
  426. * @param $job
  427. * If this function is run as a Tripal Job then this argument can be
  428. * set to the Tripaljob object for keeping track of progress.
  429. * @throws Exception
  430. */
  431. public function write($formatter = NULL, TripalJob $job = NULL) {
  432. // Initialize the downloader classes and initialize the files for writing.
  433. $formatters = array();
  434. foreach ($this->formatters as $class => $label) {
  435. if (!$this->isFormatterCompatible($class)) {
  436. throw new Exception(t('The formatter, "@formatter", is not compatible with this data collection.', array('@formatter' => $formatter)));
  437. }
  438. if (!tripal_load_include_downloader_class($class)) {
  439. throw new Exception(t('Cannot find the formatter named "@formatter".', array('@formatter', $formatter)));
  440. }
  441. $outfile = $this->getOutfile($class);
  442. if (!$formatter or ($formatter == $class)) {
  443. $formatters[$class] = new $class($this->collection_id, $outfile);
  444. $formatters[$class]->writeInit($job);
  445. if ($job) {
  446. $job->logMessage("Writing " . lcfirst($class::$full_label) . " file.");
  447. }
  448. }
  449. }
  450. // Count the total number of entities
  451. $total_entities = 0;
  452. $bundle_collections = $this->collection_bundles;
  453. foreach ($this->bundles as $bundle) {
  454. $bundle_name = $bundle->bundle_name;
  455. $entity_ids = $this->getEntityIDs($bundle_name);
  456. $total_entities += count($entity_ids);
  457. }
  458. if ($job) {
  459. $job->setTotalItems($total_entities);
  460. }
  461. // Iterate through the bundles in this collection and get the entities.
  462. foreach ($this->bundles as $bundle) {
  463. $bundle_name = $bundle->bundle_name;
  464. $site_id = $bundle->site_id;
  465. $entity_ids = array_unique($this->getEntityIDs($bundle_name));
  466. $field_ids = array_unique($this->getFieldIDs($bundle_name));
  467. // Clear any cached @context or API docs.
  468. if ($site_id and module_exists('tripal_ws')) {
  469. tripal_clear_remote_cache($site_id);
  470. }
  471. // We want to load entities in batches to speed up performance.
  472. $num_eids = count($entity_ids);
  473. $bundle_eids_handled = 0;
  474. $slice_size = 100;
  475. while ($bundle_eids_handled < $num_eids) {
  476. // Get a bantch of $slice_size elements from the entities array.
  477. $slice = array_slice($entity_ids, $bundle_eids_handled, $slice_size);
  478. if ($job) {
  479. $job->logMessage('Getting entities for ids !start to !end of !total',
  480. array('!start' => $bundle_eids_handled,
  481. '!end' => $bundle_eids_handled + count($slice),
  482. '!total' => $num_eids));
  483. }
  484. $bundle_eids_handled += count($slice);
  485. // If the bundle is from a remote site then call the appropriate
  486. // function, otherwise, call the local function.
  487. if ($site_id and module_exists('tripal_ws')) {
  488. $entities = tripal_load_remote_entities($slice, $site_id, $bundle_name, $field_ids);
  489. }
  490. else {
  491. $entities = tripal_load_entity('TripalEntity', $slice, FALSE, $field_ids, FALSE);
  492. }
  493. $job->logMessage('Got !count entities.', array('!count' => count($entities)));
  494. // Now write each entity one at a time to the files.
  495. foreach ($entities as $entity_id => $entity) {
  496. // Write the same entity to all the formatters that are supported.
  497. foreach ($formatters as $class => $formatter) {
  498. //if ($class == 'TripalTabDownloader') {
  499. $formatter->writeEntity($entity, $job);
  500. //}
  501. }
  502. }
  503. if ($job) {
  504. $job->setItemsHandled($num_handled + count($slice));
  505. }
  506. }
  507. }
  508. // Now close up all the files
  509. foreach ($formatters as $class => $formatter) {
  510. $formatter->writeDone($job);
  511. }
  512. }
  513. }