*/
protected function getSettingsMetaData() {
if (empty($this->settingsMetadata)) {
- $allSettingMetaData = civicrm_api3('setting', 'getfields', []);
- $this->settingsMetadata = array_intersect_key($allSettingMetaData['values'], $this->_settings);
+ $this->settingsMetadata = \Civi\Core\SettingsMetadata::getMetadata(['name' => array_keys($this->_settings)], NULL, TRUE);
// This array_merge re-orders to the key order of $this->_settings.
$this->settingsMetadata = array_merge($this->_settings, $this->settingsMetadata);
}
$quickFormType = $this->getQuickFormType($props);
if (isset($quickFormType)) {
$options = CRM_Utils_Array::value('options', $props);
- if (isset($props['pseudoconstant'])) {
- $options = civicrm_api3('Setting', 'getoptions', [
- 'field' => $setting,
- ])['values'];
+ if ($options) {
if ($props['html_type'] === 'Select' && isset($props['is_required']) && $props['is_required'] === FALSE && !isset($options[''])) {
// If the spec specifies the field is not required add a null option.
// Why not if empty($props['is_required']) - basically this has been added to the spec & might not be set to TRUE
* @return array
* Contact details
*/
- public static function getHierContactDetails($contactId, &$fields) {
+ public static function getHierContactDetails($contactId, $fields) {
$params = array(array('contact_id', '=', $contactId, 0, 0));
$options = array();
*/
public static function formatProfileContactParams(
&$params,
- &$fields,
+ $fields,
$contactID = NULL,
$ufGroupId = NULL,
$ctype = NULL,
) {
return;
}
- if ((($previousContributionStatus == 'Partially paid'
- && $currentContributionStatus == 'Completed')
+ // The 'right' way to add payments or refunds is through the Payment.create api. That api
+ // then updates the contribution but this process shoud not also record another financial trxn.
+ if ((($previousContributionStatus == 'Partially paid' && $currentContributionStatus == 'Completed')
+ || ($previousContributionStatus == 'Pending refund' && $currentContributionStatus == 'Completed')
+ // This concept of pay_later as different to any other sort of pending is deprecated & it's unclear
+ // why it is here or where it is handled instead.
|| ($previousContributionStatus == 'Pending' && $params['prevContribution']->is_pay_later == TRUE
&& $currentContributionStatus == 'Partially paid'))
&& $context == 'changedStatus'
$now = time();
if ($dao->start_date) {
$startDate = CRM_Utils_Date::unixTime($dao->start_date);
+ $data['start_date'] = $dao->start_date;
if ($startDate && $startDate >= $now) {
$data['is_active'] = FALSE;
$data['campaign_start'] = ts('Campaign starts on %1', [
if ($dao->end_date) {
$endDate = CRM_Utils_Date::unixTime($dao->end_date);
+ $data['end_date'] = $dao->end_date;
if ($endDate &&
$endDate < $now
) {
}
}
if (!empty($props['country_id'])) {
+ if (!CRM_Utils_Rule::commaSeparatedIntegers(implode(',', (array) $props['country_id']))) {
+ throw new CRM_Core_Exception(ts('Province limit or default country setting is incorrect'));
+ }
$params['condition'] = 'country_id IN (' . implode(',', (array) $props['country_id']) . ')';
}
break;
if ($context != 'get' && $context != 'validate') {
$config = CRM_Core_Config::singleton();
if (!empty($config->countryLimit) && is_array($config->countryLimit)) {
+ if (!CRM_Utils_Rule::commaSeparatedIntegers(implode(',', $config->countryLimit))) {
+ throw new CRM_Core_Exception(ts('Available Country setting is incorrect'));
+ }
$params['condition'] = 'id IN (' . implode(',', $config->countryLimit) . ')';
}
}
// Filter county list based on chosen state
case 'county_id':
if (!empty($props['state_province_id'])) {
+ if (!CRM_Utils_Rule::commaSeparatedIntegers(implode(',', (array) $props['state_province_id']))) {
+ throw new CRM_Core_Exception(ts('Can only accept Integers for state_province_id filtering'));
+ }
$params['condition'] = 'state_province_id IN (' . implode(',', (array) $props['state_province_id']) . ')';
}
break;
if (!empty($customDataSubType)) {
$subtypeClause = array();
foreach ($customDataSubType as $subtype) {
- $subtype = CRM_Core_DAO::VALUE_SEPARATOR . $subtype . CRM_Core_DAO::VALUE_SEPARATOR;
+ $subtype = CRM_Core_DAO::VALUE_SEPARATOR . CRM_Utils_Type::escape($subtype, 'String') . CRM_Core_DAO::VALUE_SEPARATOR;
$subtypeClause[] = "$cgTable.extends_entity_column_value LIKE '%{$subtype}%'";
}
if (!$onlySubType) {
foreach ($value as $key => $val) {
$value[$key] = str_replace(['[', ']', ','], ['\[', '\]', '[:comma:]'], $val);
$value[$key] = str_replace('|', '[:separator:]', $value[$key]);
+ if ($field['data_type'] == 'String') {
+ $value[$key] = CRM_Utils_Type::escape($value[$key], 'String');
+ }
+ elseif ($value) {
+ $value[$key] = CRM_Utils_Type::escape($value[$key], 'Integer');
+ }
}
$value = implode(',', $value);
}
}
/**
- * Delete from the previous next cache table for a pair of ids.
+ * Delete pair from the previous next cache table to remove it from further merge consideration.
+ *
+ * The pair may have been flipped, so make sure we delete using both orders
*
* @param int $id1
* @param int $id2
* @param string $cacheKey
- * @param bool $isViceVersa
- * @param string $entityTable
*/
- public static function deletePair($id1, $id2, $cacheKey = NULL, $isViceVersa = FALSE, $entityTable = 'civicrm_contact') {
- $sql = "DELETE FROM civicrm_prevnext_cache WHERE entity_table = %1";
- $params = [1 => [$entityTable, 'String']];
+ public static function deletePair($id1, $id2, $cacheKey = NULL) {
+ $sql = "DELETE FROM civicrm_prevnext_cache WHERE entity_table = 'civicrm_contact'";
- $pair = !$isViceVersa ? "entity_id1 = %2 AND entity_id2 = %3" : "(entity_id1 = %2 AND entity_id2 = %3) OR (entity_id1 = %3 AND entity_id2 = %2)";
+ $pair = "(entity_id1 = %2 AND entity_id2 = %3) OR (entity_id1 = %3 AND entity_id2 = %2)";
$sql .= " AND ( {$pair} )";
$params[2] = [$id1, 'Integer'];
$params[3] = [$id2, 'Integer'];
$mimeType = '';
$path = CRM_Core_Config::singleton()->customFileUploadDir . $fileName;
}
- $mimeType = CRM_Utils_Request::retrieveValue('mime-type', 'String', $mimeType, FALSE);
if (!$path) {
CRM_Core_Error::statusBounce('Could not retrieve the file');
}
+ if (empty($mimeType)) {
+ $passedInMimeType = self::convertBadMimeAliasTypes(CRM_Utils_Request::retrieveValue('mime-type', 'String', $mimeType, FALSE));
+ if (!in_array($passedInMimeType, explode(',', Civi::settings()->get('requestableMimeTypes')))) {
+ throw new CRM_Core_Exception("Supplied mime-type is not accepted");
+ }
+ $extension = CRM_Utils_File::getExtensionFromPath($path);
+ $candidateExtensions = CRM_Utils_File::getAcceptableExtensionsForMimeType($passedInMimeType);
+ if (!in_array($extension, $candidateExtensions)) {
+ throw new CRM_Core_Exception("Supplied mime-type does not match file extension");
+ }
+ // Now that we have validated mime-type supplied as much as possible lets now set the MimeType variable/
+ $mimeType = $passedInMimeType;
+ }
+
$buffer = file_get_contents($path);
if (!$buffer) {
CRM_Core_Error::statusBounce('The file is either empty or you do not have permission to retrieve the file');
}
}
+ /**
+ * Translate one mime type to another.
+ *
+ * Certain non-standard/weird MIME types have been common. Unfortunately, because
+ * of the way this controller is used, the weird types may baked-into URLs.
+ * We clean these up for compatibility.
+ *
+ * @param string $type
+ * Ex: 'image/jpg'
+ * @return string
+ * Ex: 'image/jpeg'.
+ */
+ protected static function convertBadMimeAliasTypes($type) {
+ $badTypes = [
+ // Before PNG format was ubiquitous, it was image/x-png?
+ 'image/x-png' => 'image/png',
+
+ // People see "image/gif" and "image/png" and wrongly guess "image/jpg"?
+ 'image/jpg' => 'image/jpeg',
+ 'image/tif' => 'image/tiff',
+ 'image/svg' => 'image/svg+xml',
+
+ // StackExchange attributes "pjpeg" to some quirk in an old version of IE?
+ 'image/pjpeg' => 'image/jpeg',
+
+ ];
+ return isset($badTypes[$type]) ? $badTypes[$type] : $type;
+ }
+
}
$xml = '<txn>';
foreach ($requestFields as $key => $value) {
- $xml .= '<' . $key . '>' . self::tidyStringforXML($value, $xmlFieldLength[$key]) . '</' . $key . '>';
+ //dev/core/966 Don't send email through the urlencode.
+ if ($key == 'ssl_email') {
+ $xml .= '<' . $key . '>' . substr($value, 0, $xmlFieldLength[$key]) . '</' . $key . '>';
+ }
+ else {
+ $xml .= '<' . $key . '>' . self::tidyStringforXML($value, $xmlFieldLength[$key]) . '</' . $key . '>';
+ }
}
$xml .= '</txn>';
return $xml;
$main = self::getMergeContactDetails($mainId);
$other = self::getMergeContactDetails($otherId);
- $specialValues['main'] = self::getSpecialValues($main);
- $specialValues['other'] = self::getSpecialValues($other);
$compareFields = self::retrieveFields($main, $other);
// CRM-15681 don't display sub-types in UI
continue;
}
- foreach (['main', 'other'] as $moniker) {
- $contact = &$$moniker;
- $value = CRM_Utils_Array::value($field, $contact);
- if (isset($specialValues[$moniker][$field]) && is_string($specialValues[$moniker][$field])) {
- $value = CRM_Core_DAO::VALUE_SEPARATOR . trim($specialValues[$moniker][$field], CRM_Core_DAO::VALUE_SEPARATOR) . CRM_Core_DAO::VALUE_SEPARATOR;
+ foreach (['main' => $main, 'other' => $other] as $moniker => $contact) {
+ $value = $label = CRM_Utils_Array::value($field, $contact);
+ $fieldSpec = $fields[$field];
+ if (!empty($fieldSpec['serialize']) && is_array($value)) {
+ // In practice this only applies to preferred_communication_method as the sub types are skipped above
+ // and no others are serialized.
+ $labels = [];
+ foreach ($value as $individualValue) {
+ $labels[] = CRM_Core_PseudoConstant::getLabel('CRM_Contact_BAO_Contact', $field, $individualValue);
+ }
+ $label = implode(', ', $labels);
+ // We serialize this due to historic handling but it's likely that if we just left it as an
+ // array all would be well & we would have less code.
+ $value = CRM_Core_DAO::serializeField($value, $fieldSpec['serialize']);
}
- $label = isset($specialValues[$moniker]["{$field}_display"]) ? $specialValues[$moniker]["{$field}_display"] : $value;
- if (!empty($fields[$field]['type']) && $fields[$field]['type'] == CRM_Utils_Type::T_DATE) {
+ elseif (!empty($fieldSpec['type']) && $fieldSpec['type'] == CRM_Utils_Type::T_DATE) {
if ($value) {
$value = str_replace('-', '', $value);
$label = CRM_Utils_Date::customFormat($label);
$label = ts('[x]');
}
}
- elseif ($field == 'prefix_id') {
- $label = CRM_Utils_Array::value('individual_prefix', $contact);
- }
- elseif ($field == 'suffix_id') {
- $label = CRM_Utils_Array::value('individual_suffix', $contact);
- }
- elseif ($field == 'gender_id' && !empty($value)) {
- $genderOptions = civicrm_api3('contact', 'getoptions', ['field' => 'gender_id']);
- $label = $genderOptions['values'][$value];
+ elseif (!empty($fieldSpec['pseudoconstant'])) {
+ $label = CRM_Core_PseudoConstant::getLabel('CRM_Contact_BAO_Contact', $field, $value);
}
elseif ($field == 'current_employer_id' && !empty($value)) {
$label = "$value (" . CRM_Contact_BAO_Contact::displayName($value) . ")";
return $cacheKeyString;
}
- /**
- * @param array $contact
- * @return array
- * $specialValues
- */
- public static function getSpecialValues($contact) {
- $preferred_communication_method = CRM_Utils_Array::value('preferred_communication_method', $contact);
- $value = empty($preferred_communication_method) ? [] : $preferred_communication_method;
- $specialValues = [
- 'preferred_communication_method' => $value,
- 'communication_style_id' => $value,
- ];
-
- if (!empty($contact['preferred_communication_method'])) {
- // api 3 returns pref_comm_method as an array, which breaks the lookup; so we reconstruct
- $prefCommList = is_array($specialValues['preferred_communication_method']) ? implode(CRM_Core_DAO::VALUE_SEPARATOR, $specialValues['preferred_communication_method']) : $specialValues['preferred_communication_method'];
- $specialValues['preferred_communication_method'] = CRM_Core_DAO::VALUE_SEPARATOR . $prefCommList . CRM_Core_DAO::VALUE_SEPARATOR;
- }
- $names = [
- 'preferred_communication_method' => [
- 'newName' => 'preferred_communication_method_display',
- 'groupName' => 'preferred_communication_method',
- ],
- ];
- CRM_Core_OptionGroup::lookupValues($specialValues, $names);
-
- if (!empty($contact['communication_style'])) {
- $specialValues['communication_style_id_display'] = $contact['communication_style'];
- }
- return $specialValues;
- }
-
/**
* Get the metadata for the merge fields.
*
CRM_Core_BAO_PrevNextCache::markConflict($mainId, $otherId, $cacheKeyString, $conflicts);
}
else {
- // delete entry from PrevNextCache table so we don't consider the pair next time
- // pair may have been flipped, so make sure we delete using both orders
- CRM_Core_BAO_PrevNextCache::deletePair($mainId, $otherId, $cacheKeyString, TRUE);
+ CRM_Core_BAO_PrevNextCache::deletePair($mainId, $otherId, $cacheKeyString);
}
}
if (is_array($value)) {
$type = implode(',', $value);
}
- $clauses[] = "event_type_id IN ({$type})";
+ $clauses[] = "event_type_id IN (%2)";
+ $params[2] = [$type, 'String'];
}
$eventsByDates = $this->get('eventsByDates');
*
* @throws \API_Exception
* @throws \CRM_Core_Exception
+ * @throws \CiviCRM_API3_Exception
*/
public static function create($params) {
$contribution = civicrm_api3('Contribution', 'getsingle', ['id' => $params['contribution_id']]);
}
if ($isPaymentCompletesContribution) {
- civicrm_api3('Contribution', 'completetransaction', ['id' => $contribution['id']]);
- // Get the trxn
- $trxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($contribution['id'], 'DESC');
- $ftParams = ['id' => $trxnId['financialTrxnId']];
- $trxn = CRM_Core_BAO_FinancialTrxn::retrieve($ftParams, CRM_Core_DAO::$_nullArray);
+ if ($contributionStatus == 'Pending refund') {
+ // Ideally we could still call completetransaction as non-payment related actions should
+ // be outside this class. However, for now we just update the contribution here.
+ // Unit test cover in CRM_Event_BAO_AdditionalPaymentTest::testTransactionInfo.
+ civicrm_api3('Contribution', 'create',
+ [
+ 'id' => $contribution['id'],
+ 'contribution_status_id' => 'Completed',
+ ]
+ );
+ }
+ else {
+ civicrm_api3('Contribution', 'completetransaction', ['id' => $contribution['id']]);
+ // Get the trxn
+ $trxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($contribution['id'], 'DESC');
+ $ftParams = ['id' => $trxnId['financialTrxnId']];
+ $trxn = CRM_Core_BAO_FinancialTrxn::retrieve($ftParams, CRM_Core_DAO::$_nullArray);
+ }
}
elseif ($contributionStatus === 'Pending') {
civicrm_api3('Contribution', 'create',
\Civi\Core\Resolver::singleton()->call($this->callback, $this->args);
}
+ /**
+ * Prohibit (de)serialization of CRM_Utils_AutoClean.
+ *
+ * The generic nature of AutoClean makes it a potential target for escalating
+ * serialization vulnerabilities, and there's no good reason for serializing it.
+ */
+ public function __sleep() {
+ throw new \RuntimeException("CRM_Utils_AutoClean is a runtime helper. It is not intended for serialization.");
+ }
+
+ /**
+ * Prohibit (de)serialization of CRM_Utils_AutoClean.
+ *
+ * The generic nature of AutoClean makes it a potential target for escalating
+ * serialization vulnerabilities, and there's no good reason for deserializing it.
+ */
+ public function __wakeup() {
+ throw new \RuntimeException("CRM_Utils_AutoClean is a runtime helper. It is not intended for deserialization.");
+ }
+
}
return FALSE;
}
+ /**
+ * Get the extensions that this MimeTpe is for
+ * @param string $mimeType the mime-type we want extensions for
+ * @return array
+ */
+ public static function getAcceptableExtensionsForMimeType($mimeType = NULL) {
+ $mapping = \MimeType\Mapping::$types;
+ $extensions = [];
+ foreach ($mapping as $extension => $type) {
+ if ($mimeType == $type) {
+ $extensions[] = $extension;
+ }
+ }
+ return $extensions;
+ }
+
+ /**
+ * Get the extension of a file based on its path
+ * @param string $path path of the file to query
+ * @return string
+ */
+ public static function getExtensionFromPath($path) {
+ return pathinfo($path, PATHINFO_EXTENSION);
+ }
+
}
*/
public static function commaSeparatedIntegers($value) {
foreach (explode(',', $value) as $val) {
+ // Remove any Whitespace around the key.
+ $val = trim($val);
if (!self::positiveInteger($val)) {
return FALSE;
}
*/
class SettingsMetadata {
- const ALL = 'all';
-
/**
* WARNING: This interface may change.
*
*
* @param array $filters
* @param int $domainID
+ * @param bool $loadOptions
*
* @return array
* the following information as appropriate for each setting
* - is_contact
* - description
* - help_text
+ * - options
+ * - pseudoconstant
*/
- public static function getMetadata($filters = [], $domainID = NULL) {
+ public static function getMetadata($filters = [], $domainID = NULL, $loadOptions = FALSE) {
if ($domainID === NULL) {
$domainID = \CRM_Core_Config::domainID();
}
$cache = \Civi::cache('settings');
$cacheString = 'settingsMetadata_' . $domainID . '_';
- // the caching into 'All' seems to be a duplicate of caching to
- // settingsMetadata__ - I think the reason was to cache all settings as defined & then those altered by a hook
$settingsMetadata = $cache->get($cacheString);
- $cached = is_array($settingsMetadata);
-
- if (!$cached) {
- $settingsMetadata = $cache->get(self::ALL);
- if (empty($settingsMetadata)) {
- global $civicrm_root;
- $metaDataFolders = [$civicrm_root . '/settings'];
- \CRM_Utils_Hook::alterSettingsFolders($metaDataFolders);
- $settingsMetadata = self::loadSettingsMetaDataFolders($metaDataFolders);
- $cache->set(self::ALL, $settingsMetadata);
- }
- }
-
- \CRM_Utils_Hook::alterSettingsMetaData($settingsMetadata, $domainID, NULL);
- if (!$cached) {
+ if (!is_array($settingsMetadata)) {
+ global $civicrm_root;
+ $metaDataFolders = [$civicrm_root . '/settings'];
+ \CRM_Utils_Hook::alterSettingsFolders($metaDataFolders);
+ $settingsMetadata = self::loadSettingsMetaDataFolders($metaDataFolders);
+ \CRM_Utils_Hook::alterSettingsMetaData($settingsMetadata, $domainID, NULL);
$cache->set($cacheString, $settingsMetadata);
}
self::_filterSettingsSpecification($filters, $settingsMetadata);
+ if ($loadOptions) {
+ self::loadOptions($settingsMetadata);
+ }
return $settingsMetadata;
}
* Metadata to filter.
*/
protected static function _filterSettingsSpecification($filters, &$settingSpec) {
- if (empty($filters)) {
- return;
- }
- elseif (array_keys($filters) == ['name']) {
- $settingSpec = [$filters['name'] => \CRM_Utils_Array::value($filters['name'], $settingSpec, '')];
- return;
+ if (!empty($filters['name'])) {
+ $settingSpec = array_intersect_key($settingSpec, array_flip((array) $filters['name']));
+ // FIXME: This is a workaround for settingsBag::setDb() called by unit tests with settings names that don't exist
+ $settingSpec += array_fill_keys((array) $filters['name'], []);
+ unset($filters['name']);
}
- else {
+ if (!empty($filters)) {
foreach ($settingSpec as $field => $fieldValues) {
if (array_intersect_assoc($fieldValues, $filters) != $filters) {
unset($settingSpec[$field]);
}
}
- return;
+ }
+ }
+
+ /**
+ * Retrieve options from settings metadata
+ *
+ * @param array $settingSpec
+ */
+ protected static function loadOptions(&$settingSpec) {
+ foreach ($settingSpec as &$spec) {
+ if (empty($spec['pseudoconstant'])) {
+ continue;
+ }
+ // It would be nice if we could leverage CRM_Core_PseudoConstant::get() somehow,
+ // but it's tightly coupled to DAO/field. However, if you really need to support
+ // more pseudoconstant types, then probably best to refactor it. For now, KISS.
+ if (!empty($spec['pseudoconstant']['callback'])) {
+ $spec['options'] = Resolver::singleton()->call($spec['pseudoconstant']['callback'], []);
+ }
+ elseif (!empty($spec['pseudoconstant']['optionGroupName'])) {
+ $keyColumn = \CRM_Utils_Array::value('keyColumn', $spec['pseudoconstant'], 'value');
+ $spec['options'] = \CRM_Core_OptionGroup::values($spec['pseudoconstant']['optionGroupName'], FALSE, FALSE, TRUE, NULL, 'label', TRUE, FALSE, $keyColumn);
+ }
}
}
// Validate 'context' from params
$context = CRM_Utils_Array::value('context', $apiRequest['params']);
CRM_Core_DAO::buildOptionsContext($context);
- unset($apiRequest['params']['context'], $apiRequest['params']['field']);
+ unset($apiRequest['params']['context'], $apiRequest['params']['field'], $apiRequest['params']['condition']);
$baoName = _civicrm_api3_get_BAO($apiRequest['entity']);
$options = $baoName::buildOptions($fieldName, $context, $apiRequest['params']);
* @throws \API_Exception
*/
function civicrm_api3_setting_getoptions($params) {
- $specs = CRM_Core_BAO_Setting::getSettingSpecification();
+ $domainId = CRM_Utils_Array::value('domain_id', $params);
+ $specs = \Civi\Core\SettingsMetadata::getMetadata(['name' => $params['field']], $domainId, TRUE);
- if (empty($specs[$params['field']]) || empty($specs[$params['field']]['pseudoconstant'])) {
+ if (empty($specs[$params['field']]) || !is_array(CRM_Utils_Array::value('options', $specs[$params['field']]))) {
throw new API_Exception("The field '" . $params['field'] . "' has no associated option list.");
}
- $pseudoconstant = $specs[$params['field']]['pseudoconstant'];
-
- // It would be nice if we could leverage CRM_Core_PseudoConstant::get() somehow,
- // but it's tightly coupled to DAO/field. However, if you really need to support
- // more pseudoconstant types, then probably best to refactor it. For now, KISS.
- if (!empty($pseudoconstant['callback'])) {
- $values = Civi\Core\Resolver::singleton()->call($pseudoconstant['callback'], []);
- return civicrm_api3_create_success($values, $params, 'Setting', 'getoptions');
- }
- elseif (!empty($pseudoconstant['optionGroupName'])) {
- $keyColumn = 'value';
- if (!empty($pseudoconstant['keyColumn'])) {
- $keyColumn = $pseudoconstant['keyColumn'];
- }
- return civicrm_api3_create_success(
- CRM_Core_OptionGroup::values($pseudoconstant['optionGroupName'], FALSE, FALSE, TRUE, NULL, 'label', TRUE, FALSE, $keyColumn),
- $params, 'Setting', 'getoptions'
- );
- }
-
- throw new API_Exception("The field '" . $params['field'] . "' uses an unsupported option list.");
+ return civicrm_api3_create_success($specs[$params['field']]['options'], $params, 'Setting', 'getoptions');
}
/**
"d3-3.5.x": "d3#~3.5.17",
"dc-2.1.x": "dc.js#~2.1.8",
"crossfilter-1.3.x": "crossfilter2#~1.3.11",
- "jquery": "~1.12",
+ "jquery": "civicrm/jquery#1.12.4-civicrm-1.1",
"jquery-ui": "~1.12",
"lodash-compat": "~3.0",
"google-code-prettify": "~1.0",
"es6-promise": "^4.2.4"
},
"resolutions": {
- "angular": "~1.5.11"
+ "angular": "~1.5.11",
+ "jquery": "1.12.4-civicrm-1.1"
}
}
"psr/simple-cache": "~1.0.1",
"cweagans/composer-patches": "~1.0",
"pear/log": "1.13.1",
- "ezyang/htmlpurifier": "4.10"
+ "ezyang/htmlpurifier": "4.10",
+ "katzien/php-mime-type": "2.1.0"
},
"scripts": {
"post-install-cmd": [
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "93a9f686f7eb00fb9d766d262eedb09b",
+ "content-hash": "2a06373b9174ae3aa2bfb820e2e5a35e",
"packages": [
{
"name": "civicrm/civicrm-cxn-rpc",
],
"time": "2017-03-20T17:10:46+00:00"
},
+ {
+ "name": "katzien/php-mime-type",
+ "version": "2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/katzien/PhpMimeType.git",
+ "reference": "159dfbdcd5906442f3dad89951127f0b9dfa3b78"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/katzien/PhpMimeType/zipball/159dfbdcd5906442f3dad89951127f0b9dfa3b78",
+ "reference": "159dfbdcd5906442f3dad89951127f0b9dfa3b78",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "5.*",
+ "satooshi/php-coveralls": "1.*"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "MimeType\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Kat Zien"
+ }
+ ],
+ "description": "A PHP library to detect the mime type of files.",
+ "homepage": "https://github.com/katzien/PhpMimeType",
+ "keywords": [
+ "mimetype",
+ "php"
+ ],
+ "time": "2017-03-23T02:05:33+00:00"
+ },
{
"name": "marcj/topsort",
"version": "1.1.0",
},
{
"name": "tecnickcom/tcpdf",
- "version": "6.2.13",
+ "version": "6.2.26",
"source": {
"type": "git",
"url": "https://github.com/tecnickcom/TCPDF.git",
- "reference": "95c5938aafe4b20df1454dbddb3e5005c0b26f64"
+ "reference": "367241059ca166e3a76490f4448c284e0a161f15"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/95c5938aafe4b20df1454dbddb3e5005c0b26f64",
- "reference": "95c5938aafe4b20df1454dbddb3e5005c0b26f64",
+ "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/367241059ca166e3a76490f4448c284e0a161f15",
+ "reference": "367241059ca166e3a76490f4448c284e0a161f15",
"shasum": ""
},
"require": {
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "LGPLv3"
+ "LGPL-3.0"
],
"authors": [
{
"name": "Nicola Asuni",
"email": "info@tecnick.com",
- "homepage": "http://nicolaasuni.tecnick.com"
+ "role": "lead"
}
],
"description": "TCPDF is a PHP class for generating PDF documents and barcodes.",
"pdf417",
"qrcode"
],
- "time": "2017-04-26T08:14:48+00:00"
+ "time": "2018-10-16T17:24:05+00:00"
},
{
"name": "totten/ca-config",
// Set the install type
// this is sent as a query string when the page is first loaded
// and subsequently posted to the page as a hidden field
-if (isset($_POST['civicrm_install_type'])) {
+// only permit acceptable installation types to prevent issues;
+$acceptableInstallTypes = ['drupal', 'wordpress', 'backdrop'];
+if (isset($_POST['civicrm_install_type']) && in_array($_POST['civicrm_install_type'], $acceptableInstallTypes)) {
$installType = $_POST['civicrm_install_type'];
}
-elseif (isset($_GET['civicrm_install_type'])) {
+elseif (isset($_GET['civicrm_install_type']) && in_array(strtolower($_GET['civicrm_install_type']), $acceptableInstallTypes)) {
$installType = strtolower($_GET['civicrm_install_type']);
}
else {
- // default value if not set
+ // default value if not set and not an acceptable install type.
$installType = "drupal";
}
* https://github.com/civicrm/civicrm-joomla
* https://github.com/civicrm/civicrm-wordpress
+## CiviCRM 5.13.4
+
+Released May 15, 2019
+
+- **[Security advisories](release-notes/5.3.4.md#security)**
+
## CiviCRM 5.13.3
-Released May 13, 2019
+Released May 14, 2019
- **[Synopsis](release-notes/5.13.3.md#synopsis)**
- **[Features](release-notes/5.13.3.md#features)**
# CiviCRM 5.13.3
-Released May 13, 2019
+Released May 14, 2019
- **[Synopsis](#synopsis)**
- **[Bugs resolved](#bugs)**
--- /dev/null
+# CiviCRM 5.13.4
+
+Released May 15, 2019
+
+- **[Security advisories](#security)**
+- **[Features](#features)**
+- **[Bugs resolved](#bugs)**
+- **[Miscellany](#misc)**
+- **[Credits](#credits)**
+
+## <a name="security"></a>Security advisories
+
+- **[CIVI-SA-2019-09](https://civicrm.org/advisory/civi-sa-2019-09-xxe-in-phpword)**: XXE in PHPWord
+- **[CIVI-SA-2019-10](https://civicrm.org/advisory/civi-sa-2019-10-tcpdf-xss-and-rce-vulerabilities)**: TCPDF XSS and RCE vulnerabilities
+- **[CIVI-SA-2019-11](https://civicrm.org/advisory/civi-sa-2019-11-jquery-objectprototype-pollution)**: jQuery Object.prototype pollution
+- **[CIVI-SA-2019-12](https://civicrm.org/advisory/civi-sa-2019-12-sqli-in-country-et-al)**: SQLI in "Country", et al
+- **[CIVI-SA-2019-13](https://civicrm.org/advisory/civi-sa-2019-13-harden-against-unserialize-vulnerabilities)**: Harden against unserialize vulnerabilities
+- **[CIVI-SA-2019-14](https://civicrm.org/advisory/civi-sa-2019-14-sqli-in-apiv3-getoptions)**: SQLI in APIv3 GetOptions
+- **[CIVI-SA-2019-15](https://civicrm.org/advisory/civi-sa-2019-15-xss-via-forged-mime-type)**: XSS via forged MIME type
+- **[CIVI-SA-2019-16](https://civicrm.org/advisory/civi-sa-2019-16-sqli-in-certain-checkboxes)**: SQLI in certain checkboxes
+- **[CIVI-SA-2019-17](https://civicrm.org/advisory/civi-sa-2019-17-sqli-in-manage-events)**: SQLI in "Manage Events"
+- **[CIVI-SA-2019-18](https://civicrm.org/advisory/civi-sa-2019-18-xss-in-civicrm-installer)**: XSS in CiviCRM installer
+- **[CIVIEXT-SA-2019-01](https://civicrm.org/advisory/civiext-sa-2019-01-multiple-security-issues-in-apiv4)**: Multiple security issues in APIv4
'help_text' => NULL,
'validate_callback' => 'CRM_Utils_Rule::color',
],
+ 'requestableMimeTypes' => [
+ 'group_name' => 'CiviCRM Preferences',
+ 'group' => 'core',
+ 'name' => 'requestableMimeTypes',
+ 'type' => 'String',
+ 'html_type' => 'Text',
+ 'default' => 'image/jpeg,image/pjpeg,image/gif,image/x-png,image/png,image/jpg,text/html,application/pdf',
+ 'add' => '5.13',
+ 'title' => ts('Mime Types that can be passed as URL params'),
+ 'is_domain' => 1,
+ 'is_contact' => 0,
+ 'description' => ts('Acceptable Mime Types that can be used as part of file urls'),
+ 'help_text' => NULL,
+ ],
];
), $pairs);
}
+ /**
+ * Test the special info handling is unchanged after cleanup.
+ *
+ * Note the handling is silly - we are testing to lock in over short term changes not to imply any contract on the
+ * function.
+ */
+ public function testgetRowsElementsAndInfoSpecialInfo() {
+ $contact1 = $this->individualCreate(['preferred_communication_method' => [], 'communication_style_id' => 'Familiar', 'prefix_id' => 'Mrs.', 'suffix_id' => 'III']);
+ $contact2 = $this->individualCreate(['preferred_communication_method' => ['SMS', 'Fax'], 'communication_style_id' => 'Formal', 'gender_id' => 'Female']);
+ $rowsElementsAndInfo = CRM_Dedupe_Merger::getRowsElementsAndInfo($contact1, $contact2);
+ $rows = $rowsElementsAndInfo['rows'];
+ $this->assertEquals(['main' => 'Mrs.', 'other' => 'Mr.', 'title' => 'Individual Prefix'], $rows['move_prefix_id']);
+ $this->assertEquals(['main' => 'III', 'other' => 'II', 'title' => 'Individual Suffix'], $rows['move_suffix_id']);
+ $this->assertEquals(['main' => '', 'other' => 'Female', 'title' => 'Gender'], $rows['move_gender_id']);
+ $this->assertEquals(['main' => 'Familiar', 'other' => 'Formal', 'title' => 'Communication Style'], $rows['move_communication_style_id']);
+ $this->assertEquals(1, $rowsElementsAndInfo['migration_info']['move_communication_style_id']);
+ $this->assertEquals(['main' => '', 'other' => 'SMS, Fax', 'title' => 'Preferred Communication Method'], $rows['move_preferred_communication_method']);
+ $this->assertEquals('\ 14\ 15\ 1', $rowsElementsAndInfo['migration_info']['move_preferred_communication_method']);
+ }
+
/**
* Test migration of Membership.
*/
$result = $this->addParticipantWithPayment($feeAmt, $amtPaid);
$contributionID = $result['contribution']['id'];
- //Complete the partial payment.
- $submittedValues = array(
+ $this->callAPISuccess('Payment', 'create', [
+ 'contribution_id' => $contributionID,
'total_amount' => 20,
'payment_instrument_id' => 3,
- );
- CRM_Contribute_BAO_Contribution::recordAdditionalPayment($contributionID, $submittedValues, 'owed', $result['participant']['id']);
+ 'participant_id' => $result['participant']['id'],
+ ]);
//Change selection to a lower amount.
$params['price_2'] = 50;
CRM_Price_BAO_LineItem::changeFeeSelections($params, $result['participant']['id'], 'participant', $contributionID, $result['feeBlock'], $result['lineItem']);
- //Record a refund of the remaining amount.
- $submittedValues['total_amount'] = 50;
- CRM_Contribute_BAO_Contribution::recordAdditionalPayment($contributionID, $submittedValues, 'refund', $result['participant']['id']);
+ $this->callAPISuccess('Payment', 'create', [
+ 'total_amount' => -50,
+ 'contribution_id' => $contributionID,
+ 'participant_id' => $result['participant']['id'],
+ 'payment_instrument_id' => 3,
+ ]);
$paymentInfo = CRM_Contribute_BAO_Contribution::getPaymentInfo($result['participant']['id'], 'event', TRUE);
$transaction = $paymentInfo['transaction'];
$contributionBalance = ($this->_cheapFee - $actualPaidAmount);
$this->assertEquals($contributionBalance, CRM_Contribute_BAO_Contribution::getContributionBalance($this->_contributionId));
- //Complete the refund payment.
- $submittedValues = array(
- 'total_amount' => 120,
+ $this->callAPISuccess('Payment', 'create', [
+ 'contribution_id' => $this->_contributionId,
+ 'total_amount' => -120,
'payment_instrument_id' => 3,
- );
- CRM_Contribute_BAO_Contribution::recordAdditionalPayment($this->_contributionId, $submittedValues, 'refund', $this->_participantId);
+ 'participant_id' => $this->_participantId,
+ ]);
$contributionBalance += 120;
$this->assertEquals($contributionBalance, CRM_Contribute_BAO_Contribution::getContributionBalance($this->_contributionId));
$this->assertEquals($expectedResult, CRM_Utils_File::isValidFileName($fileName));
}
+ public function pathToFileExtension() {
+ $cases = [];
+ $cases[] = ['/evil.pdf', 'pdf'];
+ $cases[] = ['/helloworld.jpg', 'jpg'];
+ $cases[] = ['/smartwatch_1736683_1280_9af3657015e8660cc234eb1601da871.jpg', 'jpg'];
+ return $cases;
+ }
+
+ /**
+ * Test returning appropriate file extension
+ * @dataProvider pathToFileExtension
+ * @param string $path
+ * @param string $expectedExtension
+ */
+ public function testPathToExtension($path, $expectedExtension) {
+ $this->assertEquals($expectedExtension, CRM_Utils_File::getExtensionFromPath($path));
+ }
+
+ public function mimeTypeToExtension() {
+ $cases = [];
+ $cases[] = ['text/plain', ['txt', 'text', 'conf', 'def', 'list', 'log', 'in']];
+ $cases[] = ['image/jpeg', ['jpeg', 'jpg', 'jpe']];
+ $cases[] = ['image/png', ['png']];
+ return $cases;
+ }
+
+ /**
+ * @dataProvider mimeTypeToExtension
+ * @param stirng $mimeType
+ * @param array $expectedExtensions
+ */
+ public function testMimeTypeToExtension($mimeType, $expectedExtensions) {
+ $this->assertEquals($expectedExtensions, CRM_Utils_File::getAcceptableExtensionsForMimeType($mimeType));
+ }
+
}
* @return void
*/
public function setMockSettingsMetaData($extras) {
- Civi::service('settings_manager')->flush();
-
CRM_Utils_Hook::singleton()
->setHook('civicrm_alterSettingsMetaData', function (&$metadata, $domainId, $profile) use ($extras) {
$metadata = array_merge($metadata, $extras);
});
+ Civi::service('settings_manager')->flush();
+
$fields = $this->callAPISuccess('setting', 'getfields', array());
foreach ($extras as $key => $spec) {
$this->assertNotEmpty($spec['title']);
$this->assertEquals($expectState, $created['state_province_id']);
}
+ public function testBuildStateProvinceOptionsWithDodgyProvinceLimit() {
+ $provinceLimit = [1228, "abcd;ef"];
+ $this->callAPISuccess('setting', 'create', [
+ 'provinceLimit' => $provinceLimit,
+ ]);
+ $result = $this->callAPIFailure('address', 'getoptions', ['field' => 'state_province_id']);
+ // confirm that we hit our error not a SQLI.
+ $this->assertEquals('Province limit or default country setting is incorrect', $result['error_message']);
+ $this->callAPISuccess('setting', 'create', [
+ 'provinceLimit' => [1228],
+ ]);
+ // Now confirm with a correct province setting it works fine
+ $this->callAPISuccess('address', 'getoptions', ['field' => 'state_province_id']);
+ }
+
+ public function testBuildCountryWithDodgyCountryLimitSetting() {
+ $countryLimit = [1228, "abcd;ef"];
+ $this->callAPISuccess('setting', 'create', [
+ 'countryLimit' => $countryLimit,
+ ]);
+ $result = $this->callAPIFailure('address', 'getoptions', ['field' => 'country_id']);
+ // confirm that we hit our error not a SQLI.
+ $this->assertEquals('Available Country setting is incorrect', $result['error_message']);
+ $this->callAPISuccess('setting', 'create', [
+ 'countryLimit' => [1228],
+ ]);
+ // Now confirm with a correct province setting it works fine
+ $this->callAPISuccess('address', 'getoptions', ['field' => 'country_id']);
+ }
+
+ public function testBuildCountyWithDodgeStateProvinceFiltering() {
+ $result = $this->callAPIFailure('Address', 'getoptions', [
+ 'field' => 'county_id',
+ 'state_province_id' => "abcd;ef",
+ ]);
+ $this->assertEquals('Can only accept Integers for state_province_id filtering', $result['error_message']);
+ $goodResult = $this->callAPISuccess('Address', 'getoptions', [
+ 'field' => 'county_id',
+ 'state_province_id' => 1004,
+ ]);
+ $this->assertEquals('San Francisco', $goodResult['values'][4]);
+ }
+
}
public function testGetFieldsCaching() {
$settingsMetadata = array();
Civi::cache('settings')->set('settingsMetadata_' . \CRM_Core_Config::domainID() . '_', $settingsMetadata);
- Civi::cache('settings')->set(\Civi\Core\SettingsMetadata::ALL, $settingsMetadata);
$result = $this->callAPISuccess('setting', 'getfields', array());
$this->assertArrayNotHasKey('customCSSURL', $result['values']);
$this->quickCleanup(array('civicrm_cache'));