DAO.php 83.1 KB
Newer Older
totten's avatar
totten committed
1 2 3
<?php
/*
  +--------------------------------------------------------------------+
totten's avatar
totten committed
4
  | CiviCRM version 5                                                  |
totten's avatar
totten committed
5
  +--------------------------------------------------------------------+
Seamus Lee's avatar
Seamus Lee committed
6
  | Copyright CiviCRM LLC (c) 2004-2019                                |
totten's avatar
totten committed
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
  +--------------------------------------------------------------------+
  | This file is a part of CiviCRM.                                    |
  |                                                                    |
  | CiviCRM is free software; you can copy, modify, and distribute it  |
  | under the terms of the GNU Affero General Public License           |
  | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
  |                                                                    |
  | CiviCRM is distributed in the hope that it will be useful, but     |
  | WITHOUT ANY WARRANTY; without even the implied warranty of         |
  | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
  | See the GNU Affero General Public License for more details.        |
  |                                                                    |
  | You should have received a copy of the GNU Affero General Public   |
  | License and the CiviCRM Licensing Exception along                  |
  | with this program; if not, contact CiviCRM LLC                     |
  | at info[AT]civicrm[DOT]org. If you have questions about the        |
  | GNU Affero General Public License or the licensing of CiviCRM,     |
  | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
  +--------------------------------------------------------------------+
Eileen McNaughton's avatar
Eileen McNaughton committed
26
 */
totten's avatar
totten committed
27 28

/**
colemanw's avatar
colemanw committed
29 30 31
 * Base Database Access Object class.
 *
 * All DAO classes should inherit from this class.
totten's avatar
totten committed
32 33
 *
 * @package CRM
Seamus Lee's avatar
Seamus Lee committed
34
 * @copyright CiviCRM LLC (c) 2004-2019
totten's avatar
totten committed
35 36
 */

37 38 39 40
if (!defined('DB_DSN_MODE')) {
  define('DB_DSN_MODE', 'auto');
}

totten's avatar
totten committed
41 42 43 44
require_once 'PEAR.php';
require_once 'DB/DataObject.php';

require_once 'CRM/Core/I18n.php';
45 46 47 48

/**
 * Class CRM_Core_DAO
 */
totten's avatar
totten committed
49 50
class CRM_Core_DAO extends DB_DataObject {

51 52 53 54 55 56 57
  /**
   * How many times has this instance been cloned.
   *
   * @var int
   */
  protected $resultCopies = 0;

totten's avatar
totten committed
58
  /**
59 60
   * @var null
   * @deprecated
totten's avatar
totten committed
61
   */
62
  public static $_nullObject = NULL;
63 64 65 66
  /**
   * @var array
   * @deprecated
   */
67
  public static $_nullArray = [];
totten's avatar
totten committed
68

69
  public static $_dbColumnValueCache = NULL;
70
  const NOT_NULL = 1, IS_NULL = 2,
totten's avatar
totten committed
71 72 73 74 75
    DB_DAO_NOTNULL = 128,
    VALUE_SEPARATOR = "",
    BULK_INSERT_COUNT = 200,
    BULK_INSERT_HIGH_COUNT = 200,
    QUERY_FORMAT_WILDCARD = 1,
76
    QUERY_FORMAT_NO_QUOTES = 2,
77 78 79 80

    /**
     * Serialized string separated by and bookended with VALUE_SEPARATOR
     */
81
    SERIALIZE_SEPARATOR_BOOKEND = 1,
82 83 84
    /**
     * @deprecated format separated by VALUE_SEPARATOR
     */
85
    SERIALIZE_SEPARATOR_TRIMMED = 2,
86 87 88 89 90 91 92
    /**
     * Recommended serialization format
     */
    SERIALIZE_JSON = 3,
    /**
     * @deprecated format using php serialize()
     */
93 94 95 96 97
    SERIALIZE_PHP = 4,
    /**
     * Comma separated string, no quotes, no spaces
     */
    SERIALIZE_COMMA = 5;
98

99
  /**
totten's avatar
totten committed
100
   * Define entities that shouldn't be created or deleted when creating/ deleting
101 102
   * test objects - this prevents world regions, countries etc from being added / deleted
   * @var array
totten's avatar
totten committed
103
   */
104
  public static $_testEntitiesToSkip = [];
totten's avatar
totten committed
105
  /**
106
   * The factory class for this application.
totten's avatar
totten committed
107 108
   * @var object
   */
109
  public static $_factory = NULL;
totten's avatar
totten committed
110

111
  public static $_checkedSqlFunctionsExist = FALSE;
112

113 114 115
  /**
   * https://issues.civicrm.org/jira/browse/CRM-17748
   * internal variable for DAO to hold per-query settings
116
   * @var array
117
   */
118
  protected $_options = [];
119

totten's avatar
totten committed
120
  /**
121
   * Class constructor.
totten's avatar
totten committed
122
   *
123
   * @return \CRM_Core_DAO
totten's avatar
totten committed
124
   */
125
  public function __construct() {
totten's avatar
totten committed
126 127 128 129
    $this->initialize();
    $this->__table = $this->getTableName();
  }

130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
  public function __clone() {
    if (!empty($this->_DB_resultid)) {
      $this->resultCopies++;
    }
  }

  /**
   * Class destructor.
   */
  public function __destruct() {
    if ($this->resultCopies === 0) {
      $this->free();
    }
    $this->resultCopies--;
  }

totten's avatar
totten committed
146
  /**
147
   * Empty definition for virtual function.
totten's avatar
totten committed
148
   */
149
  public static function getTableName() {
totten's avatar
totten committed
150 151 152 153
    return NULL;
  }

  /**
154
   * Initialize the DAO object.
totten's avatar
totten committed
155
   *
156 157
   * @param string $dsn
   *   The database connection string.
totten's avatar
totten committed
158
   */
159
  public static function init($dsn) {
160
    Civi::$statics[__CLASS__]['init'] = 1;
totten's avatar
totten committed
161 162 163 164 165
    $options = &PEAR::getStaticProperty('DB_DataObject', 'options');
    $options['database'] = $dsn;
    if (defined('CIVICRM_DAO_DEBUG')) {
      self::DebugLevel(CIVICRM_DAO_DEBUG);
    }
166 167
    $factory = new CRM_Contact_DAO_Factory();
    CRM_Core_DAO::setFactory($factory);
168
    $currentModes = CRM_Utils_SQL::getSqlModes();
169
    if (CRM_Utils_Constant::value('CIVICRM_MYSQL_STRICT', CRM_Utils_System::isDevelopment())) {
170
      if (CRM_Utils_SQL::supportsFullGroupBy() && !in_array('ONLY_FULL_GROUP_BY', $currentModes) && CRM_Utils_SQL::isGroupByModeInDefault()) {
171 172
        $currentModes[] = 'ONLY_FULL_GROUP_BY';
      }
173
      if (!in_array('STRICT_TRANS_TABLES', $currentModes)) {
174
        $currentModes = array_merge(['STRICT_TRANS_TABLES'], $currentModes);
175
      }
176
      CRM_Core_DAO::executeQuery("SET SESSION sql_mode = %1", [1 => [implode(',', $currentModes), 'String']]);
177
    }
178
    CRM_Core_DAO::executeQuery('SET NAMES utf8');
179
    CRM_Core_DAO::executeQuery('SET @uniqueID = %1', [1 => [CRM_Utils_Request::id(), 'String']]);
totten's avatar
totten committed
180 181
  }

182 183 184 185 186 187 188 189 190
  /**
   * @return DB_common
   */
  public static function getConnection() {
    global $_DB_DATAOBJECT;
    $dao = new CRM_Core_DAO();
    return $_DB_DATAOBJECT['CONNECTIONS'][$dao->_database_dsn_md5];
  }

191 192 193 194 195
  /**
   * Disables usage of the ONLY_FULL_GROUP_BY Mode if necessary
   */
  public static function disableFullGroupByMode() {
    $currentModes = CRM_Utils_SQL::getSqlModes();
196
    if (in_array('ONLY_FULL_GROUP_BY', $currentModes) && CRM_Utils_SQL::isGroupByModeInDefault()) {
197 198
      $key = array_search('ONLY_FULL_GROUP_BY', $currentModes);
      unset($currentModes[$key]);
199
      CRM_Core_DAO::executeQuery("SET SESSION sql_mode = %1", [1 => [implode(',', $currentModes), 'String']]);
200 201 202 203
    }
  }

  /**
204
   * Re-enables ONLY_FULL_GROUP_BY sql_mode as necessary..
205
   */
206
  public static function reenableFullGroupByMode() {
207
    $currentModes = CRM_Utils_SQL::getSqlModes();
208
    if (!in_array('ONLY_FULL_GROUP_BY', $currentModes) && CRM_Utils_SQL::isGroupByModeInDefault()) {
209
      $currentModes[] = 'ONLY_FULL_GROUP_BY';
210
      CRM_Core_DAO::executeQuery("SET SESSION sql_mode = %1", [1 => [implode(',', $currentModes), 'String']]);
211 212 213
    }
  }

214
  /**
colemanw's avatar
colemanw committed
215
   * @param string $fieldName
216
   * @param $fieldDef
217
   * @param array $params
218
   */
219
  protected function assignTestFK($fieldName, $fieldDef, $params) {
220 221 222
    $required = CRM_Utils_Array::value('required', $fieldDef);
    $FKClassName = CRM_Utils_Array::value('FKClassName', $fieldDef);
    $dbName = $fieldDef['name'];
223
    $daoName = str_replace('_BAO_', '_DAO_', get_class($this));
224 225 226 227 228 229 230 231 232

    // skip the FK if it is not required
    // if it's contact id we should create even if not required
    // we'll have a go @ fetching first though
    // we WILL create campaigns though for so tests with a campaign pseudoconstant will complete
    if ($FKClassName === 'CRM_Campaign_DAO_Campaign' && $daoName != $FKClassName) {
      $required = TRUE;
    }
    if (!$required && $dbName != 'contact_id') {
totten's avatar
totten committed
233
      $fkDAO = new $FKClassName();
234
      if ($fkDAO->find(TRUE)) {
235
        $this->$dbName = $fkDAO->id;
236
      }
237
      $fkDAO->free();
238 239 240 241 242
    }

    elseif (in_array($FKClassName, CRM_Core_DAO::$_testEntitiesToSkip)) {
      $depObject = new $FKClassName();
      $depObject->find(TRUE);
243
      $this->$dbName = $depObject->id;
244
      $depObject->free();
245 246 247
    }
    elseif ($daoName == 'CRM_Member_DAO_MembershipType' && $fieldName == 'member_of_contact_id') {
      // FIXME: the fields() metadata is not specific enough
248
      $depObject = CRM_Core_DAO::createTestObject($FKClassName, ['contact_type' => 'Organization']);
249
      $this->$dbName = $depObject->id;
250
      $depObject->free();
251 252 253 254
    }
    else {
      //if it is required we need to generate the dependency object first
      $depObject = CRM_Core_DAO::createTestObject($FKClassName, CRM_Utils_Array::value($dbName, $params, 1));
255
      $this->$dbName = $depObject->id;
256
      $depObject->free();
257 258 259 260
    }
  }

  /**
261 262 263 264
   * Generate and assign an arbitrary value to a field of a test object.
   *
   * @param string $fieldName
   * @param array $fieldDef
265 266
   * @param int $counter
   *   The globally-unique ID of the test object.
267
   */
268 269 270
  protected function assignTestValue($fieldName, &$fieldDef, $counter) {
    $dbName = $fieldDef['name'];
    $daoName = get_class($this);
271 272
    $handled = FALSE;

273
    if (!$handled && $dbName == 'contact_sub_type') {
274 275 276 277 278 279 280 281
      //coming up with a rule to set this is too complex let's not set it
      $handled = TRUE;
    }

    // Pick an option value if needed
    if (!$handled && $fieldDef['type'] !== CRM_Utils_Type::T_BOOLEAN) {
      $options = $daoName::buildOptions($dbName, 'create');
      if ($options) {
282
        $this->$dbName = key($options);
283 284 285 286 287 288 289 290 291 292 293
        $handled = TRUE;
      }
    }

    if (!$handled) {
      switch ($fieldDef['type']) {
        case CRM_Utils_Type::T_INT:
        case CRM_Utils_Type::T_FLOAT:
        case CRM_Utils_Type::T_MONEY:
          if (isset($fieldDef['precision'])) {
            // $object->$dbName = CRM_Utils_Number::createRandomDecimal($value['precision']);
294
            $this->$dbName = CRM_Utils_Number::createTruncatedDecimal($counter, $fieldDef['precision']);
295 296
          }
          else {
297
            $this->$dbName = $counter;
298 299 300 301 302
          }
          break;

        case CRM_Utils_Type::T_BOOLEAN:
          if (isset($fieldDef['default'])) {
303
            $this->$dbName = $fieldDef['default'];
304 305
          }
          elseif ($fieldDef['name'] == 'is_deleted' || $fieldDef['name'] == 'is_test') {
306
            $this->$dbName = 0;
307 308
          }
          else {
309
            $this->$dbName = 1;
310 311 312 313 314
          }
          break;

        case CRM_Utils_Type::T_DATE:
        case CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME:
315
          $this->$dbName = '19700101';
316 317
          if ($dbName == 'end_date') {
            // put this in the future
318
            $this->$dbName = '20200101';
319 320 321
          }
          break;

322 323 324 325
        case CRM_Utils_Type::T_TIMESTAMP:
          $this->$dbName = '19700201000000';
          break;

326
        case CRM_Utils_Type::T_TIME:
327
          CRM_Core_Error::fatal("T_TIME shouldn't be used.");
totten's avatar
totten committed
328 329
          //$object->$dbName='000000';
          //break;
330
        case CRM_Utils_Type::T_CCNUM:
331
          $this->$dbName = '4111 1111 1111 1111';
332 333 334
          break;

        case CRM_Utils_Type::T_URL:
335
          $this->$dbName = 'http://www.civicrm.org';
336 337 338 339 340 341 342 343 344 345 346 347 348
          break;

        case CRM_Utils_Type::T_STRING:
        case CRM_Utils_Type::T_BLOB:
        case CRM_Utils_Type::T_MEDIUMBLOB:
        case CRM_Utils_Type::T_TEXT:
        case CRM_Utils_Type::T_LONGTEXT:
        case CRM_Utils_Type::T_EMAIL:
        default:
          // WAS: if (isset($value['enumValues'])) {
          // TODO: see if this works with all pseudoconstants
          if (isset($fieldDef['pseudoconstant'], $fieldDef['pseudoconstant']['callback'])) {
            if (isset($fieldDef['default'])) {
349
              $this->$dbName = $fieldDef['default'];
350 351 352 353
            }
            else {
              $options = CRM_Core_PseudoConstant::get($daoName, $fieldName);
              if (is_array($options)) {
354
                $this->$dbName = $options[0];
355 356 357
              }
              else {
                $defaultValues = explode(',', $options);
358
                $this->$dbName = $defaultValues[0];
359 360 361 362
              }
            }
          }
          else {
363
            $this->$dbName = $dbName . '_' . $counter;
364
            $maxlength = CRM_Utils_Array::value('maxlength', $fieldDef);
365 366
            if ($maxlength > 0 && strlen($this->$dbName) > $maxlength) {
              $this->$dbName = substr($this->$dbName, 0, $fieldDef['maxlength']);
367 368 369 370 371 372
            }
          }
      }
    }
  }

totten's avatar
totten committed
373
  /**
eileenmcnaugton's avatar
eileenmcnaugton committed
374
   * Reset the DAO object.
totten's avatar
totten committed
375
   *
eileenmcnaugton's avatar
eileenmcnaugton committed
376 377 378
   * DAO is kinda crappy in that there is an unwritten rule of one query per DAO.
   *
   * We attempt to get around this crappy restriction by resetting some of DAO's internal fields. Use this with caution
totten's avatar
totten committed
379
   */
380
  public function reset() {
totten's avatar
totten committed
381 382 383 384 385 386 387 388

    foreach (array_keys($this->table()) as $field) {
      unset($this->$field);
    }

    /**
     * reset the various DB_DAO structures manually
     */
389
    $this->_query = [];
totten's avatar
totten committed
390 391 392 393 394
    $this->whereAdd();
    $this->selectAdd();
    $this->joinAdd();
  }

395
  /**
colemanw's avatar
colemanw committed
396
   * @param string $tableName
397 398 399
   *
   * @return string
   */
400
  public static function getLocaleTableName($tableName) {
totten's avatar
totten committed
401 402 403 404 405 406 407 408 409 410 411 412 413
    global $dbLocale;
    if ($dbLocale) {
      $tables = CRM_Core_I18n_Schema::schemaStructureTables();
      if (in_array($tableName, $tables)) {
        return $tableName . $dbLocale;
      }
    }
    return $tableName;
  }

  /**
   * Execute a query by the current DAO, localizing it along the way (if needed).
   *
414 415 416 417
   * @param string $query
   *   The SQL query for execution.
   * @param bool $i18nRewrite
   *   Whether to rewrite the query.
totten's avatar
totten committed
418
   *
419 420
   * @return object
   *   the current DAO object after the query execution
totten's avatar
totten committed
421
   */
422
  public function query($query, $i18nRewrite = TRUE) {
totten's avatar
totten committed
423
    // rewrite queries that should use $dbLocale-based views for multi-language installs
424 425
    global $dbLocale, $_DB_DATAOBJECT;

426 427 428 429 430
    if (empty($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
      // Will force connection to be populated per CRM-20541.
      new CRM_Core_DAO();
    }

431 432 433 434
    $conn = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
    $orig_options = $conn->options;
    $this->_setDBOptions($this->_options);

totten's avatar
totten committed
435 436 437 438
    if ($i18nRewrite and $dbLocale) {
      $query = CRM_Core_I18n_Schema::rewriteQuery($query);
    }

439 440 441 442
    $ret = parent::query($query);

    $this->_setDBOptions($orig_options);
    return $ret;
totten's avatar
totten committed
443 444 445 446 447
  }

  /**
   * Static function to set the factory instance for this class.
   *
448 449
   * @param object $factory
   *   The factory application object.
totten's avatar
totten committed
450
   */
451
  public static function setFactory(&$factory) {
totten's avatar
totten committed
452 453 454 455 456 457
    self::$_factory = &$factory;
  }

  /**
   * Factory method to instantiate a new object from a table name.
   *
Eileen McNaughton's avatar
Eileen McNaughton committed
458
   * @param string $table
colemanw's avatar
colemanw committed
459
   * @return \DataObject|\PEAR_Error
totten's avatar
totten committed
460
   */
461
  public function factory($table = '') {
totten's avatar
totten committed
462 463 464 465 466 467 468 469 470 471 472
    if (!isset(self::$_factory)) {
      return parent::factory($table);
    }

    return self::$_factory->create($table);
  }

  /**
   * Initialization for all DAO objects. Since we access DB_DO programatically
   * we need to set the links manually.
   */
473
  public function initialize() {
totten's avatar
totten committed
474
    $this->_connect();
475 476 477 478 479 480
    if (empty(Civi::$statics[__CLASS__]['init'])) {
      // CRM_Core_DAO::init() must be called before CRM_Core_DAO->initialize().
      // This occurs very early in bootstrap - error handlers may not be wired up.
      echo "Inconsistent system initialization sequence. Premature access of (" . get_class($this) . ")";
      CRM_Utils_System::civiExit();
    }
totten's avatar
totten committed
481 482 483 484 485 486 487
  }

  /**
   * Defines the default key as 'id'.
   *
   * @return array
   */
488
  public function keys() {
totten's avatar
totten committed
489 490
    static $keys;
    if (!isset($keys)) {
491
      $keys = ['id'];
totten's avatar
totten committed
492 493 494 495 496 497 498 499 500 501 502
    }
    return $keys;
  }

  /**
   * Tells DB_DataObject which keys use autoincrement.
   * 'id' is autoincrementing by default.
   *
   *
   * @return array
   */
503
  public function sequenceKey() {
totten's avatar
totten committed
504 505
    static $sequenceKeys;
    if (!isset($sequenceKeys)) {
506
      $sequenceKeys = ['id', TRUE];
totten's avatar
totten committed
507 508 509 510 511
    }
    return $sequenceKeys;
  }

  /**
512
   * Returns list of FK relationships.
totten's avatar
totten committed
513 514
   *
   *
515
   * @return array
516
   *   Array of CRM_Core_Reference_Interface
totten's avatar
totten committed
517
   */
518
  public static function getReferenceColumns() {
519
    return [];
totten's avatar
totten committed
520 521 522
  }

  /**
523
   * Returns all the column names of this table.
totten's avatar
totten committed
524 525 526 527
   *
   *
   * @return array
   */
totten's avatar
totten committed
528
  public static function &fields() {
totten's avatar
totten committed
529 530 531 532
    $result = NULL;
    return $result;
  }

533
  /**
colemanw's avatar
colemanw committed
534
   * Get/set an associative array of table columns
535
   *
536 537
   * @return array
   *   (associative)
538
   */
539
  public function table() {
colemanw's avatar
colemanw committed
540
    $fields = $this->fields();
totten's avatar
totten committed
541

542
    $table = [];
totten's avatar
totten committed
543 544 545
    if ($fields) {
      foreach ($fields as $name => $value) {
        $table[$value['name']] = $value['type'];
546
        if (!empty($value['required'])) {
totten's avatar
totten committed
547 548 549 550 551 552 553 554
          $table[$value['name']] += self::DB_DAO_NOTNULL;
        }
      }
    }

    return $table;
  }

555
  /**
eileenmcnaugton's avatar
eileenmcnaugton committed
556 557 558 559
   * Save DAO object.
   *
   * @param bool $hook
   *
560
   * @return CRM_Core_DAO
561
   */
562
  public function save($hook = TRUE) {
totten's avatar
totten committed
563 564
    if (!empty($this->id)) {
      $this->update();
565

566 567
      if ($hook) {
        $event = new \Civi\Core\DAO\Event\PostUpdate($this);
568
        \Civi::service('dispatcher')->dispatch("civi.dao.postUpdate", $event);
569
      }
totten's avatar
totten committed
570 571 572
    }
    else {
      $this->insert();
573

574 575
      if ($hook) {
        $event = new \Civi\Core\DAO\Event\PostUpdate($this);
576
        \Civi::service('dispatcher')->dispatch("civi.dao.postInsert", $event);
577
      }
totten's avatar
totten committed
578 579 580
    }
    $this->free();

581 582 583
    if ($hook) {
      CRM_Utils_Hook::postSave($this);
    }
totten's avatar
totten committed
584 585 586 587

    return $this;
  }

Eileen McNaughton's avatar
Eileen McNaughton committed
588
  /**
589
   * Deletes items from table which match current objects variables.
Eileen McNaughton's avatar
Eileen McNaughton committed
590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612
   *
   * Returns the true on success
   *
   * for example
   *
   * Designed to be extended
   *
   * $object = new mytable();
   * $object->ID=123;
   * echo $object->delete(); // builds a conditon
   *
   * $object = new mytable();
   * $object->whereAdd('age > 12');
   * $object->limit(1);
   * $object->orderBy('age DESC');
   * $object->delete(true); // dont use object vars, use the conditions, limit and order.
   *
   * @param bool $useWhere (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then
   *             we will build the condition only using the whereAdd's.  Default is to
   *             build the condition only using the object parameters.
   *
   *     * @return mixed Int (No. of rows affected) on success, false on failure, 0 on no data affected
   */
613
  public function delete($useWhere = FALSE) {
614 615 616
    $preEvent = new \Civi\Core\DAO\Event\PreDelete($this);
    \Civi::service('dispatcher')->dispatch("civi.dao.preDelete", $preEvent);

617
    $result = parent::delete($useWhere);
618

619
    $event = new \Civi\Core\DAO\Event\PostDelete($this, $result);
620
    \Civi::service('dispatcher')->dispatch("civi.dao.postDelete", $event);
621
    $this->free();
622

623 624 625
    return $result;
  }

626 627 628
  /**
   * @param bool $created
   */
629
  public function log($created = FALSE) {
totten's avatar
totten committed
630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645
    static $cid = NULL;

    if (!$this->getLog()) {
      return;
    }

    if (!$cid) {
      $session = CRM_Core_Session::singleton();
      $cid = $session->get('userID');
    }

    // return is we dont have handle to FK
    if (!$cid) {
      return;
    }

totten's avatar
totten committed
646 647 648 649
    $dao = new CRM_Core_DAO_Log();
    $dao->entity_table = $this->getTableName();
    $dao->entity_id = $this->id;
    $dao->modified_id = $cid;
totten's avatar
totten committed
650 651 652 653 654 655 656 657
    $dao->modified_date = date("YmdHis");
    $dao->insert();
  }

  /**
   * Given an associative array of name/value pairs, extract all the values
   * that belong to this object and initialize the object with said values
   *
658 659
   * @param array $params
   *   (reference ) associative array of name/value pairs.
660 661 662 663 664 665
   * @param bool $serializeArrays
   *   Should arrays that are passed in be serialised according to the metadata.
   *   Eventually this should be always true / gone, but in the interests of caution
   *   it is being grandfathered in. In general an array is not valid on the DAO
   *   but there may be instances where this function is called & then some handling
   *   takes place on the would-be array.
totten's avatar
totten committed
666
   *
totten's avatar
totten committed
667 668
   * @return bool
   *   Did we copy all null values into the object
totten's avatar
totten committed
669
   */
670
  public function copyValues(&$params, $serializeArrays = FALSE) {
colemanw's avatar
colemanw committed
671
    $fields = $this->fields();
totten's avatar
totten committed
672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691
    $allNull = TRUE;
    foreach ($fields as $name => $value) {
      $dbName = $value['name'];
      if (array_key_exists($dbName, $params)) {
        $pValue = $params[$dbName];
        $exists = TRUE;
      }
      elseif (array_key_exists($name, $params)) {
        $pValue = $params[$name];
        $exists = TRUE;
      }
      else {
        $exists = FALSE;
      }

      // if there is no value then make the variable NULL
      if ($exists) {
        if ($pValue === '') {
          $this->$dbName = 'null';
        }
692 693 694 695
        elseif ($serializeArrays && is_array($pValue) && !empty($value['serialize'])) {
          $this->$dbName = CRM_Core_DAO::serializeField($pValue, $value['serialize']);
          $allNull = FALSE;
        }
totten's avatar
totten committed
696
        else {
697 698 699
          if (!$serializeArrays && is_array($pValue) && !empty($value['serialize'])) {
            Civi::log()->warning(ts('use copyParams to serialize arrays (' . __CLASS__ . '.' . $name . ')'), ['civi.tag' => 'deprecated']);
          }
700 701 702 703 704 705 706 707
          $maxLength = CRM_Utils_Array::value('maxlength', $value);
          if (!is_array($pValue) && $maxLength && mb_strlen($pValue) > $maxLength
            && empty($value['pseudoconstant'])
          ) {
            Civi::log()->warning(ts('A string for field $dbName has been truncated. The original string was %1', [CRM_Utils_Type::escape($pValue, 'String')]));
            // The string is too long - what to do what to do? Well losing data is generally bad so lets' truncate
            $pValue = CRM_Utils_String::ellipsify($pValue, $maxLength);
          }
totten's avatar
totten committed
708 709 710 711 712 713 714 715 716 717 718 719 720
          $this->$dbName = $pValue;
          $allNull = FALSE;
        }
      }
    }
    return $allNull;
  }

  /**
   * Store all the values from this object in an associative array
   * this is a destructive store, calling function is responsible
   * for keeping sanity of id's.
   *
721 722 723 724
   * @param object $object
   *   The object that we are extracting data from.
   * @param array $values
   *   (reference ) associative array of name/value pairs.
totten's avatar
totten committed
725
   */
726
  public static function storeValues(&$object, &$values) {
colemanw's avatar
colemanw committed
727
    $fields = $object->fields();
totten's avatar
totten committed
728 729 730 731 732 733 734 735 736 737 738 739
    foreach ($fields as $name => $value) {
      $dbName = $value['name'];
      if (isset($object->$dbName) && $object->$dbName !== 'null') {
        $values[$dbName] = $object->$dbName;
        if ($name != $dbName) {
          $values[$name] = $object->$dbName;
        }
      }
    }
  }

  /**
colemanw's avatar
colemanw committed
740
   * Create an attribute for this specific field. We only do this for strings and text
totten's avatar
totten committed
741
   *
742 743
   * @param array $field
   *   The field under task.
totten's avatar
totten committed
744
   *
745 746
   * @return array|null
   *   the attributes for the object
totten's avatar
totten committed
747
   */
748
  public static function makeAttribute($field) {
totten's avatar
totten committed
749 750 751 752 753
    if ($field) {
      if (CRM_Utils_Array::value('type', $field) == CRM_Utils_Type::T_STRING) {
        $maxLength = CRM_Utils_Array::value('maxlength', $field);
        $size = CRM_Utils_Array::value('size', $field);
        if ($maxLength || $size) {
754
          $attributes = [];
totten's avatar
totten committed
755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773
          if ($maxLength) {
            $attributes['maxlength'] = $maxLength;
          }
          if ($size) {
            $attributes['size'] = $size;
          }
          return $attributes;
        }
      }
      elseif (CRM_Utils_Array::value('type', $field) == CRM_Utils_Type::T_TEXT) {
        $rows = CRM_Utils_Array::value('rows', $field);
        if (!isset($rows)) {
          $rows = 2;
        }
        $cols = CRM_Utils_Array::value('cols', $field);
        if (!isset($cols)) {
          $cols = 80;
        }

774
        $attributes = [];
totten's avatar
totten committed
775 776 777 778 779 780 781 782 783 784 785 786 787 788
        $attributes['rows'] = $rows;
        $attributes['cols'] = $cols;
        return $attributes;
      }
      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) {
        $attributes['size'] = 6;
        $attributes['maxlength'] = 14;
        return $attributes;
      }
    }
    return NULL;
  }

  /**
Eileen McNaughton's avatar
Eileen McNaughton committed
789
   * Get the size and maxLength attributes for this text field.
totten's avatar
totten committed
790 791
   * (or for all text fields) in the DAO object.
   *
792 793 794 795
   * @param string $class
   *   Name of DAO class.
   * @param string $fieldName
   *   Field that i'm interested in or null if.
totten's avatar
totten committed
796 797
   *                          you want the attributes for all DAO text fields
   *
798 799
   * @return array
   *   assoc array of name => attribute pairs
totten's avatar
totten committed
800
   */
801
  public static function getAttribute($class, $fieldName = NULL) {
totten's avatar
totten committed
802
    $object = new $class();
colemanw's avatar
colemanw committed
803
    $fields = $object->fields();
totten's avatar
totten committed
804 805 806 807 808
    if ($fieldName != NULL) {
      $field = CRM_Utils_Array::value($fieldName, $fields);
      return self::makeAttribute($field);
    }
    else {
809
      $attributes = [];
totten's avatar
totten committed
810 811 812 813 814 815 816 817 818 819 820 821 822 823 824
      foreach ($fields as $name => $field) {
        $attribute = self::makeAttribute($field);
        if ($attribute) {
          $attributes[$name] = $attribute;
        }
      }

      if (!empty($attributes)) {
        return $attributes;
      }
    }
    return NULL;
  }

  /**
825
   * Check if there is a record with the same name in the db.
totten's avatar
totten committed
826
   *
827 828 829 830 831 832
   * @param string $value
   *   The value of the field we are checking.
   * @param string $daoName
   *   The dao object name.
   * @param string $daoID
   *   The id of the object being updated. u can change your name.
totten's avatar
totten committed
833
   *                          as long as there is no conflict
834 835
   * @param string $fieldName
   *   The name of the field in the DAO.
totten's avatar
totten committed
836
   *
837 838 839
   * @param string $domainID
   *   The id of the domain.  Object exists only for the given domain.
   *
totten's avatar
totten committed
840
   * @return bool
841
   *   true if object exists
totten's avatar
totten committed
842
   */
843
  public static function objectExists($value, $daoName, $daoID, $fieldName = 'name', $domainID = NULL) {
totten's avatar
totten committed
844
    $object = new $daoName();
totten's avatar
totten committed
845
    $object->$fieldName = $value;
846 847 848
    if ($domainID) {
      $object->domain_id = $domainID;
    }
totten's avatar
totten committed
849 850 851 852 853 854 855 856 857 858

    if ($object->find(TRUE)) {
      return ($daoID && $object->id == $daoID) ? TRUE : FALSE;
    }
    else {
      return TRUE;
    }
  }

  /**
859
   * Check if there is a given column in a specific table.
totten's avatar
totten committed
860
   *
colemanw's avatar
colemanw committed
861 862 863
   * @deprecated
   * @see CRM_Core_BAO_SchemaHandler::checkIfFieldExists
   *
totten's avatar
totten committed
864 865
   * @param string $tableName
   * @param string $columnName
866 867
   * @param bool $i18nRewrite
   *   Whether to rewrite the query on multilingual setups.
totten's avatar
totten committed
868
   *
totten's avatar
totten committed
869
   * @return bool
870
   *   true if exists, else false
totten's avatar
totten committed
871
   */
872
  public static function checkFieldExists($tableName, $columnName, $i18nRewrite = TRUE) {
colemanw's avatar
colemanw committed
873
    return CRM_Core_BAO_SchemaHandler::checkIfFieldExists($tableName, $columnName, $i18nRewrite);
totten's avatar
totten committed
874 875 876
  }

  /**
877
   * Scans all the tables using a slow query and table name.
Eileen McNaughton's avatar
Eileen McNaughton committed
878
   *
totten's avatar
totten committed
879 880
   * @return array
   */
881 882 883 884 885 886 887
  public static function getTableNames() {
    $dao = CRM_Core_DAO::executeQuery(
      "SELECT TABLE_NAME
       FROM information_schema.TABLES
       WHERE TABLE_SCHEMA = '" . CRM_Core_DAO::getDatabaseName() . "'
         AND TABLE_NAME LIKE 'civicrm_%'
         AND TABLE_NAME NOT LIKE 'civicrm_import_job_%'
888
         AND TABLE_NAME NOT LIKE '%_temp%'
889
      ");
totten's avatar
totten committed
890 891

    while ($dao->fetch()) {
892
      $values[] = $dao->TABLE_NAME;
totten's avatar
totten committed
893 894 895 896
    }
    return $values;
  }

897 898 899 900 901
  /**
   * @param int $maxTablesToCheck
   *
   * @return bool
   */
902
  public static function isDBMyISAM($maxTablesToCheck = 10) {
903 904 905 906 907 908 909
    return CRM_Core_DAO::singleValueQuery(
      "SELECT count(*)
       FROM information_schema.TABLES
       WHERE ENGINE = 'MyISAM'
         AND TABLE_SCHEMA = '" . CRM_Core_DAO::getDatabaseName() . "'
         AND TABLE_NAME LIKE 'civicrm_%'
         AND TABLE_NAME NOT LIKE 'civicrm_import_job_%'
910
         AND TABLE_NAME NOT LIKE '%_temp%'
eileen's avatar
eileen committed
911
         AND TABLE_NAME NOT LIKE 'civicrm_tmp_%'
912 913 914 915 916 917 918 919 920 921 922
      ");
  }

  /**
   * Get the name of the CiviCRM database.
   *
   * @return string
   */
  public static function getDatabaseName() {
    $daoObj = new CRM_Core_DAO();
    return $daoObj->database();
totten's avatar
totten committed
923 924 925 926 927 928 929 930
  }

  /**
   * Checks if a constraint exists for a specified table.
   *
   * @param string $tableName
   * @param string $constraint
   *
totten's avatar
totten committed
931
   * @return bool
932
   *   true if constraint exists, false otherwise
totten's avatar
totten committed
933
   */
934
  public static function checkConstraintExists($tableName, $constraint) {
935
    static $show = [];
totten's avatar
totten committed
936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953

    if (!array_key_exists($tableName, $show)) {
      $query = "SHOW CREATE TABLE $tableName";
      $dao = CRM_Core_DAO::executeQuery($query);

      if (!$dao->fetch()) {
        CRM_Core_Error::fatal();
      }

      $show[$tableName] = $dao->Create_Table;
    }

    return preg_match("/\b$constraint\b/i", $show[$tableName]) ? TRUE : FALSE;
  }

  /**
   * Checks if CONSTRAINT keyword exists for a specified table.
   *
Eileen McNaughton's avatar
Eileen McNaughton committed
954 955 956
   * @param array $tables
   *
   * @throws Exception
totten's avatar
totten committed
957
   *
totten's avatar
totten committed
958
   * @return bool
959
   *   true if CONSTRAINT keyword exists, false otherwise
totten's avatar
totten committed
960
   */
961 962
  public static function schemaRequiresRebuilding($tables = ["civicrm_contact"]) {
    $show = [];
totten's avatar
totten committed
963
    foreach ($tables as $tableName) {
totten's avatar
totten committed
964 965 966 967 968 969 970 971 972 973 974 975
      if (!array_key_exists($tableName, $show)) {
        $query = "SHOW CREATE TABLE $tableName";
        $dao = CRM_Core_DAO::executeQuery($query);

        if (!$dao->fetch()) {
          CRM_Core_Error::fatal();
        }

        $show[$tableName] = $dao->Create_Table;
      }

      $result = preg_match("/\bCONSTRAINT\b\s/i", $show[$tableName]) ? TRUE : FALSE;
totten's avatar
totten committed
976
      if ($result == TRUE) {
totten's avatar
totten committed
977 978
        continue;
      }
979
      else {
totten's avatar
totten committed
980 981 982 983 984 985 986 987 988 989 990 991 992
        return FALSE;
      }
    }
    return TRUE;
  }

  /**
   * Checks if the FK constraint name is in the format 'FK_tableName_columnName'
   * for a specified column of a table.
   *
   * @param string $tableName
   * @param string $columnName
   *
totten's avatar
totten committed
993
   * @return bool
994
   *   true if in format, false otherwise
totten's avatar
totten committed
995
   */
996
  public static function checkFKConstraintInFormat($tableName, $columnName) {
997
    static $show = [];
totten's avatar
totten committed
998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010

    if (!array_key_exists($tableName, $show)) {
      $query = "SHOW CREATE TABLE $tableName";
      $dao = CRM_Core_DAO::executeQuery($query);

      if (!$dao->fetch()) {
        CRM_Core_Error::fatal();
      }

      $show[$tableName] = $dao->Create_Table;
    }
    $constraint = "`FK_{$tableName}_{$columnName}`";
    $pattern = "/\bCONSTRAINT\b\s+%s\s+\bFOREIGN\s+KEY\b\s/i";
totten's avatar
totten committed
1011
    return preg_match(sprintf($pattern, $constraint), $show[$tableName]) ? TRUE : FALSE;
totten's avatar
totten committed
1012 1013 1014
  }

  /**
1015
   * Check whether a specific column in a specific table has always the same value.
totten's avatar
totten committed
1016 1017 1018 1019 1020
   *
   * @param string $tableName
   * @param string $columnName
   * @param string $columnValue
   *
totten's avatar
totten committed
1021
   * @return bool
1022
   *   true if the value is always $columnValue, false otherwise
totten's avatar
totten committed
1023
   */
1024
  public static function checkFieldHasAlwaysValue($tableName, $columnName, $columnValue) {
totten's avatar
totten committed
1025 1026
    $query = "SELECT * FROM $tableName WHERE $columnName != '$columnValue'";
    $dao = CRM_Core_DAO::executeQuery($query);
totten's avatar
totten committed
1027 1028 1029 1030 1031
    $result = $dao->fetch() ? FALSE : TRUE;
    return $result;
  }

  /**
1032
   * Check whether a specific column in a specific table is always NULL.
totten's avatar
totten committed
1033 1034 1035 1036
   *
   * @param string $tableName
   * @param string $columnName
   *
totten's avatar
totten committed
1037
   * @return bool
1038
   *   true if if the value is always NULL, false otherwise
totten's avatar
totten committed
1039
   */
1040