diff --git a/civicrm.php b/civicrm.php index 8e1297af005a42631fc7038a174bb7266fb2e8ef..e1fa3472fce3de9914f66e46d64953182c783aee 100644 --- a/civicrm.php +++ b/civicrm.php @@ -2,7 +2,7 @@ /* Plugin Name: CiviCRM Description: CiviCRM - Growing and Sustaining Relationships -Version: 5.28.0 +Version: 5.28.1 Requires at least: 4.9 Requires PHP: 7.1 Author: CiviCRM LLC @@ -56,7 +56,7 @@ if ( ! defined( 'ABSPATH' ) ) exit; // Set version here: when it changes, will force JS to reload -define( 'CIVICRM_PLUGIN_VERSION', '5.28.0' ); +define( 'CIVICRM_PLUGIN_VERSION', '5.28.1' ); // Store reference to this file if (!defined('CIVICRM_PLUGIN_FILE')) { diff --git a/civicrm/CRM/Activity/Form/Activity.php b/civicrm/CRM/Activity/Form/Activity.php index afaaffac73ac6b7a2eb3fa122a4770008d9171aa..329e330960674c16ecba3fbbb74c1fcc253934ca 100644 --- a/civicrm/CRM/Activity/Form/Activity.php +++ b/civicrm/CRM/Activity/Form/Activity.php @@ -503,6 +503,7 @@ class CRM_Activity_Form_Activity extends CRM_Contact_Form_Task { } if ($this->_action & CRM_Core_Action::VIEW) { + $this->_values['details'] = CRM_Utils_String::purifyHtml($this->_values['details']); $url = CRM_Utils_System::url(implode("/", $this->urlPath), "reset=1&id={$this->_activityId}&action=view&cid={$this->_values['source_contact_id']}"); CRM_Utils_Recent::add(CRM_Utils_Array::value('subject', $this->_values, ts('(no subject)')), $url, diff --git a/civicrm/CRM/Activity/Form/Task.php b/civicrm/CRM/Activity/Form/Task.php index c16964876815780fea5e2ed74284c25bf87d21ba..462499c38fd338b1bb705f2f9e200413cd1c96e7 100644 --- a/civicrm/CRM/Activity/Form/Task.php +++ b/civicrm/CRM/Activity/Form/Task.php @@ -70,16 +70,6 @@ class CRM_Activity_Form_Task extends CRM_Core_Form_Task { // CRM-12675 $activityClause = NULL; - $components = CRM_Core_Component::getNames(); - $componentClause = []; - foreach ($components as $componentID => $componentName) { - if ($componentName != 'CiviCase' && !CRM_Core_Permission::check("access $componentName")) { - $componentClause[] = " (activity_type.component_id IS NULL OR activity_type.component_id <> {$componentID}) "; - } - } - if (!empty($componentClause)) { - $activityClause = implode(' AND ', $componentClause); - } $result = $query->searchQuery(0, 0, NULL, FALSE, FALSE, FALSE, FALSE, FALSE, $activityClause); while ($result->fetch()) { diff --git a/civicrm/CRM/Admin/Page/CKEditorConfig.php b/civicrm/CRM/Admin/Form/CKEditorConfig.php similarity index 69% rename from civicrm/CRM/Admin/Page/CKEditorConfig.php rename to civicrm/CRM/Admin/Form/CKEditorConfig.php index fb42b9e7b58aeece9ded68f33a30c13fc46dffe0..6de81d37ca4ace08b0a37fed8ce7c73b2d5bb00a 100644 --- a/civicrm/CRM/Admin/Page/CKEditorConfig.php +++ b/civicrm/CRM/Admin/Form/CKEditorConfig.php @@ -16,13 +16,9 @@ */ /** - * Page for configuring CKEditor options. - * - * Note that while this is implemented as a CRM_Core_Page, it is actually a form. - * Because the form needs to be submitted and refreshed via javascript, it seemed like - * Quickform and CRM_Core_Form/Controller might get in the way. + * Form for configuring CKEditor options. */ -class CRM_Admin_Page_CKEditorConfig extends CRM_Core_Page { +class CRM_Admin_Form_CKEditorConfig extends CRM_Core_Form { const CONFIG_FILEPATH = '[civicrm.files]/persist/crm-ckeditor-'; @@ -37,6 +33,7 @@ class CRM_Admin_Page_CKEditorConfig extends CRM_Core_Page { 'extraPlugins', 'toolbarGroups', 'removeButtons', + 'customConfig', 'filebrowserBrowseUrl', 'filebrowserImageBrowseUrl', 'filebrowserFlashBrowseUrl', @@ -45,25 +42,31 @@ class CRM_Admin_Page_CKEditorConfig extends CRM_Core_Page { 'filebrowserFlashUploadUrl', ]; - public $preset; - /** - * Run page. - * - * @return string + * Prepare form */ - public function run() { - $this->preset = CRM_Utils_Array::value('preset', $_REQUEST, 'default'); + public function preProcess() { + CRM_Utils_Request::retrieve('preset', 'String', $this, FALSE, 'default', 'GET'); - // If the form was submitted, take appropriate action. - if (!empty($_POST['revert'])) { - self::deleteConfigFile($this->preset); - self::setConfigDefault(); - } - elseif (!empty($_POST['config'])) { - $this->save($_POST); + CRM_Utils_System::appendBreadCrumb([ + [ + 'url' => CRM_Utils_System::url('civicrm/admin/setting/preferences/display', 'reset=1'), + 'title' => ts('Display Preferences'), + ], + ]); + + // Initial build + if (empty($_POST['qfKey'])) { + $this->addResources(); } + } + /** + * Add resources during initial build or rebuild + * + * @throws CRM_Core_Exception + */ + public function addResources() { $settings = $this->getConfigSettings(); CRM_Core_Resources::singleton() @@ -80,24 +83,67 @@ class CRM_Admin_Page_CKEditorConfig extends CRM_Core_Page { 'settings' => $settings, ]); - $configUrl = self::getConfigUrl($this->preset) ?: self::getConfigUrl('default'); + $configUrl = self::getConfigUrl($this->get('preset')) ?: self::getConfigUrl('default'); - $this->assign('preset', $this->preset); + $this->assign('preset', $this->get('preset')); $this->assign('presets', CRM_Core_OptionGroup::values('wysiwyg_presets', FALSE, FALSE, FALSE, NULL, 'label', TRUE, FALSE, 'name')); $this->assign('skins', $this->getCKSkins()); $this->assign('skin', CRM_Utils_Array::value('skin', $settings)); $this->assign('extraPlugins', CRM_Utils_Array::value('extraPlugins', $settings)); $this->assign('configUrl', $configUrl); - $this->assign('revertConfirm', htmlspecialchars(ts('Are you sure you want to revert all changes?', ['escape' => 'js']))); + } - CRM_Utils_System::appendBreadCrumb([ + /** + * Build form + */ + public function buildQuickForm() { + $revertConfirm = json_encode(ts('Are you sure you want to revert all changes?')); + $this->addButtons([ [ - 'url' => CRM_Utils_System::url('civicrm/admin/setting/preferences/display', 'reset=1'), - 'title' => ts('Display Preferences'), + 'type' => 'next', + 'name' => ts('Save'), + ], + // Hidden button used to refresh form + [ + 'type' => 'submit', + 'class' => 'hiddenElement', + 'name' => ts('Save'), + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + [ + 'type' => 'refresh', + 'name' => ts('Revert to Default'), + 'icon' => 'fa-undo', + 'js' => ['onclick' => "return confirm($revertConfirm);"], ], ]); + } - return parent::run(); + /** + * Handle form submission + */ + public function postProcess() { + if (!empty($_POST[$this->getButtonName('refresh')])) { + self::deleteConfigFile($this->get('preset')); + self::setConfigDefault(); + } + else { + if (!empty($_POST[$this->getButtonName('next')])) { + $this->save($_POST); + CRM_Core_Session::setStatus(ts("You may need to clear your browser's cache to see the changes in CiviCRM."), ts('CKEditor Saved'), 'success'); + } + // The "submit" hidden button saves but does not redirect + if (!empty($_POST[$this->getButtonName('submit')])) { + $this->save($_POST); + $this->addResources(); + } + else { + CRM_Core_Session::singleton()->pushUserContext(CRM_Utils_System::url('civicrm/admin/ckeditor', ['reset' => 1])); + } + } } /** @@ -110,29 +156,33 @@ class CRM_Admin_Page_CKEditorConfig extends CRM_Core_Page { // Standardize line-endings . preg_replace('~\R~u', "\n", $params['config']); - // Use all params starting with config_ + // Generate a whitelist of allowed config params + $allOptions = json_decode(file_get_contents(\Civi::paths()->getPath('[civicrm.root]/js/wysiwyg/ck-options.json')), TRUE); + // These two aren't really blacklisted they're just in a different part of the form + $blackList = array_diff($this->blackList, ['skin', 'extraPlugins']); + // All options minus blacklist = whitelist + $whiteList = array_diff(array_column($allOptions, 'id'), $blackList); + + // Save whitelisted params starting with config_ foreach ($params as $key => $val) { $val = trim($val); - if (strpos($key, 'config_') === 0 && strlen($val)) { + if (strpos($key, 'config_') === 0 && strlen($val) && in_array(substr($key, 7), $whiteList)) { if ($val != 'true' && $val != 'false' && $val != 'null' && $val[0] != '{' && $val[0] != '[' && !is_numeric($val)) { - $val = json_encode($val, JSON_UNESCAPED_SLASHES); + $val = '"' . $val . '"'; } - elseif ($val[0] == '{' || $val[0] == '[') { - if (!is_array(json_decode($val, TRUE))) { - // Invalid JSON. Do not save. - continue; - } + try { + $val = CRM_Utils_JS::encode(CRM_Utils_JS::decode($val, TRUE)); + $pos = strrpos($config, '};'); + $key = preg_replace('/^config_/', 'config.', $key); + $setting = "\n\t{$key} = {$val};\n"; + $config = substr_replace($config, $setting, $pos, 0); + } + catch (CRM_Core_Exception $e) { + CRM_Core_Session::setStatus(ts("Error saving %1.", [1 => $key]), ts('Invalid Value'), 'error'); } - $pos = strrpos($config, '};'); - $key = preg_replace('/^config_/', 'config.', $key); - $setting = "\n\t{$key} = {$val};\n"; - $config = substr_replace($config, $setting, $pos, 0); } } - self::saveConfigFile($this->preset, $config); - if (!empty($params['save'])) { - CRM_Core_Session::setStatus(ts("You may need to clear your browser's cache to see the changes in CiviCRM."), ts('CKEditor Saved'), 'success'); - } + self::saveConfigFile($this->get('preset'), $config); } /** @@ -190,7 +240,7 @@ class CRM_Admin_Page_CKEditorConfig extends CRM_Core_Page { */ private function getConfigSettings() { $matches = $result = []; - $file = self::getConfigFile($this->preset) ?: self::getConfigFile('default'); + $file = self::getConfigFile($this->get('preset')) ?: self::getConfigFile('default'); $result['skin'] = 'moono'; if ($file) { $contents = file_get_contents($file); diff --git a/civicrm/CRM/Contact/Form/Search.php b/civicrm/CRM/Contact/Form/Search.php index 322dc47e317bb42c42bd06f7eca146b49ce6b204..25c95ac6b7ebbb541e47d2d629604db82fad4673 100644 --- a/civicrm/CRM/Contact/Form/Search.php +++ b/civicrm/CRM/Contact/Form/Search.php @@ -450,6 +450,7 @@ class CRM_Contact_Form_Search extends CRM_Core_Form_Search { 'group_contact_status', ts('Group Status') ); + $this->assign('permissionEditSmartGroup', CRM_Core_Permission::check('edit groups')); $this->assign('permissionedForGroup', $permissionForGroup); } @@ -529,6 +530,10 @@ class CRM_Contact_Form_Search extends CRM_Core_Form_Search { $this->_componentMode = CRM_Utils_Request::retrieve('component_mode', 'Positive', $this, FALSE, CRM_Contact_BAO_Query::MODE_CONTACTS, $_REQUEST); $this->_operator = CRM_Utils_Request::retrieve('operator', 'String', $this, FALSE, CRM_Contact_BAO_Query::SEARCH_OPERATOR_AND, 'REQUEST'); + if (!empty($this->_ssID) && !CRM_Core_Permission::check('edit groups')) { + CRM_Core_Error::statusBounce(ts('You do not have permission to modify smart groups')); + } + /** * set the button names */ diff --git a/civicrm/CRM/Contribute/Form/ContributionRecur.php b/civicrm/CRM/Contribute/Form/ContributionRecur.php index f313d5be2237200af2ee168e7724505971f4ffb0..ce6ae68a07b552398217be900da313ea3836dd8d 100644 --- a/civicrm/CRM/Contribute/Form/ContributionRecur.php +++ b/civicrm/CRM/Contribute/Form/ContributionRecur.php @@ -192,21 +192,22 @@ class CRM_Contribute_Form_ContributionRecur extends CRM_Core_Form { */ protected function getSubscriptionContactID() { $sub = $this->getSubscriptionDetails(); - return $sub->contact_id ?? FALSE; + return $sub->contact_id ? (int) $sub->contact_id : FALSE; } /** * Is this being used by a front end user to update their own recurring. * * @return bool + * @throws \CRM_Core_Exception */ protected function isSelfService() { - if (!is_null($this->selfService)) { + if ($this->selfService !== NULL) { return $this->selfService; } $this->selfService = FALSE; if (!CRM_Core_Permission::check('edit contributions')) { - if ($this->_subscriptionDetails->contact_id != $this->getContactID()) { + if ($this->getSubscriptionContactID() !== $this->getContactIDIfAccessingOwnRecord()) { CRM_Core_Error::statusBounce(ts('You do not have permission to cancel this recurring contribution.')); } $this->selfService = TRUE; diff --git a/civicrm/CRM/Contribute/Page/Tab.php b/civicrm/CRM/Contribute/Page/Tab.php index 0a5a4f67e5050380abb9a9bc2ff4349daa7ae41b..fac5a250bc7bf7907cd5913985a540ca8754268a 100644 --- a/civicrm/CRM/Contribute/Page/Tab.php +++ b/civicrm/CRM/Contribute/Page/Tab.php @@ -86,7 +86,11 @@ class CRM_Contribute_Page_Tab extends CRM_Core_Page { ]; } - if (!$paymentProcessorObj->supports('ChangeSubscriptionAmount') && !$paymentProcessorObj->supports('EditRecurringContribution')) { + if ( + (!CRM_Core_Permission::check('edit contributions') && $context === 'contribution') || + (!$paymentProcessorObj->supports('ChangeSubscriptionAmount') + && !$paymentProcessorObj->supports('EditRecurringContribution') + )) { unset($links[CRM_Core_Action::UPDATE]); } } diff --git a/civicrm/CRM/Core/Form.php b/civicrm/CRM/Core/Form.php index 7bd57e0dd7a0722496ae5adf59b66825f1ef1e06..17b30a99865a0222dcb969ac5940fd9114973338 100644 --- a/civicrm/CRM/Core/Form.php +++ b/civicrm/CRM/Core/Form.php @@ -2219,11 +2219,13 @@ class CRM_Core_Form extends HTML_QuickForm_Page { /** * Get the contact id of the logged in user. + * + * @return int|false */ public function getLoggedInUserContactID() { // check if the user is logged in and has a contact ID $session = CRM_Core_Session::singleton(); - return $session->get('userID'); + return $session->get('userID') ? (int) $session->get('userID') : FALSE; } /** @@ -2247,6 +2249,7 @@ class CRM_Core_Form extends HTML_QuickForm_Page { * - id_field * - url (for ajax lookup) * + * @throws \CRM_Core_Exception * @todo add data attributes so we can deal with multiple instances on a form */ public function addAutoSelector($profiles = [], $autoCompleteField = []) { @@ -2637,4 +2640,26 @@ class CRM_Core_Form extends HTML_QuickForm_Page { } } + /** + * Get the contact if from the url, using the checksum or the cid if it is the logged in user. + * + * This function returns the user being validated. It is not intended to get another user + * they have permission to (setContactID does do that) and can be used to check if the user is + * accessing their own record. + * + * @return int|false + * @throws \CRM_Core_Exception + */ + protected function getContactIDIfAccessingOwnRecord() { + $contactID = (int) CRM_Utils_Request::retrieve('cid', 'Positive', $this); + if (!$contactID) { + return FALSE; + } + if ($contactID === $this->getLoggedInUserContactID()) { + return $contactID; + } + $userChecksum = CRM_Utils_Request::retrieve('cs', 'String', $this); + return CRM_Contact_BAO_Contact_Utils::validChecksum($contactID, $userChecksum) ? $contactID : FALSE; + } + } diff --git a/civicrm/CRM/Core/I18n/SchemaStructure.php b/civicrm/CRM/Core/I18n/SchemaStructure.php index ecadf83929c92afe8751d3ef86a9ba1f984827ae..02a51c9a7874743830aacb42da5238c55b6519fa 100644 --- a/civicrm/CRM/Core/I18n/SchemaStructure.php +++ b/civicrm/CRM/Core/I18n/SchemaStructure.php @@ -154,7 +154,7 @@ class CRM_Core_I18n_SchemaStructure { 'help_post' => "text COMMENT 'Description and/or help text to display after this field.'", ], 'civicrm_price_field_value' => [ - 'label' => "varchar(255) NOT NULL COMMENT 'Price field option label'", + 'label' => "varchar(255) DEFAULT NULL COMMENT 'Price field option label'", 'description' => "text DEFAULT NULL COMMENT 'Price field option description.'", 'help_pre' => "text DEFAULT NULL COMMENT 'Price field option pre help text.'", 'help_post' => "text DEFAULT NULL COMMENT 'Price field option post field help.'", @@ -586,7 +586,6 @@ class CRM_Core_I18n_SchemaStructure { 'civicrm_price_field_value' => [ 'label' => [ 'type' => "Text", - 'required' => "true", ], 'description' => [ 'type' => "TextArea", diff --git a/civicrm/CRM/Core/Key.php b/civicrm/CRM/Core/Key.php index 83317ac14946e180f0fa8f5d0c9420b7a5f3b4c8..05cc58a541410d2df16830a37de12e2e1ac6cc8c 100644 --- a/civicrm/CRM/Core/Key.php +++ b/civicrm/CRM/Core/Key.php @@ -17,6 +17,28 @@ * */ class CRM_Core_Key { + + /** + * The length of the randomly-generated, per-session signing key. + * + * Expressed as number of bytes. (Ex: 128 bits = 16 bytes) + * + * @var int + */ + const PRIVATE_KEY_LENGTH = 16; + + /** + * @var string + * @see hash_hmac_algos() + */ + const HASH_ALGO = 'sha256'; + + /** + * The length of a generated signature/digest (expressed in hex digits). + * @var int + */ + const HASH_LENGTH = 64; + public static $_key = NULL; public static $_sessionID = NULL; @@ -32,7 +54,7 @@ class CRM_Core_Key { $session = CRM_Core_Session::singleton(); self::$_key = $session->get('qfPrivateKey'); if (!self::$_key) { - self::$_key = md5(uniqid(mt_rand(), TRUE)) . md5(uniqid(mt_rand(), TRUE)); + self::$_key = base64_encode(random_bytes(self::PRIVATE_KEY_LENGTH)); $session->set('qfPrivateKey', self::$_key); } } @@ -66,9 +88,7 @@ class CRM_Core_Key { * valid formID */ public static function get($name, $addSequence = FALSE) { - $privateKey = self::privateKey(); - $sessionID = self::sessionID(); - $key = md5($sessionID . $name . $privateKey); + $key = self::sign($name); if ($addSequence) { // now generate a random number between 1 and 100K and add it to the key @@ -103,9 +123,7 @@ class CRM_Core_Key { $k = $key; } - $privateKey = self::privateKey(); - $sessionID = self::sessionID(); - if ($k != md5($sessionID . $name . $privateKey)) { + if (!hash_equals($k, self::sign($name))) { return NULL; } return $key; @@ -115,9 +133,10 @@ class CRM_Core_Key { * @param $key * * @return bool + * TRUE if the signature ($key) is well-formed. */ public static function valid($key) { - // a valid key is a 32 digit hex number + // a valid key is a hex number // followed by an optional _ and a number between 1 and 10000 if (strpos('_', $key) !== FALSE) { list($hash, $seq) = explode('_', $key); @@ -134,8 +153,26 @@ class CRM_Core_Key { $hash = $key; } - // ensure that hash is a 32 digit hex number - return (bool) preg_match('#[0-9a-f]{32}#i', $hash); + // ensure that hash is a hex number (of expected length) + return preg_match('#[0-9a-f]{' . self::HASH_LENGTH . '}#i', $hash) ? TRUE : FALSE; + } + + /** + * @param string $name + * The name of the form + * @return string + * A signed digest of $name, computed with the per-session private key + */ + private static function sign($name) { + $privateKey = self::privateKey(); + $sessionID = self::sessionID(); + $delim = chr(0); + if (strpos($sessionID, $delim) !== FALSE || strpos($name, $delim) !== FALSE) { + throw new \RuntimeException("Failed to generate signature. Malformed session-id or form-name."); + } + // Note: Unsure why $sessionID is included, but it's always been there, and it doesn't seem harmful. + return hash_hmac(self::HASH_ALGO, $sessionID . $delim . $name, $privateKey); + } } diff --git a/civicrm/CRM/Core/LegacyErrorHandler.php b/civicrm/CRM/Core/LegacyErrorHandler.php index f82b3ce6d3d2f9f2b72ff42be5118f085d54fc5c..de515ecee12ec0d5bd6ef988e742e1c24a3738b6 100644 --- a/civicrm/CRM/Core/LegacyErrorHandler.php +++ b/civicrm/CRM/Core/LegacyErrorHandler.php @@ -16,9 +16,9 @@ class CRM_Core_LegacyErrorHandler { $message = $e->getMessage(); $session = CRM_Core_Session::singleton(); $session->setStatus( - $message, - CRM_Utils_Array::value('message_title', $params), - CRM_Utils_Array::value('message_type', $params, 'error') + htmlspecialchars($message), + htmlspecialchars($params['message_title'] ?? ts('Error')), + $params['message_type'] ?? 'error' ); } } diff --git a/civicrm/CRM/Core/Payment.php b/civicrm/CRM/Core/Payment.php index 9c998dc66d604dd2dde9ee5c848cd572f2068688..b312c8e8d9318fe1d12426f75638cb074bb8c686 100644 --- a/civicrm/CRM/Core/Payment.php +++ b/civicrm/CRM/Core/Payment.php @@ -831,7 +831,7 @@ abstract class CRM_Core_Payment { 'size' => 20, 'maxlength' => 20, 'autocomplete' => 'off', - 'class' => 'creditcard', + 'class' => 'creditcard required', ], 'is_required' => TRUE, // 'description' => '16 digit card number', // If you enable a description field it will be shown below the field on the form @@ -844,6 +844,7 @@ abstract class CRM_Core_Payment { 'size' => 5, 'maxlength' => 10, 'autocomplete' => 'off', + 'class' => ($isCVVRequired ? 'required' : ''), ], 'is_required' => $isCVVRequired, 'rules' => [ @@ -867,7 +868,7 @@ abstract class CRM_Core_Payment { 'rule_parameters' => TRUE, ], ], - 'extra' => ['class' => 'crm-form-select'], + 'extra' => ['class' => 'crm-form-select required'], ], 'credit_card_type' => [ 'htmlType' => 'select', @@ -884,6 +885,7 @@ abstract class CRM_Core_Payment { 'size' => 20, 'maxlength' => 34, 'autocomplete' => 'on', + 'class' => 'required', ], 'is_required' => TRUE, ], @@ -896,6 +898,7 @@ abstract class CRM_Core_Payment { 'size' => 20, 'maxlength' => 34, 'autocomplete' => 'off', + 'class' => 'required', ], 'rules' => [ [ @@ -915,6 +918,7 @@ abstract class CRM_Core_Payment { 'size' => 20, 'maxlength' => 11, 'autocomplete' => 'off', + 'class' => 'required', ], 'is_required' => TRUE, 'rules' => [ @@ -933,6 +937,7 @@ abstract class CRM_Core_Payment { 'size' => 20, 'maxlength' => 64, 'autocomplete' => 'off', + 'class' => 'required', ], 'is_required' => TRUE, @@ -1034,6 +1039,7 @@ abstract class CRM_Core_Payment { 'size' => 30, 'maxlength' => 60, 'autocomplete' => 'off', + 'class' => 'required', ], 'is_required' => TRUE, ]; @@ -1060,6 +1066,7 @@ abstract class CRM_Core_Payment { 'size' => 30, 'maxlength' => 60, 'autocomplete' => 'off', + 'class' => 'required', ], 'is_required' => TRUE, ]; @@ -1073,6 +1080,7 @@ abstract class CRM_Core_Payment { 'size' => 30, 'maxlength' => 60, 'autocomplete' => 'off', + 'class' => 'required', ], 'is_required' => TRUE, ]; @@ -1086,6 +1094,7 @@ abstract class CRM_Core_Payment { 'size' => 30, 'maxlength' => 60, 'autocomplete' => 'off', + 'class' => 'required', ], 'is_required' => TRUE, ]; @@ -1096,6 +1105,7 @@ abstract class CRM_Core_Payment { 'name' => "billing_state_province_id-{$billingLocationID}", 'cc_field' => TRUE, 'is_required' => TRUE, + 'extra' => ['class' => 'required'], ]; $metadata["billing_postal_code-{$billingLocationID}"] = [ @@ -1107,6 +1117,7 @@ abstract class CRM_Core_Payment { 'size' => 30, 'maxlength' => 60, 'autocomplete' => 'off', + 'class' => 'required', ], 'is_required' => TRUE, ]; @@ -1120,6 +1131,7 @@ abstract class CRM_Core_Payment { '' => ts('- select -'), ] + CRM_Core_PseudoConstant::country(), 'is_required' => TRUE, + 'extra' => ['class' => 'required'], ]; return $metadata; } diff --git a/civicrm/CRM/Core/Payment/Form.php b/civicrm/CRM/Core/Payment/Form.php index 3779297a08e08327f64fa2519a97ab0e98e319af..1c7263812c522bae496812da1b71964e17012560 100644 --- a/civicrm/CRM/Core/Payment/Form.php +++ b/civicrm/CRM/Core/Payment/Form.php @@ -109,7 +109,7 @@ class CRM_Core_Payment_Form { $field['name'], $field['title'], $field['attributes'], - $field['is_required'], + FALSE, $field['extra'] ); } diff --git a/civicrm/CRM/Core/Resources.php b/civicrm/CRM/Core/Resources.php index 40dd3b09782ff88268e34fcffeddc28af162ed90..0fdc57cf21ded218925ae180b11156b934f0e5e8 100644 --- a/civicrm/CRM/Core/Resources.php +++ b/civicrm/CRM/Core/Resources.php @@ -760,11 +760,11 @@ class CRM_Core_Resources { // add wysiwyg editor $editor = Civi::settings()->get('editor_id'); if ($editor == "CKEditor") { - CRM_Admin_Page_CKEditorConfig::setConfigDefault(); + CRM_Admin_Form_CKEditorConfig::setConfigDefault(); $items[] = [ 'config' => [ 'wysisygScriptLocation' => Civi::paths()->getUrl("[civicrm.root]/js/wysiwyg/crm.ckeditor.js"), - 'CKEditorCustomConfig' => CRM_Admin_Page_CKEditorConfig::getConfigUrl(), + 'CKEditorCustomConfig' => CRM_Admin_Form_CKEditorConfig::getConfigUrl(), ], ]; } diff --git a/civicrm/CRM/Core/Task.php b/civicrm/CRM/Core/Task.php index 40985c4b451b6b99720b049adf1fe584d488ad52..a947f4729c001b9fd6fb7cfdbd10d770ed01a87e 100644 --- a/civicrm/CRM/Core/Task.php +++ b/civicrm/CRM/Core/Task.php @@ -132,7 +132,7 @@ abstract class CRM_Core_Task { */ public static function corePermissionedTaskTitles($tasks, $permission, $params) { // Only offer the "Update Smart Group" task if a smart group/saved search is already in play and we have edit permissions - if (!empty($params['ssID']) && ($permission == CRM_Core_Permission::EDIT)) { + if (!empty($params['ssID']) && ($permission == CRM_Core_Permission::EDIT) && CRM_Core_Permission::check('edit groups')) { $tasks[self::SAVE_SEARCH_UPDATE] = self::$_tasks[self::SAVE_SEARCH_UPDATE]['title']; } else { diff --git a/civicrm/CRM/Core/xml/Menu/Admin.xml b/civicrm/CRM/Core/xml/Menu/Admin.xml index acf56251f115fccd4a34b6bf21dc4f5129d96b32..20b52451cc27c20c438e6643e304fe25000f42c8 100644 --- a/civicrm/CRM/Core/xml/Menu/Admin.xml +++ b/civicrm/CRM/Core/xml/Menu/Admin.xml @@ -670,7 +670,7 @@ <item> <path>civicrm/admin/ckeditor</path> <title>Configure CKEditor</title> - <page_callback>CRM_Admin_Page_CKEditorConfig</page_callback> + <page_callback>CRM_Admin_Form_CKEditorConfig</page_callback> <access_arguments>administer CiviCRM</access_arguments> </item> </menu> diff --git a/civicrm/CRM/Dedupe/Merger.php b/civicrm/CRM/Dedupe/Merger.php index 74669495b4e596984be8ff630f6bdf19f0b50194..86217e04979f2ce3cab3c65b74aa5dd4cec04f92 100644 --- a/civicrm/CRM/Dedupe/Merger.php +++ b/civicrm/CRM/Dedupe/Merger.php @@ -541,6 +541,17 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m continue; } + if ($table === 'civicrm_setting') { + // Per https://lab.civicrm.org/dev/core/-/issues/1934 + // Note this line is not unit tested as yet as a quick-fix for a regression + // but it would be better to do a SELECT request & only update if needed (as a general rule + // more selects & less UPDATES will result in less deadlocks while de-duping. + // Note the delete is not important here - it can stay with the deleted contact on the + // off chance they get restored. + $sqls[] = "UPDATE IGNORE civicrm_setting SET contact_id = $mainId WHERE contact_id = $otherId"; + continue; + } + // use UPDATE IGNORE + DELETE query pair to skip on situations when // there's a UNIQUE restriction on ($field, some_other_field) pair if (isset($cidRefs[$table])) { diff --git a/civicrm/CRM/Price/DAO/PriceFieldValue.php b/civicrm/CRM/Price/DAO/PriceFieldValue.php index d510c14b62d2932e308347a3aae70f5e07a1b0b7..01aff179797e27985e54b00c84879293489a1e71 100644 --- a/civicrm/CRM/Price/DAO/PriceFieldValue.php +++ b/civicrm/CRM/Price/DAO/PriceFieldValue.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Price/PriceFieldValue.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:28432a14b1b1523380eb41e8e481037d) + * (GenCodeChecksum:11a02f3576be10e8c2a0ea47a19e2dac) */ /** @@ -226,10 +226,10 @@ class CRM_Price_DAO_PriceFieldValue extends CRM_Core_DAO { 'type' => CRM_Utils_Type::T_STRING, 'title' => ts('Name'), 'description' => ts('Price field option name'), - 'required' => TRUE, 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, 'where' => 'civicrm_price_field_value.name', + 'default' => 'NULL', 'table_name' => 'civicrm_price_field_value', 'entity' => 'PriceFieldValue', 'bao' => 'CRM_Price_BAO_PriceFieldValue', @@ -244,10 +244,10 @@ class CRM_Price_DAO_PriceFieldValue extends CRM_Core_DAO { 'type' => CRM_Utils_Type::T_STRING, 'title' => ts('Label'), 'description' => ts('Price field option label'), - 'required' => TRUE, 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, 'where' => 'civicrm_price_field_value.label', + 'default' => 'NULL', 'table_name' => 'civicrm_price_field_value', 'entity' => 'PriceFieldValue', 'bao' => 'CRM_Price_BAO_PriceFieldValue', diff --git a/civicrm/CRM/Report/Form.php b/civicrm/CRM/Report/Form.php index 473356912470882735db7964352d705c8fe713e1..2bd8d4d5069bbc8019b1c12d65f0594864c9ea5e 100644 --- a/civicrm/CRM/Report/Form.php +++ b/civicrm/CRM/Report/Form.php @@ -139,11 +139,6 @@ class CRM_Report_Form extends CRM_Core_Form { */ protected $_groupFilter = FALSE; - /** - * Required for civiexportexcel. - */ - public $supportsExportExcel = TRUE; - /** * Has the report been optimised for group filtering. * @@ -1440,7 +1435,7 @@ class CRM_Report_Form extends CRM_Core_Form { if (!CRM_Core_Permission::check('view report sql')) { return; } - $ignored_output_modes = ['pdf', 'csv', 'print', 'excel2007']; + $ignored_output_modes = ['pdf', 'csv', 'print']; if (in_array($this->_outputMode, $ignored_output_modes)) { return; } @@ -2866,11 +2861,6 @@ WHERE cg.extends IN ('" . implode("','", $this->_customGroupExtends) . "') AND $this->_absoluteUrl = TRUE; $this->addPaging = FALSE; } - elseif ($this->_outputMode == 'excel2007') { - $printOnly = TRUE; - $this->_absoluteUrl = TRUE; - $this->addPaging = FALSE; - } elseif ($this->_outputMode == 'copy' && $this->_criteriaForm) { $this->_createNew = TRUE; } @@ -3508,9 +3498,6 @@ WHERE cg.extends IN ('" . implode("','", $this->_customGroupExtends) . "') AND elseif ($this->_outputMode == 'csv') { CRM_Report_Utils_Report::export2csv($this, $rows); } - elseif ($this->_outputMode == 'excel2007') { - CRM_CiviExportExcel_Utils_Report::export2excel2007($this, $rows); - } elseif ($this->_outputMode == 'group') { $group = $this->_params['groups']; $this->add2group($group); diff --git a/civicrm/CRM/Upgrade/Incremental/php/FiveTwentyEight.php b/civicrm/CRM/Upgrade/Incremental/php/FiveTwentyEight.php index b035cfb16e3e7e501833bf39fe68db321a28dd72..01c899a7dedcfa168236ada4b93aab9d6228acca 100644 --- a/civicrm/CRM/Upgrade/Incremental/php/FiveTwentyEight.php +++ b/civicrm/CRM/Upgrade/Incremental/php/FiveTwentyEight.php @@ -46,6 +46,37 @@ class CRM_Upgrade_Incremental_php_FiveTwentyEight extends CRM_Upgrade_Incrementa } } + /** + * Upgrade function. + * + * @param string $rev + */ + public function upgrade_5_28_1($rev) { + $this->addTask(ts('Upgrade DB to %1: SQL', [1 => $rev]), 'runSql', $rev); + $this->addTask('Make label field non required on price field value', 'priceFieldValueLabelNonRequired'); + } + + /** + * Make the price field value label column non required + * @return bool + */ + public static function priceFieldValueLabelNonRequired() { + $domain = new CRM_Core_DAO_Domain(); + $domain->find(TRUE); + if ($domain->locales) { + $locales = explode(CRM_Core_DAO::VALUE_SEPARATOR, $domain->locales); + foreach ($locales as $locale) { + CRM_Core_DAO::executeQuery("ALTER TABLE civicrm_price_field_value CHANGE `label_{$locale}` `label_{$locale}` varchar(255) DEFAULT NULL COMMENT 'Price field option label'", [], TRUE, NULL, FALSE, FALSE); + CRM_Core_DAO::executeQuery("UPDATE civicrm_price_field_value SET label_{$locale} = NULL WHERE label_{$locale} = 'null'", [], TRUE, NULL, FALSE, FALSE); + } + } + else { + CRM_Core_DAO::executeQuery("ALTER TABLE civicrm_price_field_value CHANGE `label` `label` varchar(255) DEFAULT NULL COMMENT 'Price field option label'", [], TRUE, NULL, FALSE, FALSE); + CRM_Core_DAO::executeQuery("UPDATE civicrm_price_field_value SET label = NULL WHERE label = 'null'", [], TRUE, NULL, FALSE, FALSE); + } + return TRUE; + } + public static function createWpFilesMessage() { if (!function_exists('civi_wp')) { return ''; diff --git a/civicrm/CRM/Upgrade/Incremental/sql/5.28.1.mysql.tpl b/civicrm/CRM/Upgrade/Incremental/sql/5.28.1.mysql.tpl new file mode 100644 index 0000000000000000000000000000000000000000..46fa031fe5eb1061dbefaa23b4794866c0d37507 --- /dev/null +++ b/civicrm/CRM/Upgrade/Incremental/sql/5.28.1.mysql.tpl @@ -0,0 +1 @@ +{* file to handle db changes in 5.28.1 during upgrade *} diff --git a/civicrm/CRM/Utils/JS.php b/civicrm/CRM/Utils/JS.php index 27e52d0d1ffb0c86acaaa11ecd4a949654563178..da228d24136a30c4057f2a8468f324a55e946824 100644 --- a/civicrm/CRM/Utils/JS.php +++ b/civicrm/CRM/Utils/JS.php @@ -125,26 +125,51 @@ class CRM_Utils_JS { * ] * * @param string $js + * @param bool $throwException * @return mixed - * @throws Exception + * @throws CRM_Core_Exception */ - public static function decode($js) { + public static function decode($js, $throwException = FALSE) { $js = trim($js); $first = substr($js, 0, 1); $last = substr($js, -1); - if ($last === $first && ($first === "'" || $first === '"')) { - // Use a temp placeholder for escaped backslashes - $backslash = chr(0) . 'backslash' . chr(0); - return str_replace(['\\\\', "\\'", '\\"', '\\&', '\\/', $backslash], [$backslash, "'", '"', '&', '/', '\\'], substr($js, 1, -1)); + if ($first === "'" && $last === "'") { + $js = self::convertSingleQuoteString($js, $throwException); } - if (($first === '{' && $last === '}') || ($first === '[' && $last === ']')) { + elseif (($first === '{' && $last === '}') || ($first === '[' && $last === ']')) { $obj = self::getRawProps($js); foreach ($obj as $idx => $item) { - $obj[$idx] = self::decode($item); + $obj[$idx] = self::decode($item, $throwException); } return $obj; } - return json_decode($js); + $result = json_decode($js); + if ($throwException && $result === NULL && $js !== 'null') { + throw new CRM_Core_Exception(json_last_error_msg()); + } + return $result; + } + + /** + * @param string $str + * @return string|null + * @throws CRM_Core_Exception + */ + public static function convertSingleQuoteString(string $str, $throwException) { + // json_decode can only handle double quotes around strings, so convert single-quoted strings + $backslash = chr(0) . 'backslash' . chr(0); + $str = str_replace(['\\\\', '\\"', '"', '\\&', '\\/', $backslash], [$backslash, '"', '\\"', '&', '/', '\\'], substr($str, 1, -1)); + // Ensure the string doesn't terminate early by checking that all single quotes are escaped + $pos = -1; + while (($pos = strpos($str, "'", $pos + 1)) !== FALSE) { + if (($pos - strlen(rtrim(substr($str, 0, $pos)))) % 2) { + if ($throwException) { + throw new CRM_Core_Exception('Invalid string passed to CRM_Utils_JS::decode'); + } + return NULL; + } + } + return '"' . $str . '"'; } /** diff --git a/civicrm/Civi/Api4/Service/Spec/Provider/PriceFieldValueCreationSpecProvider.php b/civicrm/Civi/Api4/Service/Spec/Provider/PriceFieldValueCreationSpecProvider.php index 00c6b19d1e6c8952e0cb0ce32d98cecbd077b5ea..76cfa28739ca56efc33468c96fb543465ce4b09f 100644 --- a/civicrm/Civi/Api4/Service/Spec/Provider/PriceFieldValueCreationSpecProvider.php +++ b/civicrm/Civi/Api4/Service/Spec/Provider/PriceFieldValueCreationSpecProvider.php @@ -29,6 +29,8 @@ class PriceFieldValueCreationSpecProvider implements Generic\SpecProviderInterfa public function modifySpec(RequestSpec $spec) { // Name will be auto-generated from label if not supplied $spec->getFieldByName('name')->setRequired(FALSE); + // Ensure that label is required this matches v3 API but doesn't match DAO because form fields allow for NULLs + $spec->getFieldByName('label')->setRequired(TRUE); } /** diff --git a/civicrm/civicrm-version.php b/civicrm/civicrm-version.php index d9b0934df78d2053483ca866d2574a5e977dd27e..948a2c6b5f6b35fe8d2516a3b9b8d6d456d77ecb 100644 --- a/civicrm/civicrm-version.php +++ b/civicrm/civicrm-version.php @@ -1,7 +1,7 @@ <?php /** @deprecated */ function civicrmVersion( ) { - return array( 'version' => '5.28.0', + return array( 'version' => '5.28.1', 'cms' => 'Wordpress', 'revision' => '' ); } diff --git a/civicrm/js/Common.js b/civicrm/js/Common.js index 0199e80eb1c5c37574a5699cbf4b8acd53f69f3d..e6b0dbc1aacf9a38726846cf58f36239dae07a18 100644 --- a/civicrm/js/Common.js +++ b/civicrm/js/Common.js @@ -1659,4 +1659,92 @@ if (!CRM.vars) CRM.vars = {}; } }); + // CVE-2020-11022 and CVE-2020-11023 Passing HTML from untrusted sources - even after sanitizing it - to one of jQuery's DOM manipulation methods (i.e. .html(), .append(), and others) may execute untrusted code. + $.htmlPrefilter = function(html) { + // Prior to jQuery 3.5, jQuery converted XHTML-style self-closing tags to + // their XML equivalent: e.g., "<div />" to "<div></div>". This is + // problematic for several reasons, including that it's vulnerable to XSS + // attacks. However, since this was jQuery's behavior for many years, many + // Drupal modules and jQuery plugins may be relying on it. Therefore, we + // preserve that behavior, but for a limited set of tags only, that we believe + // to not be vulnerable. This is the set of HTML tags that satisfy all of the + // following conditions: + // - In DOMPurify's list of HTML tags. If an HTML tag isn't safe enough to + // appear in that list, then we don't want to mess with it here either. + // @see https://github.com/cure53/DOMPurify/blob/2.0.11/dist/purify.js#L128 + // - A normal element (not a void, template, text, or foreign element). + // @see https://html.spec.whatwg.org/multipage/syntax.html#elements-2 + // - An element that is still defined by the current HTML specification + // (not a deprecated element), because we do not want to rely on how + // browsers parse deprecated elements. + // @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element + // - Not 'html', 'head', or 'body', because this pseudo-XHTML expansion is + // designed for fragments, not entire documents. + // - Not 'colgroup', because due to an idiosyncrasy of jQuery's original + // regular expression, it didn't match on colgroup, and we don't want to + // introduce a behavior change for that. + var selfClosingTagsToReplace = [ + 'a', 'abbr', 'address', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', + 'blockquote', 'button', 'canvas', 'caption', 'cite', 'code', 'data', + 'datalist', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt', 'em', + 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', + 'h4', 'h5', 'h6', 'header', 'hgroup', 'i', 'ins', 'kbd', 'label', 'legend', + 'li', 'main', 'map', 'mark', 'menu', 'meter', 'nav', 'ol', 'optgroup', + 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', + 'ruby', 's', 'samp', 'section', 'select', 'small', 'source', 'span', + 'strong', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', + 'thead', 'time', 'tr', 'u', 'ul', 'var', 'video' + ]; + + // Define regular expressions for <TAG/> and <TAG ATTRIBUTES/>. Doing this as + // two expressions makes it easier to target <a/> without also targeting + // every tag that starts with "a". + var xhtmlRegExpGroup = '(' + selfClosingTagsToReplace.join('|') + ')'; + var whitespace = '[\\x20\\t\\r\\n\\f]'; + var rxhtmlTagWithoutSpaceOrAttributes = new RegExp('<' + xhtmlRegExpGroup + '\\/>', 'gi'); + var rxhtmlTagWithSpaceAndMaybeAttributes = new RegExp('<' + xhtmlRegExpGroup + '(' + whitespace + '[^>]*)\\/>', 'gi'); + + // jQuery 3.5 also fixed a vulnerability for when </select> appears within + // an <option> or <optgroup>, but it did that in local code that we can't + // backport directly. Instead, we filter such cases out. To do so, we need to + // determine when jQuery would otherwise invoke the vulnerable code, which it + // uses this regular expression to determine. The regular expression changed + // for version 3.0.0 and changed again for 3.4.0. + // @see https://github.com/jquery/jquery/blob/1.5/jquery.js#L4958 + // @see https://github.com/jquery/jquery/blob/3.0.0/dist/jquery.js#L4584 + // @see https://github.com/jquery/jquery/blob/3.4.0/dist/jquery.js#L4712 + var rtagName = /<([\w:]+)/; + + // The regular expression that jQuery uses to determine which self-closing + // tags to expand to open and close tags. This is vulnerable, because it + // matches all tag names except the few excluded ones. We only use this + // expression for determining vulnerability. The expression changed for + // version 3, but we only need to check for vulnerability in versions 1 and 2, + // so we use the expression from those versions. + // @see https://github.com/jquery/jquery/blob/1.5/jquery.js#L4957 + var rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi; + + // This is how jQuery determines the first tag in the HTML. + // @see https://github.com/jquery/jquery/blob/1.5/jquery.js#L5521 + var tag = ( rtagName.exec( html ) || [ "", "" ] )[ 1 ].toLowerCase(); + + // It is not valid HTML for <option> or <optgroup> to have <select> as + // either a descendant or sibling, and attempts to inject one can cause + // XSS on jQuery versions before 3.5. Since this is invalid HTML and a + // possible XSS attack, reject the entire string. + // @see https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11023 + if ((tag === 'option' || tag === 'optgroup') && html.match(/<\/?select/i)) { + html = ''; + } + + // Retain jQuery's prior to 3.5 conversion of pseudo-XHTML, but for only + // the tags in the `selfClosingTagsToReplace` list defined above. + // @see https://github.com/jquery/jquery/blob/1.5/jquery.js#L5518 + // @see https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11022 + html = html.replace(rxhtmlTagWithoutSpaceOrAttributes, "<$1></$1>"); + html = html.replace(rxhtmlTagWithSpaceAndMaybeAttributes, "<$1$2></$1>"); + + return html; + }; + })(jQuery, _); diff --git a/civicrm/js/wysiwyg/admin.ckeditor-configurator.js b/civicrm/js/wysiwyg/admin.ckeditor-configurator.js index 6c44806754b7628f144356f1aeac4fb869401c76..9d87afc0dc20b02d6edf80a7fad86be336bec3d1 100644 --- a/civicrm/js/wysiwyg/admin.ckeditor-configurator.js +++ b/civicrm/js/wysiwyg/admin.ckeditor-configurator.js @@ -65,15 +65,7 @@ } function validateJson() { - var val = $(this).val(); - $(this).parent().removeClass('crm-error'); - if (val[0] === '[' || val[0] === '{') { - try { - JSON.parse(val); - } catch (e) { - $(this).parent().addClass('crm-error'); - } - } + // TODO: strict json isn't required so we can't use JSON.parse for error checking. Need something like angular.eval. } function addOption() { @@ -109,7 +101,7 @@ var selectorOpen = false, changedWhileOpen = false; - $('#toolbarModifierForm') + $('#CKEditorConfig') .on('submit', function(e) { $('.toolbar button:last', '#toolbarModifierWrapper')[0].click(); $('.configContainer textarea', '#toolbarModifierWrapper').attr('name', 'config'); @@ -117,7 +109,8 @@ .on('change', '.config-param', function(e) { changedWhileOpen = true; if (!selectorOpen) { - $('#toolbarModifierForm').submit().block(); + $('#_qf_CKEditorConfig_submit-bottom').click(); + $('#CKEditorConfig').block(); } }) .on('change', 'input.crm-config-option-name', changeOptionName) diff --git a/civicrm/release-notes.md b/civicrm/release-notes.md index a7c7f0ef3554e82154357e519d1f0316b78dc630..d4ea4c09abb3794e17ce3a6b93c3317bf20546b2 100644 --- a/civicrm/release-notes.md +++ b/civicrm/release-notes.md @@ -15,6 +15,16 @@ Other resources for identifying changes are: * https://github.com/civicrm/civicrm-joomla * https://github.com/civicrm/civicrm-wordpress +## CiviCRM 5.28.1 + +Released August 19, 2020 + +- **[Synopsis](release-notes/5.28.1.md#synopsis)** +- **[Security advisories](release-notes/5.28.1.md#security)** +- **[Bugs resolved](release-notes/5.28.1.md#bugs)** +- **[Credits](release-notes/5.28.1.md#credits)** +- **[Feedback](release-notes/5.28.1.md#feedback)** + ## CiviCRM 5.28.0 Released August 5, 2020 diff --git a/civicrm/release-notes/5.28.1.md b/civicrm/release-notes/5.28.1.md new file mode 100644 index 0000000000000000000000000000000000000000..5c85559bbfcf0172905d43780a828095e2fd4458 --- /dev/null +++ b/civicrm/release-notes/5.28.1.md @@ -0,0 +1,58 @@ +# CiviCRM 5.28.1 + +Released August 19, 2020 + +- **[Security advisories](#security)** +- **[Bugs Resolved](#bugs)** +- **[Credits](#credits)** + +## <a name="synopsis"></a>Synopsis + +| *Does this version...?* | | +|:--------------------------------------------------------------- |:-------:| +| **Fix security vulnerabilities?** | **yes** | +| Change the database schema? | no | +| Alter the API? | no | +| Require attention to configuration options? | no | +| Fix problems installing or upgrading to a previous version? | no | +| Introduce features? | no | +| **Fix bugs?** | **yes** | + +## <a name="security"></a>Security advisories + +- **[CIVI-SA-2020-09](https://civicrm.org/advisory/civi-sa-2020-09-privilege-escalation-acl-smart-groups): Privilege Escalation via Smart Groups** +- **[CIVI-SA-2020-10](https://civicrm.org/advisory/civi-sa-2020-10-cross-site-scripting-activity-details): Cross Site Scripting in Activity Details** +- **[CIVI-SA-2020-11](https://civicrm.org/advisory/civi-sa-2020-11-csrf-ckeditor-configuration-form): CSRF on CKEditor Configuration** +- **[CIVI-SA-2020-12](https://civicrm.org/advisory/civi-sa-2020-12-xss-ckeditor-configuration): XSS in CKEditor Configuration** +- **[CIVI-SA-2020-13](https://civicrm.org/advisory/civi-sa-2020-13-xss-event-summary): XSS in Event Summary** +- **[CIVI-SA-2020-14](https://civicrm.org/advisory/civi-sa-2020-14-xss-profile-description-field): XSS in Profile Description** +- **[CIVI-SA-2020-15](https://civicrm.org/advisory/civi-sa-2020-15-persistent-xss-contact-activity-tab): Persistant XSS in Contact Activity Tab** +- **[CIVI-SA-2020-16](https://civicrm.org/advisory/civi-sa-2020-16-jquery-security-update-cve-2020-11022-cve-2020-11023): jQuery CVE-202-11022, CVE-2020-11023** +- **[CIVI-SA-2020-17](https://civicrm.org/advisory/civi-sa-2020-17-harden-session-private-key): Harden Per-Session Private Key** +- **[CIVI-SA-2020-18](https://civicrm.org/advisory/civi-sa-2020-18-html-injection-through-error-message): HTML Injection via Error Message** +- **[CIVI-SA-2020-19](https://civicrm.org/advisory/civi-sa-2020-19-edit-permission-recurring-contributions): Edit Permission for Recurring Contributions** + +## <a name="bugs"></a>Bugs Resolved + +* **_Activities_: Exporting all activities from a "Find Activity" search as an ACLed user causes DB error ([dev/core#1952](https://lab.civicrm.org/dev/core/-/issues/1952): + [#18017](https://github.com/civicrm/civicrm-core/pull/18017))** +* **_CiviContribute_: Receipts display unlabeled price options as "null" ([dev/core#1936](https://lab.civicrm.org/dev/core/-/issues/1936): + [#18124](https://github.com/civicrm/civicrm-core/pull/18124))** +* **_CiviContribute_: Credit card fields are required even when the amount is 0 ([dev/core#1953](https://lab.civicrm.org/dev/core/-/issues/1953): + [#18144](https://github.com/civicrm/civicrm-core/pull/18144), [#16163](https://github.com/civicrm/civicrm-core/pull/16163), [#18166](https://github.com/civicrm/civicrm-core/pull/16166))** +* **_Dedupe_: Merging contacts with certain "Settings" produces error ([dev/core#1934](https://lab.civicrm.org/dev/core/-/issues/1934): + [#18126](https://github.com/civicrm/civicrm-core/pull/18126))** + +## <a name="credits"></a>Credits + +This release was developed by the following people, who participated in +various stages of reporting, analysis, development, review, and testing: + +Ben Hubbard - Armadillo Security; Coleman Watts - CiviCRM; Cure53; Dave D; +Dennis Brinkrolf - RIPS Technologies; Eileen McNaughton - Wikipedia +Foundation; Jamie Novick - Compucorp; Jens Schuppe; Jude Hungerford - Asylum +Seekers Center; Karin Gerritsen - Semper IT; Kevin Cristiano - Tadpole +Collective; Mark Rogers; Mozilla Open Source Support (MOSS); Patrick Figel - +Greenpeace CEE; Pradeep Nayak - Circle Interactive; Rich Lott - Artful +Robot; Seamus Lee - CiviCRM and JMA Consulting; Sean Colsen - Left Join +Labs; Shitij Gugnai - Compucorp; Tim Otten - CiviCRM diff --git a/civicrm/sql/civicrm.mysql b/civicrm/sql/civicrm.mysql index f8eada46b4d5f925e5fdc4a84a20fcc0f5c2f2bd..b181200c6874be1e3bcf887448d09ac1845fd0d0 100644 --- a/civicrm/sql/civicrm.mysql +++ b/civicrm/sql/civicrm.mysql @@ -4354,8 +4354,8 @@ CREATE TABLE `civicrm_price_field_value` ( `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'Price Field Value', `price_field_id` int unsigned NOT NULL COMMENT 'FK to civicrm_price_field', - `name` varchar(255) NOT NULL COMMENT 'Price field option name', - `label` varchar(255) NOT NULL COMMENT 'Price field option label', + `name` varchar(255) DEFAULT NULL COMMENT 'Price field option name', + `label` varchar(255) DEFAULT NULL COMMENT 'Price field option label', `description` text DEFAULT NULL COMMENT 'Price field option description.', `help_pre` text DEFAULT NULL COMMENT 'Price field option pre help text.', `help_post` text DEFAULT NULL COMMENT 'Price field option post field help.', diff --git a/civicrm/sql/civicrm_data.mysql b/civicrm/sql/civicrm_data.mysql index 12bbf583f36145b26231ce9be72f9698a534f6c3..9a30bb9939c8a96223789103cd70029899a81cff 100644 --- a/civicrm/sql/civicrm_data.mysql +++ b/civicrm/sql/civicrm_data.mysql @@ -23897,4 +23897,4 @@ INSERT INTO `civicrm_report_instance` ( `domain_id`, `title`, `report_id`, `description`, `permission`, `form_values`) VALUES ( @domainID, 'Survey Details', 'survey/detail', 'Detailed report for canvassing, phone-banking, walk lists or other surveys.', 'access CiviReport', 'a:39:{s:6:"fields";a:2:{s:9:"sort_name";s:1:"1";s:6:"result";s:1:"1";}s:22:"assignee_contact_id_op";s:2:"eq";s:25:"assignee_contact_id_value";s:0:"";s:12:"sort_name_op";s:3:"has";s:15:"sort_name_value";s:0:"";s:17:"street_number_min";s:0:"";s:17:"street_number_max";s:0:"";s:16:"street_number_op";s:3:"lte";s:19:"street_number_value";s:0:"";s:14:"street_name_op";s:3:"has";s:17:"street_name_value";s:0:"";s:15:"postal_code_min";s:0:"";s:15:"postal_code_max";s:0:"";s:14:"postal_code_op";s:3:"lte";s:17:"postal_code_value";s:0:"";s:7:"city_op";s:3:"has";s:10:"city_value";s:0:"";s:20:"state_province_id_op";s:2:"in";s:23:"state_province_id_value";a:0:{}s:13:"country_id_op";s:2:"in";s:16:"country_id_value";a:0:{}s:12:"survey_id_op";s:2:"in";s:15:"survey_id_value";a:0:{}s:12:"status_id_op";s:2:"eq";s:15:"status_id_value";s:1:"1";s:11:"custom_1_op";s:2:"in";s:14:"custom_1_value";a:0:{}s:11:"custom_2_op";s:2:"in";s:14:"custom_2_value";a:0:{}s:17:"custom_3_relative";s:1:"0";s:13:"custom_3_from";s:0:"";s:11:"custom_3_to";s:0:"";s:11:"description";s:75:"Detailed report for canvassing, phone-banking, walk lists or other surveys.";s:13:"email_subject";s:0:"";s:8:"email_to";s:0:"";s:8:"email_cc";s:0:"";s:10:"permission";s:17:"access CiviReport";s:6:"groups";s:0:"";s:9:"domain_id";i:1;}'); -UPDATE civicrm_domain SET version = '5.28.0'; +UPDATE civicrm_domain SET version = '5.28.1'; diff --git a/civicrm/sql/civicrm_generated.mysql b/civicrm/sql/civicrm_generated.mysql index e5eb00b717d05817ad3f867c4753e745150f18fd..bbf0de2711ac4f901e4990d750f7cd8abd5e0676 100644 --- a/civicrm/sql/civicrm_generated.mysql +++ b/civicrm/sql/civicrm_generated.mysql @@ -399,7 +399,7 @@ UNLOCK TABLES; LOCK TABLES `civicrm_domain` WRITE; /*!40000 ALTER TABLE `civicrm_domain` DISABLE KEYS */; -INSERT INTO `civicrm_domain` (`id`, `name`, `description`, `version`, `contact_id`, `locales`, `locale_custom_strings`) VALUES (1,'Default Domain Name',NULL,'5.28.0',1,NULL,'a:1:{s:5:\"en_US\";a:0:{}}'); +INSERT INTO `civicrm_domain` (`id`, `name`, `description`, `version`, `contact_id`, `locales`, `locale_custom_strings`) VALUES (1,'Default Domain Name',NULL,'5.28.1',1,NULL,'a:1:{s:5:\"en_US\";a:0:{}}'); /*!40000 ALTER TABLE `civicrm_domain` ENABLE KEYS */; UNLOCK TABLES; diff --git a/civicrm/templates/CRM/Activity/Selector/Selector.tpl b/civicrm/templates/CRM/Activity/Selector/Selector.tpl index ffb235266b9b01db7122fd5960f86b4778fa30fb..4817467ee5cc16e28ad0168db7a4eb6b89fa0069 100644 --- a/civicrm/templates/CRM/Activity/Selector/Selector.tpl +++ b/civicrm/templates/CRM/Activity/Selector/Selector.tpl @@ -50,7 +50,7 @@ {literal} <script type="text/javascript"> - (function($) { + (function($, _) { var context = {/literal}"{$context}"{literal}; CRM.$('table.contact-activity-selector-' + context).data({ "ajax": { @@ -67,11 +67,16 @@ } }); $(function($) { + $('table.contact-activity-selector-' + context).on('xhr.dt', function(e, settings, json, xhr) { + for (var i=0, ien=json.data.length; i<ien; i++) { + json.data[i].subject = _.escape(json.data[i].subject); + } + }); $('.activity-search-options :input').change(function(){ - CRM.$('table.contact-activity-selector-' + context).DataTable().draw(); + $('table.contact-activity-selector-' + context).DataTable().draw(); }); }); - })(CRM.$); + })(CRM.$, CRM._); </script> {/literal} <style type="text/css"> diff --git a/civicrm/templates/CRM/Admin/Page/CKEditorConfig.tpl b/civicrm/templates/CRM/Admin/Form/CKEditorConfig.tpl similarity index 82% rename from civicrm/templates/CRM/Admin/Page/CKEditorConfig.tpl rename to civicrm/templates/CRM/Admin/Form/CKEditorConfig.tpl index 14af566be27a39197e590abce48a285869c94c5e..ae51de9d1815e995e00fa3657cd572b547b4f2a1 100644 --- a/civicrm/templates/CRM/Admin/Page/CKEditorConfig.tpl +++ b/civicrm/templates/CRM/Admin/Form/CKEditorConfig.tpl @@ -44,10 +44,6 @@ border-bottom: 0 none; padding: 3px 10px 1px !important; } - .crm-config-option-row span.crm-error:after { - font-family: FontAwesome; - content: " \f071 Invalid JSON" - } {/literal}</style> {* Force the custom config file to reload by appending a new query string *} <script type="text/javascript"> @@ -64,7 +60,7 @@ {/foreach} </ul> </div> -<form method="post" action="{crmURL}" id="toolbarModifierForm"> +<div id="toolbarModifierForm"> <fieldset> <div class="crm-block crm-form-block"> <label for="skin">{ts}Skin{/ts}</label> @@ -89,7 +85,6 @@ </div> </div> - <div class="crm-block crm-form-block"> <fieldset> <legend>{ts}Advanced Options{/ts}</legend> @@ -98,17 +93,9 @@ </fieldset> </div> - <div class="crm-submit-buttons"> - <span class="crm-button crm-i-button"> - <i class="crm-i fa-wrench" aria-hidden="true"></i> <input type="submit" value="{ts}Save{/ts}" name="save" class="crm-form-submit" accesskey="S"/> - </span> - <span class="crm-button crm-i-button"> - <i class="crm-i fa-times" aria-hidden="true"></i> <input type="submit" value="{ts}Revert to Default{/ts}" name="revert" class="crm-form-submit" onclick="return confirm('{$revertConfirm}');"/> - </span> - </div> - <input type="hidden" value="{$preset}" name="preset" /> + <div class="crm-submit-buttons">{include file="CRM/common/formButtons.tpl" location="bottom"}</div> </fieldset> -</form> +</div> <script type="text/template" id="config-row-tpl"> <div class="crm-config-option-row"> <input class="huge crm-config-option-name" placeholder="{ts}Option{/ts}"/> diff --git a/civicrm/templates/CRM/Contact/Form/Search/Intro.tpl b/civicrm/templates/CRM/Contact/Form/Search/Intro.tpl index d5d791c3b807d738b57fe467380e43ffa4c34b00..adc70018d03a78153a74ec7d66eb4b4cc943de96 100644 --- a/civicrm/templates/CRM/Contact/Form/Search/Intro.tpl +++ b/civicrm/templates/CRM/Contact/Form/Search/Intro.tpl @@ -10,26 +10,28 @@ {* $context indicates where we are searching, values = "search,advanced,smog,amtg" *} {* smog = 'show members of group'; amtg = 'add members to group' *} {if $context EQ 'smog'} - {* Provide link to modify smart group search criteria if we are viewing a smart group (ssID = saved search ID) *} + {* Provide link to modify smart group search criteria if we are viewing a smart group (ssID = saved search ID) *} + {if $permissionEditSmartGroup} {if !empty($ssID)} - {if $ssMappingID} - {capture assign=editSmartGroupURL}{crmURL p="civicrm/contact/search/builder" q="reset=1&ssID=`$ssID`"}{/capture} - {elseif $savedSearch.search_custom_id} - {capture assign=editSmartGroupURL}{crmURL p="civicrm/contact/search/custom" q="reset=1&ssID=`$ssID`"}{/capture} - {else} - {capture assign=editSmartGroupURL}{crmURL p="civicrm/contact/search/advanced" q="reset=1&ssID=`$ssID`"}{/capture} - {/if} - <div class="crm-submit-buttons"> - <a href="{$editSmartGroupURL}" class="button no-popup"><span><i class="crm-i fa-pencil" aria-hidden="true"></i> {ts 1=$group.title}Edit Smart Group Search Criteria for %1{/ts}</span></a> - {help id="id-edit-smartGroup"} - </div> + {if $ssMappingID} + {capture assign=editSmartGroupURL}{crmURL p="civicrm/contact/search/builder" q="reset=1&ssID=`$ssID`"}{/capture} + {elseif $savedSearch.search_custom_id} + {capture assign=editSmartGroupURL}{crmURL p="civicrm/contact/search/custom" q="reset=1&ssID=`$ssID`"}{/capture} + {else} + {capture assign=editSmartGroupURL}{crmURL p="civicrm/contact/search/advanced" q="reset=1&ssID=`$ssID`"}{/capture} + {/if} + <div class="crm-submit-buttons"> + <a href="{$editSmartGroupURL}" class="button no-popup"><span><i class="crm-i fa-pencil" aria-hidden="true"></i> {ts 1=$group.title}Edit Smart Group Search Criteria for %1{/ts}</span></a> + {help id="id-edit-smartGroup"} + </div> {/if} + {/if} - {if $permissionedForGroup} - {capture assign=addMembersURL}{crmURL q="context=amtg&amtgID=`$group.id`&reset=1"}{/capture} - <div class="crm-submit-buttons"> - <a href="{$addMembersURL}" class="button no-popup"><span><i class="crm-i fa-user-plus" aria-hidden="true"></i> {ts 1=$group.title}Add Contacts to %1{/ts}</span></a> - {if $ssID}{help id="id-add-to-smartGroup"}{/if} - </div> - {/if} + {if $permissionedForGroup} + {capture assign=addMembersURL}{crmURL q="context=amtg&amtgID=`$group.id`&reset=1"}{/capture} + <div class="crm-submit-buttons"> + <a href="{$addMembersURL}" class="button no-popup"><span><i class="crm-i fa-user-plus" aria-hidden="true"></i> {ts 1=$group.title}Add Contacts to %1{/ts}</span></a> + {if $ssID}{help id="id-add-to-smartGroup"}{/if} + </div> + {/if} {/if} diff --git a/civicrm/templates/CRM/Contribute/Form/Contribution/PremiumBlock.tpl b/civicrm/templates/CRM/Contribute/Form/Contribution/PremiumBlock.tpl index db7e56220c678ebe73e392f5537e995dc3e4db36..6ca766cc26b31a2dbd8d4009d9d201ecc68647fd 100644 --- a/civicrm/templates/CRM/Contribute/Form/Contribution/PremiumBlock.tpl +++ b/civicrm/templates/CRM/Contribute/Form/Contribution/PremiumBlock.tpl @@ -332,8 +332,6 @@ $('#selectProduct').rules('add', 'premiums'); }); - // need to use jquery validate's ignore option, so that it will not ignore hidden fields - CRM.validate.params['ignore'] = '.ignore'; }); </script> {/literal} diff --git a/civicrm/templates/CRM/Core/BillingBlock.tpl b/civicrm/templates/CRM/Core/BillingBlock.tpl index 10a830c1267b43a6c4265a1d74907bd71abb5eb9..f8bb2088f5064eb060a201e25cafe51ce1317bf6 100644 --- a/civicrm/templates/CRM/Core/BillingBlock.tpl +++ b/civicrm/templates/CRM/Core/BillingBlock.tpl @@ -20,7 +20,9 @@ {foreach from=$paymentFields item=paymentField} {assign var='name' value=$form.$paymentField.name} <div class="crm-section {$form.$paymentField.name}-section"> - <div class="label">{$form.$paymentField.label}</div> + <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} {if $paymentFieldsMetadata.$name.description} @@ -49,7 +51,9 @@ {foreach from=$billingDetailsFields item=billingField} {assign var='name' value=$form.$billingField.name} <div class="crm-section {$form.$billingField.name}-section"> - <div class="label">{$form.$billingField.label}</div> + <div class="label">{$form.$billingField.label} + {if $requiredPaymentFields.$name}<span class="crm-marker" title="{ts}This field is required.{/ts}">*</span>{/if} + </div> {if $form.$billingField.type == 'text'} <div class="content">{$form.$billingField.html}</div> {else} diff --git a/civicrm/templates/CRM/Event/Page/EventInfo.tpl b/civicrm/templates/CRM/Event/Page/EventInfo.tpl index 38a9beaba2c67a1186a7924e614cf4c2e065f111..4b858973e4d9e609b379e664a5cfec1332269451 100644 --- a/civicrm/templates/CRM/Event/Page/EventInfo.tpl +++ b/civicrm/templates/CRM/Event/Page/EventInfo.tpl @@ -89,12 +89,12 @@ {if $event.summary} <div class="crm-section event_summary-section"> - {$event.summary} + {$event.summary|purify} </div> {/if} {if $event.description} <div class="crm-section event_description-section summary"> - {$event.description} + {$event.description|purify} </div> {/if} <div class="clear"></div> diff --git a/civicrm/templates/CRM/Event/Page/List.tpl b/civicrm/templates/CRM/Event/Page/List.tpl index e5f5fa182f19c98b3c0ea81d370162e0ef5bc0aa..4cbf20b541d3734812f51880f39a0e87fb6880bd 100644 --- a/civicrm/templates/CRM/Event/Page/List.tpl +++ b/civicrm/templates/CRM/Event/Page/List.tpl @@ -30,7 +30,7 @@ {foreach from=$events key=uid item=event} <tr class="{cycle values="odd-row,even-row"} {$row.class}"> <td><a href="{crmURL p='civicrm/event/info' q="reset=1&id=`$event.event_id`"}" title="{ts}read more{/ts}"><strong>{$event.title}</strong></a></td> - <td>{if $event.summary}{$event.summary} (<a href="{crmURL p='civicrm/event/info' q="reset=1&id=`$event.event_id`"}" title="{ts}details...{/ts}">{ts}read more{/ts}...</a>){else} {/if}</td> + <td>{if $event.summary}{$event.summary|purify} (<a href="{crmURL p='civicrm/event/info' q="reset=1&id=`$event.event_id`"}" title="{ts}details...{/ts}">{ts}read more{/ts}...</a>){else} {/if}</td> <td class="nowrap" data-order="{$event.start_date|crmDate:'%Y-%m-%d'}"> {if $event.start_date}{$event.start_date|crmDate}{if $event.end_date}<br /><em>{ts}through{/ts}</em><br />{strip} {* Only show end time if end date = start date *} diff --git a/civicrm/templates/CRM/UF/Page/Group.tpl b/civicrm/templates/CRM/UF/Page/Group.tpl index 1eb5ee8ef0a9a609f65ad607f1a9578e417017de..eb25d0e9b73f10cb2ce39299bcd8451d04a11729 100644 --- a/civicrm/templates/CRM/UF/Page/Group.tpl +++ b/civicrm/templates/CRM/UF/Page/Group.tpl @@ -77,7 +77,7 @@ <a href="{crmURL p='civicrm/contact/view' q="reset=1&cid=`$row.created_id`"}">{ts}{$row.created_by}{/ts}</a> {/if} </td> - <td class="crmf-description crm-editable" data-type="textarea">{$row.description}</td> + <td class="crmf-description crm-editable" data-type="textarea">{$row.description|escape}</td> <td>{$row.group_type}</td> <td>{$row.id}</td> <td>{$row.module}</td> @@ -122,7 +122,7 @@ <a href="{crmURL p='civicrm/contact/view' q="reset=1&cid=`$row.created_id`"}">{ts}{$row.created_by}{/ts}</a> {/if} </td> - <td>{$row.description}</td> + <td>{$row.description|escape}</td> <td>{$row.group_type}</td> <td>{$row.id}</td> <td>{$row.module}</td> diff --git a/civicrm/vendor/autoload.php b/civicrm/vendor/autoload.php index a81209dbdf9ee5b5403c93ba21063e31c6f8f2b2..fd2598911f621ac26cd365b776ca7777e46d27fa 100644 --- a/civicrm/vendor/autoload.php +++ b/civicrm/vendor/autoload.php @@ -4,4 +4,4 @@ require_once __DIR__ . '/composer/autoload_real.php'; -return ComposerAutoloaderInit295933fd472bb10cd7a2a298afef50f1::getLoader(); +return ComposerAutoloaderInitcab70146bb388b68615bda3f614d51ef::getLoader(); diff --git a/civicrm/vendor/composer/autoload_real.php b/civicrm/vendor/composer/autoload_real.php index b7d11681f559b4d1ad87451f7e126f47d3442610..db59d6b9be673624c32b9a2ced6260ad6983264b 100644 --- a/civicrm/vendor/composer/autoload_real.php +++ b/civicrm/vendor/composer/autoload_real.php @@ -2,7 +2,7 @@ // autoload_real.php @generated by Composer -class ComposerAutoloaderInit295933fd472bb10cd7a2a298afef50f1 +class ComposerAutoloaderInitcab70146bb388b68615bda3f614d51ef { private static $loader; @@ -19,9 +19,9 @@ class ComposerAutoloaderInit295933fd472bb10cd7a2a298afef50f1 return self::$loader; } - spl_autoload_register(array('ComposerAutoloaderInit295933fd472bb10cd7a2a298afef50f1', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInitcab70146bb388b68615bda3f614d51ef', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); - spl_autoload_unregister(array('ComposerAutoloaderInit295933fd472bb10cd7a2a298afef50f1', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInitcab70146bb388b68615bda3f614d51ef', 'loadClassLoader')); $includePaths = require __DIR__ . '/include_paths.php'; $includePaths[] = get_include_path(); @@ -31,7 +31,7 @@ class ComposerAutoloaderInit295933fd472bb10cd7a2a298afef50f1 if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; - call_user_func(\Composer\Autoload\ComposerStaticInit295933fd472bb10cd7a2a298afef50f1::getInitializer($loader)); + call_user_func(\Composer\Autoload\ComposerStaticInitcab70146bb388b68615bda3f614d51ef::getInitializer($loader)); } else { $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { @@ -52,19 +52,19 @@ class ComposerAutoloaderInit295933fd472bb10cd7a2a298afef50f1 $loader->register(true); if ($useStaticLoader) { - $includeFiles = Composer\Autoload\ComposerStaticInit295933fd472bb10cd7a2a298afef50f1::$files; + $includeFiles = Composer\Autoload\ComposerStaticInitcab70146bb388b68615bda3f614d51ef::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { - composerRequire295933fd472bb10cd7a2a298afef50f1($fileIdentifier, $file); + composerRequirecab70146bb388b68615bda3f614d51ef($fileIdentifier, $file); } return $loader; } } -function composerRequire295933fd472bb10cd7a2a298afef50f1($fileIdentifier, $file) +function composerRequirecab70146bb388b68615bda3f614d51ef($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { require $file; diff --git a/civicrm/vendor/composer/autoload_static.php b/civicrm/vendor/composer/autoload_static.php index ea0ff2944cacb39dcd523051b7a8477796b73491..98f3adb753cf2ebfd171f2793416a0931d82b46f 100644 --- a/civicrm/vendor/composer/autoload_static.php +++ b/civicrm/vendor/composer/autoload_static.php @@ -4,7 +4,7 @@ namespace Composer\Autoload; -class ComposerStaticInit295933fd472bb10cd7a2a298afef50f1 +class ComposerStaticInitcab70146bb388b68615bda3f614d51ef { public static $files = array ( '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', @@ -530,11 +530,11 @@ class ComposerStaticInit295933fd472bb10cd7a2a298afef50f1 public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { - $loader->prefixLengthsPsr4 = ComposerStaticInit295933fd472bb10cd7a2a298afef50f1::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInit295933fd472bb10cd7a2a298afef50f1::$prefixDirsPsr4; - $loader->prefixesPsr0 = ComposerStaticInit295933fd472bb10cd7a2a298afef50f1::$prefixesPsr0; - $loader->fallbackDirsPsr0 = ComposerStaticInit295933fd472bb10cd7a2a298afef50f1::$fallbackDirsPsr0; - $loader->classMap = ComposerStaticInit295933fd472bb10cd7a2a298afef50f1::$classMap; + $loader->prefixLengthsPsr4 = ComposerStaticInitcab70146bb388b68615bda3f614d51ef::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitcab70146bb388b68615bda3f614d51ef::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInitcab70146bb388b68615bda3f614d51ef::$prefixesPsr0; + $loader->fallbackDirsPsr0 = ComposerStaticInitcab70146bb388b68615bda3f614d51ef::$fallbackDirsPsr0; + $loader->classMap = ComposerStaticInitcab70146bb388b68615bda3f614d51ef::$classMap; }, null, ClassLoader::class); } diff --git a/civicrm/xml/schema/Price/PriceFieldValue.xml b/civicrm/xml/schema/Price/PriceFieldValue.xml index 74c649b916729353fbeecdde983ae791901cbbc0..2290d96282893aeaad029d8aa2b3cd672204a57e 100644 --- a/civicrm/xml/schema/Price/PriceFieldValue.xml +++ b/civicrm/xml/schema/Price/PriceFieldValue.xml @@ -37,11 +37,11 @@ <title>Name</title> <length>255</length> <comment>Price field option name</comment> - <required>true</required> <html> <type>Text</type> </html> <add>3.3</add> + <default>NULL</default> </field> <field> <name>label</name> @@ -50,11 +50,11 @@ <length>255</length> <localizable>true</localizable> <comment>Price field option label</comment> - <required>true</required> <html> <type>Text</type> </html> <add>3.3</add> + <default>NULL</default> </field> <field> <name>description</name> diff --git a/civicrm/xml/version.xml b/civicrm/xml/version.xml index a33634d10543e2a863f800b4c8d827989fd7ba9e..846f4a4abe5626378381eb4aa815f5fdc82f04d3 100644 --- a/civicrm/xml/version.xml +++ b/civicrm/xml/version.xml @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="iso-8859-1" ?> <version> - <version_no>5.28.0</version_no> + <version_no>5.28.1</version_no> </version>