$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) {
* 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'));
$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
// set activity sets
if (isset($xml->ActivitySets)) {
$definition['activitySets'] = array();
+ $definition['timelineActivityTypes'] = array();
+
foreach ($xml->ActivitySets->ActivitySet as $activitySetXML) {
// parse basic properties
$activitySet = array();
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;
// 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
*/
$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);
// 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();
* @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(
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':
}
default:
- if (empty($dataType)) {
+ if (empty($dataType) || $dataType == 'Date') {
$dataType = 'String';
}
if (is_array($value)) {
// 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']) {
$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_',
* 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));
$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);
}
$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',
),
/**
* @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'));
$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];
$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);
$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]);
}
}
$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).
$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));
}
/**
}
}
+ /**
+ * (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.
*
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;
+ }
+
}
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.
*
$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,
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;
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";
+ }
+
}
}
$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;
}
$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;
}
*/
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.
*
* 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);
}
}
}
$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).
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));
}
/**
* @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;
}
'htmlType' => 'text',
'name' => 'credit_card_number',
'title' => ts('Card Number'),
- 'cc_field' => TRUE,
'attributes' => array(
'size' => 20,
'maxlength' => 20,
'htmlType' => 'text',
'name' => 'cvv2',
'title' => ts('Security Code'),
- 'cc_field' => TRUE,
'attributes' => array(
'size' => 5,
'maxlength' => 10,
'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(
'htmlType' => 'select',
'name' => 'credit_card_type',
'title' => ts('Card Type'),
- 'cc_field' => TRUE,
'attributes' => $creditCardType,
'is_required' => FALSE,
),
'htmlType' => 'text',
'name' => 'account_holder',
'title' => ts('Account Holder'),
- 'cc_field' => TRUE,
'attributes' => array(
'size' => 20,
'maxlength' => 34,
'htmlType' => 'text',
'name' => 'bank_account_number',
'title' => ts('Bank Account Number'),
- 'cc_field' => TRUE,
'attributes' => array(
'size' => 20,
'maxlength' => 34,
'htmlType' => 'text',
'name' => 'bank_identification_number',
'title' => ts('Bank Identification Number'),
- 'cc_field' => TRUE,
'attributes' => array(
'size' => 20,
'maxlength' => 11,
'htmlType' => 'text',
'name' => 'bank_name',
'title' => ts('Bank Name'),
- 'cc_field' => TRUE,
'attributes' => array(
'size' => 20,
'maxlength' => 64,
'name' => 'check_number',
'title' => ts('Check Number'),
'is_required' => FALSE,
- 'cc_field' => TRUE,
'attributes' => NULL,
),
'pan_truncation' => array(
'name' => 'pan_truncation',
'title' => ts('Last 4 digits of the card'),
'is_required' => FALSE,
- 'cc_field' => TRUE,
'attributes' => array(
'size' => 4,
'maxlength' => 4,
),
),
),
+ 'payment_token' => array(
+ 'htmlType' => 'hidden',
+ 'name' => 'payment_token',
+ 'title' => ts('Authorization token'),
+ 'is_required' => FALSE,
+ 'attributes' => ['size' => 10, 'autocomplete' => 'off'],
+ ),
);
}
$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));
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);
}
* @return string
*/
public static function getPaymentTypeLabel($paymentProcessor) {
- return ts(($paymentProcessor['object']->getPaymentTypeLabel()) . ' Information');
+ return ts('%1 Information', [$paymentProcessor->getPaymentTypeLabel()]);
}
/**
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 : '';
+
+ }
+
}
$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)) {
$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'];
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,
return $result;
}
+ /**
+ * @return \CRM_Utils_Cache_Interface|null
+ */
+ public function getCache() {
+ return $this->cache;
+ }
+
}
/**
* @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'));
$moduleExtensions = NULL;
if ($this->cache && !$fresh) {
- $moduleExtensions = $this->cache->get($this->cacheKey . '/moduleFiles');
+ $moduleExtensions = $this->cache->get($this->cacheKey . '_moduleFiles');
}
if (!is_array($moduleExtensions)) {
}
if ($this->cache) {
- $this->cache->set($this->cacheKey . '/moduleFiles', $moduleExtensions);
+ $this->cache->set($this->cacheKey . '_moduleFiles', $moduleExtensions);
}
}
return $moduleExtensions;
$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();
*
* @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() {
*/
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;
}
* 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(
}
}
- $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,
'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);
$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'];
}
$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,
$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'];
}
$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
}
/**
- * 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) {
}
/**
- * Process that send tell a friend e-mails
+ * Process that sends tell a friend e-mails
*
* @param int $contactID
* @param array $values
$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);
}
}
}
* 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;
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
*/
/**
* @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');
$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";
NULL, NULL, NULL, NULL, ' '
);
+ $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',
$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])
'created_by' => 'String',
'group_type' => 'String',
'visibility' => 'String',
+ 'component_mode' => 'String',
'status' => 'Integer',
'parentsOnly' => 'Integer',
'showOrgInfo' => 'Boolean',
}
$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'];
$component->find(TRUE);
- $emailDomain = CRM_Core_BAO_MailSettings::defaultDomain();
-
$html = $component->body_html;
if ($component->body_text) {
$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,
);
}
}
else {
- $emailDomain = CRM_Core_BAO_MailSettings::defaultDomain();
-
if (empty($eq->display_name)) {
$from = $eq->email;
}
'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'),
$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?
$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);
'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',
$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);
/**
* @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);
* @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);
}
}
/**
* @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'));
*/
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,
'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
* 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');
* @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];
}
'title' => ts('Contribution Status'),
'default' => TRUE,
),
+ 'contribution_source' => array(
+ 'title' => ts('Source'),
+ 'name' => 'source',
+ ),
'id' => array(
'title' => ts('Contribution ID'),
'default' => TRUE,
'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,
{* 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';
* 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.
*
// 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;
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;
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;
+ }
+
}
* @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 = '';
/**
* @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;
/**
* @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;
}
/**
* @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;
}
}
*/
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.
*
*/
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;
}
}
--- /dev/null
+<?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 {
+}
* @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);
+
}
--- /dev/null
+<?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 {
+}
* @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.
*
*/
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.
*
/**
* @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'] . '/';
}
}
* @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
*/
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.
*
/**
* @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");
+ }
}
/**
* @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);
}
/**
* @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'] . '/';
}
}
--- /dev/null
+<?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);
+ }
+
+}
--- /dev/null
+<?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");
+ }
+ }
+
+}
*/
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
/**
* @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;
}
/**
return FALSE;
}
+ public function clear() {
+ return $this->flush();
+ }
+
}
*
*/
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;
/**
* @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;
}
/**
* @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();
}
}
*/
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
*/
/**
* @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];
}
/**
* @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) {
closedir($handle);
unset($this->_cache);
$this->_cache = array();
+ return TRUE;
+ }
+
+ public function clear() {
+ return $this->flush();
}
}
*/
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.
*
/**
* @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);
}
/**
* @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() {
break;
}
- Civi::settings()->set('systemStatusCheckResult', $maxSeverity);
+ Civi::cache('checks')->set('systemStatusCheckResult', $maxSeverity);
return ($max) ? $maxSeverity : $messages;
}
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
*
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
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);
* 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));
$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'),
),
)
return self::getSystemDefaults($entity);
}
- $cacheKey = 'defaults:' . $entity;
+ $cacheKey = 'defaults_' . $entity;
$defaults = $this->cache->get($cacheKey);
if (!is_array($defaults)) {
$specs = SettingsMetadata::getMetadata(array(
$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) {
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>
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>
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>
<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>
<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>
<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>
<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>
<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>
</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>
'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',
// 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.
"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": [
{
"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",
'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',
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.
{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}
<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}
+--------------------------------------------------------------------+
*}
{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}
+--------------------------------------------------------------------+
*}
<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
}
<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">
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
'activitySets' => array(),
'activityTypes' => array(),
'caseRoles' => array(),
+ 'timelineActivityTypes' => array(),
)),
'xml' => file_get_contents(__DIR__ . '/xml/empty-lists.xml'),
);
),
),
),
+ 'timelineActivityTypes' => array(
+ array('name' => 'Open Case', 'status' => 'Completed'),
+ ),
'caseRoles' => array(
array('name' => 'First role', 'creator' => 1, 'manager' => 1),
),
),
),
),
+ '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'),
$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.
*/
*/
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');
$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);
+ }
+
}
$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&reset=1&force=1&context=smog&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&reset=1&force=1&context=smog&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&reset=1&force=1&context=smog&gid=4&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&reset=1&force=1&context=smog&gid=2&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&reset=1&force=1&context=smog&gid=4" class="action-item crm-hover-button" title=\'Group Contacts\' >Contacts</a><a href="/index.php?q=civicrm/group&reset=1&action=update&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&reset=1&action=delete&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&reset=1&force=1&context=smog&gid=2" class="action-item crm-hover-button" title=\'Group Contacts\' >Contacts</a><a href="/index.php?q=civicrm/group&reset=1&action=update&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&reset=1&action=delete&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&reset=1&force=1&context=smog&gid=4&component_mode=1" class="action-item crm-hover-button" title=\'Group Contacts\' >Contacts</a><a href="/index.php?q=civicrm/group&reset=1&action=update&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&reset=1&action=delete&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&reset=1&force=1&context=smog&gid=2&component_mode=1" class="action-item crm-hover-button" title=\'Group Contacts\' >Contacts</a><a href="/index.php?q=civicrm/group&reset=1&action=update&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&reset=1&action=delete&id=2" class="action-item crm-hover-button small-popup" title=\'Delete Group\' >Delete</a></li></ul></span>', $groups['data'][1]['links']);
}
/**
*/
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;
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);
--- /dev/null
+<?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;
+ }
+
+}
--- /dev/null
+<?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'],
+ ]);
+ }
+
+}
--- /dev/null
+<?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));
+ }
+
+}
--- /dev/null
+<?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.');
+ }
+ }
+
+}
),
),
),
+ 'timelineActivityTypes' => array(
+ array('name' => 'Open Case', 'status' => 'Completed'),
+ ),
'caseRoles' => array(
array('name' => 'First role', 'creator' => 1, 'manager' => 1),
),
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',
$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));
}
);
}
+ /**
+ * 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,
+ ]);
+ }
+
}