Core - Cleanup BAO::del() functions with unnecessary FK checks
[civicrm-core.git] / CRM / Core / DAO.php
index 89340462d5fe5a7794956deccb6aeb4c3613719c..4e76a60f4d80796c89811ea9b73e468141944e4f 100644 (file)
@@ -32,6 +32,13 @@ require_once 'CRM/Core/I18n.php';
  */
 class CRM_Core_DAO extends DB_DataObject {
 
+  /**
+   * Primary key field(s).
+   *
+   * @var string[]
+   */
+  public static $_primaryKey = ['id'];
+
   /**
    * How many times has this instance been cloned.
    *
@@ -107,8 +114,6 @@ class CRM_Core_DAO extends DB_DataObject {
    */
   public static $_factory = NULL;
 
-  public static $_checkedSqlFunctionsExist = FALSE;
-
   /**
    * https://issues.civicrm.org/jira/browse/CRM-17748
    * internal variable for DAO to hold per-query settings
@@ -137,6 +142,15 @@ class CRM_Core_DAO extends DB_DataObject {
     return CRM_Core_DAO_AllCoreTables::getBriefName($className);
   }
 
+  /**
+   * Returns user-friendly description of this entity.
+   *
+   * @return string|null
+   */
+  public static function getEntityDescription() {
+    return NULL;
+  }
+
   public function __clone() {
     if (!empty($this->_DB_resultid)) {
       $this->resultCopies++;
@@ -943,6 +957,7 @@ class CRM_Core_DAO extends DB_DataObject {
     if (empty($record['id'])) {
       throw new CRM_Core_Exception("Cannot delete {$entityName} with no id.");
     }
+    CRM_Utils_Type::validate($record['id'], 'Positive');
 
     CRM_Utils_Hook::pre('delete', $entityName, $record['id'], $record);
     $instance = new $className();
@@ -1811,11 +1826,9 @@ LIKE %1
       $newObject = new $daoName();
 
       $fields = $object->fields();
-      if (!is_array($fieldsFix)) {
-        $fieldsToPrefix = [];
-        $fieldsToSuffix = [];
-        $fieldsToReplace = [];
-      }
+      $fieldsToPrefix = [];
+      $fieldsToSuffix = [];
+      $fieldsToReplace = [];
       if (!empty($fieldsFix['prefix'])) {
         $fieldsToPrefix = $fieldsFix['prefix'];
       }
@@ -1826,6 +1839,7 @@ LIKE %1
         $fieldsToReplace = $fieldsFix['replace'];
       }
 
+      $localizableFields = FALSE;
       foreach ($fields as $name => $value) {
         if ($name == 'id' || $value['name'] == 'id') {
           // copy everything but the id!
@@ -1849,11 +1863,33 @@ LIKE %1
           $newObject->$dbName = CRM_Utils_Date::isoToMysql($newObject->$dbName);
         }
 
+        if (!empty($value['localizable'])) {
+          $localizableFields = TRUE;
+        }
+
         if ($newData) {
           $newObject->copyValues($newData);
         }
       }
       $newObject->save();
+
+      // ensure we copy all localized fields as well
+      if (CRM_Core_I18n::isMultilingual() && $localizableFields) {
+        global $dbLocale;
+        $locales = CRM_Core_I18n::getMultilingual();
+        $curLocale = CRM_Core_I18n::getLocale();
+        // loop on other locales
+        foreach ($locales as $locale) {
+          if ($locale != $curLocale) {
+            // setLocale doesn't seems to be reliable to set dbLocale and we only need to change the db locale
+            $dbLocale = '_' . $locale;
+            $newObject->copyLocalizable($object->id, $newObject->id, $fieldsToPrefix, $fieldsToSuffix, $fieldsToReplace);
+          }
+        }
+        // restore dbLocale to starting value
+        $dbLocale = '_' . $curLocale;
+      }
+
       if (!$blockCopyofCustomValues) {
         $newObject->copyCustomFields($object->id, $newObject->id);
       }
@@ -1863,6 +1899,67 @@ LIKE %1
     return $newObject;
   }
 
+  /**
+   * Method that copies localizable fields from an old entity to a new one.
+   *
+   * Fixes bug dev/core#2479,
+   * where non current locale fields are copied from current locale losing translation when copying
+   *
+   * @param int $entityID
+   * @param int $newEntityID
+   * @param array $fieldsToPrefix
+   * @param array $fieldsToSuffix
+   * @param array $fieldsToReplace
+   */
+  protected function copyLocalizable($entityID, $newEntityID, $fieldsToPrefix, $fieldsToSuffix, $fieldsToReplace) {
+    $entity = get_class($this);
+    $object = new $entity();
+    $object->id = $entityID;
+    $object->find();
+
+    $newObject = new $entity();
+    $newObject->id = $newEntityID;
+
+    $newObject->find();
+
+    if ($object->fetch() && $newObject->fetch()) {
+
+      $fields = $object->fields();
+      foreach ($fields as $name => $value) {
+
+        if ($name == 'id' || $value['name'] == 'id') {
+          // copy everything but the id!
+          continue;
+        }
+
+        // only copy localizable fields
+        if (!$value['localizable']) {
+          continue;
+        }
+
+        $dbName = $value['name'];
+        $type = CRM_Utils_Type::typeToString($value['type']);
+        $newObject->$dbName = $object->$dbName;
+        if (isset($fieldsToPrefix[$dbName])) {
+          $newObject->$dbName = $fieldsToPrefix[$dbName] . $newObject->$dbName;
+        }
+        if (isset($fieldsToSuffix[$dbName])) {
+          $newObject->$dbName .= $fieldsToSuffix[$dbName];
+        }
+        if (isset($fieldsToReplace[$dbName])) {
+          $newObject->$dbName = $fieldsToReplace[$dbName];
+        }
+
+        if ($type == 'Timestamp' || $type == 'Date') {
+          $newObject->$dbName = CRM_Utils_Date::isoToMysql($newObject->$dbName);
+        }
+
+      }
+      $newObject->save();
+
+    }
+  }
+
   /**
    * Method that copies custom fields values from an old entity to a new one.
    *
@@ -2369,23 +2466,6 @@ SELECT contact_id
     Civi::service('sql_triggers')->rebuild($tableName, $force);
   }
 
-  /**
-   * Because sql functions are sometimes lost, esp during db migration, we check here to avoid numerous support requests
-   * @see http://issues.civicrm.org/jira/browse/CRM-13822
-   * TODO: Alternative solutions might be
-   *  * Stop using functions and find another way to strip numeric characters from phones
-   *  * Give better error messages (currently a missing fn fatals with "unknown error")
-   */
-  public static function checkSqlFunctionsExist() {
-    if (!self::$_checkedSqlFunctionsExist) {
-      self::$_checkedSqlFunctionsExist = TRUE;
-      $dao = CRM_Core_DAO::executeQuery("SHOW function status WHERE db = database() AND name = 'civicrm_strip_non_numeric'");
-      if (!$dao->fetch()) {
-        self::triggerRebuild();
-      }
-    }
-  }
-
   /**
    * Wrapper function to drop triggers.
    *
@@ -2500,7 +2580,7 @@ SELECT contact_id
    * @param string $tableName
    *   Table referred to.
    *
-   * @return array
+   * @return CRM_Core_Reference_Interface[]
    *   structure of table and column, listing every table with a
    *   foreign key reference to $tableName, and the column where the key appears.
    */
@@ -2537,7 +2617,12 @@ SELECT contact_id
     $contactReferences = [];
     $coreReferences = CRM_Core_DAO::getReferencesToTable('civicrm_contact');
     foreach ($coreReferences as $coreReference) {
-      if (!is_a($coreReference, 'CRM_Core_Reference_Dynamic')) {
+      if (
+        // Exclude option values
+        !is_a($coreReference, 'CRM_Core_Reference_Dynamic') &&
+        // Exclude references to other columns
+        $coreReference->getTargetKey() === 'id'
+      ) {
         $contactReferences[$coreReference->getReferenceTable()][] = $coreReference->getReferenceKey();
       }
     }
@@ -2797,20 +2882,9 @@ SELECT contact_id
    */
   public static function createSQLFilter($fieldName, $filter, $type = NULL, $alias = NULL, $returnSanitisedArray = FALSE) {
     foreach ($filter as $operator => $criteria) {
-      if (!CRM_Core_BAO_SchemaHandler::databaseSupportsUTF8MB4()) {
-        foreach ((array) $criteria as $criterion) {
-          if (!empty($criterion) && !is_numeric($criterion)
-            // The first 2 criteria are redundant but are added as they
-            // seem like they would
-            // be quicker than this 3rd check.
-            && max(array_map('ord', str_split($criterion))) >= 240) {
-            // String contains unsupported emojis.
-            // We return a clause that resolves to false as an emoji string by definition cannot be saved.
-            // note that if we return just 0 for false if gets lost in empty checks.
-            // https://stackoverflow.com/questions/16496554/can-php-detect-4-byte-encoded-utf8-chars
-            return '0 = 1';
-          }
-        }
+      $emojiFilter = CRM_Utils_SQL::handleEmojiInQuery($criteria);
+      if ($emojiFilter === '0 = 1') {
+        return $emojiFilter;
       }
 
       if (in_array($operator, self::acceptedSQLOperators(), TRUE)) {
@@ -3040,42 +3114,6 @@ SELECT contact_id
     return $clauses;
   }
 
-  /**
-   * Check whether action can be performed on a given record.
-   *
-   * Dispatches to internal BAO function ('static::_checkAccess())` and `hook_civicrm_checkAccess`.
-   *
-   * @param string $entityName
-   *   Ex: 'Contact' or 'Custom_Foobar'
-   * @param string $action
-   *   APIv4 action name.
-   *   Ex: 'create', 'get', 'delete'
-   * @param array $record
-   *   All (known/loaded) values of individual record being accessed.
-   *   The record should provide an 'id' but may otherwise be incomplete; guard accordingly.
-   * @param int|null $userID
-   *   Contact ID of the active user (whose access we must check). NULL for anonymous.
-   * @param bool $granted
-   *   Initial value (usually TRUE, but the API might pass FALSE if gatekeeper permissions fail)
-   *
-   * @return bool
-   */
-  public static function checkAccess(string $entityName, string $action, array $record, $userID, $granted = TRUE): bool {
-    // Ensure this function was either called on a BAO class or a DAO that has no BAO
-    if (!$entityName ||
-      (!strpos(static::class, '_BAO_') && CRM_Core_DAO_AllCoreTables::getBAOClassName(static::class) !== static::class)
-    ) {
-      throw new CRM_Core_Exception('Function checkAccess must be called on a BAO class');
-    }
-    // Dispatch to protected function _checkAccess in this BAO
-    if ($granted && method_exists(static::class, '_checkAccess')) {
-      $granted = static::_checkAccess($entityName, $action, $record, $userID);
-    }
-    // Dispatch to hook
-    CRM_Utils_Hook::checkAccess($entityName, $action, $record, $userID, $granted);
-    return $granted;
-  }
-
   /**
    * ensure database name is 'safe', i.e. only contains word characters (includes underscores)
    * and dashes, and contains at least one [a-z] case insensitive.