DAOs with singular/plural options for entity titles
[civicrm-core.git] / CRM / Core / DAO.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 * Base Database Access Object class.
14 *
15 * All DAO classes should inherit from this class.
16 *
17 * @package CRM
18 * @copyright CiviCRM LLC https://civicrm.org/licensing
19 */
20
21 if (!defined('DB_DSN_MODE')) {
22 define('DB_DSN_MODE', 'auto');
23 }
24
25 require_once 'PEAR.php';
26 require_once 'DB/DataObject.php';
27
28 require_once 'CRM/Core/I18n.php';
29
30 /**
31 * Class CRM_Core_DAO
32 */
33 class CRM_Core_DAO extends DB_DataObject {
34
35 /**
36 * How many times has this instance been cloned.
37 *
38 * @var int
39 */
40 protected $resultCopies = 0;
41
42 /**
43 * @var null
44 * @deprecated
45 */
46 public static $_nullObject = NULL;
47
48 /**
49 * Icon associated with this entity.
50 *
51 * @var string
52 */
53 public static $_icon = NULL;
54
55 /**
56 * @var array
57 * @deprecated
58 */
59 public static $_nullArray = [];
60
61 public static $_dbColumnValueCache = NULL;
62 const NOT_NULL = 1, IS_NULL = 2,
63 DB_DAO_NOTNULL = 128,
64 VALUE_SEPARATOR = "\ 1",
65 BULK_INSERT_COUNT = 200,
66 BULK_INSERT_HIGH_COUNT = 200,
67 QUERY_FORMAT_WILDCARD = 1,
68 QUERY_FORMAT_NO_QUOTES = 2,
69
70 /**
71 * Serialized string separated by and bookended with VALUE_SEPARATOR
72 */
73 SERIALIZE_SEPARATOR_BOOKEND = 1,
74 /**
75 * @deprecated format separated by VALUE_SEPARATOR
76 */
77 SERIALIZE_SEPARATOR_TRIMMED = 2,
78 /**
79 * Recommended serialization format
80 */
81 SERIALIZE_JSON = 3,
82 /**
83 * @deprecated format using php serialize()
84 */
85 SERIALIZE_PHP = 4,
86 /**
87 * Comma separated string, no quotes, no spaces
88 */
89 SERIALIZE_COMMA = 5;
90
91 /**
92 * Define entities that shouldn't be created or deleted when creating/ deleting
93 * test objects - this prevents world regions, countries etc from being added / deleted
94 * @var array
95 */
96 public static $_testEntitiesToSkip = [];
97 /**
98 * The factory class for this application.
99 * @var object
100 */
101 public static $_factory = NULL;
102
103 public static $_checkedSqlFunctionsExist = FALSE;
104
105 /**
106 * https://issues.civicrm.org/jira/browse/CRM-17748
107 * internal variable for DAO to hold per-query settings
108 * @var array
109 */
110 protected $_options = [];
111
112 /**
113 * Class constructor.
114 *
115 * @return \CRM_Core_DAO
116 */
117 public function __construct() {
118 $this->initialize();
119 $this->__table = $this->getTableName();
120 }
121
122 /**
123 * Returns localized title of this entity.
124 *
125 * @return string
126 */
127 public static function getEntityTitle() {
128 $className = static::class;
129 Civi::log()->warning("$className needs to be regenerated. Missing getEntityTitle method.", ['civi.tag' => 'deprecated']);
130 return CRM_Core_DAO_AllCoreTables::getBriefName($className);
131 }
132
133 public function __clone() {
134 if (!empty($this->_DB_resultid)) {
135 $this->resultCopies++;
136 }
137 }
138
139 /**
140 * Class destructor.
141 */
142 public function __destruct() {
143 if ($this->resultCopies === 0) {
144 $this->free();
145 }
146 $this->resultCopies--;
147 }
148
149 /**
150 * Empty definition for virtual function.
151 */
152 public static function getTableName() {
153 return NULL;
154 }
155
156 /**
157 * Initialize the DAO object.
158 *
159 * @param string $dsn
160 * The database connection string.
161 */
162 public static function init($dsn) {
163 Civi::$statics[__CLASS__]['init'] = 1;
164 $options = &PEAR::getStaticProperty('DB_DataObject', 'options');
165 $dsn = CRM_Utils_SQL::autoSwitchDSN($dsn);
166 $options['database'] = $dsn;
167 $options['quote_identifiers'] = TRUE;
168 if (CRM_Utils_SQL::isSSLDSN($dsn)) {
169 // There are two different options arrays.
170 $other_options = &PEAR::getStaticProperty('DB', 'options');
171 $other_options['ssl'] = TRUE;
172 }
173 if (defined('CIVICRM_DAO_DEBUG')) {
174 self::DebugLevel(CIVICRM_DAO_DEBUG);
175 }
176 $factory = new CRM_Contact_DAO_Factory();
177 CRM_Core_DAO::setFactory($factory);
178 $currentModes = CRM_Utils_SQL::getSqlModes();
179 if (CRM_Utils_Constant::value('CIVICRM_MYSQL_STRICT', CRM_Utils_System::isDevelopment())) {
180 if (CRM_Utils_SQL::supportsFullGroupBy() && !in_array('ONLY_FULL_GROUP_BY', $currentModes) && CRM_Utils_SQL::isGroupByModeInDefault()) {
181 $currentModes[] = 'ONLY_FULL_GROUP_BY';
182 }
183 if (!in_array('STRICT_TRANS_TABLES', $currentModes)) {
184 $currentModes = array_merge(['STRICT_TRANS_TABLES'], $currentModes);
185 }
186 CRM_Core_DAO::executeQuery("SET SESSION sql_mode = %1", [1 => [implode(',', $currentModes), 'String']]);
187 }
188 CRM_Core_DAO::executeQuery('SET NAMES utf8mb4');
189 CRM_Core_DAO::executeQuery('SET @uniqueID = %1', [1 => [CRM_Utils_Request::id(), 'String']]);
190 }
191
192 /**
193 * @return DB_common
194 */
195 public static function getConnection() {
196 global $_DB_DATAOBJECT;
197 $dao = new CRM_Core_DAO();
198 return $_DB_DATAOBJECT['CONNECTIONS'][$dao->_database_dsn_md5];
199 }
200
201 /**
202 * Disables usage of the ONLY_FULL_GROUP_BY Mode if necessary
203 */
204 public static function disableFullGroupByMode() {
205 $currentModes = CRM_Utils_SQL::getSqlModes();
206 if (in_array('ONLY_FULL_GROUP_BY', $currentModes) && CRM_Utils_SQL::isGroupByModeInDefault()) {
207 $key = array_search('ONLY_FULL_GROUP_BY', $currentModes);
208 unset($currentModes[$key]);
209 CRM_Core_DAO::executeQuery("SET SESSION sql_mode = %1", [1 => [implode(',', $currentModes), 'String']]);
210 }
211 }
212
213 /**
214 * Re-enables ONLY_FULL_GROUP_BY sql_mode as necessary..
215 */
216 public static function reenableFullGroupByMode() {
217 $currentModes = CRM_Utils_SQL::getSqlModes();
218 if (!in_array('ONLY_FULL_GROUP_BY', $currentModes) && CRM_Utils_SQL::isGroupByModeInDefault()) {
219 $currentModes[] = 'ONLY_FULL_GROUP_BY';
220 CRM_Core_DAO::executeQuery("SET SESSION sql_mode = %1", [1 => [implode(',', $currentModes), 'String']]);
221 }
222 }
223
224 /**
225 * @param string $fieldName
226 * @param $fieldDef
227 * @param array $params
228 */
229 protected function assignTestFK($fieldName, $fieldDef, $params) {
230 $required = $fieldDef['required'] ?? NULL;
231 $FKClassName = $fieldDef['FKClassName'] ?? NULL;
232 $dbName = $fieldDef['name'];
233 $daoName = str_replace('_BAO_', '_DAO_', get_class($this));
234
235 // skip the FK if it is not required
236 // if it's contact id we should create even if not required
237 // we'll have a go @ fetching first though
238 // we WILL create campaigns though for so tests with a campaign pseudoconstant will complete
239 if ($FKClassName === 'CRM_Campaign_DAO_Campaign' && $daoName != $FKClassName) {
240 $required = TRUE;
241 }
242 if (!$required && $dbName != 'contact_id') {
243 $fkDAO = new $FKClassName();
244 if ($fkDAO->find(TRUE)) {
245 $this->$dbName = $fkDAO->id;
246 }
247 }
248
249 elseif (in_array($FKClassName, CRM_Core_DAO::$_testEntitiesToSkip)) {
250 $depObject = new $FKClassName();
251 $depObject->find(TRUE);
252 $this->$dbName = $depObject->id;
253 }
254 elseif ($daoName == 'CRM_Member_DAO_MembershipType' && $fieldName == 'member_of_contact_id') {
255 // FIXME: the fields() metadata is not specific enough
256 $depObject = CRM_Core_DAO::createTestObject($FKClassName, ['contact_type' => 'Organization']);
257 $this->$dbName = $depObject->id;
258 }
259 else {
260 //if it is required we need to generate the dependency object first
261 $depObject = CRM_Core_DAO::createTestObject($FKClassName, CRM_Utils_Array::value($dbName, $params, 1));
262 $this->$dbName = $depObject->id;
263 }
264 }
265
266 /**
267 * Generate and assign an arbitrary value to a field of a test object.
268 *
269 * @param string $fieldName
270 * @param array $fieldDef
271 * @param int $counter
272 * The globally-unique ID of the test object.
273 *
274 * @throws \CRM_Core_Exception
275 */
276 protected function assignTestValue($fieldName, &$fieldDef, $counter) {
277 $dbName = $fieldDef['name'];
278 $daoName = get_class($this);
279 $handled = FALSE;
280
281 if (!$handled && $dbName == 'contact_sub_type') {
282 //coming up with a rule to set this is too complex let's not set it
283 $handled = TRUE;
284 }
285
286 // Pick an option value if needed
287 if (!$handled && $fieldDef['type'] !== CRM_Utils_Type::T_BOOLEAN) {
288 $options = $daoName::buildOptions($dbName, 'create');
289 if ($options) {
290 $this->$dbName = key($options);
291 $handled = TRUE;
292 }
293 }
294
295 if (!$handled) {
296 switch ($fieldDef['type']) {
297 case CRM_Utils_Type::T_INT:
298 case CRM_Utils_Type::T_FLOAT:
299 case CRM_Utils_Type::T_MONEY:
300 if (isset($fieldDef['precision'])) {
301 // $object->$dbName = CRM_Utils_Number::createRandomDecimal($value['precision']);
302 $this->$dbName = CRM_Utils_Number::createTruncatedDecimal($counter, $fieldDef['precision']);
303 }
304 else {
305 $this->$dbName = $counter;
306 }
307 break;
308
309 case CRM_Utils_Type::T_BOOLEAN:
310 if (isset($fieldDef['default'])) {
311 $this->$dbName = $fieldDef['default'];
312 }
313 elseif ($fieldDef['name'] == 'is_deleted' || $fieldDef['name'] == 'is_test') {
314 $this->$dbName = 0;
315 }
316 else {
317 $this->$dbName = 1;
318 }
319 break;
320
321 case CRM_Utils_Type::T_DATE:
322 case CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME:
323 $this->$dbName = '19700101';
324 if ($dbName == 'end_date') {
325 // put this in the future
326 $this->$dbName = '20200101';
327 }
328 break;
329
330 case CRM_Utils_Type::T_TIMESTAMP:
331 $this->$dbName = '19700201000000';
332 break;
333
334 case CRM_Utils_Type::T_TIME:
335 throw new CRM_Core_Exception('T_TIME shouldn\'t be used.');
336
337 case CRM_Utils_Type::T_CCNUM:
338 $this->$dbName = '4111 1111 1111 1111';
339 break;
340
341 case CRM_Utils_Type::T_URL:
342 $this->$dbName = 'http://www.civicrm.org';
343 break;
344
345 case CRM_Utils_Type::T_STRING:
346 case CRM_Utils_Type::T_BLOB:
347 case CRM_Utils_Type::T_MEDIUMBLOB:
348 case CRM_Utils_Type::T_TEXT:
349 case CRM_Utils_Type::T_LONGTEXT:
350 case CRM_Utils_Type::T_EMAIL:
351 default:
352 // WAS: if (isset($value['enumValues'])) {
353 // TODO: see if this works with all pseudoconstants
354 if (isset($fieldDef['pseudoconstant'], $fieldDef['pseudoconstant']['callback'])) {
355 if (isset($fieldDef['default'])) {
356 $this->$dbName = $fieldDef['default'];
357 }
358 else {
359 $options = CRM_Core_PseudoConstant::get($daoName, $fieldName);
360 if (is_array($options)) {
361 $this->$dbName = $options[0];
362 }
363 else {
364 $defaultValues = explode(',', $options);
365 $this->$dbName = $defaultValues[0];
366 }
367 }
368 }
369 else {
370 $this->$dbName = $dbName . '_' . $counter;
371 $maxlength = $fieldDef['maxlength'] ?? NULL;
372 if ($maxlength > 0 && strlen($this->$dbName) > $maxlength) {
373 $this->$dbName = substr($this->$dbName, 0, $fieldDef['maxlength']);
374 }
375 }
376 }
377 }
378 }
379
380 /**
381 * Reset the DAO object.
382 *
383 * DAO is kinda crappy in that there is an unwritten rule of one query per DAO.
384 *
385 * We attempt to get around this crappy restriction by resetting some of DAO's internal fields. Use this with caution
386 */
387 public function reset() {
388
389 foreach (array_keys($this->table()) as $field) {
390 unset($this->$field);
391 }
392
393 /**
394 * reset the various DB_DAO structures manually
395 */
396 $this->_query = [];
397 $this->whereAdd();
398 $this->selectAdd();
399 $this->joinAdd();
400 }
401
402 /**
403 * @param string $tableName
404 *
405 * @return string
406 */
407 public static function getLocaleTableName($tableName) {
408 global $dbLocale;
409 if ($dbLocale) {
410 $tables = CRM_Core_I18n_Schema::schemaStructureTables();
411 if (in_array($tableName, $tables)) {
412 return $tableName . $dbLocale;
413 }
414 }
415 return $tableName;
416 }
417
418 /**
419 * Execute a query by the current DAO, localizing it along the way (if needed).
420 *
421 * @param string $query
422 * The SQL query for execution.
423 * @param bool $i18nRewrite
424 * Whether to rewrite the query.
425 *
426 * @return object
427 * the current DAO object after the query execution
428 */
429 public function query($query, $i18nRewrite = TRUE) {
430 // rewrite queries that should use $dbLocale-based views for multi-language installs
431 global $dbLocale, $_DB_DATAOBJECT;
432
433 if (empty($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
434 // Will force connection to be populated per CRM-20541.
435 new CRM_Core_DAO();
436 }
437
438 $conn = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
439 $orig_options = $conn->options;
440 $this->_setDBOptions($this->_options);
441
442 if ($i18nRewrite and $dbLocale) {
443 $query = CRM_Core_I18n_Schema::rewriteQuery($query);
444 }
445
446 $ret = parent::query($query);
447
448 $this->_setDBOptions($orig_options);
449 return $ret;
450 }
451
452 /**
453 * Static function to set the factory instance for this class.
454 *
455 * @param object $factory
456 * The factory application object.
457 */
458 public static function setFactory(&$factory) {
459 self::$_factory = &$factory;
460 }
461
462 /**
463 * Factory method to instantiate a new object from a table name.
464 *
465 * @param string $table
466 * @return \DataObject|\PEAR_Error
467 */
468 public function factory($table = '') {
469 if (!isset(self::$_factory)) {
470 return parent::factory($table);
471 }
472
473 return self::$_factory->create($table);
474 }
475
476 /**
477 * Initialization for all DAO objects. Since we access DB_DO programatically
478 * we need to set the links manually.
479 */
480 public function initialize() {
481 $this->_connect();
482 if (empty(Civi::$statics[__CLASS__]['init'])) {
483 // CRM_Core_DAO::init() must be called before CRM_Core_DAO->initialize().
484 // This occurs very early in bootstrap - error handlers may not be wired up.
485 echo "Inconsistent system initialization sequence. Premature access of (" . get_class($this) . ")";
486 CRM_Utils_System::civiExit();
487 }
488 }
489
490 /**
491 * Defines the default key as 'id'.
492 *
493 * @return array
494 */
495 public function keys() {
496 static $keys;
497 if (!isset($keys)) {
498 $keys = ['id'];
499 }
500 return $keys;
501 }
502
503 /**
504 * Tells DB_DataObject which keys use autoincrement.
505 * 'id' is autoincrementing by default.
506 *
507 *
508 * @return array
509 */
510 public function sequenceKey() {
511 static $sequenceKeys;
512 if (!isset($sequenceKeys)) {
513 $sequenceKeys = ['id', TRUE];
514 }
515 return $sequenceKeys;
516 }
517
518 /**
519 * Returns list of FK relationships.
520 *
521 *
522 * @return array
523 * Array of CRM_Core_Reference_Interface
524 */
525 public static function getReferenceColumns() {
526 return [];
527 }
528
529 /**
530 * Returns all the column names of this table.
531 *
532 *
533 * @return array
534 */
535 public static function &fields() {
536 $result = NULL;
537 return $result;
538 }
539
540 /**
541 * Returns all usable fields, indexed by name.
542 *
543 * This function differs from fields() in that it indexes by name rather than unique_name.
544 *
545 * It excludes fields not added yet by pending upgrades.
546 * This avoids problems with trying to SELECT a field that exists in code but has not yet been added to the db.
547 *
548 * @param bool $checkPermissions
549 * Filter by field permissions.
550 * @return array
551 */
552 public static function getSupportedFields($checkPermissions = FALSE) {
553 $fields = array_column((array) static::fields(), NULL, 'name');
554
555 // Exclude fields yet not added by pending upgrades
556 $dbVer = \CRM_Core_BAO_Domain::version();
557 $daoExt = defined(static::class . '::EXT') ? constant(static::class . '::EXT') : NULL;
558 if ($fields && $daoExt === 'civicrm' && version_compare($dbVer, \CRM_Utils_System::version()) < 0) {
559 $fields = array_filter($fields, function($field) use ($dbVer) {
560 $add = $field['add'] ?? '1.0.0';
561 if (substr_count($add, '.') < 2) {
562 $add .= '.alpha1';
563 }
564 return version_compare($dbVer, $add, '>=');
565 });
566 }
567
568 // Exclude fields the user does not have permission for
569 if ($checkPermissions) {
570 $fields = array_filter($fields, function($field) {
571 return empty($field['permission']) || CRM_Core_Permission::check($field['permission']);
572 });
573 }
574
575 return $fields;
576 }
577
578 /**
579 * Get/set an associative array of table columns
580 *
581 * @return array
582 * (associative)
583 */
584 public function table() {
585 $fields = $this->fields();
586
587 $table = [];
588 if ($fields) {
589 foreach ($fields as $name => $value) {
590 $table[$value['name']] = $value['type'];
591 if (!empty($value['required'])) {
592 $table[$value['name']] += self::DB_DAO_NOTNULL;
593 }
594 }
595 }
596
597 return $table;
598 }
599
600 /**
601 * Save DAO object.
602 *
603 * @param bool $hook
604 *
605 * @return CRM_Core_DAO
606 */
607 public function save($hook = TRUE) {
608 if (!empty($this->id)) {
609 if ($hook) {
610 $preEvent = new \Civi\Core\DAO\Event\PreUpdate($this);
611 \Civi::dispatcher()->dispatch("civi.dao.preUpdate", $preEvent);
612 }
613
614 $result = $this->update();
615
616 if ($hook) {
617 $event = new \Civi\Core\DAO\Event\PostUpdate($this, $result);
618 \Civi::dispatcher()->dispatch("civi.dao.postUpdate", $event);
619 }
620 $this->clearDbColumnValueCache();
621 }
622 else {
623 if ($hook) {
624 $preEvent = new \Civi\Core\DAO\Event\PreUpdate($this);
625 \Civi::dispatcher()->dispatch("civi.dao.preInsert", $preEvent);
626 }
627
628 $result = $this->insert();
629
630 if ($hook) {
631 $event = new \Civi\Core\DAO\Event\PostUpdate($this, $result);
632 \Civi::dispatcher()->dispatch("civi.dao.postInsert", $event);
633 }
634 }
635 $this->free();
636
637 if ($hook) {
638 CRM_Utils_Hook::postSave($this);
639 }
640
641 return $this;
642 }
643
644 /**
645 * Deletes items from table which match current objects variables.
646 *
647 * Returns the true on success
648 *
649 * for example
650 *
651 * Designed to be extended
652 *
653 * $object = new mytable();
654 * $object->ID=123;
655 * echo $object->delete(); // builds a conditon
656 *
657 * $object = new mytable();
658 * $object->whereAdd('age > 12');
659 * $object->limit(1);
660 * $object->orderBy('age DESC');
661 * $object->delete(true); // dont use object vars, use the conditions, limit and order.
662 *
663 * @param bool $useWhere (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then
664 * we will build the condition only using the whereAdd's. Default is to
665 * build the condition only using the object parameters.
666 *
667 * * @return mixed Int (No. of rows affected) on success, false on failure, 0 on no data affected
668 */
669 public function delete($useWhere = FALSE) {
670 $preEvent = new \Civi\Core\DAO\Event\PreDelete($this);
671 \Civi::dispatcher()->dispatch("civi.dao.preDelete", $preEvent);
672
673 $result = parent::delete($useWhere);
674
675 $event = new \Civi\Core\DAO\Event\PostDelete($this, $result);
676 \Civi::dispatcher()->dispatch("civi.dao.postDelete", $event);
677 $this->free();
678
679 $this->clearDbColumnValueCache();
680
681 return $result;
682 }
683
684 /**
685 * @param bool $created
686 */
687 public function log($created = FALSE) {
688 static $cid = NULL;
689
690 if (!$this->getLog()) {
691 return;
692 }
693
694 if (!$cid) {
695 $session = CRM_Core_Session::singleton();
696 $cid = $session->get('userID');
697 }
698
699 // return is we dont have handle to FK
700 if (!$cid) {
701 return;
702 }
703
704 $dao = new CRM_Core_DAO_Log();
705 $dao->entity_table = $this->getTableName();
706 $dao->entity_id = $this->id;
707 $dao->modified_id = $cid;
708 $dao->modified_date = date("YmdHis");
709 $dao->insert();
710 }
711
712 /**
713 * Given an associative array of name/value pairs, extract all the values
714 * that belong to this object and initialize the object with said values
715 *
716 * @param array $params
717 * Array of name/value pairs to save.
718 *
719 * @return bool
720 * Did we copy all null values into the object
721 */
722 public function copyValues($params) {
723 $allNull = TRUE;
724 foreach ($this->fields() as $uniqueName => $field) {
725 $dbName = $field['name'];
726 if (array_key_exists($dbName, $params)) {
727 $value = $params[$dbName];
728 $exists = TRUE;
729 }
730 elseif (array_key_exists($uniqueName, $params)) {
731 $value = $params[$uniqueName];
732 $exists = TRUE;
733 }
734 else {
735 $exists = FALSE;
736 }
737
738 // if there is no value then make the variable NULL
739 if ($exists) {
740 if ($value === '') {
741 $this->$dbName = 'null';
742 }
743 elseif (is_array($value) && !empty($field['serialize'])) {
744 $this->$dbName = CRM_Core_DAO::serializeField($value, $field['serialize']);
745 $allNull = FALSE;
746 }
747 else {
748 $maxLength = $field['maxlength'] ?? NULL;
749 if (!is_array($value) && $maxLength && mb_strlen($value) > $maxLength && empty($field['pseudoconstant'])) {
750 Civi::log()->warning(ts('A string for field $dbName has been truncated. The original string was %1', [CRM_Utils_Type::escape($value, 'String')]));
751 // The string is too long - what to do what to do? Well losing data is generally bad so lets' truncate
752 $value = CRM_Utils_String::ellipsify($value, $maxLength);
753 }
754 $this->$dbName = $value;
755 $allNull = FALSE;
756 }
757 }
758 }
759 return $allNull;
760 }
761
762 /**
763 * Store all the values from this object in an associative array
764 * this is a destructive store, calling function is responsible
765 * for keeping sanity of id's.
766 *
767 * @param object $object
768 * The object that we are extracting data from.
769 * @param array $values
770 * (reference ) associative array of name/value pairs.
771 */
772 public static function storeValues(&$object, &$values) {
773 $fields = $object->fields();
774 foreach ($fields as $name => $value) {
775 $dbName = $value['name'];
776 if (isset($object->$dbName) && $object->$dbName !== 'null') {
777 $values[$dbName] = $object->$dbName;
778 if ($name != $dbName) {
779 $values[$name] = $object->$dbName;
780 }
781 }
782 }
783 }
784
785 /**
786 * Create an attribute for this specific field. We only do this for strings and text
787 *
788 * @param array $field
789 * The field under task.
790 *
791 * @return array|null
792 * the attributes for the object
793 */
794 public static function makeAttribute($field) {
795 if ($field) {
796 if (CRM_Utils_Array::value('type', $field) == CRM_Utils_Type::T_STRING) {
797 $maxLength = $field['maxlength'] ?? NULL;
798 $size = $field['size'] ?? NULL;
799 if ($maxLength || $size) {
800 $attributes = [];
801 if ($maxLength) {
802 $attributes['maxlength'] = $maxLength;
803 }
804 if ($size) {
805 $attributes['size'] = $size;
806 }
807 return $attributes;
808 }
809 }
810 elseif (CRM_Utils_Array::value('type', $field) == CRM_Utils_Type::T_TEXT) {
811 $rows = $field['rows'] ?? NULL;
812 if (!isset($rows)) {
813 $rows = 2;
814 }
815 $cols = $field['cols'] ?? NULL;
816 if (!isset($cols)) {
817 $cols = 80;
818 }
819
820 $attributes = [];
821 $attributes['rows'] = $rows;
822 $attributes['cols'] = $cols;
823 return $attributes;
824 }
825 elseif (CRM_Utils_Array::value('type', $field) == CRM_Utils_Type::T_INT || CRM_Utils_Array::value('type', $field) == CRM_Utils_Type::T_FLOAT || CRM_Utils_Array::value('type', $field) == CRM_Utils_Type::T_MONEY) {
826 $attributes['size'] = 6;
827 $attributes['maxlength'] = 14;
828 return $attributes;
829 }
830 }
831 return NULL;
832 }
833
834 /**
835 * Get the size and maxLength attributes for this text field.
836 * (or for all text fields) in the DAO object.
837 *
838 * @param string $class
839 * Name of DAO class.
840 * @param string $fieldName
841 * Field that i'm interested in or null if.
842 * you want the attributes for all DAO text fields
843 *
844 * @return array
845 * assoc array of name => attribute pairs
846 */
847 public static function getAttribute($class, $fieldName = NULL) {
848 $object = new $class();
849 $fields = $object->fields();
850 if ($fieldName != NULL) {
851 $field = $fields[$fieldName] ?? NULL;
852 return self::makeAttribute($field);
853 }
854 else {
855 $attributes = [];
856 foreach ($fields as $name => $field) {
857 $attribute = self::makeAttribute($field);
858 if ($attribute) {
859 $attributes[$name] = $attribute;
860 }
861 }
862
863 if (!empty($attributes)) {
864 return $attributes;
865 }
866 }
867 return NULL;
868 }
869
870 /**
871 * Create or update a record from supplied params.
872 *
873 * If 'id' is supplied, an existing record will be updated
874 * Otherwise a new record will be created.
875 *
876 * @param array $record
877 * @return CRM_Core_DAO
878 * @throws CRM_Core_Exception
879 */
880 public static function writeRecord(array $record) {
881 $hook = empty($record['id']) ? 'create' : 'edit';
882 $className = CRM_Core_DAO_AllCoreTables::getCanonicalClassName(static::class);
883 if ($className === 'CRM_Core_DAO') {
884 throw new CRM_Core_Exception('Function writeRecord must be called on a subclass of CRM_Core_DAO');
885 }
886 $entityName = CRM_Core_DAO_AllCoreTables::getBriefName($className);
887
888 \CRM_Utils_Hook::pre($hook, $entityName, $record['id'] ?? NULL, $record);
889 $instance = new $className();
890 $instance->copyValues($record);
891 $instance->save();
892 \CRM_Utils_Hook::post($hook, $entityName, $instance->id, $instance);
893
894 return $instance;
895 }
896
897 /**
898 * Delete a record from supplied params.
899 *
900 * @param array $record
901 * 'id' is required.
902 * @return CRM_Core_DAO
903 * @throws CRM_Core_Exception
904 */
905 public static function deleteRecord(array $record) {
906 $className = CRM_Core_DAO_AllCoreTables::getCanonicalClassName(static::class);
907 if ($className === 'CRM_Core_DAO') {
908 throw new CRM_Core_Exception('Function deleteRecord must be called on a subclass of CRM_Core_DAO');
909 }
910 $entityName = CRM_Core_DAO_AllCoreTables::getBriefName($className);
911 if (empty($record['id'])) {
912 throw new CRM_Core_Exception("Cannot delete {$entityName} with no id.");
913 }
914
915 CRM_Utils_Hook::pre('delete', $entityName, $record['id'], $record);
916 $instance = new $className();
917 $instance->id = $record['id'];
918 if (!$instance->delete()) {
919 throw new CRM_Core_Exception("Could not delete {$entityName} id {$record['id']}");
920 }
921 CRM_Utils_Hook::post('delete', $entityName, $record['id'], $instance);
922
923 return $instance;
924 }
925
926 /**
927 * Check if there is a record with the same name in the db.
928 *
929 * @param string $value
930 * The value of the field we are checking.
931 * @param string $daoName
932 * The dao object name.
933 * @param string $daoID
934 * The id of the object being updated. u can change your name.
935 * as long as there is no conflict
936 * @param string $fieldName
937 * The name of the field in the DAO.
938 *
939 * @param string $domainID
940 * The id of the domain. Object exists only for the given domain.
941 *
942 * @return bool
943 * true if object exists
944 */
945 public static function objectExists($value, $daoName, $daoID, $fieldName = 'name', $domainID = NULL) {
946 $object = new $daoName();
947 $object->$fieldName = $value;
948 if ($domainID) {
949 $object->domain_id = $domainID;
950 }
951
952 if ($object->find(TRUE)) {
953 return $daoID && $object->id == $daoID;
954 }
955 else {
956 return TRUE;
957 }
958 }
959
960 /**
961 * Check if there is a given column in a specific table.
962 *
963 * @deprecated
964 * @see CRM_Core_BAO_SchemaHandler::checkIfFieldExists
965 *
966 * @param string $tableName
967 * @param string $columnName
968 * @param bool $i18nRewrite
969 * Whether to rewrite the query on multilingual setups.
970 *
971 * @return bool
972 * true if exists, else false
973 */
974 public static function checkFieldExists($tableName, $columnName, $i18nRewrite = TRUE) {
975 CRM_Core_Error::deprecatedFunctionWarning('CRM_Core_BAO_SchemaHandler::checkIfFieldExists');
976 return CRM_Core_BAO_SchemaHandler::checkIfFieldExists($tableName, $columnName, $i18nRewrite);
977 }
978
979 /**
980 * Scans all the tables using a slow query and table name.
981 *
982 * @return array
983 */
984 public static function getTableNames() {
985 $dao = CRM_Core_DAO::executeQuery(
986 "SELECT TABLE_NAME
987 FROM information_schema.TABLES
988 WHERE TABLE_SCHEMA = '" . CRM_Core_DAO::getDatabaseName() . "'
989 AND TABLE_NAME LIKE 'civicrm_%'
990 AND TABLE_NAME NOT LIKE 'civicrm_import_job_%'
991 AND TABLE_NAME NOT LIKE '%_temp%'
992 ");
993
994 while ($dao->fetch()) {
995 $values[] = $dao->TABLE_NAME;
996 }
997 return $values;
998 }
999
1000 /**
1001 * @param int $maxTablesToCheck
1002 *
1003 * @return bool
1004 */
1005 public static function isDBMyISAM($maxTablesToCheck = 10) {
1006 return CRM_Core_DAO::singleValueQuery(
1007 "SELECT count(*)
1008 FROM information_schema.TABLES
1009 WHERE ENGINE = 'MyISAM'
1010 AND TABLE_SCHEMA = '" . CRM_Core_DAO::getDatabaseName() . "'
1011 AND TABLE_NAME LIKE 'civicrm_%'
1012 AND TABLE_NAME NOT LIKE 'civicrm_import_job_%'
1013 AND TABLE_NAME NOT LIKE '%_temp%'
1014 AND TABLE_NAME NOT LIKE 'civicrm_tmp_%'
1015 ");
1016 }
1017
1018 /**
1019 * Get the name of the CiviCRM database.
1020 *
1021 * @return string
1022 */
1023 public static function getDatabaseName() {
1024 $daoObj = new CRM_Core_DAO();
1025 return $daoObj->database();
1026 }
1027
1028 /**
1029 * Checks if a constraint exists for a specified table.
1030 *
1031 * @param string $tableName
1032 * @param string $constraint
1033 *
1034 * @return bool
1035 * true if constraint exists, false otherwise
1036 *
1037 * @throws \CRM_Core_Exception
1038 */
1039 public static function checkConstraintExists($tableName, $constraint) {
1040 static $show = [];
1041
1042 if (!array_key_exists($tableName, $show)) {
1043 $query = "SHOW CREATE TABLE $tableName";
1044 $dao = CRM_Core_DAO::executeQuery($query, [], TRUE, NULL, FALSE, FALSE);
1045
1046 if (!$dao->fetch()) {
1047 throw new CRM_Core_Exception('query failed');
1048 }
1049
1050 $show[$tableName] = $dao->Create_Table;
1051 }
1052
1053 return (bool) preg_match("/\b$constraint\b/i", $show[$tableName]);
1054 }
1055
1056 /**
1057 * Checks if CONSTRAINT keyword exists for a specified table.
1058 *
1059 * @param array $tables
1060 *
1061 * @throws CRM_Core_Exception
1062 *
1063 * @return bool
1064 * true if CONSTRAINT keyword exists, false otherwise
1065 */
1066 public static function schemaRequiresRebuilding($tables = ["civicrm_contact"]) {
1067 $show = [];
1068 foreach ($tables as $tableName) {
1069 if (!array_key_exists($tableName, $show)) {
1070 $query = "SHOW CREATE TABLE $tableName";
1071 $dao = CRM_Core_DAO::executeQuery($query, [], TRUE, NULL, FALSE, FALSE);
1072
1073 if (!$dao->fetch()) {
1074 throw new CRM_Core_Exception('Show create table failed.');
1075 }
1076
1077 $show[$tableName] = $dao->Create_Table;
1078 }
1079
1080 $result = (bool) preg_match("/\bCONSTRAINT\b\s/i", $show[$tableName]);
1081 if ($result == TRUE) {
1082 continue;
1083 }
1084 else {
1085 return FALSE;
1086 }
1087 }
1088 return TRUE;
1089 }
1090
1091 /**
1092 * Checks if the FK constraint name is in the format 'FK_tableName_columnName'
1093 * for a specified column of a table.
1094 *
1095 * @param string $tableName
1096 * @param string $columnName
1097 *
1098 * @return bool
1099 * true if in format, false otherwise
1100 *
1101 * @throws \CRM_Core_Exception
1102 */
1103 public static function checkFKConstraintInFormat($tableName, $columnName) {
1104 static $show = [];
1105
1106 if (!array_key_exists($tableName, $show)) {
1107 $query = "SHOW CREATE TABLE $tableName";
1108 $dao = CRM_Core_DAO::executeQuery($query);
1109
1110 if (!$dao->fetch()) {
1111 throw new CRM_Core_Exception('query failed');
1112 }
1113
1114 $show[$tableName] = $dao->Create_Table;
1115 }
1116 $constraint = "`FK_{$tableName}_{$columnName}`";
1117 $pattern = "/\bCONSTRAINT\b\s+%s\s+\bFOREIGN\s+KEY\b\s/i";
1118 return (bool) preg_match(sprintf($pattern, $constraint), $show[$tableName]);
1119 }
1120
1121 /**
1122 * Check whether a specific column in a specific table has always the same value.
1123 *
1124 * @param string $tableName
1125 * @param string $columnName
1126 * @param string $columnValue
1127 *
1128 * @return bool
1129 * true if the value is always $columnValue, false otherwise
1130 */
1131 public static function checkFieldHasAlwaysValue($tableName, $columnName, $columnValue) {
1132 $query = "SELECT * FROM $tableName WHERE $columnName != '$columnValue'";
1133 $dao = CRM_Core_DAO::executeQuery($query);
1134 $result = $dao->fetch() ? FALSE : TRUE;
1135 return $result;
1136 }
1137
1138 /**
1139 * Check whether a specific column in a specific table is always NULL.
1140 *
1141 * @param string $tableName
1142 * @param string $columnName
1143 *
1144 * @return bool
1145 * true if if the value is always NULL, false otherwise
1146 */
1147 public static function checkFieldIsAlwaysNull($tableName, $columnName) {
1148 $query = "SELECT * FROM $tableName WHERE $columnName IS NOT NULL";
1149 $dao = CRM_Core_DAO::executeQuery($query);
1150 $result = $dao->fetch() ? FALSE : TRUE;
1151 return $result;
1152 }
1153
1154 /**
1155 * Checks if this DAO's table ought to exist.
1156 *
1157 * If there are pending DB updates, this function compares the CiviCRM version of the table to the current schema version.
1158 *
1159 * @return bool
1160 * @throws CRM_Core_Exception
1161 */
1162 public static function tableHasBeenAdded() {
1163 if (CRM_Utils_System::version() === CRM_Core_BAO_Domain::version()) {
1164 return TRUE;
1165 }
1166 $daoExt = defined(static::class . '::EXT') ? constant(static::class . '::EXT') : NULL;
1167 $daoVersion = defined(static::class . '::TABLE_ADDED') ? constant(static::class . '::TABLE_ADDED') : '1.0';
1168 return !($daoExt === 'civicrm' && version_compare(CRM_Core_BAO_Domain::version(), $daoVersion, '<'));
1169 }
1170
1171 /**
1172 * Check if there is a given table in the database.
1173 *
1174 * @param string $tableName
1175 *
1176 * @return bool
1177 * true if exists, else false
1178 */
1179 public static function checkTableExists($tableName) {
1180 $query = "
1181 SHOW TABLES
1182 LIKE %1
1183 ";
1184 $params = [1 => [$tableName, 'String']];
1185
1186 $dao = CRM_Core_DAO::executeQuery($query, $params);
1187 return (bool) $dao->fetch();
1188 }
1189
1190 /**
1191 * Check if a given table has data.
1192 *
1193 * @param string $tableName
1194 * @return bool
1195 * TRUE if $tableName has at least one record.
1196 */
1197 public static function checkTableHasData($tableName) {
1198 $c = CRM_Core_DAO::singleValueQuery(sprintf('SELECT count(*) c FROM `%s`', $tableName));
1199 return $c > 0;
1200 }
1201
1202 /**
1203 * @param $version
1204 * @deprecated
1205 * @return bool
1206 */
1207 public function checkVersion($version) {
1208 CRM_Core_Error::deprecatedFunctionWarning('CRM_Core_BAO_Domain::version');
1209 $dbVersion = CRM_Core_BAO_Domain::version();
1210 return trim($version) == trim($dbVersion);
1211 }
1212
1213 /**
1214 * Find a DAO object for the given ID and return it.
1215 *
1216 * @param int $id
1217 * Id of the DAO object being searched for.
1218 *
1219 * @return CRM_Core_DAO
1220 * Object of the type of the class that called this function.
1221 *
1222 * @throws Exception
1223 */
1224 public static function findById($id) {
1225 $object = new static();
1226 $object->id = $id;
1227 if (!$object->find(TRUE)) {
1228 throw new Exception("Unable to find a " . get_called_class() . " with id {$id}.");
1229 }
1230 return $object;
1231 }
1232
1233 /**
1234 * Returns all results as array-encoded records.
1235 *
1236 * @return array
1237 */
1238 public function fetchAll($k = FALSE, $v = FALSE, $method = FALSE) {
1239 $result = [];
1240 while ($this->fetch()) {
1241 $result[] = $this->toArray();
1242 }
1243 return $result;
1244 }
1245
1246 /**
1247 * Return the results as PHP generator.
1248 *
1249 * @param string $type
1250 * Whether the generator yields 'dao' objects or 'array's.
1251 */
1252 public function fetchGenerator($type = 'dao') {
1253 while ($this->fetch()) {
1254 switch ($type) {
1255 case 'dao':
1256 yield $this;
1257 break;
1258
1259 case 'array':
1260 yield $this->toArray();
1261 break;
1262
1263 default:
1264 throw new \RuntimeException("Invalid record type ($type)");
1265 }
1266 }
1267 }
1268
1269 /**
1270 * Returns a singular value.
1271 *
1272 * @return mixed|NULL
1273 */
1274 public function fetchValue() {
1275 $result = $this->getDatabaseResult();
1276 $row = $result->fetchRow();
1277 $ret = NULL;
1278 if ($row) {
1279 $ret = $row[0];
1280 }
1281 $this->free();
1282 return $ret;
1283 }
1284
1285 /**
1286 * Get all the result records as mapping between columns.
1287 *
1288 * @param string $keyColumn
1289 * Ex: "name"
1290 * @param string $valueColumn
1291 * Ex: "label"
1292 * @return array
1293 * Ex: ["foo" => "The Foo Bar", "baz" => "The Baz Qux"]
1294 */
1295 public function fetchMap($keyColumn, $valueColumn) {
1296 $result = [];
1297 while ($this->fetch()) {
1298 $result[$this->{$keyColumn}] = $this->{$valueColumn};
1299 }
1300 return $result;
1301 }
1302
1303 /**
1304 * Given a DAO name, a column name and a column value, find the record and GET the value of another column in that record
1305 *
1306 * @param string $daoName
1307 * Name of the DAO (Example: CRM_Contact_DAO_Contact to retrieve value from a contact).
1308 * @param int $searchValue
1309 * Value of the column you want to search by.
1310 * @param string $returnColumn
1311 * Name of the column you want to GET the value of.
1312 * @param string $searchColumn
1313 * Name of the column you want to search by.
1314 * @param bool $force
1315 * Skip use of the cache.
1316 *
1317 * @return string|int|null
1318 * Value of $returnColumn in the retrieved record
1319 *
1320 * @throws \CRM_Core_Exception
1321 */
1322 public static function getFieldValue($daoName, $searchValue, $returnColumn = 'name', $searchColumn = 'id', $force = FALSE) {
1323 if (
1324 empty($searchValue) ||
1325 trim(strtolower($searchValue)) == 'null'
1326 ) {
1327 // adding this here since developers forget to check for an id
1328 // or for the 'null' (which is a bad DAO kludge)
1329 // and hence we get the first value in the db
1330 throw new CRM_Core_Exception('getFieldValue failed');
1331 }
1332
1333 self::$_dbColumnValueCache = self::$_dbColumnValueCache ?? [];
1334
1335 while (strpos($daoName, '_BAO_') !== FALSE) {
1336 $daoName = get_parent_class($daoName);
1337 }
1338
1339 if ($force ||
1340 empty(self::$_dbColumnValueCache[$daoName][$searchColumn][$searchValue]) ||
1341 !array_key_exists($returnColumn, self::$_dbColumnValueCache[$daoName][$searchColumn][$searchValue])
1342 ) {
1343 $object = new $daoName();
1344 $object->$searchColumn = $searchValue;
1345 $object->selectAdd();
1346 $object->selectAdd($returnColumn);
1347
1348 $result = NULL;
1349 if ($object->find(TRUE)) {
1350 $result = $object->$returnColumn;
1351 }
1352
1353 self::$_dbColumnValueCache[$daoName][$searchColumn][$searchValue][$returnColumn] = $result;
1354 }
1355 return self::$_dbColumnValueCache[$daoName][$searchColumn][$searchValue][$returnColumn];
1356 }
1357
1358 /**
1359 * Given a DAO name, a column name and a column value, find the record and SET the value of another column in that record
1360 *
1361 * @param string $daoName
1362 * Name of the DAO (Example: CRM_Contact_DAO_Contact to retrieve value from a contact).
1363 * @param int $searchValue
1364 * Value of the column you want to search by.
1365 * @param string $setColumn
1366 * Name of the column you want to SET the value of.
1367 * @param string $setValue
1368 * SET the setColumn to this value.
1369 * @param string $searchColumn
1370 * Name of the column you want to search by.
1371 *
1372 * @return bool
1373 * true if we found and updated the object, else false
1374 */
1375 public static function setFieldValue($daoName, $searchValue, $setColumn, $setValue, $searchColumn = 'id') {
1376 $object = new $daoName();
1377 $object->selectAdd();
1378 $object->selectAdd("$searchColumn, $setColumn");
1379 $object->$searchColumn = $searchValue;
1380 $result = FALSE;
1381 if ($object->find(TRUE)) {
1382 $object->$setColumn = $setValue;
1383 if ($object->save()) {
1384 $result = TRUE;
1385 }
1386 }
1387 $object->free();
1388 return $result;
1389 }
1390
1391 /**
1392 * Get sort string.
1393 *
1394 * @param array|object $sort either array or CRM_Utils_Sort
1395 * @param string $default
1396 * Default sort value.
1397 *
1398 * @return string
1399 */
1400 public static function getSortString($sort, $default = NULL) {
1401 // check if sort is of type CRM_Utils_Sort
1402 if (is_a($sort, 'CRM_Utils_Sort')) {
1403 return $sort->orderBy();
1404 }
1405
1406 $sortString = '';
1407
1408 // is it an array specified as $field => $sortDirection ?
1409 if ($sort) {
1410 foreach ($sort as $k => $v) {
1411 $sortString .= "$k $v,";
1412 }
1413 return rtrim($sortString, ',');
1414 }
1415 return $default;
1416 }
1417
1418 /**
1419 * Fetch object based on array of properties.
1420 *
1421 * @param string $daoName
1422 * Name of the dao object.
1423 * @param array $params
1424 * (reference ) an assoc array of name/value pairs.
1425 * @param array $defaults
1426 * (reference ) an assoc array to hold the flattened values.
1427 * @param array $returnProperities
1428 * An assoc array of fields that need to be returned, eg array( 'first_name', 'last_name').
1429 *
1430 * @return object
1431 * an object of type referenced by daoName
1432 */
1433 public static function commonRetrieve($daoName, &$params, &$defaults, $returnProperities = NULL) {
1434 $object = new $daoName();
1435 $object->copyValues($params);
1436
1437 // return only specific fields if returnproperties are sent
1438 if (!empty($returnProperities)) {
1439 $object->selectAdd();
1440 $object->selectAdd(implode(',', $returnProperities));
1441 }
1442
1443 if ($object->find(TRUE)) {
1444 self::storeValues($object, $defaults);
1445 return $object;
1446 }
1447 return NULL;
1448 }
1449
1450 /**
1451 * Delete the object records that are associated with this contact.
1452 *
1453 * @param string $daoName
1454 * Name of the dao object.
1455 * @param int $contactId
1456 * Id of the contact to delete.
1457 */
1458 public static function deleteEntityContact($daoName, $contactId) {
1459 $object = new $daoName();
1460
1461 $object->entity_table = 'civicrm_contact';
1462 $object->entity_id = $contactId;
1463 $object->delete();
1464 }
1465
1466 /**
1467 * Execute an unbuffered query.
1468 *
1469 * This is a wrapper around new functionality exposed with CRM-17748.
1470 *
1471 * @param string $query query to be executed
1472 *
1473 * @param array $params
1474 * @param bool $abort
1475 * @param null $daoName
1476 * @param bool $freeDAO
1477 * @param bool $i18nRewrite
1478 * @param bool $trapException
1479 *
1480 * @return CRM_Core_DAO
1481 * Object that points to an unbuffered result set
1482 */
1483 public static function executeUnbufferedQuery(
1484 $query,
1485 $params = [],
1486 $abort = TRUE,
1487 $daoName = NULL,
1488 $freeDAO = FALSE,
1489 $i18nRewrite = TRUE,
1490 $trapException = FALSE
1491 ) {
1492
1493 return self::executeQuery(
1494 $query,
1495 $params,
1496 $abort,
1497 $daoName,
1498 $freeDAO,
1499 $i18nRewrite,
1500 $trapException,
1501 ['result_buffering' => 0]
1502 );
1503 }
1504
1505 /**
1506 * Execute a query.
1507 *
1508 * @param string $query
1509 * Query to be executed.
1510 *
1511 * @param array $params
1512 * @param bool $abort
1513 * @param null $daoName
1514 * @param bool $freeDAO
1515 * @param bool $i18nRewrite
1516 * @param bool $trapException
1517 * @param array $options
1518 *
1519 * @return CRM_Core_DAO|object
1520 * object that holds the results of the query
1521 * NB - if this is defined as just returning a DAO phpstorm keeps pointing
1522 * out all the properties that are not part of the DAO
1523 */
1524 public static function &executeQuery(
1525 $query,
1526 $params = [],
1527 $abort = TRUE,
1528 $daoName = NULL,
1529 $freeDAO = FALSE,
1530 $i18nRewrite = TRUE,
1531 $trapException = FALSE,
1532 $options = []
1533 ) {
1534 $queryStr = self::composeQuery($query, $params, $abort);
1535
1536 if (!$daoName) {
1537 $dao = new CRM_Core_DAO();
1538 }
1539 else {
1540 $dao = new $daoName();
1541 }
1542
1543 if ($trapException) {
1544 CRM_Core_Error::deprecatedFunctionWarning('calling functions should handle exceptions');
1545 $errorScope = CRM_Core_TemporaryErrorScope::ignoreException();
1546 }
1547
1548 if ($dao->isValidOption($options)) {
1549 $dao->setOptions($options);
1550 }
1551
1552 $result = $dao->query($queryStr, $i18nRewrite);
1553
1554 // since it is unbuffered, ($dao->N==0) is true. This blocks the standard fetch() mechanism.
1555 if (CRM_Utils_Array::value('result_buffering', $options) === 0) {
1556 $dao->N = TRUE;
1557 }
1558
1559 if (is_a($result, 'DB_Error')) {
1560 CRM_Core_Error::deprecatedFunctionWarning('calling functions should handle exceptions');
1561 return $result;
1562 }
1563
1564 return $dao;
1565 }
1566
1567 /**
1568 * Wrapper to validate internal DAO options before passing to DB_mysql/DB_Common level
1569 *
1570 * @param array $options
1571 *
1572 * @return bool
1573 * Provided options are valid
1574 */
1575 public function isValidOption($options) {
1576 $isValid = FALSE;
1577 $validOptions = [
1578 'result_buffering',
1579 'persistent',
1580 'ssl',
1581 'portability',
1582 ];
1583
1584 if (empty($options)) {
1585 return $isValid;
1586 }
1587
1588 foreach (array_keys($options) as $option) {
1589 if (!in_array($option, $validOptions)) {
1590 return FALSE;
1591 }
1592 $isValid = TRUE;
1593 }
1594
1595 return $isValid;
1596 }
1597
1598 /**
1599 * Execute a query and get the single result.
1600 *
1601 * @param string $query
1602 * Query to be executed.
1603 * @param array $params
1604 * @param bool $abort
1605 * @param bool $i18nRewrite
1606 * @return string|null
1607 * the result of the query if any
1608 *
1609 */
1610 public static function &singleValueQuery(
1611 $query,
1612 $params = [],
1613 $abort = TRUE,
1614 $i18nRewrite = TRUE
1615 ) {
1616 $queryStr = self::composeQuery($query, $params, $abort);
1617
1618 static $_dao = NULL;
1619
1620 if (!$_dao) {
1621 $_dao = new CRM_Core_DAO();
1622 }
1623
1624 $_dao->query($queryStr, $i18nRewrite);
1625
1626 $result = $_dao->getDatabaseResult();
1627 $ret = NULL;
1628 if ($result) {
1629 $row = $result->fetchRow();
1630 if ($row) {
1631 $ret = $row[0];
1632 }
1633 }
1634 $_dao->free();
1635 return $ret;
1636 }
1637
1638 /**
1639 * Compose the query by merging the parameters into it.
1640 *
1641 * @param string $query
1642 * @param array $params
1643 * @param bool $abort
1644 *
1645 * @return string
1646 * @throws CRM_Core_Exception
1647 */
1648 public static function composeQuery($query, $params = [], $abort = TRUE) {
1649 $tr = [];
1650 foreach ($params as $key => $item) {
1651 if (is_numeric($key)) {
1652 if (CRM_Utils_Type::validate($item[0], $item[1]) !== NULL) {
1653 $item[0] = self::escapeString($item[0]);
1654 if ($item[1] == 'String' ||
1655 $item[1] == 'Memo' ||
1656 $item[1] == 'Link'
1657 ) {
1658 // Support class constants stipulating wildcard characters and/or
1659 // non-quoting of strings. Also support legacy code which may be
1660 // passing in TRUE or 1 for $item[2], which used to indicate the
1661 // use of wildcard characters.
1662 if (!empty($item[2])) {
1663 if ($item[2] & CRM_Core_DAO::QUERY_FORMAT_WILDCARD || $item[2] === TRUE) {
1664 $item[0] = "'%{$item[0]}%'";
1665 }
1666 elseif (!($item[2] & CRM_Core_DAO::QUERY_FORMAT_NO_QUOTES)) {
1667 $item[0] = "'{$item[0]}'";
1668 }
1669 }
1670 else {
1671 $item[0] = "'{$item[0]}'";
1672 }
1673 }
1674
1675 if (($item[1] == 'Date' || $item[1] == 'Timestamp') &&
1676 strlen($item[0]) == 0
1677 ) {
1678 $item[0] = 'null';
1679 }
1680
1681 $tr['%' . $key] = $item[0];
1682 }
1683 elseif ($abort) {
1684 throw new CRM_Core_Exception("{$item[0]} is not of type {$item[1]}");
1685 }
1686 }
1687 }
1688
1689 return strtr($query, $tr);
1690 }
1691
1692 /**
1693 * @param null $ids
1694 */
1695 public static function freeResult($ids = NULL) {
1696 global $_DB_DATAOBJECT;
1697
1698 if (!$ids) {
1699 if (!$_DB_DATAOBJECT ||
1700 !isset($_DB_DATAOBJECT['RESULTS'])
1701 ) {
1702 return;
1703 }
1704 $ids = array_keys($_DB_DATAOBJECT['RESULTS']);
1705 }
1706
1707 foreach ($ids as $id) {
1708 if (isset($_DB_DATAOBJECT['RESULTS'][$id])) {
1709 $_DB_DATAOBJECT['RESULTS'][$id]->free();
1710 unset($_DB_DATAOBJECT['RESULTS'][$id]);
1711 }
1712
1713 if (isset($_DB_DATAOBJECT['RESULTFIELDS'][$id])) {
1714 unset($_DB_DATAOBJECT['RESULTFIELDS'][$id]);
1715 }
1716 }
1717 }
1718
1719 /**
1720 * Make a shallow copy of an object and all the fields in the object.
1721 *
1722 * @param string $daoName
1723 * Name of the dao.
1724 * @param array $criteria
1725 * Array of all the fields & values.
1726 * on which basis to copy
1727 * @param array $newData
1728 * Array of all the fields & values.
1729 * to be copied besides the other fields
1730 * @param string $fieldsFix
1731 * Array of fields that you want to prefix/suffix/replace.
1732 * @param string $blockCopyOfDependencies
1733 * Fields that you want to block from.
1734 * getting copied
1735 * @param bool $blockCopyofCustomValues
1736 * Case when you don't want to copy the custom values set in a
1737 * template as it will override/ignore the submitted custom values
1738 *
1739 * @return CRM_Core_DAO|bool
1740 * the newly created copy of the object. False if none created.
1741 */
1742 public static function copyGeneric($daoName, $criteria, $newData = NULL, $fieldsFix = NULL, $blockCopyOfDependencies = NULL, $blockCopyofCustomValues = FALSE) {
1743 $object = new $daoName();
1744 $newObject = FALSE;
1745 if (!$newData) {
1746 $object->id = $criteria['id'];
1747 }
1748 else {
1749 foreach ($criteria as $key => $value) {
1750 $object->$key = $value;
1751 }
1752 }
1753
1754 $object->find();
1755 while ($object->fetch()) {
1756
1757 // all the objects except with $blockCopyOfDependencies set
1758 // be copied - addresses #CRM-1962
1759
1760 if ($blockCopyOfDependencies && $object->$blockCopyOfDependencies) {
1761 break;
1762 }
1763
1764 $newObject = new $daoName();
1765
1766 $fields = $object->fields();
1767 if (!is_array($fieldsFix)) {
1768 $fieldsToPrefix = [];
1769 $fieldsToSuffix = [];
1770 $fieldsToReplace = [];
1771 }
1772 if (!empty($fieldsFix['prefix'])) {
1773 $fieldsToPrefix = $fieldsFix['prefix'];
1774 }
1775 if (!empty($fieldsFix['suffix'])) {
1776 $fieldsToSuffix = $fieldsFix['suffix'];
1777 }
1778 if (!empty($fieldsFix['replace'])) {
1779 $fieldsToReplace = $fieldsFix['replace'];
1780 }
1781
1782 foreach ($fields as $name => $value) {
1783 if ($name == 'id' || $value['name'] == 'id') {
1784 // copy everything but the id!
1785 continue;
1786 }
1787
1788 $dbName = $value['name'];
1789 $type = CRM_Utils_Type::typeToString($value['type']);
1790 $newObject->$dbName = $object->$dbName;
1791 if (isset($fieldsToPrefix[$dbName])) {
1792 $newObject->$dbName = $fieldsToPrefix[$dbName] . $newObject->$dbName;
1793 }
1794 if (isset($fieldsToSuffix[$dbName])) {
1795 $newObject->$dbName .= $fieldsToSuffix[$dbName];
1796 }
1797 if (isset($fieldsToReplace[$dbName])) {
1798 $newObject->$dbName = $fieldsToReplace[$dbName];
1799 }
1800
1801 if ($type == 'Timestamp' || $type == 'Date') {
1802 $newObject->$dbName = CRM_Utils_Date::isoToMysql($newObject->$dbName);
1803 }
1804
1805 if ($newData) {
1806 $newObject->copyValues($newData);
1807 }
1808 }
1809 $newObject->save();
1810 if (!$blockCopyofCustomValues) {
1811 $newObject->copyCustomFields($object->id, $newObject->id);
1812 }
1813 CRM_Utils_Hook::post('create', CRM_Core_DAO_AllCoreTables::getBriefName($daoName), $newObject->id, $newObject);
1814 }
1815
1816 return $newObject;
1817 }
1818
1819 /**
1820 * Method that copies custom fields values from an old entity to a new one.
1821 *
1822 * Fixes bug CRM-19302,
1823 * where if a custom field of File type was present, left both events using the same file,
1824 * breaking download URL's for the old event.
1825 *
1826 * @todo the goal here is to clean this up so that it works for any entity. Copy Generic already DOES some custom field stuff
1827 * but it seems to be bypassed & perhaps less good than this (or this just duplicates it...)
1828 *
1829 * @param int $entityID
1830 * @param int $newEntityID
1831 */
1832 public function copyCustomFields($entityID, $newEntityID) {
1833 $entity = CRM_Core_DAO_AllCoreTables::getBriefName(get_class($this));
1834 $tableName = CRM_Core_DAO_AllCoreTables::getTableForClass(get_class($this));
1835 // Obtain custom values for old event
1836 $customParams = $htmlType = [];
1837 $customValues = CRM_Core_BAO_CustomValueTable::getEntityValues($entityID, $entity);
1838
1839 // If custom values present, we copy them
1840 if (!empty($customValues)) {
1841 // Get Field ID's and identify File type attributes, to handle file copying.
1842 $fieldIds = implode(', ', array_keys($customValues));
1843 $sql = "SELECT id FROM civicrm_custom_field WHERE html_type = 'File' AND id IN ( {$fieldIds} )";
1844 $result = CRM_Core_DAO::executeQuery($sql);
1845
1846 // Build array of File type fields
1847 while ($result->fetch()) {
1848 $htmlType[] = $result->id;
1849 }
1850
1851 // Build params array of custom values
1852 foreach ($customValues as $field => $value) {
1853 if ($value !== NULL) {
1854 // Handle File type attributes
1855 if (in_array($field, $htmlType)) {
1856 $fileValues = CRM_Core_BAO_File::path($value, $entityID);
1857 $customParams["custom_{$field}_-1"] = [
1858 'name' => CRM_Utils_File::duplicate($fileValues[0]),
1859 'type' => $fileValues[1],
1860 ];
1861 }
1862 // Handle other types
1863 else {
1864 $customParams["custom_{$field}_-1"] = $value;
1865 }
1866 }
1867 }
1868
1869 // Save Custom Fields for new Event
1870 CRM_Core_BAO_CustomValueTable::postProcess($customParams, $tableName, $newEntityID, $entity);
1871 }
1872
1873 // copy activity attachments ( if any )
1874 CRM_Core_BAO_File::copyEntityFile($tableName, $entityID, $tableName, $newEntityID);
1875 }
1876
1877 /**
1878 * Cascade update through related entities.
1879 *
1880 * @param string $daoName
1881 * @param $fromId
1882 * @param $toId
1883 * @param array $newData
1884 *
1885 * @return CRM_Core_DAO|null
1886 */
1887 public static function cascadeUpdate($daoName, $fromId, $toId, $newData = []) {
1888 $object = new $daoName();
1889 $object->id = $fromId;
1890
1891 if ($object->find(TRUE)) {
1892 $newObject = new $daoName();
1893 $newObject->id = $toId;
1894
1895 if ($newObject->find(TRUE)) {
1896 $fields = $object->fields();
1897 foreach ($fields as $name => $value) {
1898 if ($name == 'id' || $value['name'] == 'id') {
1899 // copy everything but the id!
1900 continue;
1901 }
1902
1903 $colName = $value['name'];
1904 $newObject->$colName = $object->$colName;
1905
1906 if (substr($name, -5) == '_date' ||
1907 substr($name, -10) == '_date_time'
1908 ) {
1909 $newObject->$colName = CRM_Utils_Date::isoToMysql($newObject->$colName);
1910 }
1911 }
1912 foreach ($newData as $k => $v) {
1913 $newObject->$k = $v;
1914 }
1915 $newObject->save();
1916 return $newObject;
1917 }
1918 }
1919 return NULL;
1920 }
1921
1922 /**
1923 * Given the component id, compute the contact id
1924 * since its used for things like send email
1925 *
1926 * @param $componentIDs
1927 * @param string $tableName
1928 * @param string $idField
1929 *
1930 * @return array
1931 */
1932 public static function getContactIDsFromComponent($componentIDs, $tableName, $idField = 'id') {
1933 $contactIDs = [];
1934
1935 if (empty($componentIDs)) {
1936 return $contactIDs;
1937 }
1938
1939 $IDs = implode(',', $componentIDs);
1940 $query = "
1941 SELECT contact_id
1942 FROM $tableName
1943 WHERE $idField IN ( $IDs )
1944 ";
1945
1946 $dao = CRM_Core_DAO::executeQuery($query);
1947 while ($dao->fetch()) {
1948 $contactIDs[] = $dao->contact_id;
1949 }
1950 return $contactIDs;
1951 }
1952
1953 /**
1954 * Fetch object based on array of properties.
1955 *
1956 * @param string $daoName
1957 * Name of the dao object.
1958 * @param string $fieldIdName
1959 * @param int $fieldId
1960 * @param $details
1961 * @param array $returnProperities
1962 * An assoc array of fields that need to be returned, eg array( 'first_name', 'last_name').
1963 *
1964 * @return object
1965 * an object of type referenced by daoName
1966 */
1967 public static function commonRetrieveAll($daoName, $fieldIdName = 'id', $fieldId, &$details, $returnProperities = NULL) {
1968 require_once str_replace('_', DIRECTORY_SEPARATOR, $daoName) . ".php";
1969 $object = new $daoName();
1970 $object->$fieldIdName = $fieldId;
1971
1972 // return only specific fields if returnproperties are sent
1973 if (!empty($returnProperities)) {
1974 $object->selectAdd();
1975 $object->selectAdd('id');
1976 $object->selectAdd(implode(',', $returnProperities));
1977 }
1978
1979 $object->find();
1980 while ($object->fetch()) {
1981 $defaults = [];
1982 self::storeValues($object, $defaults);
1983 $details[$object->id] = $defaults;
1984 }
1985
1986 return $details;
1987 }
1988
1989 /**
1990 * Drop all CiviCRM tables.
1991 *
1992 * @throws \CRM_Core_Exception
1993 */
1994 public static function dropAllTables() {
1995
1996 // first drop all the custom tables we've created
1997 CRM_Core_BAO_CustomGroup::dropAllTables();
1998
1999 // drop all multilingual views
2000 CRM_Core_I18n_Schema::dropAllViews();
2001
2002 CRM_Utils_File::sourceSQLFile(CIVICRM_DSN,
2003 dirname(__FILE__) . DIRECTORY_SEPARATOR .
2004 '..' . DIRECTORY_SEPARATOR .
2005 '..' . DIRECTORY_SEPARATOR .
2006 'sql' . DIRECTORY_SEPARATOR .
2007 'civicrm_drop.mysql'
2008 );
2009 }
2010
2011 /**
2012 * @param $string
2013 *
2014 * @return string
2015 */
2016 public static function escapeString($string) {
2017 static $_dao = NULL;
2018 if (!$_dao) {
2019 // If this is an atypical case (e.g. preparing .sql file before CiviCRM
2020 // has been installed), then we fallback DB-less str_replace escaping, as
2021 // we can't use mysqli_real_escape_string, as there is no DB connection.
2022 // Note: In typical usage, escapeString() will only check one conditional
2023 // ("if !$_dao") rather than two conditionals ("if !defined(DSN)")
2024 if (!defined('CIVICRM_DSN')) {
2025 // See http://php.net/manual/en/mysqli.real-escape-string.php for the
2026 // list of characters mysqli_real_escape_string escapes.
2027 $search = ["\\", "\x00", "\n", "\r", "'", '"', "\x1a"];
2028 $replace = ["\\\\", "\\0", "\\n", "\\r", "\'", '\"', "\\Z"];
2029 return str_replace($search, $replace, $string);
2030 }
2031 $_dao = new CRM_Core_DAO();
2032 }
2033 return $_dao->escape($string);
2034 }
2035
2036 /**
2037 * Escape a list of strings for use with "WHERE X IN (...)" queries.
2038 *
2039 * @param array $strings
2040 * @param string $default
2041 * the value to use if $strings has no elements.
2042 * @return string
2043 * eg "abc","def","ghi"
2044 */
2045 public static function escapeStrings($strings, $default = NULL) {
2046 static $_dao = NULL;
2047 if (!$_dao) {
2048 $_dao = new CRM_Core_DAO();
2049 }
2050
2051 if (empty($strings)) {
2052 return $default;
2053 }
2054
2055 $escapes = array_map([$_dao, 'escape'], $strings);
2056 return '"' . implode('","', $escapes) . '"';
2057 }
2058
2059 /**
2060 * @param $string
2061 *
2062 * @return string
2063 */
2064 public static function escapeWildCardString($string) {
2065 // CRM-9155
2066 // ensure we escape the single characters % and _ which are mysql wild
2067 // card characters and could come in via sortByCharacter
2068 // note that mysql does not escape these characters
2069 if ($string && in_array($string,
2070 ['%', '_', '%%', '_%']
2071 )
2072 ) {
2073 return '\\' . $string;
2074 }
2075
2076 return self::escapeString($string);
2077 }
2078
2079 /**
2080 * Creates a test object, including any required objects it needs via recursion
2081 * createOnly: only create in database, do not store or return the objects (useful for perf testing)
2082 * ONLY USE FOR TESTING
2083 *
2084 * @param string $daoName
2085 * @param array $params
2086 * @param int $numObjects
2087 * @param bool $createOnly
2088 *
2089 * @return object|array|NULL
2090 * NULL if $createOnly. A single object if $numObjects==1. Otherwise, an array of multiple objects.
2091 */
2092 public static function createTestObject(
2093 $daoName,
2094 $params = [],
2095 $numObjects = 1,
2096 $createOnly = FALSE
2097 ) {
2098 //this is a test function also backtrace is set for the test suite it sometimes unsets itself
2099 // so we re-set here in case
2100 $config = CRM_Core_Config::singleton();
2101 $config->backtrace = TRUE;
2102
2103 static $counter = 0;
2104 CRM_Core_DAO::$_testEntitiesToSkip = [
2105 'CRM_Core_DAO_Worldregion',
2106 'CRM_Core_DAO_StateProvince',
2107 'CRM_Core_DAO_Country',
2108 'CRM_Core_DAO_Domain',
2109 'CRM_Financial_DAO_FinancialType',
2110 //because valid ones exist & we use pick them due to pseudoconstant can't reliably create & delete these
2111 ];
2112
2113 // Prefer to instantiate BAO's instead of DAO's (when possible)
2114 // so that assignTestValue()/assignTestFK() can be overloaded.
2115 $baoName = str_replace('_DAO_', '_BAO_', $daoName);
2116 if ($baoName === 'CRM_Financial_BAO_FinancialTrxn') {
2117 // OMG OMG OMG this is so incredibly bad. The BAO is insanely named.
2118 // @todo create a new class called what the BAO SHOULD be
2119 // that extends BAO-crazy-name.... migrate.
2120 $baoName = 'CRM_Core_BAO_FinancialTrxn';
2121 }
2122 if (class_exists($baoName)) {
2123 $daoName = $baoName;
2124 }
2125
2126 for ($i = 0; $i < $numObjects; ++$i) {
2127
2128 ++$counter;
2129 /** @var CRM_Core_DAO $object */
2130 $object = new $daoName();
2131
2132 $fields = $object->fields();
2133 foreach ($fields as $fieldName => $fieldDef) {
2134 $dbName = $fieldDef['name'];
2135 $FKClassName = $fieldDef['FKClassName'] ?? NULL;
2136
2137 if (isset($params[$dbName]) && !is_array($params[$dbName])) {
2138 $object->$dbName = $params[$dbName];
2139 }
2140
2141 elseif ($dbName != 'id') {
2142 if ($FKClassName != NULL) {
2143 $object->assignTestFK($fieldName, $fieldDef, $params);
2144 continue;
2145 }
2146 else {
2147 $object->assignTestValue($fieldName, $fieldDef, $counter);
2148 }
2149 }
2150 }
2151
2152 $object->save();
2153
2154 if (!$createOnly) {
2155 $objects[$i] = $object;
2156 }
2157 else {
2158 unset($object);
2159 }
2160 }
2161
2162 if ($createOnly) {
2163 return NULL;
2164 }
2165 elseif ($numObjects == 1) {
2166 return $objects[0];
2167 }
2168 else {
2169 return $objects;
2170 }
2171 }
2172
2173 /**
2174 * Deletes the this object plus any dependent objects that are associated with it.
2175 * ONLY USE FOR TESTING
2176 *
2177 * @param string $daoName
2178 * @param array $params
2179 */
2180 public static function deleteTestObjects($daoName, $params = []) {
2181 //this is a test function also backtrace is set for the test suite it sometimes unsets itself
2182 // so we re-set here in case
2183 $config = CRM_Core_Config::singleton();
2184 $config->backtrace = TRUE;
2185
2186 $object = new $daoName();
2187 $object->id = $params['id'] ?? NULL;
2188
2189 // array(array(0 => $daoName, 1 => $daoParams))
2190 $deletions = [];
2191 if ($object->find(TRUE)) {
2192
2193 $fields = $object->fields();
2194 foreach ($fields as $name => $value) {
2195
2196 $dbName = $value['name'];
2197
2198 $FKClassName = $value['FKClassName'] ?? NULL;
2199 $required = $value['required'] ?? NULL;
2200 if ($FKClassName != NULL
2201 && $object->$dbName
2202 && !in_array($FKClassName, CRM_Core_DAO::$_testEntitiesToSkip)
2203 && ($required || $dbName == 'contact_id')
2204 //I'm a bit stuck on this one - we might need to change the singleValueAlter so that the entities don't share a contact
2205 // to make this test process pass - line below makes pass for now
2206 && $dbName != 'member_of_contact_id'
2207 ) {
2208 // x
2209 $deletions[] = [$FKClassName, ['id' => $object->$dbName]];
2210 }
2211 }
2212 }
2213
2214 $object->delete();
2215
2216 foreach ($deletions as $deletion) {
2217 CRM_Core_DAO::deleteTestObjects($deletion[0], $deletion[1]);
2218 }
2219 }
2220
2221 /**
2222 * Set defaults when creating new entity.
2223 * (don't call this set defaults as already in use with different signature in some places)
2224 *
2225 * @param array $params
2226 * @param $defaults
2227 */
2228 public static function setCreateDefaults(&$params, $defaults) {
2229 if (!empty($params['id'])) {
2230 return;
2231 }
2232 foreach ($defaults as $key => $value) {
2233 if (!array_key_exists($key, $params) || $params[$key] === NULL) {
2234 $params[$key] = $value;
2235 }
2236 }
2237 }
2238
2239 /**
2240 * @param string $prefix
2241 * @param bool $addRandomString
2242 * @param null $string
2243 *
2244 * @return string
2245 * @deprecated
2246 * @see CRM_Utils_SQL_TempTable
2247 */
2248 public static function createTempTableName($prefix = 'civicrm', $addRandomString = TRUE, $string = NULL) {
2249 CRM_Core_Error::deprecatedFunctionWarning('Use CRM_Utils_SQL_TempTable interface to create temporary tables');
2250 $tableName = $prefix . "_temp";
2251
2252 if ($addRandomString) {
2253 if ($string) {
2254 $tableName .= "_" . $string;
2255 }
2256 else {
2257 $tableName .= "_" . md5(uniqid('', TRUE));
2258 }
2259 }
2260 return $tableName;
2261 }
2262
2263 /**
2264 * @param bool $view
2265 * @param bool $trigger
2266 *
2267 * @return bool
2268 */
2269 public static function checkTriggerViewPermission($view = TRUE, $trigger = TRUE) {
2270 if (\Civi::settings()->get('logging_no_trigger_permission')) {
2271 return TRUE;
2272 }
2273 // test for create view and trigger permissions and if allowed, add the option to go multilingual
2274 // and logging
2275 // I'm not sure why we use the getStaticProperty for an error, rather than checking for DB_Error
2276 CRM_Core_TemporaryErrorScope::ignoreException();
2277 $dao = new CRM_Core_DAO();
2278 if ($view) {
2279 $result = $dao->query('CREATE OR REPLACE VIEW civicrm_domain_view AS SELECT * FROM civicrm_domain');
2280 if (PEAR::getStaticProperty('DB_DataObject', 'lastError') || is_a($result, 'DB_Error')) {
2281 return FALSE;
2282 }
2283 }
2284
2285 if ($trigger) {
2286 $result = $dao->query('CREATE TRIGGER civicrm_domain_trigger BEFORE INSERT ON civicrm_domain FOR EACH ROW BEGIN END');
2287 if (PEAR::getStaticProperty('DB_DataObject', 'lastError') || is_a($result, 'DB_Error')) {
2288 if ($view) {
2289 $dao->query('DROP VIEW IF EXISTS civicrm_domain_view');
2290 }
2291 return FALSE;
2292 }
2293
2294 $dao->query('DROP TRIGGER IF EXISTS civicrm_domain_trigger');
2295 if (PEAR::getStaticProperty('DB_DataObject', 'lastError')) {
2296 if ($view) {
2297 $dao->query('DROP VIEW IF EXISTS civicrm_domain_view');
2298 }
2299 return FALSE;
2300 }
2301 }
2302
2303 if ($view) {
2304 $dao->query('DROP VIEW IF EXISTS civicrm_domain_view');
2305 if (PEAR::getStaticProperty('DB_DataObject', 'lastError')) {
2306 return FALSE;
2307 }
2308 }
2309
2310 return TRUE;
2311 }
2312
2313 /**
2314 * @param null $message
2315 * @param bool $printDAO
2316 */
2317 public static function debugPrint($message = NULL, $printDAO = TRUE) {
2318 CRM_Utils_System::xMemory("{$message}: ");
2319
2320 if ($printDAO) {
2321 global $_DB_DATAOBJECT;
2322 $q = [];
2323 foreach (array_keys($_DB_DATAOBJECT['RESULTS']) as $id) {
2324 $q[] = $_DB_DATAOBJECT['RESULTS'][$id]->query;
2325 }
2326 CRM_Core_Error::debug('_DB_DATAOBJECT', $q);
2327 }
2328 }
2329
2330 /**
2331 * Build a list of triggers via hook and add them to (err, reconcile them
2332 * with) the database.
2333 *
2334 * @param string $tableName
2335 * the specific table requiring a rebuild; or NULL to rebuild all tables.
2336 * @param bool $force
2337 * @deprecated
2338 *
2339 * @see CRM-9716
2340 */
2341 public static function triggerRebuild($tableName = NULL, $force = FALSE) {
2342 Civi::service('sql_triggers')->rebuild($tableName, $force);
2343 }
2344
2345 /**
2346 * Because sql functions are sometimes lost, esp during db migration, we check here to avoid numerous support requests
2347 * @see http://issues.civicrm.org/jira/browse/CRM-13822
2348 * TODO: Alternative solutions might be
2349 * * Stop using functions and find another way to strip numeric characters from phones
2350 * * Give better error messages (currently a missing fn fatals with "unknown error")
2351 */
2352 public static function checkSqlFunctionsExist() {
2353 if (!self::$_checkedSqlFunctionsExist) {
2354 self::$_checkedSqlFunctionsExist = TRUE;
2355 $dao = CRM_Core_DAO::executeQuery("SHOW function status WHERE db = database() AND name = 'civicrm_strip_non_numeric'");
2356 if (!$dao->fetch()) {
2357 self::triggerRebuild();
2358 }
2359 }
2360 }
2361
2362 /**
2363 * Wrapper function to drop triggers.
2364 *
2365 * @param string $tableName
2366 * the specific table requiring a rebuild; or NULL to rebuild all tables.
2367 * @deprecated
2368 */
2369 public static function dropTriggers($tableName = NULL) {
2370 Civi::service('sql_triggers')->dropTriggers($tableName);
2371 }
2372
2373 /**
2374 * @param array $info
2375 * per hook_civicrm_triggerInfo.
2376 * @param string $onlyTableName
2377 * the specific table requiring a rebuild; or NULL to rebuild all tables.
2378 * @deprecated
2379 */
2380 public static function createTriggers(&$info, $onlyTableName = NULL) {
2381 Civi::service('sql_triggers')->createTriggers($info, $onlyTableName);
2382 }
2383
2384 /**
2385 * Given a list of fields, create a list of references.
2386 *
2387 * @param string $className
2388 * BAO/DAO class name.
2389 * @return array<CRM_Core_Reference_Interface>
2390 */
2391 public static function createReferenceColumns($className) {
2392 $result = [];
2393 $fields = $className::fields();
2394 foreach ($fields as $field) {
2395 if (isset($field['pseudoconstant'], $field['pseudoconstant']['optionGroupName'])) {
2396 $result[] = new CRM_Core_Reference_OptionValue(
2397 $className::getTableName(),
2398 $field['name'],
2399 'civicrm_option_value',
2400 CRM_Utils_Array::value('keyColumn', $field['pseudoconstant'], 'value'),
2401 $field['pseudoconstant']['optionGroupName']
2402 );
2403 }
2404 }
2405 return $result;
2406 }
2407
2408 /**
2409 * Find all records which refer to this entity.
2410 *
2411 * @return array
2412 * Array of objects referencing this
2413 */
2414 public function findReferences() {
2415 $links = self::getReferencesToTable(static::getTableName());
2416
2417 $occurrences = [];
2418 foreach ($links as $refSpec) {
2419 /** @var $refSpec CRM_Core_Reference_Interface */
2420 $daoName = CRM_Core_DAO_AllCoreTables::getClassForTable($refSpec->getReferenceTable());
2421 $result = $refSpec->findReferences($this);
2422 if ($result) {
2423 while ($result->fetch()) {
2424 $obj = new $daoName();
2425 $obj->id = $result->id;
2426 $occurrences[] = $obj;
2427 }
2428 }
2429 }
2430
2431 return $occurrences;
2432 }
2433
2434 /**
2435 * @return array
2436 * each item has keys:
2437 * - name: string
2438 * - type: string
2439 * - count: int
2440 * - table: string|null SQL table name
2441 * - key: string|null SQL column name
2442 */
2443 public function getReferenceCounts() {
2444 $links = self::getReferencesToTable(static::getTableName());
2445
2446 $counts = [];
2447 foreach ($links as $refSpec) {
2448 /** @var $refSpec CRM_Core_Reference_Interface */
2449 $count = $refSpec->getReferenceCount($this);
2450 if ($count['count'] != 0) {
2451 $counts[] = $count;
2452 }
2453 }
2454
2455 foreach (CRM_Core_Component::getEnabledComponents() as $component) {
2456 /** @var $component CRM_Core_Component_Info */
2457 $counts = array_merge($counts, $component->getReferenceCounts($this));
2458 }
2459 CRM_Utils_Hook::referenceCounts($this, $counts);
2460
2461 return $counts;
2462 }
2463
2464 /**
2465 * List all tables which have hard foreign keys to this table.
2466 *
2467 * For now, this returns a description of every entity_id/entity_table
2468 * reference.
2469 * TODO: filter dynamic entity references on the $tableName, based on
2470 * schema metadata in dynamicForeignKey which enumerates a restricted
2471 * set of possible entity_table's.
2472 *
2473 * @param string $tableName
2474 * Table referred to.
2475 *
2476 * @return array
2477 * structure of table and column, listing every table with a
2478 * foreign key reference to $tableName, and the column where the key appears.
2479 */
2480 public static function getReferencesToTable($tableName) {
2481 $refsFound = [];
2482 foreach (CRM_Core_DAO_AllCoreTables::getClasses() as $daoClassName) {
2483 $links = $daoClassName::getReferenceColumns();
2484
2485 foreach ($links as $refSpec) {
2486 /** @var $refSpec CRM_Core_Reference_Interface */
2487 if ($refSpec->matchesTargetTable($tableName)) {
2488 $refsFound[] = $refSpec;
2489 }
2490 }
2491 }
2492 return $refsFound;
2493 }
2494
2495 /**
2496 * Get all references to contact table.
2497 *
2498 * This includes core tables, custom group tables, tables added by the merge
2499 * hook and the entity_tag table.
2500 *
2501 * Refer to CRM-17454 for information on the danger of querying the information
2502 * schema to derive this.
2503 *
2504 * @throws \CiviCRM_API3_Exception
2505 */
2506 public static function getReferencesToContactTable() {
2507 if (isset(\Civi::$statics[__CLASS__]) && isset(\Civi::$statics[__CLASS__]['contact_references'])) {
2508 return \Civi::$statics[__CLASS__]['contact_references'];
2509 }
2510 $contactReferences = [];
2511 $coreReferences = CRM_Core_DAO::getReferencesToTable('civicrm_contact');
2512 foreach ($coreReferences as $coreReference) {
2513 if (!is_a($coreReference, 'CRM_Core_Reference_Dynamic')) {
2514 $contactReferences[$coreReference->getReferenceTable()][] = $coreReference->getReferenceKey();
2515 }
2516 }
2517 self::appendCustomTablesExtendingContacts($contactReferences);
2518 self::appendCustomContactReferenceFields($contactReferences);
2519 \Civi::$statics[__CLASS__]['contact_references'] = $contactReferences;
2520 return \Civi::$statics[__CLASS__]['contact_references'];
2521 }
2522
2523 /**
2524 * Get all dynamic references to the given table.
2525 *
2526 * @param string $tableName
2527 *
2528 * @return array
2529 */
2530 public static function getDynamicReferencesToTable($tableName) {
2531 if (!isset(\Civi::$statics[__CLASS__]['contact_references_dynamic'][$tableName])) {
2532 \Civi::$statics[__CLASS__]['contact_references_dynamic'][$tableName] = [];
2533 $coreReferences = CRM_Core_DAO::getReferencesToTable($tableName);
2534 foreach ($coreReferences as $coreReference) {
2535 if ($coreReference instanceof \CRM_Core_Reference_Dynamic) {
2536 \Civi::$statics[__CLASS__]['contact_references_dynamic'][$tableName][$coreReference->getReferenceTable()][] = [$coreReference->getReferenceKey(), $coreReference->getTypeColumn()];
2537 }
2538 }
2539 }
2540 return \Civi::$statics[__CLASS__]['contact_references_dynamic'][$tableName];
2541 }
2542
2543 /**
2544 * Add custom tables that extend contacts to the list of contact references.
2545 *
2546 * CRM_Core_BAO_CustomGroup::getAllCustomGroupsByBaseEntity seems like a safe-ish
2547 * function to be sure all are retrieved & we don't miss subtypes or inactive or multiples
2548 * - the down side is it is not cached.
2549 *
2550 * Further changes should be include tests in the CRM_Core_MergerTest class
2551 * to ensure that disabled, subtype, multiple etc groups are still captured.
2552 *
2553 * @param array $cidRefs
2554 */
2555 public static function appendCustomTablesExtendingContacts(&$cidRefs) {
2556 $customValueTables = CRM_Core_BAO_CustomGroup::getAllCustomGroupsByBaseEntity('Contact');
2557 $customValueTables->find();
2558 while ($customValueTables->fetch()) {
2559 $cidRefs[$customValueTables->table_name][] = 'entity_id';
2560 }
2561 }
2562
2563 /**
2564 * Add custom ContactReference fields to the list of contact references
2565 *
2566 * This includes active and inactive fields/groups
2567 *
2568 * @param array $cidRefs
2569 *
2570 * @throws \CiviCRM_API3_Exception
2571 */
2572 public static function appendCustomContactReferenceFields(&$cidRefs) {
2573 $fields = civicrm_api3('CustomField', 'get', [
2574 'return' => ['column_name', 'custom_group_id.table_name'],
2575 'data_type' => 'ContactReference',
2576 'options' => ['limit' => 0],
2577 ])['values'];
2578 foreach ($fields as $field) {
2579 $cidRefs[$field['custom_group_id.table_name']][] = $field['column_name'];
2580 }
2581 }
2582
2583 /**
2584 * Lookup the value of a MySQL global configuration variable.
2585 *
2586 * @param string $name
2587 * E.g. "thread_stack".
2588 * @param mixed $default
2589 * @return mixed
2590 */
2591 public static function getGlobalSetting($name, $default = NULL) {
2592 // Alternatively, SELECT @@GLOBAL.thread_stack, but
2593 // that has been reported to fail under MySQL 5.0 for OS X
2594 $escapedName = self::escapeString($name);
2595 $dao = CRM_Core_DAO::executeQuery("SHOW VARIABLES LIKE '$escapedName'");
2596 if ($dao->fetch()) {
2597 return $dao->Value;
2598 }
2599 else {
2600 return $default;
2601 }
2602 }
2603
2604 /**
2605 * Update the fields array to also hold keys for pseudoconstant fields that relate to contained fields.
2606 *
2607 * This is relevant where we want to offer both the ID field and the label field
2608 * as an option, e.g. search builder.
2609 *
2610 * It is currently limited for optionGroupName & id+ name+ FK combos for purposes keeping the scope of the
2611 * change small, but is appropriate for other sorts of pseudoconstants.
2612 *
2613 * @param array $fields
2614 */
2615 public static function appendPseudoConstantsToFields(&$fields) {
2616 foreach ($fields as $fieldUniqueName => $field) {
2617 if (!empty($field['pseudoconstant'])) {
2618 $pseudoConstant = $field['pseudoconstant'];
2619 if (!empty($pseudoConstant['optionGroupName'])) {
2620 $fields[$pseudoConstant['optionGroupName']] = [
2621 'title' => CRM_Core_BAO_OptionGroup::getTitleByName($pseudoConstant['optionGroupName']),
2622 'name' => $pseudoConstant['optionGroupName'],
2623 'data_type' => CRM_Utils_Type::T_STRING,
2624 'is_pseudofield_for' => $fieldUniqueName,
2625 ];
2626 }
2627 // We restrict to id + name + FK as we are extending this a bit, but cautiously.
2628 elseif (
2629 !empty($field['FKClassName'])
2630 && CRM_Utils_Array::value('keyColumn', $pseudoConstant) === 'id'
2631 && CRM_Utils_Array::value('labelColumn', $pseudoConstant) === 'name'
2632 ) {
2633 $pseudoFieldName = str_replace('_' . $pseudoConstant['keyColumn'], '', $field['name']);
2634 // This if is just an extra caution when adding change.
2635 if (!isset($fields[$pseudoFieldName])) {
2636 $daoName = $field['FKClassName'];
2637 $fkFields = $daoName::fields();
2638 foreach ($fkFields as $fkField) {
2639 if ($fkField['name'] === $pseudoConstant['labelColumn']) {
2640 $fields[$pseudoFieldName] = [
2641 'name' => $pseudoFieldName,
2642 'is_pseudofield_for' => $field['name'],
2643 'title' => $fkField['title'],
2644 'data_type' => $fkField['type'],
2645 'where' => $field['where'],
2646 ];
2647 }
2648 }
2649 }
2650 }
2651 }
2652 }
2653 }
2654
2655 /**
2656 * Get options for the called BAO object's field.
2657 *
2658 * This function can be overridden by each BAO to add more logic related to context.
2659 * The overriding function will generally call the lower-level CRM_Core_PseudoConstant::get
2660 *
2661 * @param string $fieldName
2662 * @param string $context
2663 * @see CRM_Core_DAO::buildOptionsContext
2664 * @param array $props
2665 * Raw field values; whatever is known about this bao object.
2666 *
2667 * Note: $props can contain unsanitized input and should not be passed directly to CRM_Core_PseudoConstant::get
2668 *
2669 * @return array|bool
2670 */
2671 public static function buildOptions($fieldName, $context = NULL, $props = []) {
2672 // If a given bao does not override this function
2673 $baoName = get_called_class();
2674 return CRM_Core_PseudoConstant::get($baoName, $fieldName, [], $context);
2675 }
2676
2677 /**
2678 * Populate option labels for this object's fields.
2679 *
2680 * @throws exception if called directly on the base class
2681 */
2682 public function getOptionLabels() {
2683 $fields = $this->fields();
2684 if ($fields === NULL) {
2685 throw new Exception('Cannot call getOptionLabels on CRM_Core_DAO');
2686 }
2687 foreach ($fields as $field) {
2688 $name = $field['name'] ?? NULL;
2689 if ($name && isset($this->$name)) {
2690 $label = CRM_Core_PseudoConstant::getLabel(get_class($this), $name, $this->$name);
2691 if ($label !== FALSE) {
2692 // Append 'label' onto the field name
2693 $labelName = $name . '_label';
2694 $this->$labelName = $label;
2695 }
2696 }
2697 }
2698 }
2699
2700 /**
2701 * Provides documentation and validation for the buildOptions $context param
2702 *
2703 * @param string $context
2704 *
2705 * @throws CRM_Core_Exception
2706 * @return array
2707 */
2708 public static function buildOptionsContext($context = NULL) {
2709 $contexts = [
2710 'get' => "get: all options are returned, even if they are disabled; labels are translated.",
2711 'create' => "create: options are filtered appropriately for the object being created/updated; labels are translated.",
2712 'search' => "search: searchable options are returned; labels are translated.",
2713 'validate' => "validate: all options are returned, even if they are disabled; machine names are used in place of labels.",
2714 'abbreviate' => "abbreviate: enabled options are returned; labels are replaced with abbreviations.",
2715 'match' => "match: enabled options are returned using machine names as keys; labels are translated.",
2716 ];
2717 // Validation: enforce uniformity of this param
2718 if ($context !== NULL && !isset($contexts[$context])) {
2719 throw new CRM_Core_Exception("'$context' is not a valid context for buildOptions.");
2720 }
2721 return $contexts;
2722 }
2723
2724 /**
2725 * @param string $fieldName
2726 * @return bool|array
2727 */
2728 public function getFieldSpec($fieldName) {
2729 $fields = $this->fields();
2730 $fieldKeys = $this->fieldKeys();
2731
2732 // Support "unique names" as well as sql names
2733 $fieldKey = $fieldName;
2734 if (empty($fields[$fieldKey])) {
2735 $fieldKey = $fieldKeys[$fieldName] ?? NULL;
2736 }
2737 // If neither worked then this field doesn't exist. Return false.
2738 if (empty($fields[$fieldKey])) {
2739 return FALSE;
2740 }
2741 return $fields[$fieldKey];
2742 }
2743
2744 /**
2745 * Get SQL where clause for SQL filter syntax input parameters.
2746 *
2747 * SQL version of api function to assign filters to the DAO based on the syntax
2748 * $field => array('IN' => array(4,6,9))
2749 * OR
2750 * $field => array('LIKE' => array('%me%))
2751 * etc
2752 *
2753 * @param string $fieldName
2754 * Name of fields.
2755 * @param array $filter
2756 * filter to be applied indexed by operator.
2757 * @param string $type
2758 * type of field (not actually used - nor in api @todo ).
2759 * @param string $alias
2760 * alternative field name ('as') @todo- not actually used.
2761 * @param bool $returnSanitisedArray
2762 * Return a sanitised array instead of a clause.
2763 * this is primarily so we can add filters @ the api level to the Query object based fields
2764 *
2765 * @throws Exception
2766 *
2767 * @return NULL|string|array
2768 * a string is returned if $returnSanitisedArray is not set, otherwise and Array or NULL
2769 * depending on whether it is supported as yet
2770 */
2771 public static function createSQLFilter($fieldName, $filter, $type = NULL, $alias = NULL, $returnSanitisedArray = FALSE) {
2772 foreach ($filter as $operator => $criteria) {
2773 if (in_array($operator, self::acceptedSQLOperators(), TRUE)) {
2774 switch ($operator) {
2775 // unary operators
2776 case 'IS NULL':
2777 case 'IS NOT NULL':
2778 if (!$returnSanitisedArray) {
2779 return (sprintf('%s %s', $fieldName, $operator));
2780 }
2781 else {
2782 return (sprintf('%s %s ', $fieldName, $operator));
2783 }
2784 break;
2785
2786 // ternary operators
2787 case 'BETWEEN':
2788 case 'NOT BETWEEN':
2789 if ((empty($criteria[0]) && !in_array($criteria[0], ['0', 0]))|| (empty($criteria[1]) && !in_array($criteria[1], ['0', 0]))) {
2790 throw new Exception("invalid criteria for $operator");
2791 }
2792 if (!$returnSanitisedArray) {
2793 return (sprintf('%s ' . $operator . ' "%s" AND "%s"', $fieldName, CRM_Core_DAO::escapeString($criteria[0]), CRM_Core_DAO::escapeString($criteria[1])));
2794 }
2795 else {
2796 // not yet implemented (tests required to implement)
2797 return NULL;
2798 }
2799 break;
2800
2801 // n-ary operators
2802 case 'IN':
2803 case 'NOT IN':
2804 if (empty($criteria)) {
2805 throw new Exception("invalid criteria for $operator");
2806 }
2807 $escapedCriteria = array_map([
2808 'CRM_Core_DAO',
2809 'escapeString',
2810 ], $criteria);
2811 if (!$returnSanitisedArray) {
2812 return (sprintf('%s %s ("%s")', $fieldName, $operator, implode('", "', $escapedCriteria)));
2813 }
2814 return $escapedCriteria;
2815
2816 // binary operators
2817
2818 default:
2819 if (!$returnSanitisedArray) {
2820 return (sprintf('%s %s "%s"', $fieldName, $operator, CRM_Core_DAO::escapeString($criteria)));
2821 }
2822 else {
2823 // not yet implemented (tests required to implement)
2824 return NULL;
2825 }
2826 }
2827 }
2828 }
2829 }
2830
2831 /**
2832 * @see http://issues.civicrm.org/jira/browse/CRM-9150
2833 * support for other syntaxes is discussed in ticket but being put off for now
2834 * @return string[]
2835 */
2836 public static function acceptedSQLOperators() {
2837 return [
2838 '=',
2839 '<=',
2840 '>=',
2841 '>',
2842 '<',
2843 'LIKE',
2844 "<>",
2845 "!=",
2846 "NOT LIKE",
2847 'IN',
2848 'NOT IN',
2849 'BETWEEN',
2850 'NOT BETWEEN',
2851 'IS NOT NULL',
2852 'IS NULL',
2853 ];
2854 }
2855
2856 /**
2857 * SQL has a limit of 64 characters on various names:
2858 * table name, trigger name, column name ...
2859 *
2860 * For custom groups and fields we generated names from user entered input
2861 * which can be longer than this length, this function helps with creating
2862 * strings that meet various criteria.
2863 *
2864 * @param string $string
2865 * The string to be shortened.
2866 * @param int $length
2867 * The max length of the string.
2868 *
2869 * @param bool $makeRandom
2870 *
2871 * @return string
2872 */
2873 public static function shortenSQLName($string, $length = 60, $makeRandom = FALSE) {
2874 // early return for strings that meet the requirements
2875 if (strlen($string) <= $length) {
2876 return $string;
2877 }
2878
2879 // easy return for calls that dont need a randomized uniq string
2880 if (!$makeRandom) {
2881 return substr($string, 0, $length);
2882 }
2883
2884 // the string is longer than the length and we need a uniq string
2885 // for the same tablename we need the same uniq string every time
2886 // hence we use md5 on the string, which is not random
2887 // we'll append 8 characters to the end of the tableName
2888 $md5string = substr(md5($string), 0, 8);
2889 return substr($string, 0, $length - 8) . "_{$md5string}";
2890 }
2891
2892 /**
2893 * https://issues.civicrm.org/jira/browse/CRM-17748
2894 * Sets the internal options to be used on a query
2895 *
2896 * @param array $options
2897 *
2898 */
2899 public function setOptions($options) {
2900 if (is_array($options)) {
2901 $this->_options = $options;
2902 }
2903 }
2904
2905 /**
2906 * https://issues.civicrm.org/jira/browse/CRM-17748
2907 * wrapper to pass internal DAO options down to DB_mysql/DB_Common level
2908 *
2909 * @param array $options
2910 *
2911 */
2912 protected function _setDBOptions($options) {
2913 global $_DB_DATAOBJECT;
2914
2915 if (is_array($options) && count($options)) {
2916 $conn = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
2917 foreach ($options as $option_name => $option_value) {
2918 $conn->setOption($option_name, $option_value);
2919 }
2920 }
2921 }
2922
2923 /**
2924 * @deprecated
2925 * @param array $params
2926 */
2927 public function setApiFilter(&$params) {
2928 }
2929
2930 /**
2931 * Generates acl clauses suitable for adding to WHERE or ON when doing an api.get for this entity
2932 *
2933 * Return format is in the form of fieldname => clauses starting with an operator. e.g.:
2934 * ```
2935 * array(
2936 * 'location_type_id' => array('IS NOT NULL', 'IN (1,2,3)')
2937 * )
2938 * ```
2939 *
2940 * Note that all array keys must be actual field names in this entity. Use subqueries to filter on other tables e.g. custom values.
2941 *
2942 * @return array
2943 */
2944 public function addSelectWhereClause() {
2945 $clauses = [];
2946 $fields = $this->fields();
2947 foreach ($fields as $fieldName => $field) {
2948 // Clause for contact-related entities like Email, Relationship, etc.
2949 if (strpos($fieldName, 'contact_id') === 0 && CRM_Utils_Array::value('FKClassName', $field) == 'CRM_Contact_DAO_Contact') {
2950 $clauses[$fieldName] = CRM_Utils_SQL::mergeSubquery('Contact');
2951 }
2952 // Clause for an entity_table/entity_id combo
2953 if ($fieldName == 'entity_id' && isset($fields['entity_table'])) {
2954 $relatedClauses = [];
2955 $relatedEntities = $this->buildOptions('entity_table', 'get');
2956 foreach ((array) $relatedEntities as $table => $ent) {
2957 if (!empty($ent)) {
2958 $ent = CRM_Core_DAO_AllCoreTables::getBriefName(CRM_Core_DAO_AllCoreTables::getClassForTable($table));
2959 $subquery = CRM_Utils_SQL::mergeSubquery($ent);
2960 if ($subquery) {
2961 $relatedClauses[] = "(entity_table = '$table' AND entity_id " . implode(' AND entity_id ', $subquery) . ")";
2962 }
2963 else {
2964 $relatedClauses[] = "(entity_table = '$table')";
2965 }
2966 }
2967 }
2968 if ($relatedClauses) {
2969 $clauses['id'] = 'IN (SELECT id FROM `' . $this->tableName() . '` WHERE (' . implode(') OR (', $relatedClauses) . '))';
2970 }
2971 }
2972 }
2973 CRM_Utils_Hook::selectWhereClause($this, $clauses);
2974 return $clauses;
2975 }
2976
2977 /**
2978 * This returns the final permissioned query string for this entity
2979 *
2980 * With acls from related entities + additional clauses from hook_civicrm_selectWhereClause
2981 *
2982 * @param string $tableAlias
2983 * @return array
2984 */
2985 public static function getSelectWhereClause($tableAlias = NULL) {
2986 $bao = new static();
2987 if ($tableAlias === NULL) {
2988 $tableAlias = $bao->tableName();
2989 }
2990 $clauses = [];
2991 foreach ((array) $bao->addSelectWhereClause() as $field => $vals) {
2992 $clauses[$field] = NULL;
2993 if ($vals) {
2994 $clauses[$field] = "(`$tableAlias`.`$field` IS NULL OR (`$tableAlias`.`$field` " . implode(" AND `$tableAlias`.`$field` ", (array) $vals) . '))';
2995 }
2996 }
2997 return $clauses;
2998 }
2999
3000 /**
3001 * ensure database name is 'safe', i.e. only contains word characters (includes underscores)
3002 * and dashes, and contains at least one [a-z] case insenstive.
3003 *
3004 * @param $database
3005 *
3006 * @return bool
3007 */
3008 public static function requireSafeDBName($database) {
3009 $matches = [];
3010 preg_match(
3011 "/^[\w\-]*[a-z]+[\w\-]*$/i",
3012 $database,
3013 $matches
3014 );
3015 if (empty($matches)) {
3016 return FALSE;
3017 }
3018 return TRUE;
3019 }
3020
3021 /**
3022 * Transform an array to a serialized string for database storage.
3023 *
3024 * @param array|null $value
3025 * @param int $serializationType
3026 * @return string|null
3027 *
3028 * @throws \Exception
3029 */
3030 public static function serializeField($value, $serializationType) {
3031 if ($value === NULL) {
3032 return NULL;
3033 }
3034 switch ($serializationType) {
3035 case self::SERIALIZE_SEPARATOR_BOOKEND:
3036 return $value === [] ? '' : CRM_Utils_Array::implodePadded($value);
3037
3038 case self::SERIALIZE_SEPARATOR_TRIMMED:
3039 return is_array($value) ? implode(self::VALUE_SEPARATOR, $value) : $value;
3040
3041 case self::SERIALIZE_JSON:
3042 return is_array($value) ? json_encode($value) : $value;
3043
3044 case self::SERIALIZE_PHP:
3045 return is_array($value) ? serialize($value) : $value;
3046
3047 case self::SERIALIZE_COMMA:
3048 return is_array($value) ? implode(',', $value) : $value;
3049
3050 default:
3051 throw new Exception('Unknown serialization method for field.');
3052 }
3053 }
3054
3055 /**
3056 * Transform a serialized string from the database into an array.
3057 *
3058 * @param string|null $value
3059 * @param $serializationType
3060 *
3061 * @return array|null
3062 * @throws CRM_Core_Exception
3063 */
3064 public static function unSerializeField($value, $serializationType) {
3065 if ($value === NULL) {
3066 return NULL;
3067 }
3068 if ($value === '') {
3069 return [];
3070 }
3071 switch ($serializationType) {
3072 case self::SERIALIZE_SEPARATOR_BOOKEND:
3073 return (array) CRM_Utils_Array::explodePadded($value);
3074
3075 case self::SERIALIZE_SEPARATOR_TRIMMED:
3076 return explode(self::VALUE_SEPARATOR, trim($value));
3077
3078 case self::SERIALIZE_JSON:
3079 return strlen($value) ? json_decode($value, TRUE) : [];
3080
3081 case self::SERIALIZE_PHP:
3082 return strlen($value) ? CRM_Utils_String::unserialize($value) : [];
3083
3084 case self::SERIALIZE_COMMA:
3085 return explode(',', trim(str_replace(', ', '', $value)));
3086
3087 default:
3088 throw new CRM_Core_Exception('Unknown serialization method for field.');
3089 }
3090 }
3091
3092 /**
3093 * @return array
3094 */
3095 public static function getEntityRefFilters() {
3096 return [];
3097 }
3098
3099 /**
3100 * Get exportable fields with pseudoconstants rendered as an extra field.
3101 *
3102 * @param string $baoClass
3103 *
3104 * @return array
3105 */
3106 public static function getExportableFieldsWithPseudoConstants($baoClass) {
3107 if (method_exists($baoClass, 'exportableFields')) {
3108 $fields = $baoClass::exportableFields();
3109 }
3110 else {
3111 $fields = $baoClass::export();
3112 }
3113 CRM_Core_DAO::appendPseudoConstantsToFields($fields);
3114 return $fields;
3115 }
3116
3117 /**
3118 * Remove item from static cache during update/delete operations
3119 */
3120 private function clearDbColumnValueCache() {
3121 $daoName = get_class($this);
3122 while (strpos($daoName, '_BAO_') !== FALSE) {
3123 $daoName = get_parent_class($daoName);
3124 }
3125 if (isset($this->id)) {
3126 unset(self::$_dbColumnValueCache[$daoName]['id'][$this->id]);
3127 }
3128 if (isset($this->name)) {
3129 unset(self::$_dbColumnValueCache[$daoName]['name'][$this->name]);
3130 }
3131 }
3132
3133 }