3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
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
35 abstract class CRM_Contact_Form_Search_Custom_FullText_AbstractPartialQuery
{
51 * @param string $label
53 public function __construct($name, $label) {
55 $this->label
= $label;
63 public function getLabel() {
72 public function getName() {
77 * Execute a query and write out a page worth of matches to $detailTable.
79 * TODO: Consider removing $entityIDTableName from the function-signature. Each implementation could be
80 * responsible for its own temp tables.
82 * TODO: Understand why $queryLimit and $detailLimit are different
84 * @param string $queryText
85 * A string of text to search for.
86 * @param string $entityIDTableName
87 * A temporary table into which we can write a list of all matching IDs.
88 * @param string $detailTable
89 * A table into which we can write details about a page worth of matches.
90 * @param array|NULL $queryLimit overall limit (applied when building $entityIDTableName)
91 * NULL if no limit; or array(0 => $limit, 1 => $offset)
92 * @param array|NULL $detailLimit final limit (applied when building $detailTable)
93 * NULL if no limit; or array(0 => $limit, 1 => $offset)
95 * keys: match-descriptor
98 public abstract function fillTempTable($queryText, $entityIDTableName, $detailTable, $queryLimit, $detailLimit);
103 public function isActive() {
111 public function fillCustomInfo(&$tables, $extends) {
113 SELECT cg.table_name, cf.column_name
114 FROM civicrm_custom_group cg
115 INNER JOIN civicrm_custom_field cf ON cf.custom_group_id = cg.id
116 WHERE cg.extends IN $extends
119 AND cf.is_searchable = 1
120 AND cf.html_type IN ( 'Text', 'TextArea', 'RichTextEditor' )
123 $dao = CRM_Core_DAO
::executeQuery($sql);
124 while ($dao->fetch()) {
125 if (!array_key_exists($dao->table_name
, $tables)) {
126 $tables[$dao->table_name
] = array(
131 $tables[$dao->table_name
]['fields'][$dao->column_name
] = NULL;
137 * @param string $queryText
138 * @param array $tables
139 * A list of places to query. Keys may be:.
140 * - sql: an array of SQL queries to execute
141 * - final: an array of SQL queries to execute at the end
142 * - *: All other keys are treated as table names
144 * keys: match-descriptor
146 * - files: NULL | array
148 public function runQueries($queryText, &$tables, $entityIDTableName, $limit) {
149 $sql = "TRUNCATE {$entityIDTableName}";
150 CRM_Core_DAO
::executeQuery($sql);
154 foreach ($tables as $tableName => $tableValues) {
155 if ($tableName == 'final') {
159 if ($tableName == 'sql') {
160 foreach ($tableValues as $sqlStatement) {
162 REPLACE INTO {$entityIDTableName} ( entity_id )
164 {$this->toLimit($limit)}
166 CRM_Core_DAO
::executeQuery($sql);
169 elseif ($tableName == 'file') {
170 $searcher = CRM_Core_BAO_File
::getSearchService();
171 if (!($searcher && CRM_Core_Permission
::check('access uploaded files'))) {
175 $query = $tableValues +
array(
176 'text' => CRM_Utils_QueryFormatter
::singleton()
177 ->format($queryText, CRM_Utils_QueryFormatter
::LANG_SOLR
),
179 list($intLimit, $intOffset) = $this->parseLimitOffset($limit);
180 $files = $searcher->search($query, $intLimit, $intOffset);
182 foreach ($files as $file) {
183 $matches[] = array('entity_id' => $file['xparent_id']);
186 $insertSql = CRM_Utils_SQL_Insert
::into($entityIDTableName)->usingReplace()->rows($matches)->toSQL();
187 CRM_Core_DAO
::executeQuery($insertSql);
191 $fullTextFields = array(); // array (string $sqlColumnName)
192 $clauses = array(); // array (string $sqlExpression)
194 foreach ($tableValues['fields'] as $fieldName => $fieldType) {
195 if ($fieldType == 'Int') {
196 if (is_numeric($queryText)) {
197 $clauses[] = "$fieldName = {$queryText}";
201 $fullTextFields[] = $fieldName;
205 if (!empty($fullTextFields)) {
206 $clauses[] = $this->matchText($tableName, $fullTextFields, $queryText);
209 if (empty($clauses)) {
213 $whereClause = implode(' OR ', $clauses);
215 //resolve conflict between entity tables.
216 if ($tableName == 'civicrm_note' &&
217 $entityTable = CRM_Utils_Array
::value('entity_table', $tableValues)
219 $whereClause .= " AND entity_table = '{$entityTable}'";
223 REPLACE INTO {$entityIDTableName} ( entity_id )
224 SELECT {$tableValues['id']}
226 WHERE ( $whereClause )
227 AND {$tableValues['id']} IS NOT NULL
228 GROUP BY {$tableValues['id']}
229 {$this->toLimit($limit)}
231 CRM_Core_DAO
::executeQuery($sql);
236 if (isset($tables['final'])) {
237 foreach ($tables['final'] as $sqlStatement) {
238 CRM_Core_DAO
::executeQuery($sqlStatement);
243 'count' => CRM_Core_DAO
::singleValueQuery("SELECT count(*) FROM {$entityIDTableName}"),
249 * Create a SQL expression for matching against a list of.
252 * @param string $table
253 * Eg "civicrm_note" or "civicrm_note mynote".
254 * @param array|string $fullTextFields list of field names
255 * @param string $queryText
257 * SQL, eg "MATCH (col1) AGAINST (queryText)" or "col1 LIKE '%queryText%'"
259 public function matchText($table, $fullTextFields, $queryText) {
260 $strtolower = function_exists('mb_strtolower') ?
'mb_strtolower' : 'strtolower';
262 if (strpos($table, ' ') === FALSE) {
263 $tableName = $tableAlias = $table;
266 list ($tableName, $tableAlias) = explode(' ', $table);
268 if (is_scalar($fullTextFields)) {
269 $fullTextFields = array($fullTextFields);
273 if (CRM_Core_InnoDBIndexer
::singleton()->hasDeclaredIndex($tableName, $fullTextFields)) {
274 $formattedQuery = CRM_Utils_QueryFormatter
::singleton()
275 ->format($queryText, CRM_Utils_QueryFormatter
::LANG_SQL_FTSBOOL
);
277 $prefixedFieldNames = array();
278 foreach ($fullTextFields as $fieldName) {
279 $prefixedFieldNames[] = "$tableAlias.$fieldName";
282 $clauses[] = sprintf("MATCH (%s) AGAINST ('%s' IN BOOLEAN MODE)",
283 implode(',', $prefixedFieldNames),
284 $strtolower(CRM_Core_DAO
::escapeString($formattedQuery))
288 //CRM_Core_Session::setStatus(ts('Cannot use FTS for %1 (%2)', array(
290 // 2 => implode(', ', $fullTextFields),
293 $formattedQuery = CRM_Utils_QueryFormatter
::singleton()
294 ->format($queryText, CRM_Utils_QueryFormatter
::LANG_SQL_LIKE
);
295 $escapedText = $strtolower(CRM_Core_DAO
::escapeString($formattedQuery));
296 foreach ($fullTextFields as $fieldName) {
297 $clauses[] = "$tableAlias.$fieldName LIKE '{$escapedText}'";
300 return implode(' OR ', $clauses);
304 * For any records in $toTable that originated with this query,
305 * append file information.
307 * @param string $toTable
308 * @param string $parentIdColumn
309 * @param array $files
310 * See return format of CRM_Core_FileSearchInterface::search.
312 public function moveFileIDs($toTable, $parentIdColumn, $files) {
317 $filesIndex = CRM_Utils_Array
::index(array('xparent_id', 'file_id'), $files);
318 // ex: $filesIndex[$xparent_id][$file_id] = array(...the file record...);
320 $dao = CRM_Core_DAO
::executeQuery("
321 SELECT distinct {$parentIdColumn}
323 WHERE table_name = %1
325 1 => array($this->getName(), 'String'),
327 while ($dao->fetch()) {
328 if (empty($filesIndex[$dao->{$parentIdColumn}])) {
332 CRM_Core_DAO
::executeQuery("UPDATE {$toTable}
334 WHERE table_name = %2 AND {$parentIdColumn} = %3
336 1 => array(implode(',', array_keys($filesIndex[$dao->{$parentIdColumn}])), 'String'),
337 2 => array($this->getName(), 'String'),
338 3 => array($dao->{$parentIdColumn}, 'Int'),
344 * @param int|array $limit
347 * @see CRM_Contact_Form_Search_Custom_FullText::toLimit
349 public function toLimit($limit) {
350 if (is_array($limit)) {
351 list ($limit, $offset) = $limit;
356 $result = "LIMIT {$limit}";
358 $result .= " OFFSET {$offset}";
364 * @param array|int $limit
366 * (0 => $limit, 1 => $offset)
368 public function parseLimitOffset($limit) {
369 if (is_scalar($limit)) {
373 list ($intLimit, $intOffset) = $limit;
378 return array($intLimit, $intOffset);