Эх сурвалжийг харах

Add unpublish orphaned entities functionality

Abdullah Almsaeed 6 жил өмнө
parent
commit
74595866a9

+ 151 - 62
tripal_chado/includes/tripal_chado.entity.inc

@@ -1,6 +1,5 @@
 <?php
 
-
 /**
  * Implements hook_entity_create().
  *
@@ -20,7 +19,7 @@ function tripal_chado_entity_create(&$entity, $type, $bundle = NULL) {
 
     // Set some defaults on vars needed by this module.
     if (!property_exists($entity, 'chado_table')) {
-      $entity->chado_table =  NULL;
+      $entity->chado_table = NULL;
       $entity->chado_column = NULL;
       $entity->chado_linker = NULL;
       $entity->chado_type_id = NULL;
@@ -29,7 +28,7 @@ function tripal_chado_entity_create(&$entity, $type, $bundle = NULL) {
 
       // Add in the Chado table information for this entity type.
       if (!$bundle) {
-        $bundle = tripal_load_bundle_entity(array('name' => $entity->bundle));
+        $bundle = tripal_load_bundle_entity(['name' => $entity->bundle]);
       }
       if ($bundle->data_table) {
         $entity->chado_table = $bundle->data_table;
@@ -46,6 +45,7 @@ function tripal_chado_entity_create(&$entity, $type, $bundle = NULL) {
     }
   }
 }
+
 /**
  * Implements hook_entity_presave().
  */
@@ -97,7 +97,7 @@ function tripal_chado_entity_load($entities, $type) {
         $entity->chado_record_id = NULL;
 
         // Add in the Chado table information for this entity type.
-        $bundle = tripal_load_bundle_entity(array('name' => $entity->bundle));
+        $bundle = tripal_load_bundle_entity(['name' => $entity->bundle]);
         if (!$bundle) {
           continue;
         }
@@ -112,7 +112,8 @@ function tripal_chado_entity_load($entities, $type) {
           ->fetchObject();
         if ($chado_entity) {
           $schema = chado_get_schema($entity->chado_table);
-          $record = chado_generate_var($entity->chado_table, array($schema['primary key'][0] => $chado_entity->record_id));
+          $record = chado_generate_var($entity->chado_table,
+            [$schema['primary key'][0] => $chado_entity->record_id]);
           $entity->chado_record = $record;
           $entity->chado_record_id = $chado_entity->record_id;
         }
@@ -157,107 +158,107 @@ function tripal_chado_entity_delete($entity, $type) {
  * Overrides the default titles.
  */
 function tripal_chado_tripal_default_title_format($bundle, $available_tokens) {
-  $format = array();
+  $format = [];
 
   $table = $bundle->data_table;
 
   if ($table == 'organism') {
     if (chado_get_version() <= '1.2') {
-      $format[] = array(
+      $format[] = [
         'format' => '[taxrank__genus] [taxrank__species]',
-        'weight' => -5
-      );
+        'weight' => -5,
+      ];
     }
     else {
-      $format[] = array(
+      $format[] = [
         'format' => '[taxrank__genus] [taxrank__species] [taxrank__infraspecific_taxon,TAXRANK:0000045]',
-        'weight' => -5
-      );
+        'weight' => -5,
+      ];
     }
   }
   if ($table == 'arraydesign') {
-    $format[] = array(
+    $format[] = [
       'format' => '[schema__name]',
-      'weight' => -5
-    );
+      'weight' => -5,
+    ];
   }
   if ($table == 'assay') {
-    $format[] = array(
+    $format[] = [
       'format' => '[schema__name]',
-      'weight' => -5
-    );
+      'weight' => -5,
+    ];
   }
   if ($table == 'biomaterial') {
-    $format[] = array(
+    $format[] = [
       'format' => '[schema__name]',
-      'weight' => -5
-    );
+      'weight' => -5,
+    ];
   }
   if ($table == 'analysis') {
-    $format[] = array(
+    $format[] = [
       'format' => '[schema__name]',
-      'weight' => -5
-    );
+      'weight' => -5,
+    ];
   }
   if ($table == 'feature') {
-    $format[] = array(
+    $format[] = [
       'format' => '[schema__name]',
-      'weight' => -5
-    );
+      'weight' => -5,
+    ];
   }
   if ($table == 'featuremap') {
-    $format[] = array(
+    $format[] = [
       'format' => '[schema__name]',
-      'weight' => -5
-    );
+      'weight' => -5,
+    ];
   }
   if ($table == 'stock') {
-    $format[] = array(
+    $format[] = [
       'format' => '[schema__name]',
-      'weight' => -5
-    );
+      'weight' => -5,
+    ];
   }
   if ($table == 'pub') {
-    $format[] = array(
+    $format[] = [
       'format' => '[tpub__title]',
       'weight' => -5,
-    );
+    ];
   }
   if ($table == 'cvterm') {
-    $format[] = array(
+    $format[] = [
       'format' => '[schema__name]',
       'weight' => -5,
-    );
+    ];
   }
   if ($table == 'project') {
-    $format[] = array(
+    $format[] = [
       'format' => '[schema__name]',
       'weight' => -5,
-    );
+    ];
   }
   if ($table == 'contact') {
-    $format[] = array(
+    $format[] = [
       'format' => '[schema__name]',
       'weight' => -5,
-    );
+    ];
   }
   if ($table == 'phylotree') {
-    $format[] = array(
+    $format[] = [
       'format' => '[schema__name]',
       'weight' => -5,
-    );
+    ];
   }
   if ($table == 'library') {
-    $format[] = array(
+    $format[] = [
       'format' => '[schema__name]',
       'weight' => -5,
-    );
+    ];
   }
   if ($table == 'protocol') {
-    $format[] = array(
+    $format[] = [
       'format' => '[schema__name]',
       'weight' => -5,
-    );
+    ];
   }
   return $format;
 }
@@ -270,36 +271,37 @@ function tripal_chado_entity_view($entity, $type, $view_mode, $langcode) {
   // If this entity is a TripalEntity and is a full view, then
   // we want to support the legacy view, but only if the legacy
   // module is enabled (the functions exist).
-  if ($type =='TripalEntity') {
+  if ($type == 'TripalEntity') {
     // Use the generic template to render the fields
     if ($view_mode == 'full') {
 
       // Get the Chado table for this data type.
-      $bundle = tripal_load_bundle_entity(array('name' => $entity->bundle));
+      $bundle = tripal_load_bundle_entity(['name' => $entity->bundle]);
       $chado_table = $bundle->data_table;
       $chado_field = $bundle->type_column;
 
       // Get the list of templates that should be used for entities and generatte
       // the key in the array for this entity type (using the chado table the
       // entity maps to).
-      $enabled_templates = variable_get('tripal_chado_enabled_legacy_templates', array());
+      $enabled_templates = variable_get('tripal_chado_enabled_legacy_templates',
+        []);
       $legacy_template = 'legacy_template--chado_' . $chado_table;
 
       // If the site admin has indicated that this entity type should use
       // a legacy tmplate then prepare the entity and content to fake a
       // node.
-      if (key_exists($legacy_template, $enabled_templates) && $enabled_templates[$legacy_template]) {
+      if (key_exists($legacy_template,
+          $enabled_templates) && $enabled_templates[$legacy_template]) {
         // Remove the fields added by the chado_field_storage.
         $fields = field_info_fields();
-        foreach($fields as $field) {
-          if ($field['storage']['type'] == 'field_chado_storage' or
-              $field['storage']['type'] == 'tripal_no_storage') {
-                $field_name = $field['field_name'];
-                if (property_exists($entity, $field_name)) {
-                  $entity->$field_name = NULL;
-                  unset($entity->content[$field_name]);
-                }
-              }
+        foreach ($fields as $field) {
+          if ($field['storage']['type'] == 'field_chado_storage' or $field['storage']['type'] == 'tripal_no_storage') {
+            $field_name = $field['field_name'];
+            if (property_exists($entity, $field_name)) {
+              $entity->$field_name = NULL;
+              unset($entity->content[$field_name]);
+            }
+          }
         }
 
         // Make the entity look like a node.
@@ -341,10 +343,12 @@ function tripal_chado_entity_view_alter(&$build) {
   // For the legacy support, we need to make sure the TOC
   // is built.
   if ($build['#entity_type'] == 'TripalEntity') {
-    $enabled_templates = variable_get('tripal_chado_enabled_legacy_templates', array());
+    $enabled_templates = variable_get('tripal_chado_enabled_legacy_templates',
+      []);
     $entity = $build['#entity'];
     $legacy_template = 'legacy_template--' . $entity->type;
-    if (key_exists($legacy_template, $enabled_templates) && $enabled_templates[$legacy_template]) {
+    if (key_exists($legacy_template,
+        $enabled_templates) && $enabled_templates[$legacy_template]) {
       $build['#entity']->nid = NULL;
       $build['#node'] = $build['#entity'];
       $modules = module_list();
@@ -357,3 +361,88 @@ function tripal_chado_entity_view_alter(&$build) {
     }
   }
 }
+
+/**
+ * Job callback to perform the deletion of orphaned entities.
+ *
+ * @param int $bundle_id
+ *
+ * @throws \Exception
+ */
+function tripal_chado_delete_orphaned_entities($bundle_id) {
+  $bundle = db_query('SELECT bundle_id, data_table 
+             FROM {chado_bundle} CB WHERE bundle_id = :id',
+    [':id' => $bundle_id])->fetchObject();
+
+  $bundle_table = db_escape_table("chado_bio_data_{$bundle->bundle_id}");
+  $chado_table = db_escape_table($bundle->data_table);
+  $schema = chado_get_schema($chado_table);
+  $primary_key = is_array($schema) ? $schema['primary key'][0] : NULL;
+
+  $count = (int) db_query('SELECT count(*) FROM {' . $bundle_table . '} BT
+                        LEFT JOIN {chado.' . $chado_table . '} CT ON BT.record_id = CT.' . $primary_key . '
+                        WHERE CT.' . $primary_key . ' IS NULL')->fetchField();
+
+  if ($count === 0) {
+    print "Unable to find any orphaned entities.\n";
+    return;
+  }
+
+  print "Found $count orphaned entities.\n";
+
+  $chunk = 500;
+  $progress = 0;
+  print "Progress: {$progress}%; Memory: " . number_format(memory_get_usage()) . " bytes\r";
+
+  for ($i = 0; $i < $count; $i += $chunk) {
+    // Get a chunk of orphaned entities
+    $entities = db_query('SELECT entity_id FROM {' . $bundle_table . '} BT
+                          LEFT JOIN {chado}.' . $chado_table . ' CT ON BT.record_id = CT.' . $primary_key . '
+                          WHERE CT.' . $primary_key . ' IS NULL 
+                          ORDER BY entity_id desc
+                          LIMIT :limit', [
+      ':limit' => $chunk,
+    ])->fetchAll();
+
+    // Extract entity ids
+    $ids = array_map(function ($entity) {
+      return (int) $entity->entity_id;
+    }, $entities);
+
+    // Tell the user that selected entities are not present
+    if (empty($ids)) {
+      tripal_report_error('tripal_chado', TRIPAL_ERROR,
+        'Unable to find selected entities while attempting to delete orphaned entities.');
+      continue;
+    }
+
+    // Many warnings get printed when deleting an entity that has no
+    // associated chado record which make it really hard to follow progress.
+    // Therefore, let's suppress any output from the delete function. Errors
+    // are still detected when the delete function returns false.
+    ob_start(function ($buffer) {
+      unset($buffer);
+    });
+    putenv('TRIPAL_SUPPRESS_ERRORS=true');
+    try {
+      // This will trigger all needed hooks
+      /** @var \TripalEntityController $controller */
+      $controller = entity_get_controller('TripalEntity');
+      if ($controller->delete($ids) === FALSE) {
+        print "\nFailed to delete chunk {$i}/{$count}!\n";
+      }
+      $controller->resetCache($ids);
+    } catch (Exception $exception) {
+      print "\nERROR: " . $exception->getMessage()."\n";
+    }
+    putenv('TRIPAL_SUPPRESS_ERRORS=false');
+    ob_end_clean();
+
+    // Report progress
+    $progress = number_format(($i + $chunk) / $count * 100, 2);
+    print "Progress: {$progress}%; Memory: " . number_format(memory_get_usage()) . " bytes\r";
+  }
+
+  print "Progress: 100%; Memory: " . number_format(memory_get_usage()) . " bytes\n";
+  print "Done.\n";
+}

+ 211 - 0
tripal_chado/includes/tripal_chado.unpublish_form.inc

@@ -0,0 +1,211 @@
+<?php
+
+/**
+ * Unpublish orphaned entities form.
+ *
+ * @param $form
+ * @param $form_state
+ */
+function tripal_chado_unpublish_form($form, &$form_state) {
+  $bundles = tripal_chado_get_orphaned_bundles();
+
+  $form['description'] = [
+    '#markup' => t('<p>Unpublish entities that have no associated records in 
+                        their corresponding chado tables</p>')
+  ];
+
+  if (empty($bundles)) {
+    $form['message'] = [
+      '#type' => 'markup',
+      '#markup' => t("<p>No orphaned entities detected.</p>"),
+    ];
+
+    return $form;
+  }
+
+  $form['bundles'] = [
+    '#title' => t('Select a bundle'),
+    '#type' => 'select',
+    '#options' => $bundles,
+    '#empty_option' => t('-- Select a Bundle --'),
+    '#description' => t('Select a bundle to check if it has orphaned entities.'),
+    '#ajax' => [
+      'callback' => 'tripal_chado_unpublish_form_callback',
+      'wrapper' => 'bundle_info_fieldset_wrapper',
+    ],
+  ];
+
+  $form['bundle_info_fieldset'] = [
+    '#type' => 'fieldset',
+    '#title' => 'Search Results',
+    '#states' => [
+      'invisible' => [
+        'select[name="bundles"]' => ['value' => ''],
+      ],
+    ],
+    '#collapsible' => FALSE,
+    '#prefix' => '<div id="bundle_info_fieldset_wrapper">',
+    '#suffix' => '</div>',
+  ];
+
+  $selected_bundle_id = isset($form_state['values']['bundles']) ? $form_state['values']['bundles'] : NULL;
+  if ($selected_bundle_id) {
+    $count = tripal_chado_get_orphaned_counts_for_bundle($selected_bundle_id);
+    $name = $bundles[$selected_bundle_id];
+    $form['bundle_info_fieldset']['message'] = [
+      '#type' => 'markup',
+      '#markup' => t('<p><strong>There are ' . $count . ' orphaned entities in the ' . $name . ' bundle.</strong></p>'),
+    ];
+
+    if ($count > 0) {
+      $form['bundle_info_fieldset']['example_table'] = [
+        '#type' => 'markup',
+        '#prefix' => t('<p>The following is an example of the records that will get removed. This table contains a maximum of 10 records.</p>'),
+        '#markup' => tripal_chado_missing_records_table($selected_bundle_id),
+      ];
+
+      $form['bundle_info_fieldset']['submit'] = [
+        '#type' => 'submit',
+        '#value' => 'Permanently Delete Orphaned Entities',
+      ];
+    }
+  }
+
+  return $form;
+}
+
+/**
+ * Validate form entries.
+ *
+ * @param array $form
+ * @param array $form_state
+ */
+function tripal_chado_unpublish_form_validate($form, &$form_state) {
+  $bundle_id = isset($form_state['values']['bundles']) ? $form_state['values']['bundles'] : NULL;
+
+  if (empty($bundle_id) || !is_numeric($bundle_id)) {
+    form_set_error('bundles', t('Please select a valid bundle.'));
+  }
+}
+
+/**
+ * Process unpublish form.
+ *
+ * @param array $form
+ * @param array $form_state
+ */
+function tripal_chado_unpublish_form_submit($form, &$form_state) {
+  global $user;
+  $bundle_id = isset($form_state['values']['bundles']) ? $form_state['values']['bundles'] : NULL;
+  tripal_add_job('Delete Orphaned Entities', 'tripal_chado',
+    'tripal_chado_delete_orphaned_entities', [$bundle_id], $user->uid, 10);
+
+  drupal_set_message('Job submitted successfully!');
+  drupal_set_message('WARNING: It is expected to see a few tripal_chado errors
+   while the job is executing. The errors can be safely ignored.', 'warning');
+}
+
+/**
+ * Get bundles that have orphaned entities.
+ *
+ * @return array
+ */
+function tripal_chado_get_orphaned_bundles() {
+  $bundles = db_query('SELECT bundle_id, data_table, label 
+             FROM {chado_bundle} CB
+             INNER JOIN {tripal_bundle} TB ON TB.id = CB.bundle_id')->fetchAll();
+
+  $mapped = [];
+
+  foreach ($bundles as $bundle) {
+    $mapped[$bundle->bundle_id] = "{$bundle->label}";
+  }
+
+  return $mapped;
+}
+
+/**
+ * Get the count of orphaned entities per bundle.
+ *
+ * @param int $bundle
+ *
+ * @return int
+ */
+function tripal_chado_get_orphaned_counts_for_bundle($bundle_id) {
+  // Get the bundle
+  $bundle = db_query('SELECT bundle_id, data_table, label 
+             FROM {chado_bundle} CB
+             INNER JOIN {tripal_bundle} TB ON TB.id = CB.bundle_id
+             WHERE bundle_id = :id', [':id' => $bundle_id])->fetchObject();
+
+  $bundle_table = db_escape_table("chado_bio_data_{$bundle->bundle_id}");
+  $chado_table = db_escape_table($bundle->data_table);
+  $schema = chado_get_schema($chado_table);
+  $primary_key = is_array($schema) ? $schema['primary key'][0] : NULL;
+  $count = 0;
+
+  // Get the count
+  if ($primary_key) {
+    $count = db_query('SELECT count(*) FROM {' . $bundle_table . '} BT
+                        LEFT JOIN {chado.' . $chado_table . '} CT ON BT.record_id = CT.' . $primary_key . '
+                        WHERE CT.' . $primary_key . ' IS NULL')->fetchField();
+  }
+
+  return (int) $count;
+}
+
+/**
+ * Ajax callback for this form.
+ *
+ * @param array $form
+ *
+ * @return array
+ */
+function tripal_chado_unpublish_form_callback($form) {
+  return $form['bundle_info_fieldset'];
+}
+
+/**
+ * Create a table populated with examples of records that would get deleted.
+ *
+ * @param $bundle_id
+ *
+ * @return string
+ * @throws \Exception
+ */
+function tripal_chado_missing_records_table($bundle_id) {
+  // Get the bundle
+  $bundle = db_query('SELECT bundle_id, data_table, label 
+             FROM {chado_bundle} CB
+             INNER JOIN {tripal_bundle} TB ON TB.id = CB.bundle_id
+             WHERE bundle_id = :id', [':id' => $bundle_id])->fetchObject();
+
+  $bundle_table = db_escape_table("chado_bio_data_{$bundle->bundle_id}");
+  $chado_table = db_escape_table($bundle->data_table);
+  $schema = chado_get_schema($chado_table);
+  $primary_key = is_array($schema) ? $schema['primary key'][0] : NULL;
+
+  $entities = db_query('SELECT TE.title, TE.id, BT.record_id FROM {' . $bundle_table . '} BT
+                        LEFT JOIN {chado.' . $chado_table . '} CT ON BT.record_id = CT.' . $primary_key . '
+                        LEFT JOIN {tripal_entity} TE ON TE.id = BT.entity_id
+                        WHERE CT.' . $primary_key . ' IS NULL
+                        ORDER BY BT.entity_id ASC
+                        LIMIT 10')->fetchAll();
+
+  return theme('table', [
+    'header' => [
+      'Entity ID',
+      'Title',
+      'Chado Table',
+      str_replace('_', ' ', $primary_key),
+    ],
+    'rows' => array_map(function ($entity) use ($chado_table) {
+      return [
+        $entity->id,
+        l($entity->title, 'bio_data/' . $entity->id),
+        $chado_table,
+        $entity->record_id,
+      ];
+    }, $entities),
+  ]);
+}

+ 23 - 10
tripal_chado/tripal_chado.module

@@ -212,6 +212,19 @@ function tripal_chado_menu() {
     'weight' => 2
   );
 
+  $items['admin/content/bio_data/unpublish'] = [
+    'title' => 'Delete Orphaned Entities',
+    'description' => t('Unpublish entities that have no associated records in 
+                        their corresponding chado tables'),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => ['tripal_chado_unpublish_form'],
+    'access arguments' => ['administer tripal'],
+    'file' => 'includes/tripal_chado.unpublish_form.inc',
+    'file path' => drupal_get_path('module', 'tripal_chado'),
+    'type' => MENU_LOCAL_ACTION,
+    'weight' => 3
+  ];
+
 
   //////////////////////////////////////////////////////////////////////////////
   //                       Materialized Views
@@ -1184,9 +1197,9 @@ function tripal_feature_match_features_page($id) {
     $table_attrs = array('class' => 'tripal-data-table');
     $output = "<p>The following features match the name '$id'.</p>";
     $output .= theme_table(array(
-      'header' => $header, 
-      'rows' => $rows, 
-      'attributes' => $table_attrs, 
+      'header' => $header,
+      'rows' => $rows,
+      'attributes' => $table_attrs,
       'caption' => $caption
     ));
     return $output;
@@ -1203,7 +1216,7 @@ function tripal_chado_mail($key, &$message, $params) {
   $language = $message['language'];
   switch($key) {
     case 'import_report':
-      
+
       $content = [];
       $content[] = [
         '#type' => 'markup',
@@ -1219,10 +1232,10 @@ function tripal_chado_mail($key, &$message, $params) {
           }
           $content[] = [
             '#type' => 'item',
-            '#title' => $title, 
+            '#title' => $title,
             '#markup' => theme_item_list([
               'title' => '',
-              'type' => 'ol', 
+              'type' => 'ol',
               'items' => $pubs,
               'attributes' => [],
             ]),
@@ -1230,10 +1243,10 @@ function tripal_chado_mail($key, &$message, $params) {
         }
       }
       $content = '<html>' . drupal_render($content) . '</html';
-      
+
       $message['subject'] = t('Publication import from !site', array('!site' => $site_name));
-      
-      
+
+
       if (module_exists('htmlmail') or module_exists('mimemail')) {
         $headers = array(
           'MIME-Version' => '1.0',
@@ -1249,7 +1262,7 @@ function tripal_chado_mail($key, &$message, $params) {
       else {
         $message['body'][] =  drupal_html_to_text($content);
       }
-            
+
       break;
   }
 }