Merge pull request #12345 from MiyaNoctem/CRM-195-add-counts-to-contribution-sub...
authorEileen McNaughton <eileen@mcnaughty.com>
Sun, 1 Jul 2018 07:39:31 +0000 (00:39 -0700)
committerGitHub <noreply@github.com>
Sun, 1 Jul 2018 07:39:31 +0000 (00:39 -0700)
dev/core#195 Add Contribution Counts to Sub-tabs

103 files changed:
CRM/ACL/BAO/ACL.php
CRM/Activity/Form/Task.php
CRM/Campaign/BAO/Petition.php
CRM/Case/BAO/CaseType.php
CRM/Case/Form/Task.php
CRM/Contact/BAO/ContactType.php
CRM/Contact/BAO/Group.php
CRM/Contact/BAO/Query.php
CRM/Contact/Form/Search/Builder.php
CRM/Contact/Form/Task.php
CRM/Contribute/Form/ContributionBase.php
CRM/Contribute/Form/ContributionPage/Widget.php
CRM/Contribute/Form/Task.php
CRM/Core/BAO/Cache.php
CRM/Core/BAO/CustomField.php
CRM/Core/BAO/CustomQuery.php
CRM/Core/BAO/Domain.php
CRM/Core/BAO/OptionGroup.php
CRM/Core/Form.php
CRM/Core/Form/Task.php
CRM/Core/Invoke.php
CRM/Core/OptionGroup.php
CRM/Core/Payment.php
CRM/Core/Payment/Form.php
CRM/Core/PseudoConstant.php
CRM/Cxn/CiviCxnHttp.php
CRM/Event/Form/Task.php
CRM/Extension/Mapper.php
CRM/Friend/BAO/Friend.php
CRM/Friend/Form.php
CRM/Grant/Form/Task.php
CRM/Group/Form/Search.php
CRM/Group/Page/AJAX.php
CRM/Mailing/BAO/Mailing.php
CRM/Mailing/Event/BAO/Confirm.php
CRM/Mailing/Event/BAO/Reply.php
CRM/Mailing/Event/BAO/Resubscribe.php
CRM/Mailing/Event/BAO/Subscribe.php
CRM/Mailing/Event/BAO/Unsubscribe.php
CRM/Mailing/Form/Task.php
CRM/Member/BAO/MembershipType.php
CRM/Member/Form/Task.php
CRM/Member/Page/RecurringContributions.php
CRM/Member/Task.php
CRM/Pledge/Form/Task.php
CRM/Report/Form.php
CRM/Report/Form/Contribute/Bookkeeping.php
CRM/Upgrade/Incremental/sql/5.4.alpha1.mysql.tpl
CRM/Utils/Cache.php
CRM/Utils/Cache/APCcache.php
CRM/Utils/Cache/ArrayCache.php
CRM/Utils/Cache/CacheException.php [new file with mode: 0644]
CRM/Utils/Cache/Interface.php
CRM/Utils/Cache/InvalidArgumentException.php [new file with mode: 0644]
CRM/Utils/Cache/Memcache.php
CRM/Utils/Cache/Memcached.php
CRM/Utils/Cache/NaiveHasTrait.php [new file with mode: 0644]
CRM/Utils/Cache/NaiveMultipleTrait.php [new file with mode: 0644]
CRM/Utils/Cache/NoCache.php
CRM/Utils/Cache/Redis.php
CRM/Utils/Cache/SerializeCache.php
CRM/Utils/Cache/SqlGroup.php
CRM/Utils/Check.php
CRM/Utils/Date.php
CRM/Utils/System.php
Civi.php
Civi/Angular/Manager.php
Civi/Core/Container.php
Civi/Core/SettingsManager.php
ang/crmCaseType.js
ang/crmCaseType/timelineTable.html
ang/crmMailing/BlockPreview.html
ang/crmMailing/EditMailingCtrl/2step.html
ang/crmMailing/EditMailingCtrl/unified.html
ang/crmMailing/EditMailingCtrl/unified2.html
ang/crmMailing/EditMailingCtrl/wizard.html
ang/crmMailing/EditMailingCtrl/workflow.html
ang/crmUi/wizard.html
api/v3/examples/Setting/GetFields.php
api/v3/utils.php
composer.json
composer.lock
settings/Core.setting.php
templates/CRM/Contact/Form/Search/Builder.js
templates/CRM/Contribute/Form/ContributionPage/Widget.tpl
templates/CRM/Core/BillingBlock.tpl
templates/CRM/Event/Form/EventFees.tpl
templates/CRM/Group/Form/Search.tpl
templates/CRM/Member/Form/MembershipType.tpl
templates/CRM/common/civicrm.settings.php.template
tests/phpunit/CRM/Case/BAO/CaseTypeTest.php
tests/phpunit/CRM/Contact/SelectorTest.php
tests/phpunit/CRM/Core/BAO/CacheTest.php
tests/phpunit/CRM/Group/Page/AjaxTest.php
tests/phpunit/Civi/Core/SettingsManagerTest.php
tests/phpunit/CiviTest/CiviUnitTestCase.php
tests/phpunit/E2E/Cache/APCcacheTest.php [new file with mode: 0644]
tests/phpunit/E2E/Cache/ArrayCacheTest.php [new file with mode: 0644]
tests/phpunit/E2E/Cache/CacheTestCase.php [new file with mode: 0644]
tests/phpunit/E2E/Cache/ConfiguredMemoryTest.php [new file with mode: 0644]
tests/phpunit/api/v3/CaseTypeTest.php
tests/phpunit/api/v3/MembershipTypeTest.php
tests/phpunit/api/v3/OptionValueTest.php

index cd9c25dfeee764dfad9dae2447bcbf84cb6ac632..15221f74565ead4bc8a0177ce0147f34df3c4c6a 100644 (file)
@@ -877,7 +877,7 @@ SELECT g.*
       $aclKeys = array_keys($acls);
       $aclKeys = implode(',', $aclKeys);
 
-      $cacheKey = "$tableName-$aclKeys";
+      $cacheKey = CRM_Core_BAO_Cache::cleanKey("$tableName-$aclKeys");
       $cache = CRM_Utils_Cache::singleton();
       $ids = $cache->get($cacheKey);
       if (!$ids) {
index 3ec9429bee13ea00b181ac518f0f4f8f3e33d862..3a052beec467105460210501ced3edefbe120892 100644 (file)
@@ -55,9 +55,8 @@ class CRM_Activity_Form_Task extends CRM_Core_Form_Task {
    * Common pre-process function.
    *
    * @param CRM_Core_Form $form
-   * @param bool $useTable
    */
-  public static function preProcessCommon(&$form, $useTable = FALSE) {
+  public static function preProcessCommon(&$form) {
     $form->_activityHolderIds = array();
 
     $values = $form->controller->exportValues($form->get('searchFormName'));
index 43246cc66f65eb038a5e28262c0d1472e5aedf77..f8b0500ca277bb0e4cdff8b32bd7cfd89592a778 100644 (file)
@@ -586,7 +586,7 @@ AND         tag_id = ( SELECT id FROM civicrm_tag WHERE name = %2 )";
 
     $toName = CRM_Contact_BAO_Contact::displayName($params['contactId']);
 
-    $replyTo = "do-not-reply@$emailDomain";
+    $replyTo = CRM_Core_BAO_Domain::getNoReplyEmailAddress();
 
     // set additional general message template params (custom tokens to use in email msg templates)
     // tokens then available in msg template as {$petition.title}, etc
index 42cc1baedde825e5ec2075a6dc75f2b749780d04..89baec6098e0224babbbac96f9508b18857481d6 100644 (file)
@@ -242,6 +242,8 @@ class CRM_Case_BAO_CaseType extends CRM_Case_DAO_CaseType {
     // set activity sets
     if (isset($xml->ActivitySets)) {
       $definition['activitySets'] = array();
+      $definition['timelineActivityTypes'] = array();
+
       foreach ($xml->ActivitySets->ActivitySet as $activitySetXML) {
         // parse basic properties
         $activitySet = array();
@@ -257,7 +259,11 @@ class CRM_Case_BAO_CaseType extends CRM_Case_DAO_CaseType {
         if (isset($activitySetXML->ActivityTypes)) {
           $activitySet['activityTypes'] = array();
           foreach ($activitySetXML->ActivityTypes->ActivityType as $activityTypeXML) {
-            $activitySet['activityTypes'][] = json_decode(json_encode($activityTypeXML), TRUE);
+            $activityType = json_decode(json_encode($activityTypeXML), TRUE);
+            $activitySet['activityTypes'][] = $activityType;
+            if ($activitySetXML->timeline) {
+              $definition['timelineActivityTypes'][] = $activityType;
+            }
           }
         }
         $definition['activitySets'][] = $activitySet;
index b9d05e68758cb3a7419117fcbc53d5b20386617e..398cdffd044dac994f727d689d68123e4a5e9c85 100644 (file)
@@ -40,6 +40,13 @@ class CRM_Case_Form_Task extends CRM_Core_Form_Task {
   // Must be set to entity shortname (eg. event)
   static $entityShortname = 'case';
 
+  /**
+   * Must be set to queryMode
+   *
+   * @var int
+   */
+  static $queryMode = CRM_Contact_BAO_Query::MODE_CASE;
+
   /**
    * @inheritDoc
    */
index a299e9e59115790e58d3211a55e89f39fa009794..cb13346f556a1bd91fa98d7b7010b056757c2b17 100644 (file)
@@ -387,6 +387,7 @@ WHERE  type.name IS NOT NULL
     $argString = $all ? 'CRM_CT_GSE_1' : 'CRM_CT_GSE_0';
     $argString .= $isSeparator ? '_1' : '_0';
     $argString .= $separator;
+    $argString = CRM_Core_BAO_Cache::cleanKey($argString);
     if (!array_key_exists($argString, $_cache)) {
       $cache = CRM_Utils_Cache::singleton();
       $_cache[$argString] = $cache->get($argString);
index 759a6b3eaced3bfd9e966fd5bb0a0257e1bc9db6..8c08654256d78acd477d7017d4c70506a31a3d0b 100644 (file)
@@ -882,7 +882,7 @@ class CRM_Contact_BAO_Group extends CRM_Contact_DAO_Group {
     // CRM-9936
     $reservedPermission = CRM_Core_Permission::check('administer reserved groups');
 
-    $links = self::actionLinks();
+    $links = self::actionLinks($params);
 
     $allTypes = CRM_Core_OptionGroup::values('group_type');
     $values = array();
@@ -1254,12 +1254,17 @@ WHERE {$whereClause}";
    * @return array
    *   array of action links
    */
-  public static function actionLinks() {
+  public static function actionLinks($params) {
+    // If component_mode is set we change the "View" link to match the requested component type
+    if (!isset($params['component_mode'])) {
+      $params['component_mode'] = CRM_Contact_BAO_Query::MODE_CONTACTS;
+    }
+    $modeValue = CRM_Contact_Form_Search::getModeValue($params['component_mode']);
     $links = array(
       CRM_Core_Action::VIEW => array(
-        'name' => ts('Contacts'),
+        'name' => $modeValue['selectorLabel'],
         'url' => 'civicrm/group/search',
-        'qs' => 'reset=1&force=1&context=smog&gid=%%id%%',
+        'qs' => 'reset=1&force=1&context=smog&gid=%%id%%&component_mode=' . $params['component_mode'],
         'title' => ts('Group Contacts'),
       ),
       CRM_Core_Action::UPDATE => array(
index 2525c3884432b692e694a8a7a48c77458f6beed9..eda7792aa7d315e4c17307b001562a4b3acf2dd9 100644 (file)
@@ -5662,11 +5662,11 @@ SELECT COUNT( conts.total_amount ) as cancel_count,
         return $clause;
 
       case 'IS EMPTY':
-        $clause = " (NULLIF($field, '') IS NULL) ";
+        $clause = ($dataType == 'Date') ? " $field IS NULL " : " (NULLIF($field, '') IS NULL) ";
         return $clause;
 
       case 'IS NOT EMPTY':
-        $clause = " (NULLIF($field, '') IS NOT NULL) ";
+        $clause = ($dataType == 'Date') ? " $field IS NOT NULL " : " (NULLIF($field, '') IS NOT NULL) ";
         return $clause;
 
       case 'IN':
@@ -5677,7 +5677,7 @@ SELECT COUNT( conts.total_amount ) as cancel_count,
         }
 
       default:
-        if (empty($dataType)) {
+        if (empty($dataType) || $dataType == 'Date') {
           $dataType = 'String';
         }
         if (is_array($value)) {
index 2a5dd79351b47a673a22bb98d082df6fcaf8f324..4b5f0ddc29c84ba29f922fc5435d75d7b5063366 100644 (file)
@@ -95,18 +95,10 @@ class CRM_Contact_Form_Search_Builder extends CRM_Contact_Form_Search {
     // This array contain list of available fields and their corresponding data type,
     //  later assigned as json string, to be used to filter list of mysql operators
     $fieldNameTypes = [];
-    $dataType = [
-      CRM_Utils_Type::T_STRING => 'String',
-      CRM_Utils_Type::T_TEXT => 'String',
-      CRM_Utils_Type::T_LONGTEXT => 'String',
-      CRM_Utils_Type::T_BOOLEAN => 'Boolean',
-      CRM_Utils_Type::T_DATE => 'Date',
-      CRM_Utils_Type::T_TIMESTAMP => 'Date',
-    ];
     foreach ($fields as $name => $field) {
       // Assign date type to respective field name, which will be later used to modify operator list
-      if (isset($field['type']) && array_key_exists($field['type'], $dataType)) {
-        $fieldNameTypes[$name] = $dataType[$field['type']];
+      if ($type = CRM_Utils_Array::key(CRM_Utils_Array::value('type', $field), CRM_Utils_Type::getValidTypes())) {
+        $fieldNameTypes[$name] = $type;
       }
       // it's necessary to know which of the fields are searchable by label
       if (isset($field['searchByLabel']) && $field['searchByLabel']) {
@@ -477,8 +469,10 @@ class CRM_Contact_Form_Search_Builder extends CRM_Contact_Form_Search {
             $options[substr($field, 0, -3)] = $entity;
           }
         }
-        elseif (!empty($info['data_type']) && in_array($info['data_type'], array('StateProvince', 'Country'))) {
-          $options[$field] = $entity;
+        elseif (!empty($info['data_type'])) {
+          if (in_array($info['data_type'], array('StateProvince', 'Country'))) {
+            $options[$field] = $entity;
+          }
         }
         elseif (in_array(substr($field, 0, 3), array(
               'is_',
index 27edba918fb9e9395abef9707eca76eb796e4bce..2ccf1552eb88921d07971527f12bddb6bb9c2792 100644 (file)
@@ -94,12 +94,14 @@ class CRM_Contact_Form_Task extends CRM_Core_Form_Task {
    * Common pre-processing function.
    *
    * @param CRM_Core_Form $form
-   * @param bool $useTable
    */
-  public static function preProcessCommon(&$form, $useTable = FALSE) {
+  public static function preProcessCommon(&$form) {
     $form->_contactIds = array();
     $form->_contactTypes = array();
 
+    $formName = CRM_Utils_System::getClassName($form->controller->getStateMachine());
+    $useTable = $formName == 'CRM_Export_StateMachine_Standalone';
+
     $isStandAlone = in_array('task', $form->urlPath) || in_array('standalone', $form->urlPath);
     if ($isStandAlone) {
       list($form->_task, $title) = CRM_Contact_Task::getTaskAndTitleByClass(get_class($form));
index 352395daf55122e6b0f9f14cd03bd60154eb4ebb..561adfe00d4d0c61d90ce67c8da0452f54ded010 100644 (file)
@@ -614,11 +614,7 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
           $this->assign($paymentField, $this->_params[$paymentField]);
         }
       }
-      $paymentFieldsetLabel = ts('%1 Information', array($paymentProcessorObject->getPaymentTypeLabel()));
-      if (empty($paymentFields)) {
-        $paymentFieldsetLabel = '';
-      }
-      $this->assign('paymentFieldsetLabel', $paymentFieldsetLabel);
+      $this->assign('paymentFieldsetLabel', CRM_Core_Payment_Form::getPaymentLabel($paymentProcessorObject));
       $this->assign('paymentFields', $paymentFields);
 
     }
index df8905af0af200bc94ebe9fb20eba394b6e169b0..9d8a621354b847c0625e1148db4880dee11d3c9c 100644 (file)
@@ -85,55 +85,55 @@ class CRM_Contribute_Form_ContributionPage_Widget extends CRM_Contribute_Form_Co
     $this->_colorFields = array(
       'color_title' => array(
         ts('Title Text Color'),
-        'text',
+        'color',
         FALSE,
         '#2786C2',
       ),
       'color_bar' => array(
         ts('Progress Bar Color'),
-        'text',
+        'color',
         FALSE,
         '#2786C2',
       ),
       'color_main_text' => array(
         ts('Additional Text Color'),
-        'text',
+        'color',
         FALSE,
         '#FFFFFF',
       ),
       'color_main' => array(
         ts('Background Color'),
-        'text',
+        'color',
         FALSE,
         '#96C0E7',
       ),
       'color_main_bg' => array(
         ts('Background Color Top Area'),
-        'text',
+        'color',
         FALSE,
         '#B7E2FF',
       ),
       'color_bg' => array(
         ts('Border Color'),
-        'text',
+        'color',
         FALSE,
         '#96C0E7',
       ),
       'color_about_link' => array(
         ts('Button Text Color'),
-        'text',
+        'color',
         FALSE,
         '#556C82',
       ),
       'color_button' => array(
         ts('Button Background Color'),
-        'text',
+        'color',
         FALSE,
         '#FFFFFF',
       ),
       'color_homepage_link' => array(
         ts('Homepage Link Color'),
-        'text',
+        'color',
         FALSE,
         '#FFFFFF',
       ),
index 70cf6325cef09c0cf42fc77163f75e97bba4352c..f089448db49c37ab3ebf243e8b2d7a91cf6caf1d 100644 (file)
@@ -67,9 +67,8 @@ class CRM_Contribute_Form_Task extends CRM_Core_Form_Task {
 
   /**
    * @param CRM_Core_Form $form
-   * @param bool $useTable
    */
-  public static function preProcessCommon(&$form, $useTable = FALSE) {
+  public static function preProcessCommon(&$form) {
     $form->_contributionIds = array();
 
     $values = $form->controller->exportValues($form->get('searchFormName'));
index 0e619143e33a830c4623ac49657158bff82a32c7..271bc71ddedef3fc2014c826016c3c39c1f18fd4 100644 (file)
@@ -66,15 +66,15 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
     $argString = "CRM_CT_{$group}_{$path}_{$componentID}";
     if (!array_key_exists($argString, self::$_cache)) {
       $cache = CRM_Utils_Cache::singleton();
-      self::$_cache[$argString] = $cache->get($argString);
+      self::$_cache[$argString] = $cache->get(self::cleanKey($argString));
       if (!self::$_cache[$argString]) {
         $table = self::getTableName();
         $where = self::whereCache($group, $path, $componentID);
         $rawData = CRM_Core_DAO::singleValueQuery("SELECT data FROM $table WHERE $where");
-        $data = $rawData ? unserialize($rawData) : NULL;
+        $data = $rawData ? self::decode($rawData) : NULL;
 
         self::$_cache[$argString] = $data;
-        $cache->set($argString, self::$_cache[$argString]);
+        $cache->set(self::cleanKey($argString), self::$_cache[$argString]);
       }
     }
     return self::$_cache[$argString];
@@ -99,7 +99,7 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
     $argString = "CRM_CT_CI_{$group}_{$componentID}";
     if (!array_key_exists($argString, self::$_cache)) {
       $cache = CRM_Utils_Cache::singleton();
-      self::$_cache[$argString] = $cache->get($argString);
+      self::$_cache[$argString] = $cache->get(self::cleanKey($argString));
       if (!self::$_cache[$argString]) {
         $table = self::getTableName();
         $where = self::whereCache($group, NULL, $componentID);
@@ -107,12 +107,12 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
 
         $result = array();
         while ($dao->fetch()) {
-          $result[$dao->path] = unserialize($dao->data);
+          $result[$dao->path] = self::decode($dao->data);
         }
         $dao->free();
 
         self::$_cache[$argString] = $result;
-        $cache->set($argString, self::$_cache[$argString]);
+        $cache->set(self::cleanKey($argString), self::$_cache[$argString]);
       }
     }
 
@@ -148,7 +148,7 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
     $where = self::whereCache($group, $path, $componentID);
     $dataExists = CRM_Core_DAO::singleValueQuery("SELECT COUNT(*) FROM $table WHERE {$where}");
     $now = date('Y-m-d H:i:s'); // FIXME - Use SQL NOW() or CRM_Utils_Time?
-    $dataSerialized = serialize($data);
+    $dataSerialized = self::encode($data);
 
     // This table has a wonky index, so we cannot use REPLACE or
     // "INSERT ... ON DUPE". Instead, use SELECT+(INSERT|UPDATE).
@@ -180,13 +180,13 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
 
     $argString = "CRM_CT_{$group}_{$path}_{$componentID}";
     $cache = CRM_Utils_Cache::singleton();
-    $data = unserialize($dataSerialized);
+    $data = self::decode($dataSerialized);
     self::$_cache[$argString] = $data;
-    $cache->set($argString, $data);
+    $cache->set(self::cleanKey($argString), $data);
 
     $argString = "CRM_CT_CI_{$group}_{$componentID}";
     unset(self::$_cache[$argString]);
-    $cache->delete($argString);
+    $cache->delete(self::cleanKey($argString));
   }
 
   /**
@@ -365,6 +365,33 @@ AND         created_date < date_sub( NOW( ), INTERVAL $timeIntervalDays DAY )
     }
   }
 
+  /**
+   * (Quasi-private) Encode an object/array/string/int as a string.
+   *
+   * @param $mixed
+   * @return string
+   */
+  public static function encode($mixed) {
+    return base64_encode(serialize($mixed));
+  }
+
+  /**
+   * (Quasi-private) Decode an object/array/string/int from a string.
+   *
+   * @param $string
+   * @return mixed
+   */
+  public static function decode($string) {
+    // Upgrade support -- old records (serialize) always have this punctuation,
+    // and new records (base64) never do.
+    if (strpos($string, ':') !== FALSE || strpos($string, ';') !== FALSE) {
+      return unserialize($string);
+    }
+    else {
+      return unserialize(base64_decode($string));
+    }
+  }
+
   /**
    * Compose a SQL WHERE clause for the cache.
    *
@@ -390,4 +417,35 @@ AND         created_date < date_sub( NOW( ), INTERVAL $timeIntervalDays DAY )
     return $clauses ? implode(' AND ', $clauses) : '(1)';
   }
 
+  /**
+   * Normalize a cache key.
+   *
+   * This bridges an impedance mismatch between our traditional caching
+   * and PSR-16 -- PSR-16 accepts a narrower range of cache keys.
+   *
+   * @param string $key
+   *   Ex: 'ab/cd:ef'
+   * @return string
+   *   Ex: '_abcd1234abcd1234' or 'ab_xx/cd_xxef'.
+   *   A similar key, but suitable for use with PSR-16-compliant cache providers.
+   */
+  public static function cleanKey($key) {
+    if (!is_string($key) && !is_int($key)) {
+      throw new \RuntimeException("Malformed cache key");
+    }
+
+    $maxLen = 64;
+    $escape = '-';
+
+    if (strlen($key) >= $maxLen) {
+      return $escape . md5($key);
+    }
+
+    $r = preg_replace_callback(';[^A-Za-z0-9_\. ];', function($m) use ($escape) {
+      return $escape . dechex(ord($m[0]));
+    }, $key);
+
+    return strlen($r) >= $maxLen ? $escape . md5($key) : $r;
+  }
+
 }
index 8dc1cce54fce176de1c6fe686fb002fd087b33fd..554eb94dc9ea141e0178b67a996f8da24a37452a 100644 (file)
@@ -83,6 +83,30 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
     return self::$_dataType;
   }
 
+  /**
+   * Build the map of custom field's data types and there respective Util type
+   *
+   * @return array
+   *   Data data-type => CRM_Utils_Type
+   */
+  public static function dataToType() {
+    return [
+      'String' => CRM_Utils_Type::T_STRING,
+      'Int' => CRM_Utils_Type::T_INT,
+      'Money' => CRM_Utils_Type::T_MONEY,
+      'Memo' => CRM_Utils_Type::T_LONGTEXT,
+      'Float' => CRM_Utils_Type::T_FLOAT,
+      'Date' => CRM_Utils_Type::T_DATE,
+      'DateTime' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME,
+      'Boolean' => CRM_Utils_Type::T_BOOLEAN,
+      'StateProvince' => CRM_Utils_Type::T_INT,
+      'File' => CRM_Utils_Type::T_STRING,
+      'Link' => CRM_Utils_Type::T_STRING,
+      'ContactReference' => CRM_Utils_Type::T_INT,
+      'Country' => CRM_Utils_Type::T_INT,
+    ];
+  }
+
   /**
    * Get data to html array.
    *
@@ -691,6 +715,7 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
       $regexp = preg_replace('/[.,;:!?]/', '', CRM_Utils_Array::value(0, $values));
       $importableFields[$key] = array(
         'name' => $key,
+        'type' => CRM_Utils_Array::value(CRM_Utils_Array::value('data_type', $values), self::dataToType()),
         'title' => CRM_Utils_Array::value('label', $values),
         'headerPattern' => '/' . preg_quote($regexp, '/') . '/',
         'import' => 1,
index bc082255a3d635cf49569f27e46f709f6ccc7b4b..b71acb1081ce6a18c7e4400587eb1d6dc3934fb9 100644 (file)
@@ -433,7 +433,7 @@ SELECT f.id, f.label, f.data_type,
             break;
 
           case 'Date':
-            $this->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause($fieldName, $op, $value, 'String');
+            $this->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause($fieldName, $op, $value, 'Date');
             list($qillOp, $qillVal) = CRM_Contact_BAO_Query::buildQillForFieldValue(NULL, $field['label'], $value, $op, array(), CRM_Utils_Type::T_DATE);
             $this->_qill[$grouping][] = "{$field['label']} $qillOp '$qillVal'";
             break;
index c3a9094d6f44f3e7d4c58303a5a410517adb1234..58a06e16d1620700b22fa986078b05349ac92c32 100644 (file)
@@ -319,4 +319,12 @@ class CRM_Core_BAO_Domain extends CRM_Core_DAO_Domain {
     return array($userName, $userEmail);
   }
 
+  /**
+   * Get address to be used for system from addresses when a reply is not expected.
+   */
+  public static function getNoReplyEmailAddress() {
+    $emailDomain = CRM_Core_BAO_MailSettings::defaultDomain();
+    return "do-not-reply@$emailDomain";
+  }
+
 }
index 85b2e5203de0b00ba48c7caa1bb3fc423e05ddb9..fdc415b5cab5df557d057de5d33c57f22310d6d0 100644 (file)
@@ -93,17 +93,11 @@ class CRM_Core_BAO_OptionGroup extends CRM_Core_DAO_OptionGroup {
     }
 
     $params['is_active'] = CRM_Utils_Array::value('is_active', $params, FALSE);
-    $params['is_default'] = CRM_Utils_Array::value('is_default', $params, FALSE);
 
     // action is taken depending upon the mode
     $optionGroup = new CRM_Core_DAO_OptionGroup();
     $optionGroup->copyValues($params);;
 
-    if ($params['is_default']) {
-      $query = "UPDATE civicrm_option_group SET is_default = 0";
-      CRM_Core_DAO::executeQuery($query);
-    }
-
     $optionGroup->save();
     return $optionGroup;
   }
index ce004204d6f0bbb0f982c1f7c67898f3cc607113..abfbcdfcd44236ebd000ba53e94106db9beea29a 100644 (file)
@@ -1868,7 +1868,7 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
     $setDefaultCurrency = TRUE
   ) {
     $currencies = CRM_Core_OptionGroup::values('currencies_enabled');
-    if (!array_key_exists($defaultCurrency, $currencies)) {
+    if (!empty($defaultCurrency) && !array_key_exists($defaultCurrency, $currencies)) {
       Civi::log()->warning('addCurrency: Currency ' . $defaultCurrency . ' is disabled but still in use!');
       $currencies[$defaultCurrency] = $defaultCurrency;
     }
index 2a487ba70f1d0f705e68d85e208ea7884508dd4e..d842232921ad5d2677d5eb8d35c69d0af8e9a083 100644 (file)
@@ -70,11 +70,27 @@ abstract class CRM_Core_Form_Task extends CRM_Core_Form {
    */
   public $_contactIds;
 
-  // Must be set to entity table name (eg. civicrm_participant) by child class
+  /**
+   * Must be set to entity table name (eg. civicrm_participant) by child class
+   *
+   * @var string
+   */
   static $tableName = NULL;
-  // Must be set to entity shortname (eg. event)
+
+  /**
+   * Must be set to entity shortname (eg. event)
+   *
+   * @var string
+   */
   static $entityShortname = NULL;
 
+  /**
+   * Must be set to queryMode
+   *
+   * @var int
+   */
+  static $queryMode = CRM_Contact_BAO_Query::MODE_CONTACTS;
+
   /**
    * Build all the data structures needed to build the form.
    *
@@ -88,25 +104,24 @@ abstract class CRM_Core_Form_Task extends CRM_Core_Form {
    * Common pre-processing function.
    *
    * @param CRM_Core_Form $form
-   * @param bool $useTable FIXME This parameter could probably be deprecated as it's not used here
    *
    * @throws \CRM_Core_Exception
    */
-  public static function preProcessCommon(&$form, $useTable = FALSE) {
+  public static function preProcessCommon(&$form) {
     $form->_entityIds = array();
 
-    $values = $form->controller->exportValues($form->get('searchFormName'));
+    $searchFormValues = $form->controller->exportValues($form->get('searchFormName'));
 
-    $form->_task = $values['task'];
+    $form->_task = $searchFormValues['task'];
     $className = 'CRM_' . ucfirst($form::$entityShortname) . '_Task';
     $entityTasks = $className::tasks();
     $form->assign('taskName', $entityTasks[$form->_task]);
 
-    $ids = array();
-    if ($values['radio_ts'] == 'ts_sel') {
-      foreach ($values as $name => $value) {
+    $entityIds = array();
+    if ($searchFormValues['radio_ts'] == 'ts_sel') {
+      foreach ($searchFormValues as $name => $value) {
         if (substr($name, 0, CRM_Core_Form::CB_PREFIX_LEN) == CRM_Core_Form::CB_PREFIX) {
-          $ids[] = substr($name, CRM_Core_Form::CB_PREFIX_LEN);
+          $entityIds[] = substr($name, CRM_Core_Form::CB_PREFIX_LEN);
         }
       }
     }
@@ -117,24 +132,22 @@ abstract class CRM_Core_Form_Task extends CRM_Core_Form {
         $sortOrder = $form->get(CRM_Utils_Sort::SORT_ORDER);
       }
 
-      $query = new CRM_Contact_BAO_Query($queryParams, NULL, NULL, FALSE, FALSE,
-        CRM_Contact_BAO_Query::MODE_CASE
-      );
+      $query = new CRM_Contact_BAO_Query($queryParams, NULL, NULL, FALSE, FALSE, $form::$queryMode);
       $query->_distinctComponentClause = " ( " . $form::$tableName . ".id )";
       $query->_groupByComponentClause = " GROUP BY " . $form::$tableName . ".id ";
       $result = $query->searchQuery(0, 0, $sortOrder);
       $selector = $form::$entityShortname . '_id';
       while ($result->fetch()) {
-        $ids[] = $result->$selector;
+        $entityIds[] = $result->$selector;
       }
     }
 
-    if (!empty($ids)) {
-      $form->_componentClause = ' ' . $form::$tableName . '.id IN ( ' . implode(',', $ids) . ' ) ';
-      $form->assign('totalSelected' . ucfirst($form::$entityShortname) . 's', count($ids));
+    if (!empty($entityIds)) {
+      $form->_componentClause = ' ' . $form::$tableName . '.id IN ( ' . implode(',', $entityIds) . ' ) ';
+      $form->assign('totalSelected' . ucfirst($form::$entityShortname) . 's', count($entityIds));
     }
 
-    $form->_entityIds = $form->_componentIds = $ids;
+    $form->_entityIds = $form->_componentIds = $entityIds;
 
     // Some functions (eg. PDF letter tokens) rely on Ids being in specific fields rather than the generic $form->_entityIds
     // So we set that specific field here (eg. for cases $form->_caseIds = $form->_entityIds).
index e5edcd82c95608fe0fc71d00580392a0f92d412c..eb2bef51af7f0f6c715323aa9c702aa9e8e97924 100644 (file)
@@ -350,7 +350,7 @@ class CRM_Core_Invoke {
       return;
     }
     // always use cached results - they will be refreshed by the session timer
-    $status = Civi::settings()->get('systemStatusCheckResult');
+    $status = Civi::cache('checks')->get('systemStatusCheckResult');
     $template->assign('footer_status_severity', $status);
     $template->assign('footer_status_message', CRM_Utils_Check::toStatusLabel($status));
   }
index bc1f7ad2ac8124e538fa01d5e940ed0392c58945..c7e07e67d613ce2af1cc6f7edcba0c43129229e5 100644 (file)
@@ -200,8 +200,8 @@ WHERE  v.option_group_id = g.id
   /**
    * @return string
    */
-  protected static function createCacheKey() {
-    $cacheKey = "CRM_OG_" . serialize(func_get_args());
+  protected static function createCacheKey($id) {
+    $cacheKey = "CRM_OG_" . preg_replace('/[^a-zA-Z0-9]/', '', $id) . '_' . md5(serialize(func_get_args()));
     return $cacheKey;
   }
 
index 8626cd790de353f4170e1304d647c732da91b256..832a444da57dd88c898ad4c74cd97581d5db1219 100644 (file)
@@ -682,7 +682,6 @@ abstract class CRM_Core_Payment {
         'htmlType' => 'text',
         'name' => 'credit_card_number',
         'title' => ts('Card Number'),
-        'cc_field' => TRUE,
         'attributes' => array(
           'size' => 20,
           'maxlength' => 20,
@@ -695,7 +694,6 @@ abstract class CRM_Core_Payment {
         'htmlType' => 'text',
         'name' => 'cvv2',
         'title' => ts('Security Code'),
-        'cc_field' => TRUE,
         'attributes' => array(
           'size' => 5,
           'maxlength' => 10,
@@ -714,7 +712,6 @@ abstract class CRM_Core_Payment {
         'htmlType' => 'date',
         'name' => 'credit_card_exp_date',
         'title' => ts('Expiration Date'),
-        'cc_field' => TRUE,
         'attributes' => CRM_Core_SelectValues::date('creditCard'),
         'is_required' => TRUE,
         'rules' => array(
@@ -729,7 +726,6 @@ abstract class CRM_Core_Payment {
         'htmlType' => 'select',
         'name' => 'credit_card_type',
         'title' => ts('Card Type'),
-        'cc_field' => TRUE,
         'attributes' => $creditCardType,
         'is_required' => FALSE,
       ),
@@ -737,7 +733,6 @@ abstract class CRM_Core_Payment {
         'htmlType' => 'text',
         'name' => 'account_holder',
         'title' => ts('Account Holder'),
-        'cc_field' => TRUE,
         'attributes' => array(
           'size' => 20,
           'maxlength' => 34,
@@ -750,7 +745,6 @@ abstract class CRM_Core_Payment {
         'htmlType' => 'text',
         'name' => 'bank_account_number',
         'title' => ts('Bank Account Number'),
-        'cc_field' => TRUE,
         'attributes' => array(
           'size' => 20,
           'maxlength' => 34,
@@ -770,7 +764,6 @@ abstract class CRM_Core_Payment {
         'htmlType' => 'text',
         'name' => 'bank_identification_number',
         'title' => ts('Bank Identification Number'),
-        'cc_field' => TRUE,
         'attributes' => array(
           'size' => 20,
           'maxlength' => 11,
@@ -789,7 +782,6 @@ abstract class CRM_Core_Payment {
         'htmlType' => 'text',
         'name' => 'bank_name',
         'title' => ts('Bank Name'),
-        'cc_field' => TRUE,
         'attributes' => array(
           'size' => 20,
           'maxlength' => 64,
@@ -803,7 +795,6 @@ abstract class CRM_Core_Payment {
         'name' => 'check_number',
         'title' => ts('Check Number'),
         'is_required' => FALSE,
-        'cc_field' => TRUE,
         'attributes' => NULL,
       ),
       'pan_truncation' => array(
@@ -811,7 +802,6 @@ abstract class CRM_Core_Payment {
         'name' => 'pan_truncation',
         'title' => ts('Last 4 digits of the card'),
         'is_required' => FALSE,
-        'cc_field' => TRUE,
         'attributes' => array(
           'size' => 4,
           'maxlength' => 4,
@@ -826,6 +816,13 @@ abstract class CRM_Core_Payment {
           ),
         ),
       ),
+      'payment_token' => array(
+        'htmlType' => 'hidden',
+        'name' => 'payment_token',
+        'title' => ts('Authorization token'),
+        'is_required' => FALSE,
+        'attributes' => ['size' => 10, 'autocomplete' => 'off'],
+      ),
     );
   }
 
index 05de148e8248308ddb8c12d44a53c9b2c63b1a19..60e946d7dd0283edbad023982c0f62215ec8427c 100644 (file)
@@ -68,8 +68,7 @@ class CRM_Core_Payment_Form {
     $processor['object']->setPaymentInstrumentID($paymentInstrumentID);
     $paymentTypeName = self::getPaymentTypeName($processor);
     $form->assign('paymentTypeName', $paymentTypeName);
-    $paymentTypeLabel = self::getPaymentTypeLabel($processor);
-    $form->assign('paymentTypeLabel', $paymentTypeLabel);
+    $form->assign('paymentTypeLabel', self::getPaymentLabel($processor['object']));
     $form->assign('isBackOffice', $isBackOffice);
     $form->_paymentFields = $form->billingFieldSets[$paymentTypeName]['fields'] = self::getPaymentFieldMetadata($processor);
     $form->_paymentFields = array_merge($form->_paymentFields, self::getBillingAddressMetadata($processor, $form->_bltID));
@@ -116,24 +115,22 @@ class CRM_Core_Payment_Form {
   protected static function addCommonFields(&$form, $paymentFields) {
     $requiredPaymentFields = array();
     foreach ($paymentFields as $name => $field) {
-      // @todo - remove the cc_field check - no longer useful.
-      if (!empty($field['cc_field'])) {
-        if ($field['htmlType'] == 'chainSelect') {
-          $form->addChainSelect($field['name'], array('required' => FALSE));
-        }
-        else {
-          $form->add($field['htmlType'],
-            $field['name'],
-            $field['title'],
-            $field['attributes'],
-            FALSE
-          );
-        }
+      if ($field['htmlType'] == 'chainSelect') {
+        $form->addChainSelect($field['name'], array('required' => FALSE));
+      }
+      else {
+        $form->add($field['htmlType'],
+          $field['name'],
+          $field['title'],
+          $field['attributes'],
+          FALSE
+        );
       }
       // This will cause the fields to be marked as required - but it is up to the payment processor to
       // validate it.
       $requiredPaymentFields[$field['name']] = $field['is_required'];
     }
+
     $form->assign('requiredPaymentFields', $requiredPaymentFields);
   }
 
@@ -207,7 +204,7 @@ class CRM_Core_Payment_Form {
    * @return string
    */
   public static function getPaymentTypeLabel($paymentProcessor) {
-    return ts(($paymentProcessor['object']->getPaymentTypeLabel()) . ' Information');
+    return ts('%1 Information', [$paymentProcessor->getPaymentTypeLabel()]);
   }
 
   /**
@@ -426,4 +423,25 @@ class CRM_Core_Payment_Form {
     return CRM_Utils_Array::value('Y', $src['credit_card_exp_date']);
   }
 
+  /**
+   * Get the label for the processor.
+   *
+   * We do not use a label if there are no enterable fields.
+   *
+   * @param \CRM_Core_Payment $processor
+   *
+   * @return string
+   */
+  public static function getPaymentLabel($processor) {
+    $isVisible = FALSE;
+    $paymentTypeLabel = self::getPaymentTypeLabel($processor);
+    foreach (self::getPaymentFieldMetadata(['object' => $processor]) as $paymentField) {
+      if ($paymentField['htmlType'] !== 'hidden') {
+        $isVisible = TRUE;
+      }
+    }
+    return $isVisible ? $paymentTypeLabel : '';
+
+  }
+
 }
index 81bdb6f232eb264a21ebf2f532fa3a1b75c7a2b0..3392a8e371e968977ab504b40d5cdb80130430fd 100644 (file)
@@ -536,7 +536,7 @@ class CRM_Core_PseudoConstant {
     $key = 'id',
     $force = NULL
   ) {
-    $cacheKey = "CRM_PC_{$name}_{$all}_{$key}_{$retrieve}_{$filter}_{$condition}_{$orderby}";
+    $cacheKey = CRM_Core_BAO_Cache::cleanKey("CRM_PC_{$name}_{$all}_{$key}_{$retrieve}_{$filter}_{$condition}_{$orderby}");
     $cache = CRM_Utils_Cache::singleton();
     $var = $cache->get($cacheKey);
     if ($var && empty($force)) {
index f2b2c6357c8c1038b844817d7585473d2d756809..0d6024812cc0c40441ab0acd4a49dd157eedf06c 100644 (file)
@@ -54,7 +54,7 @@ class CRM_Cxn_CiviCxnHttp extends \Civi\Cxn\Rpc\Http\PhpHttp {
     $lowVerb = strtolower($verb);
 
     if ($lowVerb === 'get' && $this->cache) {
-      $cachePath = 'get/' . md5($url);
+      $cachePath = 'get_' . md5($url);
       $cacheLine = $this->cache->get($cachePath);
       if ($cacheLine && $cacheLine['expires'] > CRM_Utils_Time::getTimeRaw()) {
         return $cacheLine['data'];
@@ -66,7 +66,7 @@ class CRM_Cxn_CiviCxnHttp extends \Civi\Cxn\Rpc\Http\PhpHttp {
     if ($lowVerb === 'get' && $this->cache) {
       $expires = CRM_Utils_Http::parseExpiration($result[0]);
       if ($expires !== NULL) {
-        $cachePath = 'get/' . md5($url);
+        $cachePath = 'get_' . md5($url);
         $cacheLine = array(
           'url' => $url,
           'expires' => $expires,
@@ -106,4 +106,11 @@ class CRM_Cxn_CiviCxnHttp extends \Civi\Cxn\Rpc\Http\PhpHttp {
     return $result;
   }
 
+  /**
+   * @return \CRM_Utils_Cache_Interface|null
+   */
+  public function getCache() {
+    return $this->cache;
+  }
+
 }
index 00331ddb28d7a8c2aa3b0ad68a85d22d52ba15f1..cc7df4d31d5f8673e4bd4fb61733395faa3d505c 100644 (file)
@@ -59,9 +59,8 @@ class CRM_Event_Form_Task extends CRM_Core_Form_Task {
 
   /**
    * @param CRM_Core_Form $form
-   * @param bool $useTable
    */
-  public static function preProcessCommon(&$form, $useTable = FALSE) {
+  public static function preProcessCommon(&$form) {
     $form->_participantIds = array();
 
     $values = $form->controller->exportValues($form->get('searchFormName'));
index 8615513566bbc5fa9e738fca291139cce5849c39..38ff7d1951b78812ad7790f25547ecffd6c691b9 100644 (file)
@@ -282,7 +282,7 @@ class CRM_Extension_Mapper {
 
     $moduleExtensions = NULL;
     if ($this->cache && !$fresh) {
-      $moduleExtensions = $this->cache->get($this->cacheKey . '/moduleFiles');
+      $moduleExtensions = $this->cache->get($this->cacheKey . '_moduleFiles');
     }
 
     if (!is_array($moduleExtensions)) {
@@ -315,7 +315,7 @@ class CRM_Extension_Mapper {
       }
 
       if ($this->cache) {
-        $this->cache->set($this->cacheKey . '/moduleFiles', $moduleExtensions);
+        $this->cache->set($this->cacheKey . '_moduleFiles', $moduleExtensions);
       }
     }
     return $moduleExtensions;
@@ -461,7 +461,7 @@ class CRM_Extension_Mapper {
     $this->infos = array();
     $this->moduleExtensions = NULL;
     if ($this->cache) {
-      $this->cache->delete($this->cacheKey . '/moduleFiles');
+      $this->cache->delete($this->cacheKey . '_moduleFiles');
     }
     // FIXME: How can code so code wrong be so right?
     CRM_Extension_System::singleton()->getClassLoader()->refresh();
index 2d650fd8d7e3c5286af9d8a9704a5e373a8d38bd..75e65ba21652abe4a277dabeba5a83319ba7dfc0 100644 (file)
  *
  * @package CRM
  * @copyright CiviCRM LLC (c) 2004-2018
- * $Id$
  *
  */
 
 /**
- * This class contains the funtions for Friend
+ * This class contains the functions for Friend
  *
  */
 class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
+
+  /**
+   * Tell a friend id in db.
+   *
+   * @var int
+   */
+  public $_friendId;
+
   /**
    */
   public function __construct() {
@@ -74,13 +81,9 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
    */
   public static function retrieve(&$params, &$values) {
     $friend = new CRM_Friend_DAO_Friend();
-
     $friend->copyValues($params);
-
     $friend->find(TRUE);
-
     CRM_Core_DAO::storeValues($friend, $values);
-
     return $values;
   }
 
@@ -88,15 +91,17 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
    * Takes an associative array and creates a friend object.
    *
    * @param array $params
-   *   (reference ) an assoc array of name/value pairs.
+   *   (reference) an assoc array of name/value pairs.
    *
-   * @return void
+   * @throws \CRM_Core_Exception
    */
   public static function create(&$params) {
     $transaction = new CRM_Core_Transaction();
 
     $mailParams = array();
-    //create contact corresponding to each friend
+    $contactParams = array();
+
+    // create contact corresponding to each friend
     foreach ($params['friend'] as $key => $details) {
       if ($details["first_name"]) {
         $contactParams[$key] = array(
@@ -111,14 +116,15 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
       }
     }
 
-    $frndParams = array();
-    $frndParams['entity_id'] = $params['entity_id'];
-    $frndParams['entity_table'] = $params['entity_table'];
-    self::getValues($frndParams);
+    $friendParams = [
+      'entity_id' => $params['entity_id'],
+      'entity_table' => $params['entity_table'],
+    ];
+    self::getValues($friendParams);
 
     $activityTypeId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionValue', 'Tell a Friend', 'value', 'name');
 
-    //create activity
+    // create activity
     $activityParams = array(
       'source_contact_id' => $params['source_contact_id'],
       'source_record_id' => NULL,
@@ -127,20 +133,19 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
       'activity_date_time' => date("YmdHis"),
       'subject' => ts('Tell a Friend') . ": {$params['title']}",
       'details' => $params['suggested_message'],
-      'status_id' => 2,
+      'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed'),
       'is_test' => $params['is_test'],
       'campaign_id' => CRM_Utils_Array::value('campaign_id', $params),
     );
 
-    //activity creation
+    // activity creation
     $activity = CRM_Activity_BAO_Activity::create($activityParams);
     $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
     $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts);
 
-    //friend contacts creation
+    // friend contacts creation
     foreach ($contactParams as $key => $value) {
-
-      //create contact only if it does not exits in db
+      // create contact only if it does not exits in db
       $value['email'] = $value['email-Primary'];
       $contactID = CRM_Contact_BAO_Contact::getFirstDuplicateContact($value, 'Individual', 'Supervised', array(), FALSE);
 
@@ -167,28 +172,28 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
 
     $transaction->commit();
 
-    //process sending of mails
+    // Process sending of mails
     $mailParams['title'] = CRM_Utils_Array::value('title', $params);
-    $mailParams['general_link'] = CRM_Utils_Array::value('general_link', $frndParams);
+    $mailParams['general_link'] = CRM_Utils_Array::value('general_link', $friendParams);
     $mailParams['message'] = CRM_Utils_Array::value('suggested_message', $params);
 
-    // get domain
-    $domainDetails = CRM_Core_BAO_Domain::getNameAndEmail();
-    list($username, $mailParams['domain']) = explode('@', $domainDetails[1]);
+    // Default "from email address" is default domain address.
+    // This is normally overridden by one of the if statements below
+    list($_, $mailParams['email_from']) = CRM_Core_BAO_Domain::getNameAndEmail();
+    list($username, $mailParams['domain']) = explode('@', $mailParams['email_from']);
 
     $default = array();
     $findProperties = array('id' => $params['entity_id']);
 
     if ($params['entity_table'] == 'civicrm_contribution_page') {
-
       $returnProperties = array('receipt_from_email', 'is_email_receipt');
       CRM_Core_DAO::commonRetrieve('CRM_Contribute_DAO_ContributionPage',
         $findProperties,
         $default,
         $returnProperties
       );
-      //if is_email_receipt is set then take receipt_from_email
-      //as from_email
+
+      // if is_email_receipt is set then take receipt_from_email as from_email
       if (!empty($default['is_email_receipt']) && !empty($default['receipt_from_email'])) {
         $mailParams['email_from'] = $default['receipt_from_email'];
       }
@@ -197,7 +202,6 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
       $mailParams['module'] = 'contribute';
     }
     elseif ($params['entity_table'] == 'civicrm_event') {
-
       $returnProperties = array('confirm_from_email', 'is_email_confirm');
       CRM_Core_DAO::commonRetrieve('CRM_Event_DAO_Event',
         $findProperties,
@@ -205,10 +209,7 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
         $returnProperties
       );
 
-      $mailParams['email_from'] = $domainDetails['1'];
-
-      //if is_email_confirm is set then take confirm_from_email
-      //as from_email
+      // if is_email_confirm is set then take confirm_from_email as from_email
       if (!empty($default['is_email_confirm']) && !empty($default['confirm_from_email'])) {
         $mailParams['email_from'] = $default['confirm_from_email'];
       }
@@ -226,14 +227,14 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
 
     $mailParams['page_url'] = CRM_Utils_System::url($urlPath, "reset=1&id={$params['entity_id']}", TRUE, NULL, FALSE, TRUE);
 
-    //send mail
+    // Send the email
     self::sendMail($params['source_contact_id'], $mailParams);
   }
 
   /**
    * Build the form object.
    *
-   * @param CRM_Core_Form $form
+   * @param CRM_Friend_Form $form
    *   Form object.
    *
    * @return void
@@ -263,12 +264,12 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
   }
 
   /**
-   * The function sets the deafult values of the form.
+   * The function sets the default values of the form.
    *
    * @param array $defaults
    *   (reference) the default values.
    *
-   * @return booelan
+   * @return bool
    *   whether anything was found
    */
   public static function getValues(&$defaults) {
@@ -283,7 +284,7 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
   }
 
   /**
-   * Process that send tell a friend e-mails
+   * Process that sends tell a friend e-mails
    *
    * @param int $contactID
    * @param array $values
@@ -302,28 +303,27 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
       $values['email_from'] = $email;
     }
 
+    $templateParams = array(
+      'groupName' => 'msg_tpl_workflow_friend',
+      'valueName' => 'friend',
+      'contactId' => $contactID,
+      'tplParams' => array(
+        $values['module'] => $values['module'],
+        'senderContactName' => $fromName,
+        'title' => $values['title'],
+        'generalLink' => $values['general_link'],
+        'pageURL' => $values['page_url'],
+        'senderMessage' => $values['message'],
+      ),
+      'from' => "$fromName (via {$values['domain']}) <{$values['email_from']}>",
+      'replyTo' => $email,
+    );
+
     foreach ($values['email'] as $displayName => $emailTo) {
       if ($emailTo) {
-        // FIXME: factor the below out of the foreach loop
-        CRM_Core_BAO_MessageTemplate::sendTemplate(
-          array(
-            'groupName' => 'msg_tpl_workflow_friend',
-            'valueName' => 'friend',
-            'contactId' => $contactID,
-            'tplParams' => array(
-              $values['module'] => $values['module'],
-              'senderContactName' => $fromName,
-              'title' => $values['title'],
-              'generalLink' => $values['general_link'],
-              'pageURL' => $values['page_url'],
-              'senderMessage' => $values['message'],
-            ),
-            'from' => "$fromName (via {$values['domain']}) <{$values['email_from']}>",
-            'toName' => $displayName,
-            'toEmail' => $emailTo,
-            'replyTo' => $email,
-          )
-        );
+        $templateParams['toName'] = $displayName;
+        $templateParams['toEmail'] = $emailTo;
+        CRM_Core_BAO_MessageTemplate::sendTemplate($templateParams);
       }
     }
   }
@@ -336,16 +336,14 @@ class CRM_Friend_BAO_Friend extends CRM_Friend_DAO_Friend {
    * pairs
    *
    * @param array $params
-   *   (reference ) an assoc array of name/value pairs.
+   *   (reference) an assoc array of name/value pairs.
    *
-   * @return CRM_Friend_BAO_Friend
+   * @return CRM_Friend_DAO_Friend
    */
   public static function addTellAFriend(&$params) {
     $friendDAO = new CRM_Friend_DAO_Friend();
-
     $friendDAO->copyValues($params);
     $friendDAO->is_active = CRM_Utils_Array::value('is_active', $params, FALSE);
-
     $friendDAO->save();
 
     return $friendDAO;
index 7f0329ac49a125208a43ed2d654e8e1d126f2d2e..9b2a9c9f765540efcb5a4c9a63e36c8eb216eadc 100644 (file)
@@ -45,14 +45,21 @@ class CRM_Friend_Form extends CRM_Core_Form {
   const NUM_OPTION = 3;
 
   /**
-   * The id of the entity that we are proceessing.
+   * The id of the entity that we are processing.
    *
    * @var int
    */
   protected $_entityId;
 
   /**
-   * The table name of the entity that we are proceessing.
+   * Tell a friend id in db.
+   *
+   * @var int
+   */
+  public $_friendId;
+
+  /**
+   * The table name of the entity that we are processing.
    *
    * @var string
    */
index fc1e831f98b34b10c25138392cfd20992059e8c5..58fd8f9acffa5d5b89aeed1ca610cebed0824108 100644 (file)
@@ -59,9 +59,8 @@ class CRM_Grant_Form_Task extends CRM_Core_Form_Task {
 
   /**
    * @param CRM_Core_Form $form
-   * @param bool $useTable
    */
-  public static function preProcessCommon(&$form, $useTable = FALSE) {
+  public static function preProcessCommon(&$form) {
     $form->_grantIds = array();
 
     $values = $form->controller->exportValues('Search');
@@ -106,7 +105,7 @@ class CRM_Grant_Form_Task extends CRM_Core_Form_Task {
     $form->_grantIds = $form->_componentIds = $ids;
 
     //set the context for redirection for any task actions
-    $qfKey = CRM_Utils_Request::retrieve('qfKey', 'String', $this);
+    $qfKey = CRM_Utils_Request::retrieve('qfKey', 'String', $form);
     $urlParams = 'force=1';
     if (CRM_Utils_Rule::qfKey($qfKey)) {
       $urlParams .= "&qfKey=$qfKey";
index f05dfdcb830dac242da11adde82d29dcac72c9ed..a8edc042fde030f50750f9def9f3c8e51118e73d 100644 (file)
@@ -81,6 +81,17 @@ class CRM_Group_Form_Search extends CRM_Core_Form {
       NULL, NULL, NULL, NULL, '&nbsp;&nbsp;&nbsp;'
     );
 
+    $componentModes = CRM_Contact_Form_Search::getModeSelect();
+    if (count($componentModes) > 1) {
+      $this->add('select',
+        'component_mode',
+        ts('View Results As'),
+        $componentModes,
+        FALSE,
+        array('class' => 'crm-select2')
+      );
+    }
+
     $this->addButtons(array(
       array(
         'type' => 'refresh',
@@ -97,7 +108,7 @@ class CRM_Group_Form_Search extends CRM_Core_Form {
     $params = $this->controller->exportValues($this->_name);
     $parent = $this->controller->getParent();
     if (!empty($params)) {
-      $fields = array('title', 'created_by', 'group_type', 'visibility', 'active_status', 'inactive_status');
+      $fields = array('title', 'created_by', 'group_type', 'visibility', 'active_status', 'inactive_status', 'component_mode');
       foreach ($fields as $field) {
         if (isset($params[$field]) &&
           !CRM_Utils_System::isNull($params[$field])
index a1d9211f9f06bc262b10335fff835ebb03de6d57..da2e8f1ea65dcc40bb996930532584ee54accb9c 100644 (file)
@@ -54,6 +54,7 @@ class CRM_Group_Page_AJAX {
         'created_by' => 'String',
         'group_type' => 'String',
         'visibility' => 'String',
+        'component_mode' => 'String',
         'status' => 'Integer',
         'parentsOnly' => 'Integer',
         'showOrgInfo' => 'Boolean',
index 8e34d917e12a2350bffe1727c070c04a8560b887..27a46fd693200a7f6b863f5000402840db9351f6 100644 (file)
@@ -1453,7 +1453,7 @@ ORDER BY   civicrm_email.is_bulkmail DESC
     }
     $mailing->domain_id = CRM_Utils_Array::value('domain_id', $params, CRM_Core_Config::domainID());
 
-    if (!isset($params['replyto_email']) &&
+    if (((!$id && empty($params['replyto_email'])) || !isset($params['replyto_email'])) &&
       isset($params['from_email'])
     ) {
       $params['replyto_email'] = $params['from_email'];
index f01e188d8abbeec204e2515664c436d6665b3781..1d3c2d4d18d34bb68d1f70ee20da5ae670494e49 100644 (file)
@@ -118,8 +118,6 @@ class CRM_Mailing_Event_BAO_Confirm extends CRM_Mailing_Event_DAO_Confirm {
 
     $component->find(TRUE);
 
-    $emailDomain = CRM_Core_BAO_MailSettings::defaultDomain();
-
     $html = $component->body_html;
 
     if ($component->body_text) {
@@ -143,11 +141,11 @@ class CRM_Mailing_Event_BAO_Confirm extends CRM_Mailing_Event_DAO_Confirm {
     $mailParams = array(
       'groupName' => 'Mailing Event ' . $component->component_type,
       'subject' => $component->subject,
-      'from' => "\"$domainEmailName\" <do-not-reply@$emailDomain>",
+      'from' => "\"$domainEmailName\" <" . CRM_Core_BAO_Domain::getNoReplyEmailAddress() . '>',
       'toEmail' => $email,
       'toName' => $display_name,
-      'replyTo' => "do-not-reply@$emailDomain",
-      'returnPath' => "do-not-reply@$emailDomain",
+      'replyTo' => CRM_Core_BAO_Domain::getNoReplyEmailAddress(),
+      'returnPath' => CRM_Core_BAO_Domain::getNoReplyEmailAddress(),
       'html' => $html,
       'text' => $text,
     );
index 00d8cd3eac0877e84f70d4ce1911df589bff6928..679822ffe024dc8f04803e22a13420e15a0878d5 100644 (file)
@@ -173,8 +173,6 @@ class CRM_Mailing_Event_BAO_Reply extends CRM_Mailing_Event_DAO_Reply {
       }
     }
     else {
-      $emailDomain = CRM_Core_BAO_MailSettings::defaultDomain();
-
       if (empty($eq->display_name)) {
         $from = $eq->email;
       }
@@ -189,7 +187,7 @@ class CRM_Mailing_Event_BAO_Reply extends CRM_Mailing_Event_DAO_Reply {
         'To' => $mailing->replyto_email,
         'From' => $from,
         'Reply-To' => empty($replyto) ? $eq->email : $replyto,
-        'Return-Path' => "do-not-reply@{$emailDomain}",
+        'Return-Path' => CRM_Core_BAO_Domain::getNoReplyEmailAddress(),
         // CRM-17754 Include re-sent headers to indicate that we have forwarded on the email
         'Resent-From' => $domainValues['values'][0]['from_email'],
         'Resent-Date' => date('r'),
@@ -253,14 +251,12 @@ class CRM_Mailing_Event_BAO_Reply extends CRM_Mailing_Event_DAO_Reply {
     $domain = CRM_Core_BAO_Domain::getDomain();
     list($domainEmailName, $_) = CRM_Core_BAO_Domain::getNameAndEmail();
 
-    $emailDomain = CRM_Core_BAO_MailSettings::defaultDomain();
-
     $headers = array(
       'Subject' => $component->subject,
       'To' => $to,
-      'From' => "\"$domainEmailName\" <do-not-reply@$emailDomain>",
-      'Reply-To' => "do-not-reply@$emailDomain",
-      'Return-Path' => "do-not-reply@$emailDomain",
+      'From' => "\"$domainEmailName\" <" . CRM_Core_BAO_Domain::getNoReplyEmailAddress() . '>',
+      'Reply-To' => CRM_Core_BAO_Domain::getNoReplyEmailAddress(),
+      'Return-Path' => CRM_Core_BAO_Domain::getNoReplyEmailAddress(),
     );
 
     // TODO: do we need reply tokens?
index 483f35641d2d40507fefba4a048524afdf9a0023..360bd36bfec2f85a978dd4543875aae78ecfa77f 100644 (file)
@@ -267,14 +267,12 @@ class CRM_Mailing_Event_BAO_Resubscribe {
       $message->setTxtBody($text);
     }
 
-    $emailDomain = CRM_Core_BAO_MailSettings::defaultDomain();
-
     $headers = array(
       'Subject' => $component->subject,
-      'From' => "\"$domainEmailName\" <do-not-reply@$emailDomain>",
+      'From' => "\"$domainEmailName\" <" . CRM_Core_BAO_Domain::getNoReplyEmailAddress() . '>',
       'To' => $eq->email,
-      'Reply-To' => "do-not-reply@$emailDomain",
-      'Return-Path' => "do-not-reply@$emailDomain",
+      'Reply-To' => CRM_Core_BAO_Domain::getNoReplyEmailAddress(),
+      'Return-Path' => CRM_Core_BAO_Domain::getNoReplyEmailAddress(),
     );
     CRM_Mailing_BAO_Mailing::addMessageIdHeader($headers, 'e', $job, $queue_id, $eq->hash);
     $b = CRM_Utils_Mail::setMimeParams($message);
index d3257557b7597bc677829fbcfa97cbf28b50a68e..d82776413b4c2cfe13914b8a551e14cce528c90f 100644 (file)
@@ -227,7 +227,7 @@ SELECT     civicrm_email.id as email_id
       'From' => "\"{$domainEmailName}\" <{$domainEmailAddress}>",
       'To' => $email,
       'Reply-To' => $confirm,
-      'Return-Path' => "do-not-reply@$emailDomain",
+      'Return-Path' => CRM_Core_BAO_Domain::getNoReplyEmailAddress(),
     );
 
     $url = CRM_Utils_System::url('civicrm/mailing/confirm',
index 76f3fef7fb6aefa4930e8e87e175926187e0f065..aa6950cb8c8d8be61cb0179636ceb8afb3993aab 100644 (file)
@@ -418,10 +418,10 @@ WHERE  email = %2
 
     $headers = array(
       'Subject' => $component->subject,
-      'From' => "\"$domainEmailName\" <do-not-reply@$emailDomain>",
+      'From' => "\"$domainEmailName\" <" . CRM_Core_BAO_Domain::getNoReplyEmailAddress() . '>',
       'To' => $eq->email,
-      'Reply-To' => "do-not-reply@$emailDomain",
-      'Return-Path' => "do-not-reply@$emailDomain",
+      'Reply-To' => CRM_Core_BAO_Domain::getNoReplyEmailAddress(),
+      'Return-Path' => CRM_Core_BAO_Domain::getNoReplyEmailAddress(),
     );
     CRM_Mailing_BAO_Mailing::addMessageIdHeader($headers, 'u', $job, $queue_id, $eq->hash);
 
index c241e191dc6f1148ebd37804c34219858435d0c0..dc9290be3fccbc53436f99f3900fefeb0068d9e8 100644 (file)
@@ -46,9 +46,8 @@ class CRM_Mailing_Form_Task extends CRM_Core_Form_Task {
 
   /**
    * @param CRM_Core_Form $form
-   * @param bool $useTable
    */
-  public static function preProcessCommon(&$form, $useTable = FALSE) {
+  public static function preProcessCommon(&$form) {
     $values = $form->controller->exportValues($form->get('searchFormName'));
 
     $form->_task = CRM_Utils_Array::value('task', $values);
index 66c9cd2a8e9787d72096d645889e70fef5e22df0..796e047a0731b3b474d446b7240f668795a69e39 100644 (file)
@@ -807,41 +807,39 @@ class CRM_Member_BAO_MembershipType extends CRM_Member_DAO_MembershipType {
    * @param array $params
    */
   public static function updateAllPriceFieldValue($membershipTypeId, $params) {
-    $updateFields = array();
-    if (!empty($params['minimum_fee'])) {
-      $amount = $params['minimum_fee'];
-    }
-    else {
-      $amount = 0;
-    }
-
-    $updateValues = array(
-      2 => array('financial_type_id', 'financial_type_id', 'Integer'),
-      3 => array('label', 'name', 'String'),
-      4 => array('amount', 'minimum_fee', 'Float'),
-      5 => array('description', 'description', 'String'),
+    $defaults = array();
+    $fieldsToUpdate = array(
+      'financial_type_id' => 'financial_type_id',
+      'name' => 'label',
+      'minimum_fee' => 'amount',
+      'description' => 'description',
+      'visibility' => 'visibility_id',
     );
-
-    $queryParams = array(1 => array($membershipTypeId, 'Integer'));
-    foreach ($updateValues as $key => $value) {
-      if (array_key_exists($value[1], $params)) {
-        $updateFields[] = "cpfv." . $value[0] . " = %$key";
-        if ($value[1] == 'minimum_fee') {
-          $fieldValue = $amount;
-        }
-        else {
-          $fieldValue = $params[$value[1]];
+    $priceFieldValueBAO = new CRM_Price_BAO_PriceFieldValue();
+    $priceFieldValueBAO->membership_type_id = $membershipTypeId;
+    $priceFieldValueBAO->find();
+    while ($priceFieldValueBAO->fetch()) {
+      $updateParams = array(
+        'id' => $priceFieldValueBAO->id,
+        'price_field_id' => $priceFieldValueBAO->price_field_id,
+      );
+      //Get priceset details.
+      $fieldParams = array('fid' => $priceFieldValueBAO->price_field_id);
+      $setID = CRM_Price_BAO_PriceSet::getSetId($fieldParams);
+      $setParams = array('id' => $setID);
+      $setValues = CRM_Price_BAO_PriceSet::retrieve($setParams, $defaults);
+      if (!empty($setValues->is_quick_config) && $setValues->name != 'default_membership_type_amount') {
+        foreach ($fieldsToUpdate as $key => $value) {
+          if ($value == 'visibility_id' && !empty($params['visibility'])) {
+            $updateParams['visibility_id'] = CRM_Price_BAO_PriceField::getVisibilityOptionID(strtolower($params['visibility']));
+          }
+          else {
+            $updateParams[$value] = CRM_Utils_Array::value($key, $params);
+          }
         }
-        $queryParams[$key] = array($fieldValue, $value[2]);
+        CRM_Price_BAO_PriceFieldValue::add($updateParams);
       }
     }
-
-    $query = "UPDATE `civicrm_price_field_value` cpfv
-INNER JOIN civicrm_price_field cpf on cpf.id = cpfv.price_field_id
-INNER JOIN civicrm_price_set cps on cps.id = cpf.price_set_id
-SET " . implode(' , ', $updateFields) . " WHERE cpfv.membership_type_id = %1
-AND cps.is_quick_config = 1 AND cps.name != 'default_membership_type_amount'";
-    CRM_Core_DAO::executeQuery($query, $queryParams);
   }
 
 }
index a093365a0e2ef778c257c9d148eaf4658c487317..e8c6e9bfe11c5316be1eb8095dec030bcd1fce99 100644 (file)
@@ -59,9 +59,8 @@ class CRM_Member_Form_Task extends CRM_Core_Form_Task {
 
   /**
    * @param CRM_Core_Form $form
-   * @param bool $useTable
    */
-  public static function preProcessCommon(&$form, $useTable = FALSE) {
+  public static function preProcessCommon(&$form) {
     $form->_memberIds = array();
 
     $values = $form->controller->exportValues($form->get('searchFormName'));
index 67ef43d1d2ccb82774bd158f705e99620b82557d..67b2b93938145ab593879302bde2485075ddf8f9 100644 (file)
@@ -112,14 +112,18 @@ class CRM_Member_Page_RecurringContributions extends CRM_Core_Page {
    */
   private function setActionsForRecurringContribution($recurID, &$recurringContribution) {
     $action = array_sum(array_keys($this->recurLinks($recurID)));
+
     // no action allowed if it's not active
     $recurringContribution['is_active'] = ($recurringContribution['contribution_status_id'] != 3);
+
     if ($recurringContribution['is_active']) {
       $details = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($recurringContribution['id'], 'recur');
       $hideUpdate = $details->membership_id & $details->auto_renew;
-      if ($hideUpdate || empty($details->processor_id)) {
+
+      if ($hideUpdate) {
         $action -= CRM_Core_Action::UPDATE;
       }
+
       $recurringContribution['action'] = CRM_Core_Action::formLink(
         $this->recurLinks($recurID),
         $action,
index a8843f6e6c91202149766a9a28b75f890d91fb12..572dbae0114dca2ae92a1b171d91be870cb50a77 100644 (file)
@@ -102,6 +102,16 @@ class CRM_Member_Task extends CRM_Core_Task {
           'class' => 'CRM_Member_Form_Task_PDFLetter',
           'result' => FALSE,
         ),
+        self::SAVE_SEARCH => array(
+          'title' => ts('Group - create smart group'),
+          'class' => 'CRM_Contact_Form_Task_SaveSearch',
+          'result' => TRUE,
+        ),
+        self::SAVE_SEARCH_UPDATE => array(
+          'title' => ts('Group - update smart group'),
+          'class' => 'CRM_Contact_Form_Task_SaveSearch_Update',
+          'result' => TRUE,
+        ),
       );
 
       //CRM-4418, check for delete
index dd5946e5c7a16cfcf622d9a540a912d7b483eee6..4a8b85561449a409c5fc3beac7be79bbe4698658 100644 (file)
@@ -55,9 +55,8 @@ class CRM_Pledge_Form_Task extends CRM_Core_Form_Task {
    * Common pre-processing.
    *
    * @param CRM_Core_Form $form
-   * @param bool $useTable
    */
-  public static function preProcessCommon(&$form, $useTable = FALSE) {
+  public static function preProcessCommon(&$form) {
     $form->_pledgeIds = array();
 
     $values = $form->controller->exportValues('Search');
index e745e93f63dae58e231d8f8660c194c509e6ff6e..802c4319c91f7350eac415b1d8bb1cd715ee0b9f 100644 (file)
@@ -2450,7 +2450,7 @@ WHERE cg.extends IN ('" . implode("','", $this->_customGroupExtends) . "') AND
    * @return mixed
    */
   protected function alterBoolean($value) {
-    $options = array(0 => ts('No'), 1 => ts('Yes'));
+    $options = array(0 => '', 1 => ts('Yes'));
     if (isset($options[$value])) {
       return $options[$value];
     }
index 088e2d730e85975ef1e7305e83e25915982953e4..8e4c25d1e3ddc6af814d04007b890892837b074b 100644 (file)
@@ -301,6 +301,10 @@ class CRM_Report_Form_Contribute_Bookkeeping extends CRM_Report_Form {
             'title' => ts('Contribution Status'),
             'default' => TRUE,
           ),
+          'contribution_source' => array(
+            'title' => ts('Source'),
+            'name' => 'source',
+          ),
           'id' => array(
             'title' => ts('Contribution ID'),
             'default' => TRUE,
@@ -314,6 +318,11 @@ class CRM_Report_Form_Contribute_Bookkeeping extends CRM_Report_Form {
             'type' => CRM_Utils_Type::T_INT,
           ),
           'receive_date' => array('operatorType' => CRM_Report_Form::OP_DATE),
+          'contribution_source' => array(
+            'title' => ts('Source'),
+            'name' => 'source',
+            'type' => CRM_Utils_Type::T_STRING,
+          ),
           'contribution_status_id' => array(
             'title' => ts('Contribution Status'),
             'operatorType' => CRM_Report_Form::OP_MULTISELECT,
index ad0a5717f9a6bda8369e4de5593def3be6fe234b..e47d96cf832499a74dde24f87daee56b4da22b83 100644 (file)
@@ -1 +1,13 @@
 {* file to handle db changes in 5.4.alpha1 during upgrade *}
+
+{*
+v4.7.20 updated these colums so that new installs would default to TIMESTAMP instead of DATETIME.
+Status-checks and DoctorWhen have been encouraging a transition, but it wasn't mandated, and there
+was little urgency... because `expired_date` was ignored, and adhoc TTLs on `created_date` had
+generally long windows. Now that we're using `expired_date` in more important ways for 5.4,
+we want to ensure that these values are handled precisely and consistently.
+*}
+
+ALTER TABLE civicrm_cache
+  CHANGE created_date created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'When was the cache item created',
+  CHANGE expired_date expired_date  TIMESTAMP NULL DEFAULT NULL COMMENT 'When should the cache item expire';
index 00a92888fc8b7eaec46a99a0e5508f9f6854e6c6..171e4d54374c62db9af0a237900dfb1a72fa93fd 100644 (file)
@@ -35,6 +35,9 @@
  * Cache is an empty base object, we'll modify the scheme when we have different caching schemes
  */
 class CRM_Utils_Cache {
+
+  const DELIMITER = '/';
+
   /**
    * (Quasi-Private) Treat this as private. It is marked public to facilitate testing.
    *
@@ -83,6 +86,7 @@ class CRM_Utils_Cache {
       // a generic method for utilizing any of the available db caches.
       $dbCacheClass = 'CRM_Utils_Cache_' . $className;
       $settings = self::getCacheSettings($className);
+      $settings['prefix'] = CRM_Utils_Array::value('prefix', $settings, '') . self::DELIMITER . 'default' . self::DELIMITER;
       self::$_singleton = new $dbCacheClass($settings);
     }
     return self::$_singleton;
@@ -186,7 +190,7 @@ class CRM_Utils_Cache {
           if (defined('CIVICRM_DB_CACHE_CLASS') && in_array(CIVICRM_DB_CACHE_CLASS, array('Memcache', 'Memcached', 'Redis'))) {
             $dbCacheClass = 'CRM_Utils_Cache_' . CIVICRM_DB_CACHE_CLASS;
             $settings = self::getCacheSettings(CIVICRM_DB_CACHE_CLASS);
-            $settings['prefix'] = $settings['prefix'] . '_' . $params['name'];
+            $settings['prefix'] = CRM_Utils_Array::value('prefix', $settings, '') . self::DELIMITER . $params['name'] . self::DELIMITER;
             return new $dbCacheClass($settings);
           }
           break;
@@ -210,4 +214,30 @@ class CRM_Utils_Cache {
     throw new CRM_Core_Exception("Failed to instantiate cache. No supported cache type found. " . print_r($params, 1));
   }
 
+  /**
+   * Assert that a key is well-formed.
+   *
+   * @param string $key
+   * @return string
+   *   Same $key, if it's valid.
+   * @throws \CRM_Utils_Cache_InvalidArgumentException
+   */
+  public static function assertValidKey($key) {
+    $strict = CRM_Utils_Constant::value('CIVICRM_PSR16_STRICT', FALSE) || defined('CIVICRM_TEST');
+
+    if (!is_string($key)) {
+      throw new CRM_Utils_Cache_InvalidArgumentException("Invalid cache key: Not a string");
+    }
+
+    if ($strict && !preg_match(';^[A-Za-z0-9_\-\. ]+$;', $key)) {
+      throw new CRM_Utils_Cache_InvalidArgumentException("Invalid cache key: Illegal characters");
+    }
+
+    if ($strict && strlen($key) > 255) {
+      throw new CRM_Utils_Cache_InvalidArgumentException("Invalid cache key: Too long");
+    }
+
+    return $key;
+  }
+
 }
index e696aa19c3d2cd884fe2f69fab52070918f9caf0..26fa86b24166bd6321aae2a44dbe0b8c7fd612f6 100644 (file)
  * @copyright CiviCRM LLC (c) 2004-2018
  */
 class CRM_Utils_Cache_APCcache implements CRM_Utils_Cache_Interface {
+
+  use CRM_Utils_Cache_NaiveMultipleTrait; // TODO Consider native implementation.
+  use CRM_Utils_Cache_NaiveHasTrait; // TODO Native implementation
+
   const DEFAULT_TIMEOUT = 3600;
   const DEFAULT_PREFIX = '';
 
@@ -72,11 +76,19 @@ class CRM_Utils_Cache_APCcache implements CRM_Utils_Cache_Interface {
   /**
    * @param $key
    * @param $value
+   * @param null|int|\DateInterval $ttl
    *
    * @return bool
    */
-  public function set($key, &$value) {
-    if (!apc_store($this->_prefix . $key, $value, $this->_timeout)) {
+  public function set($key, $value, $ttl = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
+    if (is_int($ttl) && $ttl <= 0) {
+      return $this->delete($key);
+    }
+
+    $ttl = CRM_Utils_Date::convertCacheTtl($ttl, $this->_timeout);
+    $expires = time() + $ttl;
+    if (!apc_store($this->_prefix . $key, ['e' => $expires, 'v' => $value], $ttl)) {
       return FALSE;
     }
     return TRUE;
@@ -84,11 +96,17 @@ class CRM_Utils_Cache_APCcache implements CRM_Utils_Cache_Interface {
 
   /**
    * @param $key
+   * @param mixed $default
    *
    * @return mixed
    */
-  public function get($key) {
-    return apc_fetch($this->_prefix . $key);
+  public function get($key, $default = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
+    $result = apc_fetch($this->_prefix . $key, $success);
+    if ($success && isset($result['e']) && $result['e'] > time()) {
+      return $this->reobjectify($result['v']);
+    }
+    return $default;
   }
 
   /**
@@ -97,22 +115,33 @@ class CRM_Utils_Cache_APCcache implements CRM_Utils_Cache_Interface {
    * @return bool|string[]
    */
   public function delete($key) {
-    return apc_delete($this->_prefix . $key);
+    CRM_Utils_Cache::assertValidKey($key);
+    apc_delete($this->_prefix . $key);
+    return TRUE;
   }
 
   public function flush() {
     $allinfo = apc_cache_info('user');
     $keys = $allinfo['cache_list'];
-    $prefix = $this->_prefix . "CRM_";  // Our keys follows this pattern: ([A-Za-z0-9_]+)?CRM_[A-Za-z0-9_]+
+    $prefix = $this->_prefix;  // Our keys follows this pattern: ([A-Za-z0-9_]+)?CRM_[A-Za-z0-9_]+
     $lp = strlen($prefix);              // Get prefix length
 
     foreach ($keys as $key) {
       $name = $key['info'];
       if ($prefix == substr($name, 0, $lp)) {
         // Ours?
-        apc_delete($this->_prefix . $name);
+        apc_delete($name);
       }
     }
+    return TRUE;
+  }
+
+  public function clear() {
+    return $this->flush();
+  }
+
+  private function reobjectify($value) {
+    return is_object($value) ? unserialize(serialize($value)) : $value;
   }
 
 }
index b2272fa1eb6c9879f5a195ccad2e2a5426a00291..287c6a3e5ce157870389971fb58800817ef1e427 100644 (file)
  */
 class CRM_Utils_Cache_Arraycache implements CRM_Utils_Cache_Interface {
 
+  use CRM_Utils_Cache_NaiveMultipleTrait;
+  use CRM_Utils_Cache_NaiveHasTrait; // TODO Native implementation
+
+  const DEFAULT_TIMEOUT = 3600;
+
   /**
    * The cache storage container, an in memory array by default
    */
   protected $_cache;
 
+  protected $_expires;
+
   /**
    * Constructor.
    *
@@ -51,35 +58,67 @@ class CRM_Utils_Cache_Arraycache implements CRM_Utils_Cache_Interface {
    */
   public function __construct($config) {
     $this->_cache = array();
+    $this->_expires = array();
   }
 
   /**
    * @param string $key
    * @param mixed $value
+   * @param null|int|\DateInterval $ttl
+   * @return bool
+   * @throws \Psr\SimpleCache\InvalidArgumentException
    */
-  public function set($key, &$value) {
-    $this->_cache[$key] = $value;
+  public function set($key, $value, $ttl = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
+    $this->_cache[$key] = $this->reobjectify($value);
+    $this->_expires[$key] = CRM_Utils_Date::convertCacheTtlToExpires($ttl, self::DEFAULT_TIMEOUT);
+    return TRUE;
   }
 
   /**
    * @param string $key
+   * @param mixed $default
    *
    * @return mixed
+   * @throws \Psr\SimpleCache\InvalidArgumentException
    */
-  public function get($key) {
-    return CRM_Utils_Array::value($key, $this->_cache);
+  public function get($key, $default = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
+    if (isset($this->_expires[$key]) && is_numeric($this->_expires[$key]) && $this->_expires[$key] <= time()) {
+      return $default;
+    }
+    if (array_key_exists($key, $this->_cache)) {
+      return $this->reobjectify($this->_cache[$key]);
+    }
+    return $default;
   }
 
   /**
    * @param string $key
+   * @return bool
+   * @throws \Psr\SimpleCache\InvalidArgumentException
    */
   public function delete($key) {
+    CRM_Utils_Cache::assertValidKey($key);
+
     unset($this->_cache[$key]);
+    unset($this->_expires[$key]);
+    return TRUE;
   }
 
   public function flush() {
     unset($this->_cache);
+    unset($this->_expires);
     $this->_cache = array();
+    return TRUE;
+  }
+
+  public function clear() {
+    return $this->flush();
+  }
+
+  private function reobjectify($value) {
+    return is_object($value) ? unserialize(serialize($value)) : $value;
   }
 
 }
diff --git a/CRM/Utils/Cache/CacheException.php b/CRM/Utils/Cache/CacheException.php
new file mode 100644 (file)
index 0000000..cdb0821
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5                                                  |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2018                                |
+ +--------------------------------------------------------------------+
+ | 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        |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * Class CRM_Utils_Cache_CacheException
+ *
+ * NOTE: PSR-16 specifies its exceptions using interfaces. For cache-consumers,
+ * it's better to catch based on the interface. For cache-drivers, we need
+ * a concrete class.
+ */
+class CRM_Utils_Cache_CacheException extends \CRM_Core_Exception implements \Psr\SimpleCache\CacheException {
+}
index f11327cb59c730d4f85eaab883e8a1d5c027efcd..9effdd9d3aa6581d490fe13e015e56aa3940b6ca 100644 (file)
  * @package CRM
  * @copyright CiviCRM LLC (c) 2004-2018
  *
- * CRM_Utils_Cache_Interface
+ * CRM_Utils_Cache_Interface is a long-standing interface used within CiviCRM
+ * for interacting with a cache service. In style and substance, it is extremely
+ * similar to PHP-FIG's SimpleCache interface (PSR-16). Consequently, beginning
+ * with CiviCRM v5.4, this extends \Psr\SimpleCache\CacheInterface.
  *
- * PHP-FIG has been developing a draft standard for caching,
- * PSR-6. The standard has not been ratified yet. When
- * making changes to this interface, please take care to
- * avoid *conflicst* with PSR-6's CacheItemPoolInterface. At
- * time of writing, they do not conflict. Avoiding conflicts
- * will enable more transition paths where Civi
- * simultaneously supports both interfaces in the same
- * implementation.
- *
- * For example, the current interface defines:
- *
- *   function get($key) => mixed $value
- *
- * and PSR-6 defines:
- *
- *   function getItem($key) => ItemInterface $item
- *
- * These are different styles (e.g. "weak item" vs "strong item"),
- * but the two methods do not *conflict*. They can coexist,
- * and you can trivially write adapters between the two.
- *
- * @see https://github.com/php-fig/fig-standards/blob/master/proposed/cache.md
+ * @see https://www.php-fig.org/psr/psr-16/
  */
-interface CRM_Utils_Cache_Interface {
+interface CRM_Utils_Cache_Interface extends \Psr\SimpleCache\CacheInterface {
 
   /**
    * Set the value in the cache.
    *
    * @param string $key
    * @param mixed $value
+   * @param null|int|\DateInterval $ttl
+   * @return bool
    */
-  public function set($key, &$value);
+  public function set($key, $value, $ttl = NULL);
 
   /**
    * Get a value from the cache.
    *
    * @param string $key
+   * @param mixed $default
    * @return mixed
-   *   NULL if $key has not been previously set
+   *   The previously set value value, or $default (NULL).
    */
-  public function get($key);
+  public function get($key, $default = NULL);
 
   /**
    * Delete a value from the cache.
    *
    * @param string $key
+   * @return bool
    */
   public function delete($key);
 
   /**
    * Delete all values from the cache.
+   *
+   * NOTE: flush() and clear() should be aliases. flush() is specified by
+   * Civi's traditional interface, and clear() is specified by PSR-16.
+   *
+   * @return bool
+   * @see clear
+   * @deprecated
    */
   public function flush();
 
+  /**
+   * Delete all values from the cache.
+   *
+   * NOTE: flush() and clear() should be aliases. flush() is specified by
+   * Civi's traditional interface, and clear() is specified by PSR-16.
+   *
+   * @return bool
+   * @see flush
+   */
+  public function clear();
+
+  /**
+   * Determines whether an item is present in the cache.
+   *
+   * NOTE: It is recommended that has() is only to be used for cache warming type purposes
+   * and not to be used within your live applications operations for get/set, as this method
+   * is subject to a race condition where your has() will return true and immediately after,
+   * another script can remove it making the state of your app out of date.
+   *
+   * @param string $key The cache item key.
+   *
+   * @return bool
+   */
+  public function has($key);
+
 }
diff --git a/CRM/Utils/Cache/InvalidArgumentException.php b/CRM/Utils/Cache/InvalidArgumentException.php
new file mode 100644 (file)
index 0000000..5d9747d
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5                                                  |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2018                                |
+ +--------------------------------------------------------------------+
+ | 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        |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * Class CRM_Utils_Cache_InvalidArgumentException
+ *
+ * NOTE: PSR-16 specifies its exceptions using interfaces. For cache-consumers,
+ * it's better to catch based on the interface. For cache-drivers, we need
+ * a concrete class.
+ */
+class CRM_Utils_Cache_InvalidArgumentException extends \CRM_Core_Exception implements \Psr\SimpleCache\InvalidArgumentException {
+}
index e7455bd498757afcbb9d9d538ae0d33b91293106..5bbdf5e0e6c69adc0cf538d6f9e68aa8dfcdca88 100644 (file)
  * @copyright CiviCRM LLC (c) 2004-2018
  */
 class CRM_Utils_Cache_Memcache implements CRM_Utils_Cache_Interface {
+
+  use CRM_Utils_Cache_NaiveMultipleTrait; // TODO Consider native implementation.
+
   const DEFAULT_HOST = 'localhost';
   const DEFAULT_PORT = 11211;
   const DEFAULT_TIMEOUT = 3600;
   const DEFAULT_PREFIX = '';
 
+  /**
+   * If another process clears namespace, we'll find out in ~5 sec.
+   */
+  const NS_LOCAL_TTL = 5;
+
   /**
    * The host name of the memcached server.
    *
@@ -75,6 +83,15 @@ class CRM_Utils_Cache_Memcache implements CRM_Utils_Cache_Interface {
    */
   protected $_cache;
 
+  /**
+   * @var NULL|array
+   *
+   * This is the effective prefix. It may be bumped up whenever the dataset is flushed.
+   *
+   * @see https://github.com/memcached/memcached/wiki/ProgrammingTricks#deleting-by-namespace
+   */
+  protected $_truePrefix = NULL;
+
   /**
    * Constructor.
    *
@@ -109,40 +126,82 @@ class CRM_Utils_Cache_Memcache implements CRM_Utils_Cache_Interface {
   /**
    * @param $key
    * @param $value
+   * @param null|int|\DateInterval $ttl
    *
    * @return bool
    */
-  public function set($key, &$value) {
-    if (!$this->_cache->set($this->_prefix . $key, $value, FALSE, $this->_timeout)) {
-      return FALSE;
+  public function set($key, $value, $ttl = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
+    if (is_int($ttl) && $ttl <= 0) {
+      return $this->delete($key);
     }
-    return TRUE;
+    $expires = CRM_Utils_Date::convertCacheTtlToExpires($ttl, $this->_timeout);
+    return $this->_cache->set($this->getTruePrefix() . $key, serialize($value), FALSE, $expires);
   }
 
   /**
    * @param $key
+   * @param mixed $default
    *
    * @return mixed
    */
-  public function &get($key) {
-    $result = $this->_cache->get($this->_prefix . $key);
-    return $result;
+  public function get($key, $default = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
+    $result = $this->_cache->get($this->getTruePrefix() . $key);
+    return ($result === FALSE) ? $default : unserialize($result);
   }
 
+  /**
+   * @param string $key
+   *
+   * @return bool
+   * @throws \Psr\SimpleCache\CacheException
+   */
+  public function has($key) {
+    CRM_Utils_Cache::assertValidKey($key);
+    $result = $this->_cache->get($this->getTruePrefix() . $key);
+    return ($result !== FALSE);
+  }
+
+
   /**
    * @param $key
    *
-   * @return mixed
+   * @return bool
    */
   public function delete($key) {
-    return $this->_cache->delete($this->_prefix . $key);
+    CRM_Utils_Cache::assertValidKey($key);
+    $this->_cache->delete($this->getTruePrefix() . $key);
+    return TRUE;
   }
 
   /**
-   * @return mixed
+   * @return bool
    */
   public function flush() {
-    return $this->_cache->flush();
+    $this->_truePrefix = NULL;
+    $this->_cache->delete($this->_prefix);
+    return TRUE;
+  }
+
+  public function clear() {
+    return $this->flush();
+  }
+
+  protected function getTruePrefix() {
+    if ($this->_truePrefix === NULL || $this->_truePrefix['expires'] < time()) {
+      $key = $this->_prefix;
+      $value = $this->_cache->get($key);
+      if ($value === FALSE) {
+        $value = uniqid();
+        $this->_cache->set($key, $value, FALSE, 0); // Indefinite.
+      }
+      $this->_truePrefix = [
+        'value' => $value,
+        'expires' => time() + self::NS_LOCAL_TTL,
+      ];
+    }
+    return $this->_prefix . $this->_truePrefix['value'] . '/';
   }
 
 }
index e80b1af4b354cdae264b157fd1b0b31d30d71201..546c5b933b9a0b006c478ed15f8895dfa0b41219 100644 (file)
  * @copyright CiviCRM LLC (c) 2004-2018
  */
 class CRM_Utils_Cache_Memcached implements CRM_Utils_Cache_Interface {
+
+  use CRM_Utils_Cache_NaiveMultipleTrait; // TODO Consider native implementation.
+
   const DEFAULT_HOST = 'localhost';
   const DEFAULT_PORT = 11211;
   const DEFAULT_TIMEOUT = 3600;
   const DEFAULT_PREFIX = '';
-  const MAX_KEY_LEN = 62;
+  const MAX_KEY_LEN = 200;
+
+  /**
+   * If another process clears namespace, we'll find out in ~5 sec.
+   */
+  const NS_LOCAL_TTL = 5;
 
   /**
    * The host name of the memcached server
@@ -76,6 +84,15 @@ class CRM_Utils_Cache_Memcached implements CRM_Utils_Cache_Interface {
    */
   protected $_cache;
 
+  /**
+   * @var NULL|array
+   *
+   * This is the effective prefix. It may be bumped up whenever the dataset is flushed.
+   *
+   * @see https://github.com/memcached/memcached/wiki/ProgrammingTricks#deleting-by-namespace
+   */
+  protected $_truePrefix = NULL;
+
   /**
    * Constructor.
    *
@@ -110,29 +127,79 @@ class CRM_Utils_Cache_Memcached implements CRM_Utils_Cache_Interface {
   /**
    * @param $key
    * @param $value
+   * @param null|int|\DateInterval $ttl
    *
    * @return bool
    * @throws Exception
    */
-  public function set($key, &$value) {
+  public function set($key, $value, $ttl = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
+    if (is_int($ttl) && $ttl <= 0) {
+      return $this->delete($key);
+    }
+    $expires = CRM_Utils_Date::convertCacheTtlToExpires($ttl, $this->_timeout);
+
     $key = $this->cleanKey($key);
-    if (!$this->_cache->set($key, $value, $this->_timeout)) {
-      CRM_Core_Error::debug('Result Code: ', $this->_cache->getResultMessage());
-      CRM_Core_Error::fatal("memcached set failed, wondering why?, $key", $value);
+    if (!$this->_cache->set($key, serialize($value), $expires)) {
+      if (PHP_SAPI === 'cli' || (Civi\Core\Container::isContainerBooted() && CRM_Core_Permission::check('view debug output'))) {
+        throw new CRM_Utils_Cache_CacheException("Memcached::set($key) failed: " . $this->_cache->getResultMessage());
+      }
+      else {
+        Civi::log()->error("Memcached::set($key) failed: " . $this->_cache->getResultMessage());
+        throw new CRM_Utils_Cache_CacheException("Memcached::set($key) failed");
+      }
       return FALSE;
+
     }
     return TRUE;
   }
 
   /**
    * @param $key
+   * @param mixed $default
    *
    * @return mixed
    */
-  public function &get($key) {
+  public function get($key, $default = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
     $key = $this->cleanKey($key);
     $result = $this->_cache->get($key);
-    return $result;
+    switch ($this->_cache->getResultCode()) {
+      case Memcached::RES_SUCCESS:
+        return unserialize($result);
+
+      case Memcached::RES_NOTFOUND:
+        return $default;
+
+      default:
+        Civi::log()->error("Memcached::get($key) failed: " . $this->_cache->getResultMessage());
+        throw new CRM_Utils_Cache_CacheException("Memcached set ($key) failed");
+    }
+  }
+
+  /**
+   * @param string $key
+   *
+   * @return bool
+   * @throws \Psr\SimpleCache\CacheException
+   */
+  public function has($key) {
+    CRM_Utils_Cache::assertValidKey($key);
+    $key = $this->cleanKey($key);
+    if ($this->_cache->get($key) !== FALSE) {
+      return TRUE;
+    }
+    switch ($this->_cache->getResultCode()) {
+      case Memcached::RES_NOTFOUND:
+        return FALSE;
+
+      case Memcached::RES_SUCCESS:
+        return TRUE;
+
+      default:
+        Civi::log()->error("Memcached::has($key) failed: " . $this->_cache->getResultMessage());
+        throw new CRM_Utils_Cache_CacheException("Memcached set ($key) failed");
+    }
   }
 
   /**
@@ -141,8 +208,13 @@ class CRM_Utils_Cache_Memcached implements CRM_Utils_Cache_Interface {
    * @return mixed
    */
   public function delete($key) {
+    CRM_Utils_Cache::assertValidKey($key);
     $key = $this->cleanKey($key);
-    return $this->_cache->delete($key);
+    if ($this->_cache->delete($key)) {
+      return TRUE;
+    }
+    $code = $this->_cache->getResultCode();
+    return ($code == Memcached::RES_DELETED || $code == Memcached::RES_NOTFOUND);
   }
 
   /**
@@ -151,20 +223,47 @@ class CRM_Utils_Cache_Memcached implements CRM_Utils_Cache_Interface {
    * @return mixed|string
    */
   public function cleanKey($key) {
-    $key = preg_replace('/\s+|\W+/', '_', $this->_prefix . $key);
-    if (strlen($key) > self::MAX_KEY_LEN) {
+    $truePrefix = $this->getTruePrefix();
+    $maxLen = self::MAX_KEY_LEN - strlen($truePrefix);
+    $key = preg_replace('/\s+|\W+/', '_', $key);
+    if (strlen($key) > $maxLen) {
       $md5Key = md5($key);  // this should be 32 characters in length
-      $subKeyLen = self::MAX_KEY_LEN - 1 - strlen($md5Key);
+      $subKeyLen = $maxLen - 1 - strlen($md5Key);
       $key = substr($key, 0, $subKeyLen) . "_" . $md5Key;
     }
-    return $key;
+    return $truePrefix . $key;
   }
 
   /**
-   * @return mixed
+   * @return bool
    */
   public function flush() {
-    return $this->_cache->flush();
+    $this->_truePrefix = NULL;
+    if ($this->_cache->delete($this->_prefix)) {
+      return TRUE;
+    }
+    $code = $this->_cache->getResultCode();
+    return ($code == Memcached::RES_DELETED || $code == Memcached::RES_NOTFOUND);
+  }
+
+  public function clear() {
+    return $this->flush();
+  }
+
+  protected function getTruePrefix() {
+    if ($this->_truePrefix === NULL || $this->_truePrefix['expires'] < time()) {
+      $key = $this->_prefix;
+      $value = $this->_cache->get($key);
+      if ($this->_cache->getResultCode() === Memcached::RES_NOTFOUND) {
+        $value = uniqid();
+        $this->_cache->add($key, $value, 0); // Indefinite.
+      }
+      $this->_truePrefix = [
+        'value' => $value,
+        'expires' => time() + self::NS_LOCAL_TTL,
+      ];
+    }
+    return $this->_prefix . $this->_truePrefix['value'] . '/';
   }
 
 }
diff --git a/CRM/Utils/Cache/NaiveHasTrait.php b/CRM/Utils/Cache/NaiveHasTrait.php
new file mode 100644 (file)
index 0000000..78ecc67
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5                                                  |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2018                                |
+ +--------------------------------------------------------------------+
+ | 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        |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ *
+ * @package CRM
+ * @copyright CiviCRM LLC (c) 2004-2018
+ *
+ * The traditional CRM_Utils_Cache_Interface did not support has().
+ * To get drop-in compliance with PSR-16, we use a naive adapter.
+ *
+ * Ideally, these should be replaced with more performant/native versions.
+ */
+trait CRM_Utils_Cache_NaiveHasTrait {
+
+  public function has($key) {
+    // This is crazy-talk. If you've got an environment setup where you might
+    // be investigating this, fix your preferred cache driver by
+    // replacing `NaiveHasTrait` with a decent function.
+    $hasDefaultA = ($this->get($key, NULL) === NULL);
+    $hasDefaultB = ($this->get($key, 123) === 123);
+    return !($hasDefaultA && $hasDefaultB);
+  }
+
+}
diff --git a/CRM/Utils/Cache/NaiveMultipleTrait.php b/CRM/Utils/Cache/NaiveMultipleTrait.php
new file mode 100644 (file)
index 0000000..d7ead9f
--- /dev/null
@@ -0,0 +1,121 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5                                                  |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2018                                |
+ +--------------------------------------------------------------------+
+ | 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        |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ *
+ * @package CRM
+ * @copyright CiviCRM LLC (c) 2004-2018
+ *
+ * The traditional CRM_Utils_Cache_Interface did not support multiple-key
+ * operations. To get drop-in compliance with PSR-16, we use a naive adapter.
+ * An operation like `getMultiple()` just calls `get()` multiple times.
+ *
+ * Ideally, these should be replaced with more performant/native versions.
+ */
+trait CRM_Utils_Cache_NaiveMultipleTrait {
+
+  /**
+   * Obtains multiple cache items by their unique keys.
+   *
+   * @param iterable $keys A list of keys that can obtained in a single operation.
+   * @param mixed $default Default value to return for keys that do not exist.
+   *
+   * @return iterable A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value.
+   *
+   * @throws \Psr\SimpleCache\InvalidArgumentException
+   *   MUST be thrown if $keys is neither an array nor a Traversable,
+   *   or if any of the $keys are not a legal value.
+   */
+  public function getMultiple($keys, $default = NULL) {
+    $this->assertIterable('getMultiple', $keys);
+
+    $result = [];
+    foreach ($keys as $key) {
+      $result[$key] = $this->get($key, $default);
+    }
+    return $result;
+  }
+
+  /**
+   * Persists a set of key => value pairs in the cache, with an optional TTL.
+   *
+   * @param iterable $values A list of key => value pairs for a multiple-set operation.
+   * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and
+   *                                       the driver supports TTL then the library may set a default value
+   *                                       for it or let the driver take care of that.
+   *
+   * @return bool True on success and false on failure.
+   *
+   * @throws \Psr\SimpleCache\InvalidArgumentException
+   *   MUST be thrown if $values is neither an array nor a Traversable,
+   *   or if any of the $values are not a legal value.
+   */
+  public function setMultiple($values, $ttl = NULL) {
+    $this->assertIterable('setMultiple', $values);
+
+    $result = TRUE;
+    foreach ($values as $key => $value) {
+      if (is_int($key)) {
+        $key = (string) $key;
+      }
+      $result = $this->set($key, $value, $ttl) || $result;
+    }
+    return $result;
+  }
+
+  /**
+   * Deletes multiple cache items in a single operation.
+   *
+   * @param iterable $keys A list of string-based keys to be deleted.
+   *
+   * @return bool True if the items were successfully removed. False if there was an error.
+   *
+   * @throws \Psr\SimpleCache\InvalidArgumentException
+   *   MUST be thrown if $keys is neither an array nor a Traversable,
+   *   or if any of the $keys are not a legal value.
+   */
+  public function deleteMultiple($keys) {
+    $this->assertIterable('deleteMultiple', $keys);
+
+    $result = TRUE;
+    foreach ($keys as $key) {
+      $result = $this->delete($key) || $result;
+    }
+    return $result;
+  }
+
+  /**
+   * @param $keys
+   * @throws \CRM_Utils_Cache_InvalidArgumentException
+   */
+  private function assertIterable($func, $keys) {
+    if (!is_array($keys) && !($keys instanceof Traversable)) {
+      throw new CRM_Utils_Cache_InvalidArgumentException("$func expects iterable input");
+    }
+  }
+
+}
index 66c6b74cff410ec6280e951843140dde66409bde..3c1d9b9445273b4c63e546e218d007e515235f57 100644 (file)
@@ -32,6 +32,9 @@
  */
 class CRM_Utils_Cache_NoCache implements CRM_Utils_Cache_Interface {
 
+  use CRM_Utils_Cache_NaiveMultipleTrait; // TODO Consider native implementation.
+  use CRM_Utils_Cache_NaiveHasTrait; // TODO Native implementation
+
   /**
    * We only need one instance of this object. So we use the singleton
    * pattern and cache the instance in this variable
@@ -54,20 +57,22 @@ class CRM_Utils_Cache_NoCache implements CRM_Utils_Cache_Interface {
   /**
    * @param string $key
    * @param mixed $value
+   * @param null|int|\DateInterval $ttl
    *
    * @return bool
    */
-  public function set($key, &$value) {
+  public function set($key, $value, $ttl = NULL) {
     return FALSE;
   }
 
   /**
    * @param string $key
+   * @param mixed $default
    *
    * @return null
    */
-  public function get($key) {
-    return NULL;
+  public function get($key, $default = NULL) {
+    return $default;
   }
 
   /**
@@ -86,4 +91,8 @@ class CRM_Utils_Cache_NoCache implements CRM_Utils_Cache_Interface {
     return FALSE;
   }
 
+  public function clear() {
+    return $this->flush();
+  }
+
 }
index 0cfccdf80f00e1efabb8bb86db2bcd0f8be7e29f..3e2f2f7f9b3fa64ecbb6e786399f2c4d350441ab 100644 (file)
  *
  */
 class CRM_Utils_Cache_Redis implements CRM_Utils_Cache_Interface {
+
+  use CRM_Utils_Cache_NaiveMultipleTrait; // TODO Consider native implementation.
+  use CRM_Utils_Cache_NaiveHasTrait; // TODO Native implementation
+
   const DEFAULT_HOST    = 'localhost';
   const DEFAULT_PORT    = 6379;
   const DEFAULT_TIMEOUT = 3600;
@@ -113,18 +117,24 @@ class CRM_Utils_Cache_Redis implements CRM_Utils_Cache_Interface {
   /**
    * @param $key
    * @param $value
+   * @param null|int|\DateInterval $ttl
    *
    * @return bool
    * @throws Exception
    */
-  public function set($key, &$value) {
-    if (!$this->_cache->set($this->_prefix . $key, serialize($value), $this->_timeout)) {
+  public function set($key, $value, $ttl = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
+    if (is_int($ttl) && $ttl <= 0) {
+      return $this->delete($key);
+    }
+    $ttl = CRM_Utils_Date::convertCacheTtl($ttl, self::DEFAULT_TIMEOUT);
+    if (!$this->_cache->setex($this->_prefix . $key, $ttl, serialize($value))) {
       if (PHP_SAPI === 'cli' || (Civi\Core\Container::isContainerBooted() && CRM_Core_Permission::check('view debug output'))) {
-        CRM_Core_Error::fatal("Redis set ($key) failed: " . $this->_cache->getLastError());
+        throw new CRM_Utils_Cache_CacheException("Redis set ($key) failed: " . $this->_cache->getLastError());
       }
       else {
         Civi::log()->error("Redis set ($key) failed: " . $this->_cache->getLastError());
-        CRM_Core_Error::fatal("Redis set ($key) failed");
+        throw new CRM_Utils_Cache_CacheException("Redis set ($key) failed");
       }
       return FALSE;
     }
@@ -133,28 +143,42 @@ class CRM_Utils_Cache_Redis implements CRM_Utils_Cache_Interface {
 
   /**
    * @param $key
+   * @param mixed $default
    *
    * @return mixed
    */
-  public function get($key) {
+  public function get($key, $default = NULL) {
+    CRM_Utils_Cache::assertValidKey($key);
     $result = $this->_cache->get($this->_prefix . $key);
-    return ($result === FALSE) ? NULL : unserialize($result);
+    return ($result === FALSE) ? $default : unserialize($result);
   }
 
   /**
    * @param $key
    *
-   * @return mixed
+   * @return bool
    */
   public function delete($key) {
-    return $this->_cache->delete($this->_prefix . $key);
+    CRM_Utils_Cache::assertValidKey($key);
+    $this->_cache->delete($this->_prefix . $key);
+    return TRUE;
   }
 
   /**
-   * @return mixed
+   * @return bool
    */
   public function flush() {
-    return $this->_cache->flushDB();
+    // FIXME: Ideally, we'd map each prefix to a different 'hash' object in Redis,
+    // and this would be simpler. However, that needs to go in tandem with a
+    // more general rethink of cache expiration/TTL.
+
+    $keys = $this->_cache->keys($this->_prefix . '*');
+    $this->_cache->del($keys);
+    return TRUE;
+  }
+
+  public function clear() {
+    return $this->flush();
   }
 
 }
index 6164694c3e5cc5eb5263ce0dd08d4ed4801fc746..fdf0eb6d47b588ba1c5fe0eb087f59a97f97d221 100644 (file)
@@ -36,6 +36,9 @@
  */
 class CRM_Utils_Cache_SerializeCache implements CRM_Utils_Cache_Interface {
 
+  use CRM_Utils_Cache_NaiveMultipleTrait;
+  use CRM_Utils_Cache_NaiveHasTrait; // TODO Native implementation
+
   /**
    * The cache storage container, an array by default, stored in a file under templates
    */
@@ -67,10 +70,15 @@ class CRM_Utils_Cache_SerializeCache implements CRM_Utils_Cache_Interface {
 
   /**
    * @param string $key
+   * @param mixed $default
    *
    * @return mixed
    */
-  public function get($key) {
+  public function get($key, $default = NULL) {
+    if ($default !== NULL) {
+      throw new \RuntimeException("FIXME: " . __CLASS__ . "::get() only supports NULL default");
+    }
+
     if (array_key_exists($key, $this->_cache)) {
       return $this->_cache[$key];
     }
@@ -85,32 +93,41 @@ class CRM_Utils_Cache_SerializeCache implements CRM_Utils_Cache_Interface {
   /**
    * @param string $key
    * @param mixed $value
+   * @param null|int|\DateInterval $ttl
+   * @return bool
    */
-  public function set($key, &$value) {
+  public function set($key, $value, $ttl = NULL) {
+    if ($ttl !== NULL) {
+      throw new \RuntimeException("FIXME: " . __CLASS__ . "::set() should support non-NULL TTL");
+    }
     if (file_exists($this->fileName($key))) {
-      return;
+      return FALSE; // WTF, write-once cache?!
     }
     $this->_cache[$key] = $value;
-    file_put_contents($this->fileName($key), "<?php //" . serialize($value));
+    $bytes = file_put_contents($this->fileName($key), "<?php //" . serialize($value));
+    return ($bytes !== FALSE);
   }
 
   /**
    * @param string $key
+   * @return bool
    */
   public function delete($key) {
     if (file_exists($this->fileName($key))) {
       unlink($this->fileName($key));
     }
     unset($this->_cache[$key]);
+    return TRUE;
   }
 
   /**
    * @param null $key
+   * @return bool
    */
   public function flush($key = NULL) {
     $prefix = "CRM_";
     if (!$handle = opendir(CIVICRM_TEMPLATE_COMPILEDIR)) {
-      return; // die? Error?
+      return FALSE; // die? Error?
     }
     while (FALSE !== ($entry = readdir($handle))) {
       if (substr($entry, 0, 4) == $prefix) {
@@ -120,6 +137,11 @@ class CRM_Utils_Cache_SerializeCache implements CRM_Utils_Cache_Interface {
     closedir($handle);
     unset($this->_cache);
     $this->_cache = array();
+    return TRUE;
+  }
+
+  public function clear() {
+    return $this->flush();
   }
 
 }
index 6f3f45f792743f727bab58ff670ecca6a40e8451..3b983ae106329899d622229e18c868a7da34a3c8 100644 (file)
@@ -38,6 +38,9 @@
  */
 class CRM_Utils_Cache_SqlGroup implements CRM_Utils_Cache_Interface {
 
+  use CRM_Utils_Cache_NaiveMultipleTrait; // TODO Consider native implementation.
+  use CRM_Utils_Cache_NaiveHasTrait; // TODO Native implementation
+
   /**
    * The host name of the memcached server.
    *
@@ -89,18 +92,28 @@ class CRM_Utils_Cache_SqlGroup implements CRM_Utils_Cache_Interface {
   /**
    * @param string $key
    * @param mixed $value
+   * @param null|int|\DateInterval $ttl
+   * @return bool
    */
-  public function set($key, &$value) {
+  public function set($key, $value, $ttl = NULL) {
+    if ($ttl !== NULL) {
+      throw new \RuntimeException("FIXME: " . __CLASS__ . "::set() should support non-NULL TTL");
+    }
     CRM_Core_BAO_Cache::setItem($value, $this->group, $key, $this->componentID);
     $this->frontCache[$key] = $value;
+    return TRUE;
   }
 
   /**
    * @param string $key
+   * @param mixed $default
    *
    * @return mixed
    */
-  public function get($key) {
+  public function get($key, $default = NULL) {
+    if ($default !== NULL) {
+      throw new \RuntimeException("FIXME: " . __CLASS__ . "::get() only supports NULL default");
+    }
     if (!array_key_exists($key, $this->frontCache)) {
       $this->frontCache[$key] = CRM_Core_BAO_Cache::getItem($this->group, $key, $this->componentID);
     }
@@ -119,15 +132,26 @@ class CRM_Utils_Cache_SqlGroup implements CRM_Utils_Cache_Interface {
 
   /**
    * @param string $key
+   * @return bool
    */
   public function delete($key) {
-    CRM_Core_BAO_Cache::deleteGroup($this->group, $key);
+    CRM_Core_BAO_Cache::deleteGroup($this->group, $key, FALSE);
+    CRM_Core_BAO_Cache::$_cache = NULL; // FIXME: remove multitier cache
+    CRM_Utils_Cache::singleton()->flush(); // FIXME: remove multitier cache
     unset($this->frontCache[$key]);
+    return TRUE;
   }
 
   public function flush() {
-    CRM_Core_BAO_Cache::deleteGroup($this->group);
+    CRM_Core_BAO_Cache::deleteGroup($this->group, NULL, FALSE);
+    CRM_Core_BAO_Cache::$_cache = NULL; // FIXME: remove multitier cache
+    CRM_Utils_Cache::singleton()->flush(); // FIXME: remove multitier cache
     $this->frontCache = array();
+    return TRUE;
+  }
+
+  public function clear() {
+    return $this->flush();
   }
 
   public function prefetch() {
index 24fd07062a1862571b4196ba3fa4306c81dda4bb..57d15a741ff8c06689727020de2b8c2d9a6d5b29 100644 (file)
@@ -223,7 +223,7 @@ class CRM_Utils_Check {
       break;
     }
 
-    Civi::settings()->set('systemStatusCheckResult', $maxSeverity);
+    Civi::cache('checks')->set('systemStatusCheckResult', $maxSeverity);
 
     return ($max) ? $maxSeverity : $messages;
   }
index 3d4570d7e194fdca20ffdd930f027345d3323a91..74aa2e35caec7e36103714ad5a4caea4da46ae7a 100644 (file)
@@ -695,6 +695,58 @@ class CRM_Utils_Date {
     return TRUE;
   }
 
+  /**
+   * Translate a TTL to a concrete expiration time.
+   *
+   * @param NULL|int|DateInterval $ttl
+   * @param int $default
+   *   The value to use if $ttl is not specified (NULL).
+   * @return int
+   *   Timestamp (seconds since epoch).
+   * @throws \CRM_Utils_Cache_InvalidArgumentException
+   */
+  public static function convertCacheTtlToExpires($ttl, $default) {
+    if ($ttl === NULL) {
+      $ttl = $default;
+    }
+
+    if (is_int($ttl)) {
+      return time() + $ttl;
+    }
+    elseif ($ttl instanceof DateInterval) {
+      return date_add(new DateTime(), $ttl)->getTimestamp();
+    }
+    else {
+      throw new CRM_Utils_Cache_InvalidArgumentException("Invalid cache TTL");
+    }
+  }
+
+  /**
+   * Normalize a TTL.
+   *
+   * @param NULL|int|DateInterval $ttl
+   * @param int $default
+   *   The value to use if $ttl is not specified (NULL).
+   * @return int
+   *   Seconds until expiration.
+   * @throws \CRM_Utils_Cache_InvalidArgumentException
+   */
+  public static function convertCacheTtl($ttl, $default) {
+    if ($ttl === NULL) {
+      return $default;
+    }
+    elseif (is_int($ttl)) {
+      return $ttl;
+    }
+    elseif ($ttl instanceof DateInterval) {
+      return date_add(new DateTime(), $ttl)->getTimestamp() - time();
+    }
+    else {
+      throw new CRM_Utils_Cache_InvalidArgumentException("Invalid cache TTL");
+    }
+  }
+
+
   /**
    * @param null $timeStamp
    *
index e2d72f5303244d9eb8c031c82696994bedd3f47f..e88076c21735a520bf9440825df4d50f55421673 100644 (file)
@@ -1416,8 +1416,14 @@ class CRM_Utils_System {
   public static function flushCache() {
     // flush out all cache entries so we can reload new data
     // a bit aggressive, but livable for now
-    $cache = CRM_Utils_Cache::singleton();
-    $cache->flush();
+    CRM_Utils_Cache::singleton()->flush();
+    if (Civi\Core\Container::isContainerBooted()) {
+      Civi::cache('settings')->flush();
+      Civi::cache('js_strings')->flush();
+      Civi::cache('community_messages')->flush();
+      CRM_Extension_System::singleton()->getCache()->flush();
+      CRM_Cxn_CiviCxnHttp::singleton()->getCache()->flush();
+    }
 
     // also reset the various static memory caches
 
index ae5dc6f4b1dda2f1a883542edbb0ec98c17b14dc..8acfdba76862963e124de00335dd48a67e58806c 100644 (file)
--- a/Civi.php
+++ b/Civi.php
@@ -26,20 +26,15 @@ class Civi {
   public static $statics = array();
 
   /**
-   * EXPERIMENTAL. Retrieve a named cache instance.
-   *
-   * This interface is flagged as experimental due to political
-   * ambiguity in PHP community -- PHP-FIG has an open but
-   * somewhat controversial draft standard for caching. Based on
-   * the current draft, it's expected that this function could
-   * simultaneously support both CRM_Utils_Cache_Interface and
-   * PSR-6, but that depends on whether PSR-6 changes any more.
+   * Retrieve a named cache instance.
    *
    * @param string $name
    *   The name of the cache. The 'default' cache is biased toward
    *   high-performance caches (eg memcache/redis/apc) when
    *   available and falls back to single-request (static) caching.
    * @return CRM_Utils_Cache_Interface
+   *   NOTE: Beginning in CiviCRM v5.4, the cache instance complies with
+   *   PSR-16 (\Psr\SimpleCache\CacheInterface).
    */
   public static function cache($name = 'default') {
     return \Civi\Core\Container::singleton()->get('cache.' . $name);
index 62d896e2f8f505dc018bc4a3734bd028a7c4b82d..61f36af0eeb9dd50d93b87a51c49686ed796b3b3 100644 (file)
@@ -246,7 +246,7 @@ class Manager {
    *   Invalid partials configuration.
    */
   public function getPartials($name) {
-    $cacheKey = "angular-partials::$name";
+    $cacheKey = "angular-partials_$name";
     $cacheValue = $this->cache->get($cacheKey);
     if ($cacheValue === NULL) {
       $cacheValue = ChangeSet::applyResourceFilters($this->getChangeSets(), 'partials', $this->getRawPartials($name));
index ffe1bfbe3bcc93284bd68df4ecb706e19f5de7f5..a137d166b56a0567df3077db50c0504eebe5f202 100644 (file)
@@ -160,12 +160,17 @@ class Container {
 
     $container->setDefinition('psr_log', new Definition('CRM_Core_Error_Log', array()));
 
-    foreach (array('js_strings', 'community_messages') as $cacheName) {
-      $container->setDefinition("cache.{$cacheName}", new Definition(
+    $basicCaches = array(
+      'js_strings' => 'js_strings',
+      'community_messages' => 'community_messages',
+      'checks' => 'checks',
+    );
+    foreach ($basicCaches as $cacheSvc => $cacheGrp) {
+      $container->setDefinition("cache.{$cacheSvc}", new Definition(
         'CRM_Utils_Cache_Interface',
         array(
           array(
-            'name' => $cacheName,
+            'name' => $cacheGrp,
             'type' => array('*memory*', 'SqlGroup', 'ArrayCache'),
           ),
         )
index 8147afca982570b15757300a30a0253a1ca88467..e56d723b2fbaf501a18acd9953530bd9462b872b 100644 (file)
@@ -205,7 +205,7 @@ class SettingsManager {
       return self::getSystemDefaults($entity);
     }
 
-    $cacheKey = 'defaults:' . $entity;
+    $cacheKey = 'defaults_' . $entity;
     $defaults = $this->cache->get($cacheKey);
     if (!is_array($defaults)) {
       $specs = SettingsMetadata::getMetadata(array(
index ee9efb960306a0300db811fe211f5b17a779b9fe..0f74d410f8c90850daa6f0c40d6a49d8453445d7 100644 (file)
     $scope.caseType.definition.caseRoles = $scope.caseType.definition.caseRoles || [];
     $scope.caseType.definition.statuses = $scope.caseType.definition.statuses || [];
 
+    $scope.caseType.definition.timelineActivityTypes = $scope.caseType.definition.timelineActivityTypes || [];
+
     $scope.selectedStatuses = {};
     _.each(apiCalls.caseStatuses.values, function (status) {
       $scope.selectedStatuses[status.name] = !$scope.caseType.definition.statuses.length || $scope.caseType.definition.statuses.indexOf(status.name) > -1;
     }
 
     function addActivityToSet(activitySet, activityTypeName) {
-      activitySet.activityTypes.push({
-        name: activityTypeName,
-        label: $scope.activityTypes[activityTypeName].label,
-        status: 'Scheduled',
-        reference_activity: 'Open Case',
-        reference_offset: '1',
-        reference_select: 'newest'
-      });
+      var activity = {
+          name: activityTypeName,
+          label: $scope.activityTypes[activityTypeName].label,
+          status: 'Scheduled',
+          reference_activity: 'Open Case',
+          reference_offset: '1',
+          reference_select: 'newest'
+      };
+      activitySet.activityTypes.push(activity);
+      if(typeof activitySet.timeline !== "undefined" && activitySet.timeline == "1") {
+        $scope.caseType.definition.timelineActivityTypes.push(activity);
+      }
+    }
+
+    function resetTimelineActivityTypes() {
+        $scope.caseType.definition.timelineActivityTypes = [];
+        angular.forEach($scope.caseType.definition.activitySets, function(activitySet) {
+            angular.forEach(activitySet.activityTypes, function(activityType) {
+                $scope.caseType.definition.timelineActivityTypes.push(activityType);
+            });
+        });
     }
 
     function createActivity(name, callback) {
       var idx = _.indexOf(array, item);
       if (idx != -1) {
         array.splice(idx, 1);
+        resetTimelineActivityTypes();
       }
     };
 
     if (!$scope.isForkable()) {
       CRM.alert(ts('The CiviCase XML file for this case-type prohibits editing the definition.'));
     }
+
   });
 
   crmCaseType.controller('CaseTypeListCtrl', function($scope, crmApi, caseTypes) {
index bc38d3ddd57438bcd13f93a04e3f7e23f4b71b79..0dba5c98bda64b6dfda5dec7f5a8777ad46b71e7 100644 (file)
@@ -39,7 +39,7 @@ Required vars: activitySet
         ui-jq="select2"
         ui-options="{dropdownAutoWidth : true}"
         ng-model="activity.reference_activity"
-        ng-options="activityType.name as activityType.label for activityType in activitySet.activityTypes"
+        ng-options="activityType.name as activityType.label for activityType in caseType.definition.timelineActivityTypes"
         >
         <option value="">-- Case Start --</option>
       </select>
index 1addd5fb46600fc63c37cbc23f471ddc132ef99c..6315466e83cf65ca57601ce2b54c398fb0296e31 100644 (file)
@@ -37,7 +37,7 @@ Vars: mailing:obj, testContact:obj, testGroup:obj, crmMailing:FormController
         crm-multiple-email
       />
     </div>
-    <button crm-icon="fa-paper-plane" title="{{crmMailing.$invalid || !testContact.email ? ts('Complete all required fields first') : ts('Send test message to %1', {1: testContact.email})}}" ng-disabled="crmMailing.$invalid || !testContact.email" ng-click="doSend({email: testContact.email})">{{ts('Send test')}}</button>
+    <button crm-icon="fa-paper-plane" title="{{crmMailing.$invalid || !testContact.email ? ts('Complete all required fields first') : ts('Send test message to %1', {1: testContact.email})}}" ng-disabled="crmMailing.$invalid || !testContact.email" ng-click="doSend({email: testContact.email})" class="crmMailing-btn-primary">{{ts('Send test')}}</button>
   </div>
   <div class="preview-group" ng-form="">
     <div>
@@ -51,7 +51,7 @@ Vars: mailing:obj, testContact:obj, testGroup:obj, crmMailing:FormController
         class="crm-action-menu fa-envelope-o"
         />
     </div>
-    <button crm-icon="fa-paper-plane" title="{{crmMailing.$invalid || !testGroup.gid ? ts('Complete all required fields first') : ts('Send test message to group')}}" ng-disabled="crmMailing.$invalid || !testGroup.gid" crm-confirm="{resizable: true, width: '40%', height: '40%', open: previewTestGroup}" on-yes="doSend({gid: testGroup.gid})">{{ts('Send test')}}</button>
+    <button crm-icon="fa-paper-plane" title="{{crmMailing.$invalid || !testGroup.gid ? ts('Complete all required fields first') : ts('Send test message to group')}}" ng-disabled="crmMailing.$invalid || !testGroup.gid" crm-confirm="{resizable: true, width: '40%', height: '40%', open: previewTestGroup}" on-yes="doSend({gid: testGroup.gid})" class="crmMailing-btn-primary">{{ts('Send test')}}</button>
   </div>
   <div class="clear"></div>
 </div>
index 94752d6d924bf87062de84ed9cc38fd5096a2630..cabd6f307c698770c9caf2fcc958f0ae069931bc 100644 (file)
@@ -42,7 +42,7 @@
           <div crm-mailing-block-schedule crm-mailing="mailing"></div>
         </div>
         <center>
-          <a class="button crmMailing-submit-button" ng-click="submit()" ng-class="{blocking: block.check(), disabled: crmMailingSubform.$invalid}">
+          <a class="button crmMailing-submit-button crmMailing-btn-primary" ng-click="submit()" ng-class="{blocking: block.check(), disabled: crmMailingSubform.$invalid}">
             <div>{{ts('Submit Mailing')}}</div>
           </a>
         </center>
         <button
           crm-icon="fa-trash"
           ng-show="checkPerm('delete in CiviMail')"
+          class="crmMailing-btn-danger-outline"
           ng-disabled="block.check()"
           crm-confirm="{title:ts('Delete Draft'), message:ts('Are you sure you want to permanently delete this mailing?')}"
           on-yes="delete()">{{ts('Delete Draft')}}</button>
-        <button crm-icon="fa-floppy-o" ng-disabled="block.check()" ng-click="save().then(leave)">{{ts('Save Draft')}}</button>
+        <button crm-icon="fa-floppy-o" ng-disabled="block.check()" ng-click="save().then(leave)" class="crmMailing-btn-secondary-outline">{{ts('Save Draft')}}</button>
       </span>
     </div>
   </div>
index 93a8758764fb0740b481dba4a8f695c4342f75c4..cc3056fa2ff34d5a3dfd31092b73fde5d47ed4a8 100644 (file)
       <div crm-mailing-block-schedule crm-mailing="mailing"></div>
     </div>
 
-    <button crm-icon="fa-paper-plane" ng-disabled="block.check() || crmMailingSubform.$invalid" ng-click="submit()">{{ts('Submit Mailing')}}</button>
-    <button crm-icon="fa-floppy-o" ng-disabled="block.check()" ng-click="save().then(leave)">{{ts('Save Draft')}}</button>
+    <button crm-icon="fa-paper-plane" class="crmMailing-btn-primary" ng-disabled="block.check() || crmMailingSubform.$invalid" ng-click="submit()">{{ts('Submit Mailing')}}</button>
+    <button crm-icon="fa-floppy-o" class="crmMailing-btn-secondary-outline" ng-disabled="block.check()" ng-click="save().then(leave)">{{ts('Save Draft')}}</button>
     <button
       crm-icon="fa-trash"
       ng-show="checkPerm('delete in CiviMail')"
+      class="crmMailing-btn-danger-outline"
       ng-disabled="block.check()"
       crm-confirm="{title:ts('Delete Draft'), message:ts('Are you sure you want to permanently delete this mailing?')}"
       on-yes="delete()">{{ts('Delete Draft')}}</button>
index 04ff30dd06aae6cbd20821ade9e44fb1914ed0fe..1506b8d5930de9d701aa5e15b5ba43b0d55d1e86 100644 (file)
       <div crm-mailing-block-schedule crm-mailing="mailing"></div>
     </div>
 
-    <button crm-icon="fa-paper-plane" ng-disabled="block.check() || crmMailingSubform.$invalid" ng-click="submit()">{{ts('Submit Mailing')}}</button>
-    <button crm-icon="fa-floppy-o" ng-disabled="block.check()" ng-click="save().then(leave)">{{ts('Save Draft')}}</button>
+    <button crm-icon="fa-paper-plane" class="crmMailing-btn-primary" ng-disabled="block.check() || crmMailingSubform.$invalid" ng-click="submit()">{{ts('Submit Mailing')}}</button>
+    <button crm-icon="fa-floppy-o" class="crmMailing-secondary-outline" ng-disabled="block.check()" ng-click="save().then(leave)">{{ts('Save Draft')}}</button>
     <button
       crm-icon="fa-trash"
       ng-show="checkPerm('delete in CiviMail')"
+      class="crmMailing-btn-danger-outline"
       ng-disabled="block.check()"
       crm-confirm="{title:ts('Delete Draft'), message:ts('Are you sure you want to permanently delete this mailing?')}"
       on-yes="delete()">{{ts('Delete Draft')}}</button>
index ee589fd7765e7bdba79d9b746b779b221a3c4975..9854cc5c95273aaddddb0109d1557216f2afefa0 100644 (file)
@@ -45,7 +45,7 @@
           <div crm-mailing-block-review crm-mailing="mailing" crm-mailing-attachments="attachments"></div>
         </div>
         <center>
-          <a class="button crmMailing-submit-button" ng-click="submit()" ng-class="{blocking: block.check(), disabled: crmMailingSubform.$invalid}">
+          <a class="button crmMailing-submit-button crmMailing-btn-primary" ng-click="submit()" ng-class="{blocking: block.check(), disabled: crmMailingSubform.$invalid}">
             <div>{{ts('Submit Mailing')}}</div>
           </a>
         </center>
         <button
           crm-icon="fa-trash"
           ng-show="checkPerm('delete in CiviMail')"
+          class="crmMailing-btn-danger-outline"
           ng-disabled="block.check()"
           crm-confirm="{title:ts('Delete Draft'), message:ts('Are you sure you want to permanently delete this mailing?')}"
           on-yes="delete()">{{ts('Delete Draft')}}</button>
-        <button crm-icon="fa-floppy-o" ng-disabled="block.check()" ng-click="save().then(leave)">{{ts('Save Draft')}}</button>
+        <button crm-icon="fa-floppy-o" class="crmMailing-btn-secondary-outline" ng-disabled="block.check()" ng-click="save().then(leave)">{{ts('Save Draft')}}</button>
       </span>
     </div>
   </div>
index b5ae948c4750ddd97f6b626594b0bd6978acd8ae..affa76d84ec178c81b006ec372acea8997214c53 100644 (file)
           <div crm-mailing-block-approve crm-mailing="mailing"></div>
         </div>
         <center ng-if="!checkPerm('approve mailings') && !checkPerm('access CiviMail')">
-          <a class="button crmMailing-submit-button" ng-click="submit()" ng-class="{blocking: block.check(), disabled: crmMailingSubform.$invalid}">
+          <a class="button crmMailing-submit-button crmMailing-btn-primary" ng-click="submit()" ng-class="{blocking: block.check(), disabled: crmMailingSubform.$invalid}">
             <div>{{ts('Submit Mailing')}}</div>
           </a>
         </center>
         <center ng-if="checkPerm('approve mailings') || checkPerm('access CiviMail')">
-          <a class="button crmMailing-submit-button" ng-click="approve('Approved')" ng-class="{blocking: block.check(), disabled: crmMailingSubform.$invalid}">
+          <a class="button crmMailing-submit-button crmMailing-btn-primary" ng-click="approve('Approved')" ng-class="{blocking: block.check(), disabled: crmMailingSubform.$invalid}">
             <div>{{ts('Submit and Approve Mailing')}}</div>
           </a>
         </center>
         <button
           crm-icon="fa-trash"
           ng-show="checkPerm('delete in CiviMail')"
+          class="crmMailing-btn-danger-outline"
           ng-disabled="block.check()"
           crm-confirm="{title:ts('Delete Draft'), message:ts('Are you sure you want to permanently delete this mailing?')}"
           on-yes="delete()">{{ts('Delete Draft')}}</button>
-        <button crm-icon="fa-floppy-o" ng-disabled="block.check()" ng-click="save().then(leave)">{{ts('Save Draft')}}</button>
+        <button crm-icon="fa-floppy-o" ng-disabled="block.check()" ng-click="save().then(leave)" class="crmMailing-btn-secondary-outline">{{ts('Save Draft')}}</button>
       </span>
     </div>
   </div>
index 539f30113d4b8ad5004916d3b815949d9e1438b2..116f060dcf506127451d875b6bb042a8af57298d 100644 (file)
@@ -9,7 +9,7 @@
   </ul>
   <div class="crm-wizard-body" ng-transclude></div>
   <div class="crm-wizard-buttons">
-    <button crm-icon="fa-chevron-left" ng-click="crmUiWizardCtrl.previous()" ng-show="!crmUiWizardCtrl.$first()">{{ts('Previous')}}</button>
-    <button crm-icon="fa-chevron-right" title="{{!crmUiWizardCtrl.$validStep() ? ts('Complete all required fields first') : ts('Next step')}}" ng-click="crmUiWizardCtrl.next()" ng-show="!crmUiWizardCtrl.$last()" ng-disabled="!crmUiWizardCtrl.$validStep()">{{ts('Next')}}</button>
+    <button crm-icon="fa-chevron-left" ng-click="crmUiWizardCtrl.previous()" ng-show="!crmUiWizardCtrl.$first()" class="crmUi-btn-primary">{{ts('Previous')}}</button>
+    <button crm-icon="fa-chevron-right" title="{{!crmUiWizardCtrl.$validStep() ? ts('Complete all required fields first') : ts('Next step')}}" ng-click="crmUiWizardCtrl.next()" ng-show="!crmUiWizardCtrl.$last()" ng-disabled="!crmUiWizardCtrl.$validStep()" class="crmUi-btn-primary">{{ts('Next')}}</button>
   </div>
 </div>
index 8a4ccd83d82282b288659cbd0112c9e20344c0fb..32c450e058a8fae06cef9b0456a6ac33e0dfef39 100644 (file)
@@ -1170,21 +1170,6 @@ function setting_getfields_expectedresult() {
         'description' => '',
         'help_text' => '',
       ),
-      'systemStatusCheckResult' => array(
-        'group_name' => 'CiviCRM Preferences',
-        'group' => 'core',
-        'name' => 'systemStatusCheckResult',
-        'type' => 'Integer',
-        'quick_form_type' => 'Element',
-        'html_type' => 'text',
-        'default' => 0,
-        'add' => '4.7',
-        'title' => 'systemStatusCheckResult',
-        'is_domain' => 1,
-        'is_contact' => 0,
-        'description' => '',
-        'help_text' => '',
-      ),
       'recentItemsMaxCount' => array(
         'group_name' => 'CiviCRM Preferences',
         'group' => 'core',
index 814d11e0b83a9ab9ccfd5a3a066e3a3a31cf5b3b..094beb2375b84225394995212bc08b0350135523 100644 (file)
@@ -1977,43 +1977,15 @@ function _civicrm_api_get_custom_fields($entity, &$params) {
     // Regular fields have a 'name' property
     $value['name'] = 'custom_' . $key;
     $value['title'] = $value['label'];
-    $value['type'] = _getStandardTypeFromCustomDataType($value);
+    if ($value['data_type'] == 'Date' && CRM_Utils_Array::value('time_format', $value, 0) > 0) {
+      $value['data_type'] = 'DateTime';
+    }
+    $value['type'] = CRM_Utils_Array::value($value['data_type'], CRM_Core_BAO_CustomField::dataToType());
     $ret['custom_' . $key] = $value;
   }
   return $ret;
 }
 
-/**
- * Translate the custom field data_type attribute into a std 'type'.
- *
- * @param array $value
- *
- * @return int
- */
-function _getStandardTypeFromCustomDataType($value) {
-  $dataType = $value['data_type'];
-  //CRM-15792 - If date custom field contains timeformat change type to DateTime
-  if ($value['data_type'] == 'Date' && isset($value['time_format']) && $value['time_format'] > 0) {
-    $dataType = 'DateTime';
-  }
-  $mapping = array(
-    'String' => CRM_Utils_Type::T_STRING,
-    'Int' => CRM_Utils_Type::T_INT,
-    'Money' => CRM_Utils_Type::T_MONEY,
-    'Memo' => CRM_Utils_Type::T_LONGTEXT,
-    'Float' => CRM_Utils_Type::T_FLOAT,
-    'Date' => CRM_Utils_Type::T_DATE,
-    'DateTime' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME,
-    'Boolean' => CRM_Utils_Type::T_BOOLEAN,
-    'StateProvince' => CRM_Utils_Type::T_INT,
-    'File' => CRM_Utils_Type::T_STRING,
-    'Link' => CRM_Utils_Type::T_STRING,
-    'ContactReference' => CRM_Utils_Type::T_INT,
-    'Country' => CRM_Utils_Type::T_INT,
-  );
-  return $mapping[$dataType];
-}
-
 
 /**
  * Fill params array with alternate (alias) values where a field has an alias and that is filled & the main field isn't.
index 9d81135dbbec3aef07669abd4aa19d64343e6c29..1078ccf3d30346eefb791349eebd4dd61199eefb 100644 (file)
@@ -55,7 +55,8 @@
     "pear/Net_SMTP": "1.6.*",
     "pear/Net_socket": "1.0.*",
     "civicrm/civicrm-setup": "~0.2.0",
-    "guzzlehttp/guzzle": "^6.3"
+    "guzzlehttp/guzzle": "^6.3",
+    "psr/simple-cache": "~1.0.1"
   },
   "repositories": [
     {
index 0cce99a2a89b969d505f497708f6b7606efa8dec..8f3e4d2659e99894ff0300d04addd2785b5eb1e3 100644 (file)
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "content-hash": "9c5441f5ce4c51ed3a8cc326693cd904",
+    "content-hash": "233f9c457d9e7d49a6d96c356e1035f1",
     "packages": [
         {
             "name": "civicrm/civicrm-cxn-rpc",
             ],
             "time": "2012-12-21T11:40:51+00:00"
         },
+        {
+            "name": "psr/simple-cache",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/simple-cache.git",
+                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\SimpleCache\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interfaces for simple caching",
+            "keywords": [
+                "cache",
+                "caching",
+                "psr",
+                "psr-16",
+                "simple-cache"
+            ],
+            "time": "2017-10-23T01:57:42+00:00"
+        },
         {
             "name": "sabberworm/php-css-parser",
             "version": "6.0.1",
index b95baf8d0ed7727855a4dc598c80af245c55c8d1..c95ed3d01850e49bb91d0f9c9591ac644a194aae 100644 (file)
@@ -883,21 +883,6 @@ return array(
     'description' => NULL,
     'help_text' => NULL,
   ),
-  'systemStatusCheckResult' => array(
-    'group_name' => 'CiviCRM Preferences',
-    'group' => 'core',
-    'name' => 'systemStatusCheckResult',
-    'type' => 'Integer',
-    'quick_form_type' => 'Element',
-    'html_type' => 'text',
-    'default' => 0,
-    'add' => '4.7',
-    'title' => 'systemStatusCheckResult',
-    'is_domain' => 1,
-    'is_contact' => 0,
-    'description' => NULL,
-    'help_text' => NULL,
-  ),
   'recentItemsMaxCount' => array(
     'group_name' => 'CiviCRM Preferences',
     'group' => 'core',
index 019add712d87a59ba286716c87d1c6edfd4cb178..904e7766c830486ade31d116867dd07d6ee5fe4a 100644 (file)
     var patt = /_1$/; // pattern to check if the change event came from field name
     if (field !== null && patt.test(this.id)) {
       // based on data type remove invalid operators e.g. IS EMPTY doesn't work with Boolean type column
+      var operators = CRM.searchBuilder.generalOperators;
       if ((field in CRM.searchBuilder.fieldTypes) === true) {
-        if (CRM.searchBuilder.fieldTypes[field] == 'Boolean') {
-          CRM.searchBuilder.generalOperators = _.omit(CRM.searchBuilder.generalOperators, ['IS NOT EMPTY', 'IS EMPTY']);
+        if ($.inArray(CRM.searchBuilder.fieldTypes[field], ['Boolean', 'Int']) > -1) {
+          operators = _.omit(operators, ['IS NOT EMPTY', 'IS EMPTY']);
         }
         else if (CRM.searchBuilder.fieldTypes[field] == 'String') {
-          CRM.searchBuilder.generalOperators = _.omit(CRM.searchBuilder.generalOperators, ['>', '<', '>=', '<=']);
+          operators = _.omit(operators, ['>', '<', '>=', '<=']);
         }
       }
-      buildOperator(operator, CRM.searchBuilder.generalOperators);
+      buildOperator(operator, operators);
     }
 
     // These Ops don't get any input field.
index d32391961fa34d280b6ea58a96f6bb8971a318c1..ff1c9f73d5ee4389def249ca3692cbabcad866e0 100644 (file)
@@ -92,9 +92,6 @@
           {ts}Edit Widget Colors{/ts}
          </div><!-- /.crm-accordion-header -->
          <div class="crm-accordion-body">
-            <div class="description">
-                {ts}Enter colors in hexadecimal format prefixed with <em>#</em>. EXAMPLE: <em>#FF0000</em> = Red. You can do a web search on 'hexadecimal colors' to find a chart of color codes.{/ts}
-            </div>
             <table class="form-layout-compressed">
             {foreach from=$colorFields item=field key=fieldName}
               <tr><td class="label">{$form.$fieldName.label}<span class="crm-marker"> *</span></td><td>{$form.$fieldName.html}</td></tr>
 </script>
 {/literal}
 {/crmRegion}
-{crmRegion name="contribute-form-contributionpage-widget-post}
+{crmRegion name="contribute-form-contributionpage-widget-post"}
 {/crmRegion}
index 1e7c56782e610ca2ec7c3ef1a05423df54cdc83d..02f2a4377ca530fa3e192854764e2c2d08b72d24 100644 (file)
@@ -39,7 +39,7 @@
             <div class="label">{$form.$paymentField.label}
               {if $requiredPaymentFields.$name}<span class="crm-marker" title="{ts}This field is required.{/ts}">*</span>{/if}
             </div>
-            <div class="content">{$form.$paymentField.html}
+            <div class="content">{if $form.$paymentField.html}{$form.$paymentField.html}{else}<input id="{$paymentField}" name="{$paymentField}" type="hidden" />{/if}
               {if $paymentField == 'cvv2'}{* @todo move to form assignment*}
                 <span class="cvv2-icon" title="{ts}Usually the last 3-4 digits in the signature area on the back of the card.{/ts}"> </span>
               {/if}
index 93887cb5d94452abbf90ee7c8db896148cec4652..32405421288c8d25e8b88d8015408095d1478070 100644 (file)
@@ -24,7 +24,7 @@
  +--------------------------------------------------------------------+
 *}
 {assign var=isRecordPayment value=1 }
-{assign var=isShowBillingBlock value=($action neq 2)}
+{capture assign="isShowBillingBlock"}{if $action neq 2}1{else}0{/if}{/capture}
 {if $paid} {* We retrieve this tpl when event is selected - keep it empty if event is not paid *}
     <table class="form-layout">
     {if $priceSet}
index 6fe311cd282cf9345217e58bd1504180eb9b9d23..99f7f48f80ffad93ec545ff7500ef0f71d123652 100644 (file)
  +--------------------------------------------------------------------+
 *}
 <div class="crm-block crm-form-block crm-group-search-form-block">
-
-<h3>{ts}Find Groups{/ts}</h3>
-<table class="form-layout">
-  <tr>
-    <td>
-      {$form.title.label}<br />
-      {$form.title.html}<br />
-      <span class="description font-italic">
+  <div class="crm-accordion-wrapper crm-search_builder-accordion {if $rows and !$showSearchForm}collapsed{/if}">
+    <div class="crm-accordion-header crm-master-accordion-header">
+      {ts}Find Groups{/ts}
+    </div>
+    <div class="crm-accordion-body">
+      <div id="searchForm">
+        <table class="form-layout">
+          <tr>
+            <td>
+              {$form.title.label}<br />
+              {$form.title.html}<br />
+              <span class="description font-italic">
           {ts}Complete OR partial group name.{/ts}
       </span>
-    </td>
-    <td>
-      {$form.created_by.label}<br />
-      {$form.created_by.html}<br />
-      <span class="description font-italic">
+            </td>
+            <td>
+              {$form.created_by.label}<br />
+              {$form.created_by.html}<br />
+              <span class="description font-italic">
           {ts}Complete OR partial creator name.{/ts}
       </span>
-    </td>
-    <td id="group_type-block">
-      {$form.group_type_search.label}<br />
-      {$form.group_type_search.html}<br />
-      <span class="description font-italic">
-          {ts}Filter search by group type(s).{/ts}
-      </span>
-    </td>
-    <td>
-      {$form.visibility.label}<br />
-      {$form.visibility.html}<br />
-      <span class="description font-italic">
+            </td>
+            <td>
+              {$form.visibility.label}<br />
+              {$form.visibility.html}<br />
+              <span class="description font-italic">
           {ts}Filter search by visibility.{/ts}
       </span>
-    </td>
-    <td>
-      {$form.group_status.label}<br />
-      {$form.group_status.html}
-    </td>
-  </tr>
-</table>
-</div>
+            </td>
+          </tr>
+          <tr>
+            <td id="group_type-block">
+              {$form.group_type_search.label}<br />
+              {$form.group_type_search.html}<br />
+              <span class="description font-italic">
+          {ts}Filter search by group type(s).{/ts}
+      </span>
+            </td>
+            <td>
+              {$form.group_status.label}<br />
+              {$form.group_status.html}
+            </td>
+            <td>
+              {$form.component_mode.label}<br />
+              {$form.component_mode.html}
+            </td>
+          </tr>
+        </table>
+      </div>
+    </div>
+  </div>
 <div class="css_right">
   <a class="crm-hover-button action-item" href="{crmURL q="reset=1&update_smart_groups=1"}">{ts}Update Smart Group Counts{/ts}</a> {help id="update_smart_groups"}
 </div>
           d.group_type = groupTypes,
           d.visibility = $(".crm-group-search-form-block select#visibility").val(),
           d.status = groupStatus,
+          d.component_mode = $(".crm-group-search-form-block select#component_mode").val(),
           d.showOrgInfo = {/literal}"{$showOrgInfo}"{literal},
           d.parentsOnly = parentsOnly
         }
index 3c6bc9a3d04c679d7c8de33d26fe865fea481a89..1e679fcc9f4f652891e754e4a8ef6e6dc68a5f5c 100644 (file)
       <tr class="crm-membership-type-form-block-visibility">
         <td class="label">{$form.visibility.label}</td>
         <td>{$form.visibility.html}<br />
-          <span class="description">{ts}Is this membership type available for self-service signups ('Public') or assigned by CiviCRM 'staff' users only ('Admin'){/ts}</span>
+          <span class="description">{ts}Can this membership type be used for self-service signups ('Public'), or is it only for CiviCRM users with 'Edit Contributions' permission ('Admin').{/ts}</span>
         </td>
       </tr>
       <tr class="crm-membership-type-form-block-weight">
index ae4d6e269fd86876a2bdfdf6a02de88816fe9da9..2895fed38c7f76c79a1f5ef4e3ec215538ee336d 100644 (file)
@@ -399,6 +399,14 @@ if (!defined('CIVICRM_DB_CACHE_PREFIX')) {
   define('CIVICRM_DB_CACHE_PREFIX', '');
 }
 
+/**
+ * The cache system traditionally allowed a wide range of cache-keys, but some
+ * cache-keys are prohibited by PSR-16.
+ */
+if (!defined('CIVICRM_PSR16_STRICT')) {
+  define('CIVICRM_PSR16_STRICT', FALSE);
+}
+
 /**
  * If you have multilingual site and you are using the "inherit CMS language"
  * configuration option, but wish to, for example, use fr_CA instead of the
index d43375c0173b6c07ec0def7115d88e8e1a38491e..d0f8bed73dc65996a1975082b084b31365c0c4dc 100644 (file)
@@ -23,6 +23,7 @@ class CRM_Case_BAO_CaseTypeTest extends CiviUnitTestCase {
         'activitySets' => array(),
         'activityTypes' => array(),
         'caseRoles' => array(),
+        'timelineActivityTypes' => array(),
       )),
       'xml' => file_get_contents(__DIR__ . '/xml/empty-lists.xml'),
     );
@@ -42,6 +43,9 @@ class CRM_Case_BAO_CaseTypeTest extends CiviUnitTestCase {
             ),
           ),
         ),
+        'timelineActivityTypes' => array(
+          array('name' => 'Open Case', 'status' => 'Completed'),
+        ),
         'caseRoles' => array(
           array('name' => 'First role', 'creator' => 1, 'manager' => 1),
         ),
@@ -80,6 +84,15 @@ class CRM_Case_BAO_CaseTypeTest extends CiviUnitTestCase {
             ),
           ),
         ),
+        'timelineActivityTypes' => array(
+          array('name' => 'Open Case', 'status' => 'Completed'),
+          array(
+            'name' => 'Meeting',
+            'reference_activity' => 'Open Case',
+            'reference_offset' => 1,
+            'reference_select' => 'newest',
+          ),
+        ),
         'caseRoles' => array(
           array('name' => 'First role', 'creator' => 1, 'manager' => 1),
           array('name' => 'Second role'),
index a000c70e1c2c8631a30cf967fdd9f51bd837f65e..23732e4669897cd614c19ef31470e16f78f4081f 100644 (file)
@@ -420,6 +420,60 @@ class CRM_Contact_Form_SelectorTest extends CiviUnitTestCase {
     $this->assertTrue(strpos($query->_fromClause, $cgTableName) !== FALSE);
   }
 
+  /**
+   * Check where clause of a date custom field when 'IS NOT EMPTY' operator is used
+   */
+  public function testCustomDateField() {
+    $contactID = $this->individualCreate();
+    //Create a test custom group and field.
+    $customGroup = $this->callAPISuccess('CustomGroup', 'create', array(
+      'title' => "test custom group",
+      'extends' => "Individual",
+    ));
+    $customTableName = $this->callAPISuccess('CustomGroup', 'getValue', ['id' => $customGroup, 'return' => 'table_name']);
+    $customGroupTableName = $customGroup['values'][$customGroup['id']]['table_name'];
+
+    $createdField = $this->callAPISuccess('customField', 'create', [
+      'data_type' => 'Date',
+      'html_type' => 'Select Date',
+      'date_format' => 'd M yy',
+      'time_format' => 1,
+      'label' => 'test field',
+      'custom_group_id' => $customGroup['id'],
+    ]);
+    $customFieldColumnName = $createdField['values'][$createdField['id']]['column_name'];
+
+    $this->callAPISuccess('Contact', 'create', [
+      'id' => $contactID,
+      'custom_' . $createdField['id'] => date('YmdHis'),
+    ]);
+
+    $selector = new CRM_Contact_Selector(
+      'CRM_Contact_Selector',
+      ['custom_' . $createdField['id'] => ['IS NOT EMPTY' => 1]],
+      [[
+        0 => 'custom_' . $createdField['id'],
+        1 => 'IS NOT NULL',
+        2 => 1,
+        3 => 1,
+        4 => 0,
+      ]],
+      [],
+      CRM_Core_Action::NONE,
+      NULL,
+      FALSE,
+      'builder'
+    );
+
+    $whereClause = $selector->getQueryObject()->query()[2];
+    $expectedClause = sprintf("( %s.%s IS NOT NULL )", $customGroupTableName, $customFieldColumnName);
+    // test the presence of expected date clause
+    $this->assertEquals(TRUE, strpos($whereClause, $expectedClause));
+
+    $rows = $selector->getRows(CRM_Core_Action::VIEW, 0, TRUE, NULL);
+    $this->assertEquals(1, count($rows));
+  }
+
   /**
    * Get the default select string since this is generally consistent.
    */
index 2b0f0113a8b4e93ffbd33e5835d88baf3c5f862a..900b96dc17e0c1da2cb1a8b34567f87cfbe55b04 100644 (file)
  */
 class CRM_Core_BAO_CacheTest extends CiviUnitTestCase {
 
-  public function testSetGetItem() {
-    $originalValue = array('abc' => 'def');
+  public function testMultiVersionDecode() {
+    $encoders = ['serialize', ['CRM_Core_BAO_Cache', 'encode']];
+    $values = [NULL, 0, 1, TRUE, FALSE, [], ['abcd'], 'ab;cd', new stdClass()];
+    foreach ($encoders as $encoder) {
+      foreach ($values as $value) {
+        $encoded = $encoder($value);
+        $decoded = CRM_Core_BAO_Cache::decode($encoded);
+        $this->assertEquals($value, $decoded, "Failure encoding/decoding value " . var_export($value, 1) . ' with ' . var_export($encoder, 1));
+      }
+    }
+  }
+
+  public function exampleValues() {
+    $binary = '';
+    for ($i = 0; $i < 256; $i++) {
+      $binary .= chr($i);
+    }
+
+    $ex = [];
+
+    $ex[] = [array('abc' => 'def')];
+    $ex[] = [0];
+    $ex[] = ['hello world'];
+    $ex[] = ['Scarabée'];
+    $ex[] = ['Iñtërnâtiônàlizætiøn'];
+    $ex[] = ['これは日本語のテキストです。読めますか'];
+    $ex[] = ['देखें हिन्दी कैसी नजर आती है। अरे वाह ये तो नजर आती है।'];
+    $ex[] = [$binary];
+
+    return $ex;
+  }
+
+  /**
+   * @param $originalValue
+   * @dataProvider exampleValues
+   */
+  public function testSetGetItem($originalValue) {
     CRM_Core_BAO_Cache::setItem($originalValue, __CLASS__, 'testSetGetItem');
 
     $return_1 = CRM_Core_BAO_Cache::getItem(__CLASS__, 'testSetGetItem');
@@ -47,4 +82,24 @@ class CRM_Core_BAO_CacheTest extends CiviUnitTestCase {
     $this->assertEquals($originalValue, $return_2);
   }
 
+  public function getCleanKeyExamples() {
+    $es = [];
+    $es[] = ['hello_world and other.planets', 'hello_world and other.planets']; // allowed chars
+    $es[] = ['hello/world+-#@{}', 'hello-2fworld-2b-2d-23-40-7b-7d']; // escaped chars
+    $es[] = ['123456789 123456789 123456789 123456789 123456789 123456789 123', '123456789 123456789 123456789 123456789 123456789 123456789 123']; // long but allowed
+    $es[] = ['123456789 123456789 123456789 123456789 123456789 123456789 1234', '-2a008e182a4dcd1a78f405f30119e5f2']; // too long, md5 fallback
+    $es[] = ['123456789 /23456789 +23456789 -23456789 123456789 123456789', '-1b6baab5961431ed443ab321f5dfa0fb']; // too long, md5 fallback
+    return $es;
+  }
+
+  /**
+   * @param $inputKey
+   * @param $expectKey
+   * @dataProvider getCleanKeyExamples
+   */
+  public function testCleanKeys($inputKey, $expectKey) {
+    $actualKey = CRM_Core_BAO_Cache::cleanKey($inputKey);
+    $this->assertEquals($expectKey, $actualKey);
+  }
+
 }
index 56f39829ced92e390c8af14d0a78455ba836ec3e..42222be567c7ece92e5fd5c074fbb14fd84d595d 100644 (file)
@@ -125,16 +125,16 @@ class CRM_Group_Page_AjaxTest extends CiviUnitTestCase {
     $params = $this->_params;
     $groups = CRM_Contact_BAO_Group::getGroupListSelector($params);
     $this->assertEquals(2, $groups['recordsTotal']);
-    $this->assertEquals('<span><a href="/index.php?q=civicrm/group/search&amp;reset=1&amp;force=1&amp;context=smog&amp;gid=4" class="action-item crm-hover-button" title=\'Group Contacts\' >Contacts</a></span>', $groups['data'][0]['links']);
-    $this->assertEquals('<span><a href="/index.php?q=civicrm/group/search&amp;reset=1&amp;force=1&amp;context=smog&amp;gid=2" class="action-item crm-hover-button" title=\'Group Contacts\' >Contacts</a></span>', $groups['data'][1]['links']);
+    $this->assertEquals('<span><a href="/index.php?q=civicrm/group/search&amp;reset=1&amp;force=1&amp;context=smog&amp;gid=4&amp;component_mode=1" class="action-item crm-hover-button" title=\'Group Contacts\' >Contacts</a></span>', $groups['data'][0]['links']);
+    $this->assertEquals('<span><a href="/index.php?q=civicrm/group/search&amp;reset=1&amp;force=1&amp;context=smog&amp;gid=2&amp;component_mode=1" class="action-item crm-hover-button" title=\'Group Contacts\' >Contacts</a></span>', $groups['data'][1]['links']);
 
     // as per changes made in PR-6822
     $this->setPermissionAndRequest(array('view all contacts', 'edit groups'));
     $params = $this->_params;
     $groups = CRM_Contact_BAO_Group::getGroupListSelector($params);
     $this->assertEquals(2, $groups['recordsTotal']);
-    $this->assertEquals('<span><a href="/index.php?q=civicrm/group/search&amp;reset=1&amp;force=1&amp;context=smog&amp;gid=4" class="action-item crm-hover-button" title=\'Group Contacts\' >Contacts</a><a href="/index.php?q=civicrm/group&amp;reset=1&amp;action=update&amp;id=4" class="action-item crm-hover-button" title=\'Edit Group\' >Settings</a></span><span class=\'btn-slide crm-hover-button\'>more<ul class=\'panel\'><li><a href="#" class="action-item crm-hover-button crm-enable-disable" title=\'Disable Group\' >Disable</a></li><li><a href="/index.php?q=civicrm/group&amp;reset=1&amp;action=delete&amp;id=4" class="action-item crm-hover-button small-popup" title=\'Delete Group\' >Delete</a></li></ul></span>', $groups['data'][0]['links']);
-    $this->assertEquals('<span><a href="/index.php?q=civicrm/group/search&amp;reset=1&amp;force=1&amp;context=smog&amp;gid=2" class="action-item crm-hover-button" title=\'Group Contacts\' >Contacts</a><a href="/index.php?q=civicrm/group&amp;reset=1&amp;action=update&amp;id=2" class="action-item crm-hover-button" title=\'Edit Group\' >Settings</a></span><span class=\'btn-slide crm-hover-button\'>more<ul class=\'panel\'><li><a href="#" class="action-item crm-hover-button crm-enable-disable" title=\'Disable Group\' >Disable</a></li><li><a href="/index.php?q=civicrm/group&amp;reset=1&amp;action=delete&amp;id=2" class="action-item crm-hover-button small-popup" title=\'Delete Group\' >Delete</a></li></ul></span>', $groups['data'][1]['links']);
+    $this->assertEquals('<span><a href="/index.php?q=civicrm/group/search&amp;reset=1&amp;force=1&amp;context=smog&amp;gid=4&amp;component_mode=1" class="action-item crm-hover-button" title=\'Group Contacts\' >Contacts</a><a href="/index.php?q=civicrm/group&amp;reset=1&amp;action=update&amp;id=4" class="action-item crm-hover-button" title=\'Edit Group\' >Settings</a></span><span class=\'btn-slide crm-hover-button\'>more<ul class=\'panel\'><li><a href="#" class="action-item crm-hover-button crm-enable-disable" title=\'Disable Group\' >Disable</a></li><li><a href="/index.php?q=civicrm/group&amp;reset=1&amp;action=delete&amp;id=4" class="action-item crm-hover-button small-popup" title=\'Delete Group\' >Delete</a></li></ul></span>', $groups['data'][0]['links']);
+    $this->assertEquals('<span><a href="/index.php?q=civicrm/group/search&amp;reset=1&amp;force=1&amp;context=smog&amp;gid=2&amp;component_mode=1" class="action-item crm-hover-button" title=\'Group Contacts\' >Contacts</a><a href="/index.php?q=civicrm/group&amp;reset=1&amp;action=update&amp;id=2" class="action-item crm-hover-button" title=\'Edit Group\' >Settings</a></span><span class=\'btn-slide crm-hover-button\'>more<ul class=\'panel\'><li><a href="#" class="action-item crm-hover-button crm-enable-disable" title=\'Disable Group\' >Disable</a></li><li><a href="/index.php?q=civicrm/group&amp;reset=1&amp;action=delete&amp;id=2" class="action-item crm-hover-button small-popup" title=\'Delete Group\' >Delete</a></li></ul></span>', $groups['data'][1]['links']);
   }
 
   /**
index f4c305665c74bd0bfbcaa4ad53104423beac1da1..ef11330a8429c6e679620ae1cea078443cdd4284 100644 (file)
@@ -132,8 +132,8 @@ class SettingsManagerTest extends \CiviUnitTestCase {
    */
   protected function createManager() {
     $cache = new \CRM_Utils_Cache_Arraycache(array());
-    $cache->set('defaults:domain', $this->domainDefaults);
-    $cache->set('defaults:contact', $this->contactDefaults);
+    $cache->set('defaults_domain', $this->domainDefaults);
+    $cache->set('defaults_contact', $this->contactDefaults);
     foreach ($this->mandates as $entity => $keyValues) {
       foreach ($keyValues as $k => $v) {
         $GLOBALS['civicrm_setting'][$entity][$k] = $v;
index b1be120c16bb8a79eb03710cc583592842154576..71cd39915efe51d5b7e75091c05c49c841df2142 100644 (file)
@@ -405,7 +405,9 @@ class CiviUnitTestCase extends PHPUnit_Extensions_Database_TestCase {
   protected function tearDown() {
     error_reporting(E_ALL & ~E_NOTICE);
     CRM_Utils_Hook::singleton()->reset();
-    $this->hookClass->reset();
+    if ($this->hookClass) {
+      $this->hookClass->reset();
+    }
     $session = CRM_Core_Session::singleton();
     $session->set('userID', NULL);
 
diff --git a/tests/phpunit/E2E/Cache/APCcacheTest.php b/tests/phpunit/E2E/Cache/APCcacheTest.php
new file mode 100644 (file)
index 0000000..4ab715e
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5                                                  |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2018                                |
+ +--------------------------------------------------------------------+
+ | 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 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        |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * Verify that CRM_Utils_Cache_APCcache complies with PSR-16.
+ *
+ * @group e2e
+ */
+class E2E_Cache_APCcacheTest extends E2E_Cache_CacheTestCase {
+
+  public function createSimpleCache() {
+    if (!function_exists('apc_store')) {
+      $this->markTestSkipped('This environment does not have the APC extension.');
+    }
+
+    if (PHP_SAPI === 'cli') {
+      $c = (string) ini_get('apc.enable_cli');
+      if ($c != 1 && strtolower($c) !== 'on') {
+        $this->markTestSkipped('This environment is not configured to use APC cache service. Set apc.enable_cli=on');
+      }
+    }
+
+    $config = [
+      'prefix' => 'foozball/',
+    ];
+    $c = new CRM_Utils_Cache_APCcache($config);
+    return $c;
+  }
+
+}
diff --git a/tests/phpunit/E2E/Cache/ArrayCacheTest.php b/tests/phpunit/E2E/Cache/ArrayCacheTest.php
new file mode 100644 (file)
index 0000000..65bc045
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5                                                  |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2018                                |
+ +--------------------------------------------------------------------+
+ | 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 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        |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * Verify that CRM_Utils_Cache_ArrayCache complies with PSR-16.
+ *
+ * @group e2e
+ */
+class E2E_Cache_ArrayCacheTest extends E2E_Cache_CacheTestCase {
+
+  public function createSimpleCache() {
+    return CRM_Utils_Cache::create([
+      'name' => 'e2e arraycache test',
+      'type' => ['ArrayCache'],
+    ]);
+  }
+
+}
diff --git a/tests/phpunit/E2E/Cache/CacheTestCase.php b/tests/phpunit/E2E/Cache/CacheTestCase.php
new file mode 100644 (file)
index 0000000..e8c1d8b
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5                                                  |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2018                                |
+ +--------------------------------------------------------------------+
+ | 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 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        |
+ +--------------------------------------------------------------------+
+ */
+
+require_once 'Cache/IntegrationTests/LegacySimpleCacheTest.php';
+
+/**
+ * Verify that a cache service complies with PSR-16.
+ *
+ * @group e2e
+ */
+abstract class E2E_Cache_CacheTestCase extends \Cache\IntegrationTests\LegacySimpleCacheTest implements \Civi\Test\EndToEndInterface {
+
+  const MAX_KEY = 255;
+
+  public static function setUpBeforeClass() {
+    CRM_Core_Config::singleton(1, 1);
+    CRM_Utils_System::loadBootStrap(array(
+      'name' => $GLOBALS['_CV']['ADMIN_USER'],
+      'pass' => $GLOBALS['_CV']['ADMIN_PASS'],
+    ));
+    CRM_Utils_System::synchronizeUsers();
+
+    parent::setUpBeforeClass();
+  }
+
+  public function testBasicUsageWithLongKey() {
+    if (isset($this->skippedTests[__FUNCTION__])) {
+      $this->markTestSkipped($this->skippedTests[__FUNCTION__]);
+    }
+
+    // Upstream test hardcodes 300, which is more permissive than PSR-16.
+    $key = str_repeat('a', self::MAX_KEY);
+
+    $this->assertFalse($this->cache->has($key));
+    $this->assertTrue($this->cache->set($key, 'value'));
+
+    $this->assertTrue($this->cache->has($key));
+    $this->assertSame('value', $this->cache->get($key));
+
+    $this->assertTrue($this->cache->delete($key));
+
+    $this->assertFalse($this->cache->has($key));
+  }
+
+}
diff --git a/tests/phpunit/E2E/Cache/ConfiguredMemoryTest.php b/tests/phpunit/E2E/Cache/ConfiguredMemoryTest.php
new file mode 100644 (file)
index 0000000..5f1ea8b
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5                                                  |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2018                                |
+ +--------------------------------------------------------------------+
+ | 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 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        |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * Verify that CRM_Utils_Cache_{Redis,Memcache} complies with PSR-16.
+ *
+ * NOTE: Only works if the local system is configured to use one of
+ * those services.
+ *
+ * @group e2e
+ */
+class E2E_Cache_ConfiguredMemoryTest extends E2E_Cache_CacheTestCase {
+
+  public function createSimpleCache() {
+    $cache = Civi::cache('default');
+
+    if ($cache instanceof CRM_Utils_Cache_Redis || $cache instanceof CRM_Utils_Cache_Memcache || $cache instanceof  CRM_Utils_Cache_Memcached) {
+      return $cache;
+    }
+    else {
+      $this->markTestSkipped('This environment is not configured to use a memory-backed cache service.');
+    }
+  }
+
+}
index fc6516a7f692e00a24a039f1480a5205046dca5f..ebdcf2e9fee59b96b05675aa8b344a07d7bfd9dd 100644 (file)
@@ -54,6 +54,9 @@ class api_v3_CaseTypeTest extends CiviCaseTestCase {
             ),
           ),
         ),
+        'timelineActivityTypes' => array(
+          array('name' => 'Open Case', 'status' => 'Completed'),
+        ),
         'caseRoles' => array(
           array('name' => 'First role', 'creator' => 1, 'manager' => 1),
         ),
index a3731534a0c7bcb836b38a8b45a58467af4a5b38..af809934e5ddd2bd4f4b16ac7972be6b46bfae99 100644 (file)
@@ -258,10 +258,14 @@ class api_v3_MembershipTypeTest extends CiviUnitTestCase {
   public function testEnableMembershipTypeOnContributionPage() {
     $memType = array();
     $memType[1] = $this->membershipTypeCreate(array('member_of_contact_id' => $this->_contactID, 'minimum_fee' => 100));
-    $priceSet = $this->callAPISuccess('price_set', 'getvalue', array(
-      'name' => 'default_membership_type_amount',
-      'return' => 'id',
+    $priceSet = $this->callAPISuccess('price_set', 'create', array(
+      'title' => "test priceset",
+      'name' => "test_priceset",
+      'extends' => "CiviMember",
+      'is_quick_config' => 1,
+      'financial_type_id' => "Member Dues",
     ));
+    $priceSet = $priceSet['id'];
     $field = $this->callAPISuccess('price_field', 'create', array(
       'price_set_id' => $priceSet,
       'name' => 'membership_amount',
@@ -297,6 +301,32 @@ class api_v3_MembershipTypeTest extends CiviUnitTestCase {
     $priceField = CRM_Price_BAO_PriceField::create($fieldParams);
     $this->assertEquals($priceField->id, $fieldParams['id']);
 
+    //Update membership type name and visibility
+    $updateParams = array(
+      'id' => $memType[1],
+      'name' => 'General - Edited',
+      'visibility' => 'Admin',
+      'financial_type_id' => 1,
+      'minimum_fee' => 300,
+      'description' => 'Test edit description',
+    );
+    $this->callAPISuccess('membership_type', 'create', $updateParams);
+    $priceFieldValue = $this->callAPISuccess('PriceFieldValue', 'get', array(
+      'sequential' => 1,
+      'membership_type_id' => $memType[1],
+    ));
+    //Verify if membership type updates are copied to pricefield value.
+    foreach ($priceFieldValue['values'] as $key => $value) {
+      $setId = $this->callAPISuccessGetValue('PriceField', array('return' => "price_set_id", 'id' => $value['price_field_id']));
+      if ($setId == $priceSet) {
+        $this->assertEquals($value['label'], $updateParams['name']);
+        $this->assertEquals($value['description'], $updateParams['description']);
+        $this->assertEquals((int) $value['amount'], $updateParams['minimum_fee']);
+        $this->assertEquals($value['financial_type_id'], $updateParams['financial_type_id']);
+        $this->assertEquals($value['visibility_id'], CRM_Price_BAO_PriceField::getVisibilityOptionID(strtolower($updateParams['visibility'])));
+      }
+    }
+
     foreach ($memType as $type) {
       $this->callAPISuccess('membership_type', 'delete', array('id' => $type));
     }
index a3b5556d953d358513ab293060dc685750c83164..1d42e3c207400d5da215f4930e226e9d17a71b44 100644 (file)
@@ -427,4 +427,86 @@ class api_v3_OptionValueTest extends CiviUnitTestCase {
     );
   }
 
+  /**
+   * Test to create and update payment method with financial account.
+   */
+  public function testCreateUpdateOptionValueForPaymentInstrument() {
+    $assetFinancialAccountId = $this->callAPISuccessGetValue('FinancialAccount', [
+      'return' => "id",
+      'financial_account_type_id' => "Asset",
+      'options' => ['limit' => 1],
+    ]);
+    // create new payment method with financial account
+    $ov = $this->callAPISuccess('OptionValue', 'create', [
+      'financial_account_id' => $assetFinancialAccountId,
+      'option_group_id' => "payment_instrument",
+      'label' => "Dummy Payment Method",
+    ]);
+
+    //check if relationship is created between Payment method and Financial Account
+    $this->checkPaymentMethodFinancialAccountRelationship($ov['id'], $assetFinancialAccountId);
+
+    // update payment method to have different non-asset financial Account
+    $nonAssetFinancialAccountId = $this->callAPISuccessGetValue('FinancialAccount', [
+      'return' => "id",
+      'financial_account_type_id' => ['NOT IN' => ["Asset"]],
+      'options' => ['limit' => 1],
+    ]);
+    try {
+      $result = $this->callAPISuccess('OptionValue', 'create', [
+        'financial_account_id' => $nonAssetFinancialAccountId,
+        'id' => $ov['id'],
+      ]);
+      throw new API_Exception(ts('Should throw error.'));
+    }
+    catch (Exception $e) {
+      try {
+        $assetAccountRelValue = $this->callAPISuccessGetValue('EntityFinancialAccount', [
+          'return' => "account_relationship",
+          'entity_table' => "civicrm_option_value",
+          'entity_id' => $ov['id'],
+          'financial_account_id' => $nonAssetFinancialAccountId,
+        ]);
+        throw new API_Exception(ts('Should throw error.'));
+      }
+      catch (Exception $e) {
+        $this->checkPaymentMethodFinancialAccountRelationship($ov['id'], $assetFinancialAccountId);
+      }
+    }
+    // update payment method to have different asset financial Account
+    $assetFinancialAccountId = $this->callAPISuccessGetValue('FinancialAccount', [
+      'return' => "id",
+      'financial_account_type_id' => "Asset",
+      'options' => ['limit' => 1],
+      'id' => ['NOT IN' => [$assetFinancialAccountId]],
+    ]);
+    $result = $this->callAPISuccess('OptionValue', 'create', [
+      'financial_account_id' => $assetFinancialAccountId,
+      'id' => $ov['id'],
+    ]);
+    //check if relationship is updated between Payment method and Financial Account
+    $this->checkPaymentMethodFinancialAccountRelationship($ov['id'], $assetFinancialAccountId);
+  }
+
+  /**
+   * Function to check relationship between FA and Payment method.
+   *
+   * @param int $paymentMethodId
+   * @param int $financialAccountId
+   */
+  protected function checkPaymentMethodFinancialAccountRelationship($paymentMethodId, $financialAccountId) {
+    $assetAccountRelValue = $this->callAPISuccessGetValue('EntityFinancialAccount', [
+      'return' => "account_relationship",
+      'entity_table' => "civicrm_option_value",
+      'entity_id' => $paymentMethodId,
+      'financial_account_id' => $financialAccountId,
+    ]);
+    $checkAssetAccountIs = $this->callAPISuccessGetValue('OptionValue', [
+      'return' => "id",
+      'option_group_id' => "account_relationship",
+      'name' => "Asset Account is",
+      'value' => $assetAccountRelValue,
+    ]);
+  }
+
 }