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