3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2015 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
31 * @copyright CiviCRM LLC (c) 2004-2015
33 abstract class CRM_Contact_Form_Search_Custom_FullText_AbstractPartialQuery
{
49 * @param string $label
51 public function __construct($name, $label) {
53 $this->label
= $label;
61 public function getLabel() {
70 public function getName() {
75 * Execute a query and write out a page worth of matches to $detailTable.
77 * TODO: Consider removing $entityIDTableName from the function-signature. Each implementation could be
78 * responsible for its own temp tables.
80 * TODO: Understand why $queryLimit and $detailLimit are different
82 * @param string $queryText
83 * A string of text to search for.
84 * @param string $entityIDTableName
85 * A temporary table into which we can write a list of all matching IDs.
86 * @param string $detailTable
87 * A table into which we can write details about a page worth of matches.
88 * @param array|NULL $queryLimit overall limit (applied when building $entityIDTableName)
89 * NULL if no limit; or array(0 => $limit, 1 => $offset)
90 * @param array|NULL $detailLimit final limit (applied when building $detailTable)
91 * NULL if no limit; or array(0 => $limit, 1 => $offset)
93 * keys: match-descriptor
96 public abstract function fillTempTable($queryText, $entityIDTableName, $detailTable, $queryLimit, $detailLimit);
101 public function isActive() {
109 public function fillCustomInfo(&$tables, $extends) {
111 SELECT cg.table_name, cf.column_name
112 FROM civicrm_custom_group cg
113 INNER JOIN civicrm_custom_field cf ON cf.custom_group_id = cg.id
114 WHERE cg.extends IN $extends
117 AND cf.is_searchable = 1
118 AND cf.html_type IN ( 'Text', 'TextArea', 'RichTextEditor' )
121 $dao = CRM_Core_DAO
::executeQuery($sql);
122 while ($dao->fetch()) {
123 if (!array_key_exists($dao->table_name
, $tables)) {
124 $tables[$dao->table_name
] = array(
129 $tables[$dao->table_name
]['fields'][$dao->column_name
] = NULL;
135 * @param string $queryText
136 * @param array $tables
137 * A list of places to query. Keys may be:.
138 * - sql: an array of SQL queries to execute
139 * - final: an array of SQL queries to execute at the end
140 * - *: All other keys are treated as table names
142 * keys: match-descriptor
144 * - files: NULL | array
146 public function runQueries($queryText, &$tables, $entityIDTableName, $limit) {
147 $sql = "TRUNCATE {$entityIDTableName}";
148 CRM_Core_DAO
::executeQuery($sql);
152 foreach ($tables as $tableName => $tableValues) {
153 if ($tableName == 'final') {
157 if ($tableName == 'sql') {
158 foreach ($tableValues as $sqlStatement) {
160 REPLACE INTO {$entityIDTableName} ( entity_id )
162 {$this->toLimit($limit)}
164 CRM_Core_DAO
::executeQuery($sql);
167 elseif ($tableName == 'file') {
168 $searcher = CRM_Core_BAO_File
::getSearchService();
169 if (!($searcher && CRM_Core_Permission
::check('access uploaded files'))) {
173 $query = $tableValues +
array(
174 'text' => CRM_Utils_QueryFormatter
::singleton()
175 ->format($queryText, CRM_Utils_QueryFormatter
::LANG_SOLR
),
177 list($intLimit, $intOffset) = $this->parseLimitOffset($limit);
178 $files = $searcher->search($query, $intLimit, $intOffset);
180 foreach ($files as $file) {
181 $matches[] = array('entity_id' => $file['xparent_id']);
184 $insertSql = CRM_Utils_SQL_Insert
::into($entityIDTableName)->usingReplace()->rows($matches)->toSQL();
185 CRM_Core_DAO
::executeQuery($insertSql);
189 $fullTextFields = array(); // array (string $sqlColumnName)
190 $clauses = array(); // array (string $sqlExpression)
192 foreach ($tableValues['fields'] as $fieldName => $fieldType) {
193 if ($fieldType == 'Int') {
194 if (is_numeric($queryText)) {
195 $clauses[] = "$fieldName = {$queryText}";
199 $fullTextFields[] = $fieldName;
203 if (!empty($fullTextFields)) {
204 $clauses[] = $this->matchText($tableName, $fullTextFields, $queryText);
207 if (empty($clauses)) {
211 $whereClause = implode(' OR ', $clauses);
213 //resolve conflict between entity tables.
214 if ($tableName == 'civicrm_note' &&
215 $entityTable = CRM_Utils_Array
::value('entity_table', $tableValues)
217 $whereClause .= " AND entity_table = '{$entityTable}'";
221 REPLACE INTO {$entityIDTableName} ( entity_id )
222 SELECT {$tableValues['id']}
224 WHERE ( $whereClause )
225 AND {$tableValues['id']} IS NOT NULL
226 GROUP BY {$tableValues['id']}
227 {$this->toLimit($limit)}
229 CRM_Core_DAO
::executeQuery($sql);
234 if (isset($tables['final'])) {
235 foreach ($tables['final'] as $sqlStatement) {
236 CRM_Core_DAO
::executeQuery($sqlStatement);
241 'count' => CRM_Core_DAO
::singleValueQuery("SELECT count(*) FROM {$entityIDTableName}"),
247 * Create a SQL expression for matching against a list of.
250 * @param string $table
251 * Eg "civicrm_note" or "civicrm_note mynote".
252 * @param array|string $fullTextFields list of field names
253 * @param string $queryText
255 * SQL, eg "MATCH (col1) AGAINST (queryText)" or "col1 LIKE '%queryText%'"
257 public function matchText($table, $fullTextFields, $queryText) {
258 $strtolower = function_exists('mb_strtolower') ?
'mb_strtolower' : 'strtolower';
260 if (strpos($table, ' ') === FALSE) {
261 $tableName = $tableAlias = $table;
264 list ($tableName, $tableAlias) = explode(' ', $table);
266 if (is_scalar($fullTextFields)) {
267 $fullTextFields = array($fullTextFields);
271 if (CRM_Core_InnoDBIndexer
::singleton()->hasDeclaredIndex($tableName, $fullTextFields)) {
272 $formattedQuery = CRM_Utils_QueryFormatter
::singleton()
273 ->format($queryText, CRM_Utils_QueryFormatter
::LANG_SQL_FTSBOOL
);
275 $prefixedFieldNames = array();
276 foreach ($fullTextFields as $fieldName) {
277 $prefixedFieldNames[] = "$tableAlias.$fieldName";
280 $clauses[] = sprintf("MATCH (%s) AGAINST ('%s' IN BOOLEAN MODE)",
281 implode(',', $prefixedFieldNames),
282 $strtolower(CRM_Core_DAO
::escapeString($formattedQuery))
286 //CRM_Core_Session::setStatus(ts('Cannot use FTS for %1 (%2)', array(
288 // 2 => implode(', ', $fullTextFields),
291 $formattedQuery = CRM_Utils_QueryFormatter
::singleton()
292 ->format($queryText, CRM_Utils_QueryFormatter
::LANG_SQL_LIKE
);
293 $escapedText = $strtolower(CRM_Core_DAO
::escapeString($formattedQuery));
294 foreach ($fullTextFields as $fieldName) {
295 $clauses[] = "$tableAlias.$fieldName LIKE '{$escapedText}'";
298 return implode(' OR ', $clauses);
302 * For any records in $toTable that originated with this query,
303 * append file information.
305 * @param string $toTable
306 * @param string $parentIdColumn
307 * @param array $files
308 * See return format of CRM_Core_FileSearchInterface::search.
310 public function moveFileIDs($toTable, $parentIdColumn, $files) {
315 $filesIndex = CRM_Utils_Array
::index(array('xparent_id', 'file_id'), $files);
316 // ex: $filesIndex[$xparent_id][$file_id] = array(...the file record...);
318 $dao = CRM_Core_DAO
::executeQuery("
319 SELECT distinct {$parentIdColumn}
321 WHERE table_name = %1
323 1 => array($this->getName(), 'String'),
325 while ($dao->fetch()) {
326 if (empty($filesIndex[$dao->{$parentIdColumn}])) {
330 CRM_Core_DAO
::executeQuery("UPDATE {$toTable}
332 WHERE table_name = %2 AND {$parentIdColumn} = %3
334 1 => array(implode(',', array_keys($filesIndex[$dao->{$parentIdColumn}])), 'String'),
335 2 => array($this->getName(), 'String'),
336 3 => array($dao->{$parentIdColumn}, 'Int'),
342 * @param int|array $limit
345 * @see CRM_Contact_Form_Search_Custom_FullText::toLimit
347 public function toLimit($limit) {
348 if (is_array($limit)) {
349 list ($limit, $offset) = $limit;
354 $result = "LIMIT {$limit}";
356 $result .= " OFFSET {$offset}";
362 * @param array|int $limit
364 * (0 => $limit, 1 => $offset)
366 public function parseLimitOffset($limit) {
367 if (is_scalar($limit)) {
371 list ($intLimit, $intOffset) = $limit;
376 return array($intLimit, $intOffset);