From: Tim Otten Date: Tue, 10 Jun 2014 04:35:18 +0000 (-0700) Subject: CRM-14765 - FullText - Display file matches as paperclip icons X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=cac9c01de1d558cbe9b32940074ce68344ab20be;p=civicrm-core.git CRM-14765 - FullText - Display file matches as paperclip icons 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. --- diff --git a/CRM/Contact/Form/Search/Custom/FullText.php b/CRM/Contact/Form/Search/Custom/FullText.php index f00b4b47b8..634f4da0be 100644 --- a/CRM/Contact/Form/Search/Custom/FullText.php +++ b/CRM/Contact/Form/Search/Custom/FullText.php @@ -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; } diff --git a/CRM/Contact/Form/Search/Custom/FullText/AbstractPartialQuery.php b/CRM/Contact/Form/Search/Custom/FullText/AbstractPartialQuery.php index 6eb226418c..501750ddb2 100644 --- a/CRM/Contact/Form/Search/Custom/FullText/AbstractPartialQuery.php +++ b/CRM/Contact/Form/Search/Custom/FullText/AbstractPartialQuery.php @@ -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 diff --git a/CRM/Contact/Form/Search/Custom/FullText/Activity.php b/CRM/Contact/Form/Search/Custom/FullText/Activity.php index e555180915..f9ded2bdc5 100644 --- a/CRM/Contact/Form/Search/Custom/FullText/Activity.php +++ b/CRM/Contact/Form/Search/Custom/FullText/Activity.php @@ -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, ); diff --git a/CRM/Contact/Form/Search/Custom/FullText/Case.php b/CRM/Contact/Form/Search/Custom/FullText/Case.php index a40aeb419c..f767df127a 100644 --- a/CRM/Contact/Form/Search/Custom/FullText/Case.php +++ b/CRM/Contact/Form/Search/Custom/FullText/Case.php @@ -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, ); diff --git a/CRM/Contact/Form/Search/Custom/FullText/Contact.php b/CRM/Contact/Form/Search/Custom/FullText/Contact.php index 583cb8b56e..0bd666f1bb 100644 --- a/CRM/Contact/Form/Search/Custom/FullText/Contact.php +++ b/CRM/Contact/Form/Search/Custom/FullText/Contact.php @@ -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, ); diff --git a/CRM/Contact/Form/Search/Custom/FullText/Contribution.php b/CRM/Contact/Form/Search/Custom/FullText/Contribution.php index 4514f2330b..b2be2c995c 100644 --- a/CRM/Contact/Form/Search/Custom/FullText/Contribution.php +++ b/CRM/Contact/Form/Search/Custom/FullText/Contribution.php @@ -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 index 239f994eef..0000000000 --- a/CRM/Contact/Form/Search/Custom/FullText/File.php +++ /dev/null @@ -1,229 +0,0 @@ -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 - * @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 $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); - } -} diff --git a/CRM/Contact/Form/Search/Custom/FullText/Membership.php b/CRM/Contact/Form/Search/Custom/FullText/Membership.php index 971716eb36..6c30c3698e 100644 --- a/CRM/Contact/Form/Search/Custom/FullText/Membership.php +++ b/CRM/Contact/Form/Search/Custom/FullText/Membership.php @@ -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, ); diff --git a/CRM/Contact/Form/Search/Custom/FullText/Participant.php b/CRM/Contact/Form/Search/Custom/FullText/Participant.php index 3721ad9ed7..2312c214d4 100644 --- a/CRM/Contact/Form/Search/Custom/FullText/Participant.php +++ b/CRM/Contact/Form/Search/Custom/FullText/Participant.php @@ -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', diff --git a/templates/CRM/Contact/Form/Search/Custom/FullText.tpl b/templates/CRM/Contact/Form/Search/Custom/FullText.tpl index 55cd24dea1..8360b5aae2 100644 --- a/templates/CRM/Contact/Form/Search/Custom/FullText.tpl +++ b/templates/CRM/Contact/Form/Search/Custom/FullText.tpl @@ -59,6 +59,7 @@ {ts}Name{/ts} + {if $allowFileSearch}{ts}File{/ts}{/if} @@ -67,6 +68,7 @@ {$row.sort_name} + {if $allowFileSearch}{$row.fileHtml}{/if} {ts}View{/ts} @@ -101,6 +103,7 @@ {ts}Added By{/ts} {ts}With{/ts} {ts}Assignee{/ts} + {if $allowFileSearch}{ts}File{/ts}{/if} @@ -121,6 +124,7 @@ {$row.assignee_sort_name} + {if $allowFileSearch}{$row.fileHtml}{/if} {if $row.case_id } {ts}Start Date{/ts} {ts}End Date{/ts} {ts}Case ID{/ts} + {if $allowFileSearch}{ts}File{/ts}{/if} @@ -173,6 +178,7 @@ {$row.case_start_date|crmDate:"%b %d, %Y %l:%M %P"} {$row.case_end_date|crmDate:"%b %d, %Y %l:%M %P"} {$row.case_id} + {if $allowFileSearch}{$row.fileHtml}{/if} {if $row.case_is_deleted} {ts}Received{/ts} {ts}Status{/ts} + {if $allowFileSearch}{ts}File{/ts}{/if} @@ -233,6 +240,7 @@ {$row.contribution_source} {$row.contribution_receive_date|crmDate:"%b %d, %Y %l:%M %P"} {$row.contribution_status} + {if $allowFileSearch}{$row.fileHtml}{/if} {ts}View{/ts} @@ -271,6 +279,7 @@ {ts}Source{/ts} {ts}Status{/ts} {ts}Role{/ts} + {if $allowFileSearch}{ts}File{/ts}{/if} @@ -288,6 +297,7 @@ {$row.participant_source} {$row.participant_status} {$row.participant_role} + {if $allowFileSearch}{$row.fileHtml}{/if} {ts}View{/ts} @@ -324,6 +334,7 @@ {ts}Membership End Date{/ts} {ts}Source{/ts} {ts}Status{/ts} + {if $allowFileSearch}{ts}File{/ts}{/if} @@ -341,6 +352,7 @@ {$row.membership_end_date|crmDate:"%b %d, %Y %l:%M %P"} {$row.membership_source} {$row.membership_status} + {if $allowFileSearch}{$row.fileHtml}{/if} {ts}View{/ts}