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