CRM-14765 - FullText - Display file matches as paperclip icons
authorTim Otten <totten@civicrm.org>
Tue, 10 Jun 2014 04:35:18 +0000 (21:35 -0700)
committerTim Otten <totten@civicrm.org>
Tue, 10 Jun 2014 04:42:47 +0000 (21:42 -0700)
Instead of defining a separate table for searching by file, treat files as
part of their corresponding entities.  If a file matches, then display a
paperclip icon on its parent record.

CRM/Contact/Form/Search/Custom/FullText.php
CRM/Contact/Form/Search/Custom/FullText/AbstractPartialQuery.php
CRM/Contact/Form/Search/Custom/FullText/Activity.php
CRM/Contact/Form/Search/Custom/FullText/Case.php
CRM/Contact/Form/Search/Custom/FullText/Contact.php
CRM/Contact/Form/Search/Custom/FullText/Contribution.php
CRM/Contact/Form/Search/Custom/FullText/File.php [deleted file]
CRM/Contact/Form/Search/Custom/FullText/Membership.php
CRM/Contact/Form/Search/Custom/FullText/Participant.php
templates/CRM/Contact/Form/Search/Custom/FullText.tpl

index f00b4b47b812fecfec19f70af2f20fe65f5afc7b..634f4da0becd214ef99c4e56871da4fafab5ea1f 100644 (file)
@@ -86,7 +86,6 @@ class CRM_Contact_Form_Search_Custom_FullText implements CRM_Contact_Form_Search
       new CRM_Contact_Form_Search_Custom_FullText_Contribution(),
       new CRM_Contact_Form_Search_Custom_FullText_Participant(),
       new CRM_Contact_Form_Search_Custom_FullText_Membership(),
-      new CRM_Contact_Form_Search_Custom_FullText_File(),
     );
 
     $formValues['table'] = $this->getFieldValue($formValues, 'table', 'String');
@@ -195,12 +194,10 @@ class CRM_Contact_Form_Search_Custom_FullText implements CRM_Contact_Form_Search
       'membership_end_date' => 'datetime',
       'membership_source' => 'varchar(255)',
       'membership_status' => 'varchar(255)',
-      'file_id' => 'int unsigned',
-      'file_name' => 'varchar(255)',
-      'file_url' => 'varchar(255)',
-      'file_mime_type' => 'varchar(255)',
-      'file_entity_table' => 'varchar(255)',
-      'file_entity_id' => 'int unsigned',
+
+      // We may have multiple files to list on one record.
+      // The temporary-table approach can't store full details for all of them
+      'file_ids' => 'varchar(255)', // comma-separate id listing
     );
 
     $sql = "
@@ -339,6 +336,9 @@ WHERE      t.table_name = 'Activity' AND
      * You can define a custom title for the search form
      */
     $this->setTitle(ts('Full-text Search'));
+
+    $searchService = CRM_Core_BAO_File::getSearchService();
+    $form->assign('allowFileSearch', !empty($searchService) && CRM_Core_Permission::check('access uploaded files'));
   }
 
   /**
@@ -392,6 +392,17 @@ WHERE      t.table_name = 'Activity' AND
         }
         $row['participant_role'] = implode(', ', $viewRoles);
       }
+      if (!empty($row['file_ids'])) {
+        $fileIds = (explode(',', $row['file_ids']));
+        $fileHtml = '';
+        foreach ($fileIds as $fileId) {
+          $paperclip = CRM_Core_BAO_File::paperIconAttachment('*', $fileId);
+          if ($paperclip) {
+            $fileHtml .= implode('', $paperclip);
+          }
+        }
+        $row['fileHtml'] = $fileHtml;
+      }
       $summary[$dao->table_name][] = $row;
     }
 
index 6eb226418cbf75332ea753098c2bd3ddfce58cce..501750ddb238c9e6b160f0db7856b8d7e9663193 100644 (file)
@@ -125,11 +125,14 @@ AND        cf.html_type IN ( 'Text', 'TextArea', 'RichTextEditor' )
    *   - *: All other keys are treated as table names
    * @return array keys: match-descriptor
    *   - count: int
+   *   - files: NULL | array
    */
   function runQueries($queryText, &$tables, $entityIDTableName, $limit) {
     $sql = "TRUNCATE {$entityIDTableName}";
     CRM_Core_DAO::executeQuery($sql);
 
+    $files = NULL;
+
     foreach ($tables as $tableName => $tableValues) {
       if ($tableName == 'final') {
         continue;
@@ -145,6 +148,26 @@ $sqlStatement
             CRM_Core_DAO::executeQuery($sql);
           }
         }
+        else if ($tableName == 'file') {
+          $searcher = CRM_Core_BAO_File::getSearchService();
+          if (!($searcher && CRM_Core_Permission::check('access uploaded files'))) {
+            continue;
+          }
+
+          $query = $tableValues + array(
+            'text' => $queryText,
+          );
+          list($intLimit, $intOffset) = $this->parseLimitOffset($limit);
+          $files = $searcher->search($query, $intLimit, $intOffset);
+          $matches = array();
+          foreach ($files as $file) {
+            $matches[] = array('entity_id' => $file['xparent_id']);
+          }
+          if ($matches) {
+            $insertSql = CRM_Utils_SQL_Insert::into($entityIDTableName)->usingReplace()->rows($matches)->toSQL();
+            CRM_Core_DAO::executeQuery($insertSql);
+          }
+        }
         else {
           $fullTextFields = array(); // array (string $sqlColumnName)
           $clauses = array(); // array (string $sqlExpression)
@@ -198,7 +221,8 @@ GROUP BY {$tableValues['id']}
     }
 
     return array(
-      'count' => CRM_Core_DAO::singleValueQuery("SELECT count(*) FROM {$entityIDTableName}")
+      'count' => CRM_Core_DAO::singleValueQuery("SELECT count(*) FROM {$entityIDTableName}"),
+      'files' => $files,
     );
   }
 
@@ -247,6 +271,45 @@ GROUP BY {$tableValues['id']}
     return implode(' OR ', $clauses);
   }
 
+  /**
+   * For any records in $toTable that originated with this query,
+   * append file information.
+   *
+   * @param string $toTable
+   * @param string $parentIdColumn
+   * @param array $files see return format of CRM_Core_FileSearchInterface::search
+   */
+  public function moveFileIDs($toTable, $parentIdColumn, $files) {
+    if (empty($files)) {
+      return;
+    }
+
+    $filesIndex = CRM_Utils_Array::index(array('xparent_id', 'file_id'), $files);
+    // ex: $filesIndex[$xparent_id][$file_id] = array(...the file record...);
+
+    $dao = CRM_Core_DAO::executeQuery("
+      SELECT distinct {$parentIdColumn}
+      FROM {$toTable}
+      WHERE table_name = %1
+    ", array(
+      1 => array($this->getName(), 'String'),
+    ));
+    while ($dao->fetch()) {
+      if (empty($filesIndex[$dao->{$parentIdColumn}])) {
+        continue;
+      }
+
+      CRM_Core_DAO::executeQuery("UPDATE {$toTable}
+        SET file_ids = %1
+        WHERE table_name = %2 AND {$parentIdColumn} = %3
+      ", array(
+        1 => array(implode(',', array_keys($filesIndex[$dao->{$parentIdColumn}])), 'String'),
+        2 => array($this->getName(), 'String'),
+        3 => array($dao->{$parentIdColumn}, 'Int'),
+      ));
+    }
+  }
+
   /**
    * Format text to include wild card characters at beginning and end
    *
@@ -291,4 +354,21 @@ GROUP BY {$tableValues['id']}
     return $result;
   }
 
+  /**
+   * @param array|int $limit
+   * @return array (0 => $limit, 1 => $offset)
+   */
+  public function parseLimitOffset($limit) {
+    if (is_scalar($limit)) {
+      $intLimit = $limit;
+    }
+    else {
+      list ($intLimit, $intOffset) = $limit;
+    }
+    if (!$intOffset) {
+      $intOffset = 0;
+    }
+    return array($intLimit, $intOffset);
+  }
+
 }
\ No newline at end of file
index e55518091589e804040f7ef1915918eaa582d417..f9ded2bdc517f4f2ea4ef3ba0c020d4ba172948f 100644 (file)
@@ -49,6 +49,9 @@ class CRM_Contact_Form_Search_Custom_FullText_Activity extends CRM_Contact_Form_
     $queries = $this->prepareQueries($queryText, $entityIDTableName);
     $result = $this->runQueries($queryText, $queries, $entityIDTableName, $queryLimit);
     $this->moveIDs($entityIDTableName, $toTable, $detailLimit);
+    if (!empty($result['files'])) {
+      $this->moveFileIDs($toTable, 'activity_id', $result['files']);
+    }
     return $result;
   }
 
@@ -102,6 +105,9 @@ AND    (ca.is_deleted = 0 OR ca.is_deleted IS NULL)
 
     $tables = array(
       'civicrm_activity' => array('fields' => array()),
+      'file' => array(
+        'xparent_table' => 'civicrm_activity',
+      ),
       'sql' => $contactSQL,
       'final' => $final,
     );
index a40aeb419cea12a6bcd4659dc390da189f6dc984..f767df127adabf5cdce1c203b37430cbe2ae82ba 100644 (file)
@@ -50,6 +50,9 @@ class CRM_Contact_Form_Search_Custom_FullText_Case extends CRM_Contact_Form_Sear
     $queries = $this->prepareQueries($queryText, $entityIDTableName);
     $result = $this->runQueries($queryText, $queries, $entityIDTableName, $queryLimit);
     $this->moveIDs($entityIDTableName, $toTable, $detailLimit);
+    if (!empty($result['files'])) {
+      $this->moveFileIDs($toTable, 'case_id', $result['files']);
+    }
     return $result;
   }
 
@@ -95,6 +98,9 @@ GROUP BY   et.entity_id
 
     $tables = array(
       'civicrm_case' => array('fields' => array()),
+      'file' => array(
+        'xparent_table' => 'civicrm_case',
+      ),
       'sql' => $contactSQL,
     );
 
index 583cb8b56e982dc979e161a6cb7cd5021931ccba..0bd666f1bbf99e1214b951a592379b38c8c99dbc 100644 (file)
@@ -49,6 +49,9 @@ class CRM_Contact_Form_Search_Custom_FullText_Contact extends CRM_Contact_Form_S
     $queries = $this->prepareQueries($queryText, $entityIDTableName);
     $result = $this->runQueries($queryText, $queries, $entityIDTableName, $queryLimit);
     $this->moveIDs($entityIDTableName, $toTable, $detailLimit);
+    if (!empty($result['files'])) {
+      $this->moveFileIDs($toTable, 'contact_id', $result['files']);
+    }
     return $result;
   }
 
@@ -110,6 +113,9 @@ GROUP BY   et.entity_id
           'note' => NULL,
         ),
       ),
+      'file' => array(
+        'xparent_table' => 'civicrm_contact',
+      ),
       'sql' => $contactSQL,
       'final' => $final,
     );
index 4514f2330b062c036f0ecebec8fda2544b3ea370..b2be2c995c90465ea17a17dca2bbec6c0e49c2c0 100644 (file)
@@ -51,6 +51,9 @@ class CRM_Contact_Form_Search_Custom_FullText_Contribution extends CRM_Contact_F
     $queries = $this->prepareQueries($queryText, $entityIDTableName);
     $result = $this->runQueries($queryText, $queries, $entityIDTableName, $queryLimit);
     $this->moveIDs($entityIDTableName, $toTable, $detailLimit);
+    if (!empty($result['files'])) {
+      $this->moveFileIDs($toTable, 'contribution_id', $result['files']);
+    }
     return $result;
   }
 
@@ -83,6 +86,9 @@ WHERE      ({$this->matchText('civicrm_contact c', array('sort_name', 'display_n
           'total_amount' => 'Int',
         ),
       ),
+      'file' => array(
+        'xparent_table' => 'civicrm_contribution',
+      ),
       'sql' => $contactSQL,
       'civicrm_note' => array(
         'id' => 'entity_id',
diff --git a/CRM/Contact/Form/Search/Custom/FullText/File.php b/CRM/Contact/Form/Search/Custom/FullText/File.php
deleted file mode 100644 (file)
index 239f994..0000000
+++ /dev/null
@@ -1,229 +0,0 @@
-<?php
-/*
- +--------------------------------------------------------------------+
- | CiviCRM version 4.5                                                |
- +--------------------------------------------------------------------+
- | Copyright CiviCRM LLC (c) 2004-2014                                |
- +--------------------------------------------------------------------+
- | This file is a part of CiviCRM.                                    |
- |                                                                    |
- | CiviCRM is free software; you can copy, modify, and distribute it  |
- | under the terms of the GNU Affero General Public License           |
- | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
- |                                                                    |
- | CiviCRM is distributed in the hope that it will be useful, but     |
- | WITHOUT ANY WARRANTY; without even the implied warranty of         |
- | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
- | See the GNU Affero General Public License for more details.        |
- |                                                                    |
- | You should have received a copy of the GNU Affero General Public   |
- | License and the CiviCRM Licensing Exception along                  |
- | with this program; if not, contact CiviCRM LLC                     |
- | at info[AT]civicrm[DOT]org. If you have questions about the        |
- | GNU Affero General Public License or the licensing of CiviCRM,     |
- | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
- +--------------------------------------------------------------------+
-*/
-
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC (c) 2004-2014
- * $Id$
- *
- */
-class CRM_Contact_Form_Search_Custom_FullText_File extends CRM_Contact_Form_Search_Custom_FullText_AbstractPartialQuery {
-
-  /**
-   * @var DrupalApacheSolrServiceInterface
-   *
-   * At time of writing, this interface is fairly minimal and doesn't seem to require Drupalisms.
-   */
-  protected $solrService;
-
-  public function __construct() {
-    parent::__construct('File', ts('Files'));
-  }
-
-  public function isActive() {
-    return
-      function_exists('apachesolr_get_solr') // Drupal site with apachesolr module
-      && function_exists('apachesolr_civiAttachments_solr_document') // Drupal site with apachesolr_civiAttachments module
-      && CRM_Core_Permission::check('access uploaded files');
-  }
-
-  /**
-   * @return DrupalApacheSolrServiceInterface
-   */
-  public function getSolrService() {
-    if ($this->solrService === NULL) {
-      $this->solrService = apachesolr_get_solr();
-    }
-    return $this->solrService;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function fillTempTable($queryText, $entityIDTableName, $toTable, $queryLimit, $detailLimit) {
-    $solrResponse = $this->doSearch($queryText, $queryLimit);
-    if (!$solrResponse) {
-      CRM_Core_Session::setStatus(ts('Search service (%1) returned an invalid response', array(1 => 'Solr')), ts('File Search'), 'error');
-      return 0;
-    }
-    $fileIds = $this->extractFileIds($solrResponse, $detailLimit);
-    $matches = $this->formatFileMatches($fileIds);
-    $this->insertMatches($toTable, $matches);
-
-    if (count($matches) < count($fileIds)) {
-      CRM_Core_Session::setStatus(
-        ts('The search service returned %1 file match(es), but only %2 match(es) exist.',
-          array(1 => count($fileIds), 2 => count($matches))
-        ),
-        ts('File Search')
-      );
-    }
-    return array(
-      'count' => count($solrResponse->docs),
-    );
-    //return $solrResponse->numFound;
-    //return count($matches);
-  }
-
-  /**
-   * @param string $queryText
-   * @param array|NULL $limit
-   * @return object|NULL
-   */
-  public function doSearch($queryText, $limit) {
-    $params = array();
-    if (is_array($limit)) {
-      list ($params['rows'], $params['start']) = $limit;
-      if (!$params['start']) {
-        $params['start'] = 0;
-      }
-    }
-    $query = $this->getSolrService()->search("entity_type:civiFile AND content:($queryText)", $params);
-    if ($query->code == 200) {
-      return $query->response;
-    }
-    else {
-      CRM_Core_Error::debug_var('failedSolrQuery', $query);
-      return NULL;
-    }
-  }
-
-  /**
-   * Extract the list of file ID#'s from a Solr response.
-   *
-   * @param array $solrResponse
-   * @param array|NULL $limit
-   * @return array<int>
-   * @throws CRM_Core_Exception
-   */
-  public function extractFileIds($solrResponse, $limit) {
-    $fileIds = array();
-    if (!empty($solrResponse->docs)) {
-      if ($limit) {
-        list($rowCount, $offset) = $limit;
-        $docs = array_slice($solrResponse->docs, $offset ? $offset : 0, $rowCount);
-      }
-      else {
-        $docs = $solrResponse->docs;
-      }
-
-      foreach ($docs as $doc) {
-        if ($doc->entity_type == 'civiFile') {
-          if (isset($doc->entity_id)) {
-            $fileIds[] = $doc->entity_id;
-          }
-          else {
-            CRM_Core_Session::setStatus(ts('Incorrect response type'), ts('File Search'));
-          }
-        }
-      }
-    }
-    return $fileIds;
-  }
-
-  /**
-   * Given a list of matching $fileIds, prepare a list of match records
-   * with details about the file (such as file-name and URL).
-   *
-   * @param array<int> $fileIds
-   * @return array
-   */
-  public function formatFileMatches($fileIds) {
-    $fileIdsCsv = implode(',', array_filter($fileIds, 'is_numeric'));
-    if (empty($fileIdsCsv)) {
-      return array();
-    }
-
-    $selectFilesSql = "
-      SELECT     f.*, ef.*, ef.id as entity_file_id
-      FROM       civicrm_file f
-      INNER JOIN civicrm_entity_file ef ON f.id = ef.file_id
-      WHERE      f.id IN ({$fileIdsCsv})
-    ";
-    $selectFilesDao = CRM_Core_DAO::executeQuery($selectFilesSql);
-
-    $matches = array();
-    while ($selectFilesDao->fetch()) {
-      $match = array(
-        'table_name' => $this->getName(),
-        'file_id' => $selectFilesDao->file_id,
-        'file_name' => CRM_Utils_File::cleanFileName($selectFilesDao->uri),
-        'file_url' => CRM_Utils_System::url('civicrm/file', "reset=1&id={$selectFilesDao->file_id}&eid={$selectFilesDao->entity_id}"),
-        'file_mime_type' => $selectFilesDao->mime_type,
-      );
-
-      if ($selectFilesDao->entity_table == 'civicrm_note') {
-        // For notes, we go up an extra level to the note's parent
-        $note = new CRM_Core_DAO_Note();
-        $note->id = $selectFilesDao->entity_id;
-        $note->find();
-        if ($note->fetch()) {
-          $match['file_entity_table'] = $note->entity_table;
-          $match['file_entity_id'] = $note->entity_id;
-        }
-        else {
-          continue; // skip; perhaps an orphan?
-        }
-      }
-      else {
-        $match['file_entity_table'] = $selectFilesDao->entity_table;
-        $match['file_entity_id'] = $selectFilesDao->entity_id;
-      }
-      $matches[] = $match;
-    }
-
-    // When possible, add 'contact_id' to matches
-    foreach (array_keys($matches) as $matchKey) {
-      switch ($matches[$matchKey]['file_entity_table']) {
-        case'civicrm_contact':
-          $matches[$matchKey]['contact_id'] = $matches[$matchKey]['file_entity_id'];
-          //$matches[$matchKey]['sort_name'] = NULL;
-          //$matches[$matchKey]['display_name'] = NULL;
-          break;
-        default:
-          $matches[$matchKey]['contact_id'] = NULL;
-        //$matches[$matchKey]['sort_name'] = NULL;
-        //$matches[$matchKey]['display_name'] = NULL;
-      }
-    }
-
-    return $matches;
-  }
-
-  /**
-   * @param string $toTable
-   * @param array $matches each $match is an array which defines a row in $toTable
-   */
-  public function insertMatches($toTable, $matches) {
-    if (empty($matches)) {
-      return;
-    }
-    $insertContactSql = CRM_Utils_SQL_Insert::into($toTable)->rows($matches)->toSQL();
-    CRM_Core_DAO::executeQuery($insertContactSql);
-  }
-}
index 971716eb368838082402515fdc35d29fcff1cfa8..6c30c3698e707f75bf176341cde180740ec7f280 100644 (file)
@@ -51,6 +51,9 @@ class CRM_Contact_Form_Search_Custom_FullText_Membership extends CRM_Contact_For
     $queries = $this->prepareQueries($queryText, $entityIDTableName);
     $result = $this->runQueries($queryText, $queries, $entityIDTableName, $queryLimit);
     $this->moveIDs($entityIDTableName, $toTable, $detailLimit);
+    if (!empty($result['files'])) {
+      $this->moveFileIDs($toTable, 'membership_id', $result['files']);
+    }
     return $result;
   }
 
@@ -76,6 +79,9 @@ WHERE      ({$this->matchText('civicrm_contact c', array('sort_name', 'display_n
         'id' => 'id',
         'fields' => array('source' => NULL),
       ),
+      'file' => array(
+        'xparent_table' => 'civicrm_membership',
+      ),
       'sql' => $contactSQL,
     );
 
index 3721ad9ed72b3a7f08d9f249a98e05769e014923..2312c214d42f6f84d5bdc539f238de8259f6917b 100644 (file)
@@ -51,6 +51,9 @@ class CRM_Contact_Form_Search_Custom_FullText_Participant extends CRM_Contact_Fo
     $queries = $this->prepareQueries($queryText, $entityIDTableName);
     $result = $this->runQueries($queryText, $queries, $entityIDTableName, $queryLimit);
     $this->moveIDs($entityIDTableName, $toTable, $detailLimit);
+    if (!empty($result['files'])) {
+      $this->moveFileIDs($toTable, 'participant_id', $result['files']);
+    }
     return $result;
   }
 
@@ -80,6 +83,9 @@ WHERE      ({$this->matchText('civicrm_contact c', array('sort_name', 'display_n
           'fee_amount' => 'Int',
         ),
       ),
+      'file' => array(
+        'xparent_table' => 'civicrm_participant',
+      ),
       'sql' => $contactSQL,
       'civicrm_note' => array(
         'id' => 'entity_id',
index 55cd24dea158c75b7eb3d636caa9fac9cf62e17f..8360b5aae23ad78508b4b7346617969ee891fe2e 100644 (file)
@@ -59,6 +59,7 @@
         <thead>
         <tr>
           <th class='link'>{ts}Name{/ts}</th>
+          {if $allowFileSearch}<th>{ts}File{/ts}</th>{/if}
           <th></th>
         </tr>
         </thead>
@@ -67,6 +68,7 @@
             <td><a
                 href="{crmURL p='civicrm/contact/view' q="reset=1&cid=`$row.contact_id`&context=fulltext&key=`$qfKey`"}"
                 title="{ts}View contact details{/ts}">{$row.sort_name}</a></td>
+            {if $allowFileSearch}<td>{$row.fileHtml}</td>{/if}
             <td><a
                 href="{crmURL p='civicrm/contact/view' q="reset=1&cid=`$row.contact_id`&context=fulltext&key=`$qfKey`"}">{ts}View{/ts}</a>
             </td>
           <th class='link'>{ts}Added By{/ts}</th>
           <th class='link'>{ts}With{/ts}</th>
           <th class='link'>{ts}Assignee{/ts}</th>
+          {if $allowFileSearch}<th>{ts}File{/ts}</th>{/if}
           <th></th>
         </tr>
         </thead>
               <a href="{crmURL p='civicrm/contact/view' q="reset=1&cid=`$row.assignee_contact_id`&context=fulltext&key=`$qfKey`"}"
                 title="{ts}View contact details{/ts}">{$row.assignee_sort_name}</a>
             </td>
+            {if $allowFileSearch}<td>{$row.fileHtml}</td>{/if}
             <td>
               {if $row.case_id }
                 <a href="{crmURL p='civicrm/case/activity/view'
           <th class="start_date">{ts}Start Date{/ts}</th>
           <th class="end_date">{ts}End Date{/ts}</th>
           <th>{ts}Case ID{/ts}</th>
+          {if $allowFileSearch}<th>{ts}File{/ts}</th>{/if}
           <th></th>
           <th class="hiddenElement"></th>
           <th class="hiddenElement"></th>
             <td>{$row.case_start_date|crmDate:"%b %d, %Y %l:%M %P"}</td>
             <td>{$row.case_end_date|crmDate:"%b %d, %Y %l:%M %P"}</td>
             <td>{$row.case_id}</td>
+            {if $allowFileSearch}<td>{$row.fileHtml}</td>{/if}
             {if $row.case_is_deleted}
               <td>
                 <a href="{crmURL p='civicrm/contact/view/case'
           <th>{ts}Source{/ts}</th>
           <th class="received_date">{ts}Received{/ts}</th>
           <th>{ts}Status{/ts}</th>
+          {if $allowFileSearch}<th>{ts}File{/ts}</th>{/if}
           <th></th>
           <th class="hiddenElement"></th>
         </tr>
             <td>{$row.contribution_source}</td>
             <td>{$row.contribution_receive_date|crmDate:"%b %d, %Y %l:%M %P"}</td>
             <td>{$row.contribution_status}</td>
+            {if $allowFileSearch}<td>{$row.fileHtml}</td>{/if}
             <td>
               <a href="{crmURL p='civicrm/contact/view/contribution'
               q="reset=1&id=`$row.contribution_id`&cid=`$row.contact_id`&action=view&context=fulltext&key=`$qfKey`"}">{ts}View{/ts}</a>
           <th>{ts}Source{/ts}</th>
           <th>{ts}Status{/ts}</th>
           <th>{ts}Role{/ts}</th>
+          {if $allowFileSearch}<th>{ts}File{/ts}</th>{/if}
           <th></th>
           <th class="hiddenElement"></th>
         </tr>
             <td>{$row.participant_source}</td>
             <td>{$row.participant_status}</td>
             <td>{$row.participant_role}</td>
+            {if $allowFileSearch}<td>{$row.fileHtml}</td>{/if}
             <td>
               <a href="{crmURL p='civicrm/contact/view/participant'
               q="reset=1&id=`$row.participant_id`&cid=`$row.contact_id`&action=view&context=fulltext&key=`$qfKey`"}">{ts}View{/ts}</a>
           <th class="end_date">{ts}Membership End Date{/ts}</th>
           <th>{ts}Source{/ts}</th>
           <th>{ts}Status{/ts}</th>
+          {if $allowFileSearch}<th>{ts}File{/ts}</th>{/if}
           <th></th>
           <th class="hiddenElement"></th>
           <th class="hiddenElement"></th>
             <td>{$row.membership_end_date|crmDate:"%b %d, %Y %l:%M %P"}</td>
             <td>{$row.membership_source}</td>
             <td>{$row.membership_status}</td>
+            {if $allowFileSearch}<td>{$row.fileHtml}</td>{/if}
             <td>
               <a href="{crmURL p='civicrm/contact/view/membership'
               q="reset=1&id=`$row.membership_id`&cid=`$row.contact_id`&action=view&context=fulltext&key=`$qfKey`"}">{ts}View{/ts}</a>