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');
'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 = "
* 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'));
}
/**
}
$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;
}
* - *: 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;
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)
}
return array(
- 'count' => CRM_Core_DAO::singleValueQuery("SELECT count(*) FROM {$entityIDTableName}")
+ 'count' => CRM_Core_DAO::singleValueQuery("SELECT count(*) FROM {$entityIDTableName}"),
+ 'files' => $files,
);
}
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
*
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
$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;
}
$tables = array(
'civicrm_activity' => array('fields' => array()),
+ 'file' => array(
+ 'xparent_table' => 'civicrm_activity',
+ ),
'sql' => $contactSQL,
'final' => $final,
);
$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;
}
$tables = array(
'civicrm_case' => array('fields' => array()),
+ 'file' => array(
+ 'xparent_table' => 'civicrm_case',
+ ),
'sql' => $contactSQL,
);
$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;
}
'note' => NULL,
),
),
+ 'file' => array(
+ 'xparent_table' => 'civicrm_contact',
+ ),
'sql' => $contactSQL,
'final' => $final,
);
$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;
}
'total_amount' => 'Int',
),
),
+ 'file' => array(
+ 'xparent_table' => 'civicrm_contribution',
+ ),
'sql' => $contactSQL,
'civicrm_note' => array(
'id' => 'entity_id',
+++ /dev/null
-<?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);
- }
-}
$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;
}
'id' => 'id',
'fields' => array('source' => NULL),
),
+ 'file' => array(
+ 'xparent_table' => 'civicrm_membership',
+ ),
'sql' => $contactSQL,
);
$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;
}
'fee_amount' => 'Int',
),
),
+ 'file' => array(
+ 'xparent_table' => 'civicrm_participant',
+ ),
'sql' => $contactSQL,
'civicrm_note' => array(
'id' => 'entity_id',
<thead>
<tr>
<th class='link'>{ts}Name{/ts}</th>
+ {if $allowFileSearch}<th>{ts}File{/ts}</th>{/if}
<th></th>
</tr>
</thead>
<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>