PageRenderTime 908ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/sites/all/modules/contrib/civicrm/CRM/Contact/Form/Search/Custom/FullText/AbstractPartialQuery.php

https://gitlab.com/virtualrealms/d7civicrm
PHP | 346 lines | 182 code | 30 blank | 134 comment | 30 complexity | e7f38b1bfbe52ffe21d44086bb2a22dd MD5 | raw file
  1. <?php
  2. /*
  3. +--------------------------------------------------------------------+
  4. | CiviCRM version 5 |
  5. +--------------------------------------------------------------------+
  6. | Copyright CiviCRM LLC (c) 2004-2019 |
  7. +--------------------------------------------------------------------+
  8. | This file is a part of CiviCRM. |
  9. | |
  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. |
  13. | |
  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. |
  18. | |
  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. +--------------------------------------------------------------------+
  26. */
  27. /**
  28. *
  29. * @package CRM
  30. * @copyright CiviCRM LLC (c) 2004-2019
  31. */
  32. abstract class CRM_Contact_Form_Search_Custom_FullText_AbstractPartialQuery {
  33. /**
  34. * @var string
  35. */
  36. protected $name;
  37. /**
  38. * @var string
  39. */
  40. protected $label;
  41. /**
  42. * Class constructor.
  43. *
  44. * @param string $name
  45. * @param string $label
  46. */
  47. public function __construct($name, $label) {
  48. $this->name = $name;
  49. $this->label = $label;
  50. }
  51. /**
  52. * Get label.
  53. *
  54. * @return string
  55. */
  56. public function getLabel() {
  57. return $this->label;
  58. }
  59. /**
  60. * Get name.
  61. *
  62. * @return string
  63. */
  64. public function getName() {
  65. return $this->name;
  66. }
  67. /**
  68. * Execute a query and write out a page worth of matches to $detailTable.
  69. *
  70. * TODO: Consider removing $entityIDTableName from the function-signature. Each implementation could be
  71. * responsible for its own temp tables.
  72. *
  73. * TODO: Understand why $queryLimit and $detailLimit are different
  74. *
  75. * @param string $queryText
  76. * A string of text to search for.
  77. * @param string $entityIDTableName
  78. * A temporary table into which we can write a list of all matching IDs.
  79. * @param string $detailTable
  80. * A table into which we can write details about a page worth of matches.
  81. * @param array|null $queryLimit overall limit (applied when building $entityIDTableName)
  82. * NULL if no limit; or array(0 => $limit, 1 => $offset)
  83. * @param array|null $detailLimit final limit (applied when building $detailTable)
  84. * NULL if no limit; or array(0 => $limit, 1 => $offset)
  85. * @return array
  86. * keys: match-descriptor
  87. * - count: int
  88. */
  89. abstract public function fillTempTable($queryText, $entityIDTableName, $detailTable, $queryLimit, $detailLimit);
  90. /**
  91. * @return bool
  92. */
  93. public function isActive() {
  94. return TRUE;
  95. }
  96. /**
  97. * @param $tables
  98. * @param $extends
  99. */
  100. public function fillCustomInfo(&$tables, $extends) {
  101. $sql = "
  102. SELECT cg.table_name, cf.column_name
  103. FROM civicrm_custom_group cg
  104. INNER JOIN civicrm_custom_field cf ON cf.custom_group_id = cg.id
  105. WHERE cg.extends IN $extends
  106. AND cg.is_active = 1
  107. AND cf.is_active = 1
  108. AND cf.is_searchable = 1
  109. AND cf.html_type IN ( 'Text', 'TextArea', 'RichTextEditor' )
  110. ";
  111. $dao = CRM_Core_DAO::executeQuery($sql);
  112. while ($dao->fetch()) {
  113. if (!array_key_exists($dao->table_name, $tables)) {
  114. $tables[$dao->table_name] = [
  115. 'id' => 'entity_id',
  116. 'fields' => [],
  117. ];
  118. }
  119. $tables[$dao->table_name]['fields'][$dao->column_name] = NULL;
  120. }
  121. }
  122. /**
  123. * Run queries.
  124. *
  125. * @param string $queryText
  126. * @param array $tables
  127. * A list of places to query. Keys may be:.
  128. * - sql: an array of SQL queries to execute
  129. * - final: an array of SQL queries to execute at the end
  130. * - *: All other keys are treated as table names
  131. * @param string $entityIDTableName
  132. * @param int $limit
  133. *
  134. * @return array
  135. * Keys: match-descriptor
  136. * - count: int
  137. * - files: NULL | array
  138. * @throws \CRM_Core_Exception
  139. */
  140. public function runQueries($queryText, &$tables, $entityIDTableName, $limit) {
  141. $sql = "TRUNCATE {$entityIDTableName}";
  142. CRM_Core_DAO::executeQuery($sql);
  143. $files = NULL;
  144. foreach ($tables as $tableName => $tableValues) {
  145. if ($tableName == 'final') {
  146. continue;
  147. }
  148. else {
  149. if ($tableName == 'sql') {
  150. foreach ($tableValues as $sqlStatement) {
  151. $sql = "
  152. REPLACE INTO {$entityIDTableName} ( entity_id )
  153. $sqlStatement
  154. {$this->toLimit($limit)}
  155. ";
  156. CRM_Core_DAO::executeQuery($sql);
  157. }
  158. }
  159. elseif ($tableName == 'file') {
  160. $searcher = CRM_Core_BAO_File::getSearchService();
  161. if (!($searcher && CRM_Core_Permission::check('access uploaded files'))) {
  162. continue;
  163. }
  164. $query = $tableValues + [
  165. 'text' => CRM_Utils_QueryFormatter::singleton()
  166. ->format($queryText, CRM_Utils_QueryFormatter::LANG_SOLR),
  167. ];
  168. list($intLimit, $intOffset) = $this->parseLimitOffset($limit);
  169. $files = $searcher->search($query, $intLimit, $intOffset);
  170. $matches = [];
  171. foreach ($files as $file) {
  172. $matches[] = ['entity_id' => $file['xparent_id']];
  173. }
  174. if ($matches) {
  175. $insertSql = CRM_Utils_SQL_Insert::into($entityIDTableName)->usingReplace()->rows($matches)->toSQL();
  176. CRM_Core_DAO::executeQuery($insertSql);
  177. }
  178. }
  179. else {
  180. // array (string $sqlColumnName)
  181. $fullTextFields = [];
  182. // array (string $sqlExpression)
  183. $clauses = [];
  184. foreach ($tableValues['fields'] as $fieldName => $fieldType) {
  185. if ($fieldType == 'Int') {
  186. if (is_numeric($queryText)) {
  187. $clauses[] = "$fieldName = {$queryText}";
  188. }
  189. }
  190. else {
  191. $fullTextFields[] = $fieldName;
  192. }
  193. }
  194. if (!empty($fullTextFields)) {
  195. $clauses[] = $this->matchText($tableName, $fullTextFields, $queryText);
  196. }
  197. if (empty($clauses)) {
  198. continue;
  199. }
  200. $whereClause = implode(' OR ', $clauses);
  201. //resolve conflict between entity tables.
  202. if ($tableName == 'civicrm_note' &&
  203. $entityTable = CRM_Utils_Array::value('entity_table', $tableValues)
  204. ) {
  205. $whereClause .= " AND entity_table = '{$entityTable}'";
  206. }
  207. $sql = "
  208. REPLACE INTO {$entityIDTableName} ( entity_id )
  209. SELECT {$tableValues['id']}
  210. FROM $tableName
  211. WHERE ( $whereClause )
  212. AND {$tableValues['id']} IS NOT NULL
  213. GROUP BY {$tableValues['id']}
  214. {$this->toLimit($limit)}
  215. ";
  216. CRM_Core_DAO::executeQuery($sql);
  217. }
  218. }
  219. }
  220. if (isset($tables['final'])) {
  221. foreach ($tables['final'] as $sqlStatement) {
  222. CRM_Core_DAO::executeQuery($sqlStatement);
  223. }
  224. }
  225. return [
  226. 'count' => CRM_Core_DAO::singleValueQuery("SELECT count(*) FROM {$entityIDTableName}"),
  227. 'files' => $files,
  228. ];
  229. }
  230. /**
  231. * Create a SQL expression for matching against a list of.
  232. * text columns.
  233. *
  234. * @param string $table
  235. * Eg "civicrm_note" or "civicrm_note mynote".
  236. * @param array|string $fullTextFields list of field names
  237. * @param string $queryText
  238. * @return string
  239. * SQL, eg "MATCH (col1) AGAINST (queryText)" or "col1 LIKE '%queryText%'"
  240. */
  241. public function matchText($table, $fullTextFields, $queryText) {
  242. return CRM_Utils_QueryFormatter::singleton()->formatSql($table, $fullTextFields, $queryText);
  243. }
  244. /**
  245. * For any records in $toTable that originated with this query,
  246. * append file information.
  247. *
  248. * @param string $toTable
  249. * @param string $parentIdColumn
  250. * @param array $files
  251. * See return format of CRM_Core_FileSearchInterface::search.
  252. */
  253. public function moveFileIDs($toTable, $parentIdColumn, $files) {
  254. if (empty($files)) {
  255. return;
  256. }
  257. $filesIndex = CRM_Utils_Array::index(['xparent_id', 'file_id'], $files);
  258. // ex: $filesIndex[$xparent_id][$file_id] = array(...the file record...);
  259. $dao = CRM_Core_DAO::executeQuery("
  260. SELECT distinct {$parentIdColumn}
  261. FROM {$toTable}
  262. WHERE table_name = %1
  263. ", [
  264. 1 => [$this->getName(), 'String'],
  265. ]);
  266. while ($dao->fetch()) {
  267. if (empty($filesIndex[$dao->{$parentIdColumn}])) {
  268. continue;
  269. }
  270. CRM_Core_DAO::executeQuery("UPDATE {$toTable}
  271. SET file_ids = %1
  272. WHERE table_name = %2 AND {$parentIdColumn} = %3
  273. ", [
  274. 1 => [implode(',', array_keys($filesIndex[$dao->{$parentIdColumn}])), 'String'],
  275. 2 => [$this->getName(), 'String'],
  276. 3 => [$dao->{$parentIdColumn}, 'Int'],
  277. ]);
  278. }
  279. }
  280. /**
  281. * @param int|array $limit
  282. * @return string
  283. * SQL
  284. * @see CRM_Contact_Form_Search_Custom_FullText::toLimit
  285. */
  286. public function toLimit($limit) {
  287. if (is_array($limit)) {
  288. list ($limit, $offset) = $limit;
  289. }
  290. if (empty($limit)) {
  291. return '';
  292. }
  293. $result = "LIMIT {$limit}";
  294. if ($offset) {
  295. $result .= " OFFSET {$offset}";
  296. }
  297. return $result;
  298. }
  299. /**
  300. * @param array|int $limit
  301. * @return array
  302. * (0 => $limit, 1 => $offset)
  303. */
  304. public function parseLimitOffset($limit) {
  305. if (is_scalar($limit)) {
  306. $intLimit = $limit;
  307. }
  308. else {
  309. list ($intLimit, $intOffset) = $limit;
  310. }
  311. if (!$intOffset) {
  312. $intOffset = 0;
  313. }
  314. return [$intLimit, $intOffset];
  315. }
  316. }