diff --git a/civicrm.php b/civicrm.php index 7dc4c0acd24eb6a1aa72692f3007e034a97a2072..9850c6a3eee386a2e88d351b010e24f3821357d1 100644 --- a/civicrm.php +++ b/civicrm.php @@ -2,7 +2,7 @@ /* Plugin Name: CiviCRM Description: CiviCRM - Growing and Sustaining Relationships -Version: 5.13.2 +Version: 5.13.3 Author: CiviCRM LLC Author URI: https://civicrm.org/ Plugin URI: https://wiki.civicrm.org/confluence/display/CRMDOC/Installing+CiviCRM+for+WordPress diff --git a/civicrm/CRM/ACL/Form/WordPress/Permissions.php b/civicrm/CRM/ACL/Form/WordPress/Permissions.php index bad293c934c17f1d0567697ce7abc3b935b3501e..65191fb9793aed95be6ef1190364b5fd59b5a109 100644 --- a/civicrm/CRM/ACL/Form/WordPress/Permissions.php +++ b/civicrm/CRM/ACL/Form/WordPress/Permissions.php @@ -54,7 +54,7 @@ class CRM_ACL_Form_WordPress_Permissions extends CRM_Core_Form { } foreach ($wp_roles->role_names as $role => $name) { // Don't show the permissions options for administrator, as they have all permissions - if ( is_multisite() OR $role !== 'administrator') { + if ($role !== 'administrator') { $roleObj = $wp_roles->get_role($role); if (!empty($roleObj->capabilities)) { foreach ($roleObj->capabilities as $ckey => $cname) { diff --git a/civicrm/CRM/Activity/BAO/Activity.php b/civicrm/CRM/Activity/BAO/Activity.php index af4961d5ab5ee4115f7928d79e39f95b73c470d5..3f1f37b6e318f0d30ff790c7fc8fcc85f4b09d62 100644 --- a/civicrm/CRM/Activity/BAO/Activity.php +++ b/civicrm/CRM/Activity/BAO/Activity.php @@ -705,7 +705,6 @@ class CRM_Activity_BAO_Activity extends CRM_Activity_DAO_Activity { 'source_contact_id', 'source_contact_name', 'assignee_contact_id', - 'target_contact_id', 'assignee_contact_name', 'status_id', 'subject', @@ -719,7 +718,7 @@ class CRM_Activity_BAO_Activity extends CRM_Activity_DAO_Activity { $activityParams['return'][] = $attr; } } - $result = civicrm_api3('Activity', 'Get', $activityParams); + $result = civicrm_api3('Activity', 'Get', $activityParams)['values']; $bulkActivityTypeID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Bulk Email'); $allCampaigns = CRM_Campaign_BAO_Campaign::getCampaigns(NULL, NULL, FALSE, FALSE, FALSE, TRUE); @@ -730,44 +729,83 @@ class CRM_Activity_BAO_Activity extends CRM_Activity_DAO_Activity { (CRM_Mailing_Info::workflowEnabled() && CRM_Core_Permission::check('create mailings')) ); + // @todo - get rid of this & just handle in the array declaration like we do with 'subject' etc. $mappingParams = [ - 'id' => 'activity_id', 'source_record_id' => 'source_record_id', 'activity_type_id' => 'activity_type_id', - 'activity_date_time' => 'activity_date_time', 'status_id' => 'status_id', - 'subject' => 'subject', 'campaign_id' => 'campaign_id', - 'assignee_contact_name' => 'assignee_contact_name', - 'source_contact_id' => 'source_contact_id', - 'source_contact_name' => 'source_contact_name', 'case_id' => 'case_id', ]; - foreach ($result['values'] as $id => $activity) { - - $activities[$id] = []; - - $isBulkActivity = (!$bulkActivityTypeID || ($bulkActivityTypeID === $activity['activity_type_id'])); - $activities[$id]['target_contact_counter'] = count($activity['target_contact_id']); - if ($activities[$id]['target_contact_counter']) { - try { - $activities[$id]['target_contact_name'][$activity['target_contact_id'][0]] = civicrm_api3('Contact', 'getvalue', ['id' => $activity['target_contact_id'][0], 'return' => 'sort_name']); + if (empty($result)) { + $targetCount = []; + } + else { + $targetCount = CRM_Core_DAO::executeQuery(' + SELECT activity_id, count(*) as target_contact_count + FROM civicrm_activity_contact + INNER JOIN civicrm_contact c ON contact_id = c.id AND c.is_deleted = 0 + WHERE activity_id IN (' . implode(',', array_keys($result)) . ') + AND record_type_id = %1 + GROUP BY activity_id', [ + 1 => [ + CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_ActivityContact', 'record_type_id', 'Activity Targets'), + 'Integer' + ] + ])->fetchAll(); + } + foreach ($targetCount as $activityTarget) { + $result[$activityTarget['activity_id']]['target_contact_count'] = $activityTarget['target_contact_count']; + } + // Iterate through & do basic mappings & determine which ones we want to retrieve target count for. + foreach ($result as $id => $activity) { + $activities[$id] = [ + 'activity_id' => $activity['id'], + 'activity_date_time' => CRM_Utils_Array::value('activity_date_time', $activity), + 'subject' => CRM_Utils_Array::value('subject', $activity), + 'assignee_contact_name' => CRM_Utils_Array::value('assignee_contact_sort_name', $activity, []), + 'source_contact_id' => CRM_Utils_Array::value('source_contact_id', $activity), + 'source_contact_name' => CRM_Utils_Array::value('source_contact_sort_name', $activity), + ]; + $activities[$id]['activity_type_name'] = CRM_Core_PseudoConstant::getName('CRM_Activity_BAO_Activity', 'activity_type_id', $activity['activity_type_id']); + $activities[$id]['activity_type'] = CRM_Core_PseudoConstant::getLabel('CRM_Activity_BAO_Activity', 'activity_type_id', $activity['activity_type_id']); + $activities[$id]['target_contact_count'] = CRM_Utils_Array::value('target_contact_count', $activity, 0); + if (!empty($activity['target_contact_count'])) { + $displayedTarget = civicrm_api3('ActivityContact', 'get', [ + 'activity_id' => $id, + 'check_permissions' => TRUE, + 'options' => ['limit' => 1], + 'record_type_id' => 'Activity Targets', + 'return' => ['contact_id.sort_name', 'contact_id'], + 'sequential' => 1, + ])['values']; + if (empty($displayedTarget[0])) { + $activities[$id]['target_contact_name'] = []; } - catch (CiviCRM_API3_Exception $e) { - // Really they should have names but a fatal here feels wrong. - $activities[$id]['target_contact_name'] = ''; + else { + $activities[$id]['target_contact_name'] = [$displayedTarget[0]['contact_id'] => $displayedTarget[0]['contact_id.sort_name']]; } } + if ($activities[$id]['activity_type_name'] === 'Bulk Email') { + $bulkActivities[] = $id; + // Get the total without permissions being passed but only display names after permissioning. + $activities[$id]['recipients'] = ts('(%1 recipients)', [1 => $activities[$id]['target_contact_count']]); + } + } + + // Eventually this second iteration should just handle the target contacts. It's a bit muddled at + // the moment as the bulk activity stuff needs unravelling & test coverage. + foreach ($result as $id => $activity) { + $isBulkActivity = (!$bulkActivityTypeID || ($bulkActivityTypeID === $activity['activity_type_id'])); foreach ($mappingParams as $apiKey => $expectedName) { if (in_array($apiKey, [ - 'assignee_contact_name', 'target_contact_name', ])) { - $activities[$id][$expectedName] = CRM_Utils_Array::value($apiKey, $activity, []); if ($isBulkActivity) { - $activities[$id]['recipients'] = ts('(%1 recipients)', [1 => count($activity['target_contact_name'])]); + // @todo - how is this used? Couldn't we use 'is_bulk' or something clearer? + // or the calling function could handle $activities[$id]['mailingId'] = FALSE; if ($accessCiviMail && ($mailingIDs === TRUE || in_array($activity['source_record_id'], $mailingIDs)) @@ -786,11 +824,9 @@ class CRM_Activity_BAO_Activity extends CRM_Activity_DAO_Activity { } } else { + // @todo this generic assign could just be handled in array declaration earlier. $activities[$id][$expectedName] = CRM_Utils_Array::value($apiKey, $activity); - if ($apiKey == 'activity_type_id') { - $activities[$id]['activity_type'] = CRM_Core_PseudoConstant::getName('CRM_Activity_BAO_Activity', 'activity_type_id', $activities[$id][$expectedName]); - } - elseif ($apiKey == 'campaign_id') { + if ($apiKey == 'campaign_id') { $activities[$id]['campaign'] = CRM_Utils_Array::value($activities[$id][$expectedName], $allCampaigns); } } @@ -2525,7 +2561,7 @@ INNER JOIN civicrm_option_group grp ON (grp.id = option_group_id AND grp.name = elseif (!empty($values['recipients'])) { $activity['target_contact_name'] = $values['recipients']; } - elseif (isset($values['target_contact_counter']) && $values['target_contact_counter']) { + elseif (isset($values['target_contact_count']) && $values['target_contact_count']) { $activity['target_contact_name'] = ''; $firstTargetName = reset($values['target_contact_name']); $firstTargetContactID = key($values['target_contact_name']); @@ -2542,7 +2578,7 @@ INNER JOIN civicrm_option_group grp ON (grp.id = option_group_id AND grp.name = $activity['target_contact_name'] .= $targetLink; } - if ($extraCount = $values['target_contact_counter'] - 1) { + if ($extraCount = $values['target_contact_count'] - 1) { $activity['target_contact_name'] .= ";<br />" . "(" . ts('%1 more', [1 => $extraCount]) . ")"; } if ($showContactOverlay) { diff --git a/civicrm/CRM/Contribute/Form/Task/Invoice.php b/civicrm/CRM/Contribute/Form/Task/Invoice.php index 4d02644bd4bb5d7990c429cda9cbcd246b9210dc..f2742ee109e2dfb00e38dfad622818ce66593646 100644 --- a/civicrm/CRM/Contribute/Form/Task/Invoice.php +++ b/civicrm/CRM/Contribute/Form/Task/Invoice.php @@ -301,7 +301,13 @@ class CRM_Contribute_Form_Task_Invoice extends CRM_Contribute_Form_Task { $invoiceDate = date("F j, Y"); $dueDate = date('F j, Y', strtotime($contributionReceiveDate . "+" . $prefixValue['due_date'] . "" . $prefixValue['due_date_period'])); - $lineItem = CRM_Price_BAO_LineItem::getLineItemsByContributionID($contribID); + if ($input['component'] == 'contribute') { + $lineItem = CRM_Price_BAO_LineItem::getLineItemsByContributionID($contribID); + } + else { + $eid = $contribution->_relatedObjects['participant']->id; + $lineItem = CRM_Price_BAO_LineItem::getLineItems($eid, 'participant', NULL, TRUE, FALSE, TRUE); + } $resultPayments = civicrm_api3('Payment', 'get', [ 'sequential' => 1, diff --git a/civicrm/CRM/Core/Form.php b/civicrm/CRM/Core/Form.php index b396667fe7f4306d153fa680da40dc5e47eacf8c..8d0c6b20d6477347c501a1d11c1ea60c65f4f44f 100644 --- a/civicrm/CRM/Core/Form.php +++ b/civicrm/CRM/Core/Form.php @@ -1314,7 +1314,7 @@ class CRM_Core_Form extends HTML_QuickForm_Page { $label, $options, $required, - NULL + ['class' => 'crm-select2'] ); $attributes = ['format' => 'searchDate']; $extra = ['time' => $isDateTime]; diff --git a/civicrm/CRM/Event/Form/Search.php b/civicrm/CRM/Event/Form/Search.php index 71e58e1f3fac64417391f98055ade153d66fc8e1..4a61657ee3088da531b5eaf057e08dc03b3514d6 100644 --- a/civicrm/CRM/Event/Form/Search.php +++ b/civicrm/CRM/Event/Form/Search.php @@ -170,7 +170,7 @@ class CRM_Event_Form_Search extends CRM_Core_Form_Search { $seatClause[] = "( participant.is_test = {$this->_formValues['participant_test']} )"; } if (!empty($this->_formValues['participant_status_id'])) { - $seatClause[] = CRM_Contact_BAO_Query::buildClause("participant.status_id", '=', $this->_formValues['participant_status_id'], 'Int'); + $seatClause[] = CRM_Contact_BAO_Query::buildClause("participant.status_id", 'IN', $this->_formValues['participant_status_id'], 'Int'); if ($status = CRM_Utils_Array::value('IN', $this->_formValues['participant_status_id'])) { $this->_formValues['participant_status_id'] = $status; } diff --git a/civicrm/CRM/UF/Form/Group.php b/civicrm/CRM/UF/Form/Group.php index 9a5dfbec8ab874c66cc66f32bf0994b15b0a3ee8..e9bf93e0f6732f6dd69abdc9bb8692d2f15e0da7 100644 --- a/civicrm/CRM/UF/Form/Group.php +++ b/civicrm/CRM/UF/Form/Group.php @@ -449,8 +449,8 @@ class CRM_UF_Form_Group extends CRM_Core_Form { */ protected function getOtherModuleString() { $otherModules = CRM_Core_BAO_UFGroup::getUFJoinRecord($this->_id, TRUE, TRUE); + $otherModuleString = NULL; if (!empty($otherModules)) { - $otherModuleString = NULL; foreach ($otherModules as $key) { $otherModuleString .= " [ x ] <label>" . $key . "</label>"; } diff --git a/civicrm/CRM/Upgrade/Incremental/sql/5.13.3.mysql.tpl b/civicrm/CRM/Upgrade/Incremental/sql/5.13.3.mysql.tpl new file mode 100644 index 0000000000000000000000000000000000000000..e342ef845e17161dfd0abae8c648f7153eb2e3bc --- /dev/null +++ b/civicrm/CRM/Upgrade/Incremental/sql/5.13.3.mysql.tpl @@ -0,0 +1 @@ +{* file to handle db changes in 5.13.3 during upgrade *} diff --git a/civicrm/CRM/Utils/System/WordPress.php b/civicrm/CRM/Utils/System/WordPress.php index c987eff083da294603886be74d26e80bbb7f2030..0ec9019e1f3b5c9df254f251c4bece6b205ef0ca 100644 --- a/civicrm/CRM/Utils/System/WordPress.php +++ b/civicrm/CRM/Utils/System/WordPress.php @@ -820,13 +820,11 @@ class CRM_Utils_System_WordPress extends CRM_Utils_System_Base { $contactCreated = 0; $contactMatching = 0; - // previously used $wpdb - which means WordPress *must* be bootstrapped - $wpUsers = get_users(array( - 'blog_id' => get_current_blog_id(), - 'number' => -1, - )); + global $wpdb; + $wpUserIds = $wpdb->get_col("SELECT $wpdb->users.ID FROM $wpdb->users"); - foreach ($wpUsers as $wpUserData) { + foreach ($wpUserIds as $wpUserId) { + $wpUserData = get_userdata($wpUserId); $contactCount++; if ($match = CRM_Core_BAO_UFMatch::synchronizeUFMatch($wpUserData, $wpUserData->$id, diff --git a/civicrm/api/v3/Activity.php b/civicrm/api/v3/Activity.php index 89402415080ab20af25349c347c453849845280f..4aa31a9da1baaa80b73205193ca0855023052516 100644 --- a/civicrm/api/v3/Activity.php +++ b/civicrm/api/v3/Activity.php @@ -644,8 +644,10 @@ function _civicrm_api3_activity_fill_activity_contact_names(&$activities, $param 'activity_id', 'record_type_id', 'contact_id.display_name', + 'contact_id.sort_name', 'contact_id', ], + 'options' => ['limit' => 0], 'check_permissions' => !empty($params['check_permissions']), ]; if (count($activityContactTypes) < 3) { @@ -658,10 +660,12 @@ function _civicrm_api3_activity_fill_activity_contact_names(&$activities, $param if (in_array($recordType, ['target', 'assignee'])) { $activities[$activityContact['activity_id']][$recordType . '_contact_id'][] = $contactID; $activities[$activityContact['activity_id']][$recordType . '_contact_name'][$contactID] = isset($activityContact['contact_id.display_name']) ? $activityContact['contact_id.display_name'] : ''; + $activities[$activityContact['activity_id']][$recordType . '_contact_sort_name'][$contactID] = isset($activityContact['contact_id.sort_name']) ? $activityContact['contact_id.sort_name'] : ''; } else { $activities[$activityContact['activity_id']]['source_contact_id'] = $contactID; $activities[$activityContact['activity_id']]['source_contact_name'] = isset($activityContact['contact_id.display_name']) ? $activityContact['contact_id.display_name'] : ''; + $activities[$activityContact['activity_id']]['source_contact_sort_name'] = isset($activityContact['contact_id.sort_name']) ? $activityContact['contact_id.sort_name'] : ''; } } } diff --git a/civicrm/civicrm-version.php b/civicrm/civicrm-version.php index 44fe45856db491bcedf3012a7933bae3ad755b7a..d29ecc277a3286e2475a338d7ead035b5f7e245e 100644 --- a/civicrm/civicrm-version.php +++ b/civicrm/civicrm-version.php @@ -1,7 +1,7 @@ <?php /** @deprecated */ function civicrmVersion( ) { - return array( 'version' => '5.13.2', + return array( 'version' => '5.13.3', 'cms' => 'Wordpress', 'revision' => '' ); } diff --git a/civicrm/ext/api4/Civi/Api4/Action/Contact/GetFields.php b/civicrm/ext/api4/Civi/Api4/Action/Contact/GetFields.php new file mode 100644 index 0000000000000000000000000000000000000000..1d6ddf66edbdd462c4eecc95ab4153b8077e1bd8 --- /dev/null +++ b/civicrm/ext/api4/Civi/Api4/Action/Contact/GetFields.php @@ -0,0 +1,19 @@ +<?php +namespace Civi\Api4\Action\Contact; + +use Civi\Api4\Generic\DAOGetFieldsAction; + +class GetFields extends DAOGetFieldsAction { + + protected function getRecords() { + $fields = parent::getRecords(); + + $apiKeyPerms = ['edit api keys', 'administer CiviCRM']; + if ($this->checkPermissions && !\CRM_Core_Permission::check([$apiKeyPerms])) { + unset($fields['api_key']); + } + + return $fields; + } + +} diff --git a/civicrm/ext/api4/Civi/Api4/Contact.php b/civicrm/ext/api4/Civi/Api4/Contact.php index 351d401fd01c0e4ade014a02f77a03a7b0699cea..cca8c335d3e36bc95c683674f92bb38ba58b47b9 100644 --- a/civicrm/ext/api4/Civi/Api4/Contact.php +++ b/civicrm/ext/api4/Civi/Api4/Contact.php @@ -29,4 +29,11 @@ class Contact extends Generic\DAOEntity { return new Generic\DAOUpdateAction(__CLASS__, __FUNCTION__, ['id', 'contact_type']); } + /** + * @return \Civi\Api4\Action\Contact\GetFields + */ + public static function getFields() { + return new Action\Contact\GetFields(__CLASS__, __FUNCTION__); + } + } diff --git a/civicrm/ext/api4/Civi/Api4/Generic/AbstractAction.php b/civicrm/ext/api4/Civi/Api4/Generic/AbstractAction.php index f2ae0e61dd16ada55535a5aaac863013f3e5fa99..1b0786eae7b4610e3be0f3b50ddbb99cc381ff1d 100644 --- a/civicrm/ext/api4/Civi/Api4/Generic/AbstractAction.php +++ b/civicrm/ext/api4/Civi/Api4/Generic/AbstractAction.php @@ -345,7 +345,10 @@ abstract class AbstractAction implements \ArrayAccess { */ protected function getEntityFields() { if (!$this->entityFields) { - $params = ['action' => $this->getActionName()]; + $params = [ + 'action' => $this->getActionName(), + 'checkPermissions' => $this->checkPermissions, + ]; if (method_exists($this, 'getBaoName')) { $params['includeCustom'] = FALSE; } diff --git a/civicrm/ext/api4/Civi/Api4/Generic/Traits/DAOActionTrait.php b/civicrm/ext/api4/Civi/Api4/Generic/Traits/DAOActionTrait.php index 1506dc712b4df34b5a7477f7e471b8bdc305e782..1099d640161ee9b2a7a213e35374dd41424595aa 100644 --- a/civicrm/ext/api4/Civi/Api4/Generic/Traits/DAOActionTrait.php +++ b/civicrm/ext/api4/Civi/Api4/Generic/Traits/DAOActionTrait.php @@ -76,6 +76,15 @@ trait DAOActionTrait { $entityId = UtilsArray::value('id', $item); FormattingUtil::formatWriteParams($item, $this->getEntityName(), $this->getEntityFields()); $this->formatCustomParams($item, $entityId); + $item['check_permissions'] = $this->getCheckPermissions(); + + if ($this->getEntityName() == 'Contact' + && array_key_exists('api_key', $item) + && !array_key_exists('api_key', $this->getEntityFields()) + && !($entityId && \CRM_Core_Permission::check('edit own api keys') && \CRM_Core_Session::getLoggedInContactID() == $entityId) + ) { + throw new \Civi\API\Exception\UnauthorizedException('Permission denied to modify api key'); + } // For some reason the contact bao requires this if ($entityId && $this->getEntityName() == 'Contact') { diff --git a/civicrm/ext/api4/Civi/Api4/Query/Api4SelectQuery.php b/civicrm/ext/api4/Civi/Api4/Query/Api4SelectQuery.php index bfc206f869caaa5565b8ed003349b8f2bcf5f4ba..a7a912dab433ff4c02d6f09b2432418b5b08c1a3 100644 --- a/civicrm/ext/api4/Civi/Api4/Query/Api4SelectQuery.php +++ b/civicrm/ext/api4/Civi/Api4/Query/Api4SelectQuery.php @@ -77,6 +77,7 @@ class Api4SelectQuery extends SelectQuery { public function __construct($entity, $checkPermissions) { require_once 'api/v3/utils.php'; $this->entity = $entity; + $this->checkPermissions = $checkPermissions; $baoName = CoreUtil::getDAOFromApiName($entity); $bao = new $baoName(); @@ -87,7 +88,6 @@ class Api4SelectQuery extends SelectQuery { \CRM_Utils_SQL_Select::from($this->getTableName($baoName) . ' ' . self::MAIN_TABLE_ALIAS); // Add ACLs first to avoid redundant subclauses - $this->checkPermissions = $checkPermissions; $this->query->where($this->getAclClause(self::MAIN_TABLE_ALIAS, $baoName)); } @@ -294,7 +294,7 @@ class Api4SelectQuery extends SelectQuery { * @inheritDoc */ protected function getFields() { - $fields = civicrm_api4($this->entity, 'getFields', ['action' => 'get', 'includeCustom' => FALSE])->indexBy('name'); + $fields = civicrm_api4($this->entity, 'getFields', ['action' => 'get', 'checkPermissions' => $this->checkPermissions, 'includeCustom' => FALSE])->indexBy('name'); return (array) $fields; } @@ -318,6 +318,7 @@ class Api4SelectQuery extends SelectQuery { /** * @param $key + * @throws \API_Exception */ protected function joinFK($key) { $pathArray = explode('.', $key); @@ -352,6 +353,10 @@ class Api4SelectQuery extends SelectQuery { } } + if (!$lastLink->getField($field)) { + throw new \API_Exception('Invalid join'); + } + // custom groups use aliases for field names if ($lastLink instanceof CustomGroupJoinable) { $field = $lastLink->getSqlColumn($field); diff --git a/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/CustomGroupJoinable.php b/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/CustomGroupJoinable.php index c8562417e0737be965d44159aafb0e04dc8475f8..a1dd1a1d678915c3baf3a568a4257eee2b2b7d98 100644 --- a/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/CustomGroupJoinable.php +++ b/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/CustomGroupJoinable.php @@ -32,6 +32,9 @@ class CustomGroupJoinable extends Joinable { self::JOIN_TYPE_ONE_TO_MANY : self::JOIN_TYPE_ONE_TO_ONE; } + /** + * @inheritDoc + */ public function getEntityFields() { if (!$this->entityFields) { $fields = CustomField::get() @@ -45,6 +48,19 @@ class CustomGroupJoinable extends Joinable { return $this->entityFields; } + /** + * @inheritDoc + */ + public function getField($fieldName) { + foreach ($this->getEntityFields() as $field) { + $name = $field->getName(); + if ($name === $fieldName || strrpos($name, '.' . $fieldName) === strlen($name) - strlen($fieldName) - 1) { + return $field; + } + } + return NULL; + } + /** * @return string */ diff --git a/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/Joinable.php b/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/Joinable.php index 25c2a3753b30c0d1cf0890b86896876eb2ed2cce..0e92e3ab83c61cea315732abe0dec25e7ca1b68f 100644 --- a/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/Joinable.php +++ b/civicrm/ext/api4/Civi/Api4/Service/Schema/Joinable/Joinable.php @@ -248,7 +248,7 @@ class Joinable { } /** - * @return FieldSpec[] + * @return \Civi\Api4\Service\Spec\FieldSpec[] */ public function getEntityFields() { if (!$this->entityFields) { @@ -262,4 +262,16 @@ class Joinable { return $this->entityFields; } + /** + * @return \Civi\Api4\Service\Spec\FieldSpec|NULL + */ + public function getField($fieldName) { + foreach ($this->getEntityFields() as $field) { + if ($field->getName() === $fieldName) { + return $field; + } + } + return NULL; + } + } diff --git a/civicrm/ext/api4/info.xml b/civicrm/ext/api4/info.xml index eb014baacb83464a49cd6da02a8015fd7bbbf678..3e0d8a432332d6632446690231729ef07ba68b0a 100644 --- a/civicrm/ext/api4/info.xml +++ b/civicrm/ext/api4/info.xml @@ -13,8 +13,8 @@ <url desc="Documentation">https://wiki.civicrm.org/confluence/display/CRM/API+v4+Spec</url> <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> - <releaseDate>2019-04-21</releaseDate> - <version>4.4.0</version> + <releaseDate>2019-05-12</releaseDate> + <version>4.4.1</version> <develStage>stable</develStage> <compatibility> <ver>5.13</ver> diff --git a/civicrm/ext/api4/tests/phpunit/Action/ContactApiKeyTest.php b/civicrm/ext/api4/tests/phpunit/Action/ContactApiKeyTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1e2b8300c9ff9326281b39706b074f457a09c473 --- /dev/null +++ b/civicrm/ext/api4/tests/phpunit/Action/ContactApiKeyTest.php @@ -0,0 +1,170 @@ +<?php + +namespace Civi\Test\Api4\Action; + +use Civi\Api4\Contact; + +/** + * @group headless + */ +class ContactApiKeyTest extends \Civi\Test\Api4\UnitTestCase { + + public function testGetApiKey() { + \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM']; + $key = uniqid(); + + $contact = Contact::create() + ->setCheckPermissions(FALSE) + ->addValue('first_name', 'Api') + ->addValue('last_name', 'Key0') + ->addValue('api_key', $key) + ->execute() + ->first(); + + $result = Contact::get() + ->setCheckPermissions(FALSE) + ->addWhere('id', '=', $contact['id']) + ->addSelect('api_key') + ->execute() + ->first(); + + $this->assertEquals($result['api_key'], $key); + + $result = Contact::get() + ->addWhere('id', '=', $contact['id']) + ->addSelect('api_key') + ->execute() + ->first(); + + $this->assertTrue(empty($result['api_key'])); + } + + public function testCreateWithApiKey() { + \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'add contacts']; + $key = uniqid(); + + $error = ''; + try { + Contact::create() + ->addValue('first_name', 'Api') + ->addValue('last_name', 'Key1') + ->addValue('api_key', $key) + ->execute() + ->first(); + } + catch (\Exception $e) { + $error = $e->getMessage(); + } + $this->assertContains('key', $error); + } + + public function testUpdateApiKey() { + \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM']; + $key = uniqid(); + + $contact = Contact::create() + ->setCheckPermissions(FALSE) + ->addValue('first_name', 'Api') + ->addValue('last_name', 'Key2') + ->addValue('api_key', $key) + ->execute() + ->first(); + + $error = ''; + try { + // Try to update the key without permissions; nothing should happen + Contact::update() + ->addWhere('id', '=', $contact['id']) + ->addValue('api_key', "NotAllowed") + ->execute(); + } + catch (\Exception $e) { + $error = $e->getMessage(); + } + + $result = Contact::get() + ->setCheckPermissions(FALSE) + ->addWhere('id', '=', $contact['id']) + ->addSelect('api_key') + ->execute() + ->first(); + + $this->assertContains('key', $error); + + // Assert key is still the same + $this->assertEquals($result['api_key'], $key); + + // Now we can update the key + \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'administer CiviCRM']; + + Contact::update() + ->addWhere('id', '=', $contact['id']) + ->addValue('api_key', "IGotThePower!") + ->execute(); + + $result = Contact::get() + ->addWhere('id', '=', $contact['id']) + ->addSelect('api_key') + ->execute() + ->first(); + + // Assert key was updated + $this->assertEquals($result['api_key'], "IGotThePower!"); + } + + public function testUpdateOwnApiKey() { + \CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'edit own api keys']; + $key = uniqid(); + + $contact = Contact::create() + ->setCheckPermissions(FALSE) + ->addValue('first_name', 'Api') + ->addValue('last_name', 'Key3') + ->addValue('api_key', $key) + ->execute() + ->first(); + + $error = ''; + try { + // Try to update the key without permissions; nothing should happen + Contact::update() + ->addWhere('id', '=', $contact['id']) + ->addValue('api_key', "NotAllowed") + ->execute(); + } + catch (\Exception $e) { + $error = $e->getMessage(); + } + + $result = Contact::get() + ->setCheckPermissions(FALSE) + ->addWhere('id', '=', $contact['id']) + ->addSelect('api_key') + ->execute() + ->first(); + + $this->assertContains('key', $error); + + // Assert key is still the same + $this->assertEquals($result['api_key'], $key); + + // Now we can update the key + \CRM_Core_Session::singleton()->set('userID', $contact['id']); + + Contact::update() + ->addWhere('id', '=', $contact['id']) + ->addValue('api_key', "MyId!") + ->execute(); + + $result = Contact::get() + ->setCheckPermissions(FALSE) + ->addWhere('id', '=', $contact['id']) + ->addSelect('api_key') + ->execute() + ->first(); + + // Assert key was updated + $this->assertEquals($result['api_key'], "MyId!"); + } + +} diff --git a/civicrm/js/crm.menubar.js b/civicrm/js/crm.menubar.js index ad91a49e1b8a458d877f3f5f1bcc46787f0eface..f3b98afc7267d426c29c2783e9e8ea5a1fedbb3c 100644 --- a/civicrm/js/crm.menubar.js +++ b/civicrm/js/crm.menubar.js @@ -78,7 +78,7 @@ }) .on('show.smapi', function(e, menu) { // Focus menu when opened with an accesskey - $(menu).siblings('a[accesskey]:not(:hover)').focus(); + $(menu).siblings('a[accesskey]').focus(); }) .smartmenus(CRM.menubar.settings); initialized = true; diff --git a/civicrm/release-notes.md b/civicrm/release-notes.md index caaac6a198a8bf03bf3cefdf60acdf36b7ca9f8a..6bdcf86a8ca62fcc1cfa0895e6bbd2aae453fcb3 100644 --- a/civicrm/release-notes.md +++ b/civicrm/release-notes.md @@ -14,6 +14,17 @@ Other resources for identifying changes are: * https://github.com/civicrm/civicrm-joomla * https://github.com/civicrm/civicrm-wordpress +## CiviCRM 5.13.3 + +Released May 13, 2019 + +- **[Synopsis](release-notes/5.13.3.md#synopsis)** +- **[Features](release-notes/5.13.3.md#features)** +- **[Bugs resolved](release-notes/5.13.3.md#bugs)** +- **[Miscellany](release-notes/5.13.3.md#misc)** +- **[Credits](release-notes/5.13.3.md#credits)** +- **[Feedback](release-notes/5.13.3.md#feedback)** + ## CiviCRM 5.13.2 Released May 6, 2019 diff --git a/civicrm/release-notes/5.13.3.md b/civicrm/release-notes/5.13.3.md new file mode 100644 index 0000000000000000000000000000000000000000..f5b361fa3b33ed58b7254506134ecd8f26692e54 --- /dev/null +++ b/civicrm/release-notes/5.13.3.md @@ -0,0 +1,50 @@ +# CiviCRM 5.13.3 + +Released May 13, 2019 + +- **[Synopsis](#synopsis)** +- **[Bugs resolved](#bugs)** +- **[Credits](#credits)** +- **[Feedback](#feedback)** + +## <a name="synopsis"></a>Synopsis + +| *Does this version...?* | | +|:--------------------------------------------------------------- |:-------:| +| Fix security vulnerabilities? | no | +| 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="bugs"></a>Bugs resolved + +- **Activity Tab - Fix regression in outputting names with > 25 activities recorded" ([dev/core#942](https://lab.civicrm.org/dev/core/issues/942): + [14231](https://github.com/civicrm/civicrm-core/pull/14231))** + +- **Activity Search - Fix regression in displaying the "Activity Date" filter + ([14230](https://github.com/civicrm/civicrm-core/pull/14230))** + +- **Profile - Fix e-notice when creating or editing a profile ([dev/core#923](https://lab.civicrm.org/dev/core/issues/923): + [14229](https://github.com/civicrm/civicrm-core/pull/14229))** + +- **Menu - Fix Javascript error involving ":hover" selector ([dev/core#950](https://lab.civicrm.org/dev/core/issues/950): + [14228](https://github.com/civicrm/civicrm-core/pull/14228))** + +- **Event Search - Fix error when filtering participants by 1 event and multiple statuses ([dev/core#956](https://lab.civicrm.org/dev/core/issues/956): + [14234](https://github.com/civicrm/civicrm-core/pull/14234))** + +## <a name="credits"></a>Credits + +This release was developed by the following authors and reviewers: + +Wikimedia Foundation - Eileen McNaughton; Pradeep Nayak; Greenpeace CEE - Patrick Figel; +Dave D; CiviCRM - Coleman Watts; Australian Greens - Seamus Lee + +## <a name="feedback"></a>Feedback + +These release notes are edited by Tim Otten and Andrew Hunt. If you'd like to +provide feedback on them, please login to https://chat.civicrm.org/civicrm and +contact `@agh1`. diff --git a/civicrm/sql/civicrm_data.mysql b/civicrm/sql/civicrm_data.mysql index 3789b2def70ce4f4b0fe62f970cdd8c5b998c6af..f559d0b8347e919be558142f0b9036168565b71c 100644 --- a/civicrm/sql/civicrm_data.mysql +++ b/civicrm/sql/civicrm_data.mysql @@ -24035,4 +24035,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.13.2'; +UPDATE civicrm_domain SET version = '5.13.3'; diff --git a/civicrm/sql/civicrm_generated.mysql b/civicrm/sql/civicrm_generated.mysql index 16330e877786ed1a533eabc11d3db4682357285b..3be7ded828bce639cac6d5cb2d532becb671f4c4 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`, `config_backend`, `version`, `contact_id`, `locales`, `locale_custom_strings`) VALUES (1,'Default Domain Name',NULL,NULL,'5.13.2',1,NULL,'a:1:{s:5:\"en_US\";a:0:{}}'); +INSERT INTO `civicrm_domain` (`id`, `name`, `description`, `config_backend`, `version`, `contact_id`, `locales`, `locale_custom_strings`) VALUES (1,'Default Domain Name',NULL,NULL,'5.13.3',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/Form/Search/Common.tpl b/civicrm/templates/CRM/Activity/Form/Search/Common.tpl index 315f6cd34e153105bd63956f84365213d5e741e4..e9ac9139fa3ef882b4d372eb6faf2d3c6e931239 100644 --- a/civicrm/templates/CRM/Activity/Form/Search/Common.tpl +++ b/civicrm/templates/CRM/Activity/Form/Search/Common.tpl @@ -89,9 +89,8 @@ </tr> <tr> - <td> - {include file="CRM/Core/DatePickerRange.tpl" fieldName="activity_date_time"} - </td> + {include file="CRM/Core/DatePickerRangeWrapper.tpl" fieldName="activity_date_time" colspan="2"} + <td> </td> </tr> <tr> <td> diff --git a/civicrm/templates/CRM/Core/DatePickerRangeWrapper.tpl b/civicrm/templates/CRM/Core/DatePickerRangeWrapper.tpl new file mode 100644 index 0000000000000000000000000000000000000000..9396375a38897e569b2ba75314aab2c3379aaa6f --- /dev/null +++ b/civicrm/templates/CRM/Core/DatePickerRangeWrapper.tpl @@ -0,0 +1,29 @@ +{* + +--------------------------------------------------------------------+ + | CiviCRM version 5 | + +--------------------------------------------------------------------+ + | Copyright CiviCRM LLC (c) 2004-2018 | + +--------------------------------------------------------------------+ + | This file is a part of CiviCRM. | + | | + | CiviCRM is free software; you can copy, modify, and distribute it | + | under the terms of the GNU Affero General Public License | + | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. | + | | + | CiviCRM is distributed in the hope that it will be useful, but | + | WITHOUT ANY WARRANTY; without even the implied warranty of | + | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | + | See the GNU Affero General Public License for more details. | + | | + | You should have received a copy of the GNU Affero General Public | + | License and the CiviCRM Licensing Exception along | + | with this program; if not, contact CiviCRM LLC | + | at info[AT]civicrm[DOT]org. If you have questions about the | + | GNU Affero General Public License or the licensing of CiviCRM, | + | see the CiviCRM license FAQ at http://civicrm.org/licensing | + +--------------------------------------------------------------------+ +*} +{* Wrapper around DatePickerRange TPL file *} +<td {if $colspan} colspan="{$colspan}" {else} colspan="2" {/if} {if $class} class="{$class}" {/if}> + {include file="CRM/Core/DatePickerRange.tpl" fieldName=$fieldName} +</td> diff --git a/civicrm/vendor/autoload.php b/civicrm/vendor/autoload.php index 0a31cabc0e8d31d5707fdc0679d9b2ef8dfbcd36..3fb56162265e5b68f1955b6521fef96a18fbf07a 100644 --- a/civicrm/vendor/autoload.php +++ b/civicrm/vendor/autoload.php @@ -4,4 +4,4 @@ require_once __DIR__ . '/composer/autoload_real.php'; -return ComposerAutoloaderInit285dea4ecfc38f35aab901dfc1c31a29::getLoader(); +return ComposerAutoloaderInit744d6860e21bf6592928b795556791fb::getLoader(); diff --git a/civicrm/vendor/composer/autoload_real.php b/civicrm/vendor/composer/autoload_real.php index 6dbe28b202a10f3baf8559857f357db24397ec73..7696c205caaad87369ec4a74b701a83c8ae172fb 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 ComposerAutoloaderInit285dea4ecfc38f35aab901dfc1c31a29 +class ComposerAutoloaderInit744d6860e21bf6592928b795556791fb { private static $loader; @@ -19,9 +19,9 @@ class ComposerAutoloaderInit285dea4ecfc38f35aab901dfc1c31a29 return self::$loader; } - spl_autoload_register(array('ComposerAutoloaderInit285dea4ecfc38f35aab901dfc1c31a29', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInit744d6860e21bf6592928b795556791fb', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); - spl_autoload_unregister(array('ComposerAutoloaderInit285dea4ecfc38f35aab901dfc1c31a29', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInit744d6860e21bf6592928b795556791fb', 'loadClassLoader')); $includePaths = require __DIR__ . '/include_paths.php'; $includePaths[] = get_include_path(); @@ -31,7 +31,7 @@ class ComposerAutoloaderInit285dea4ecfc38f35aab901dfc1c31a29 if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; - call_user_func(\Composer\Autoload\ComposerStaticInit285dea4ecfc38f35aab901dfc1c31a29::getInitializer($loader)); + call_user_func(\Composer\Autoload\ComposerStaticInit744d6860e21bf6592928b795556791fb::getInitializer($loader)); } else { $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { @@ -52,19 +52,19 @@ class ComposerAutoloaderInit285dea4ecfc38f35aab901dfc1c31a29 $loader->register(true); if ($useStaticLoader) { - $includeFiles = Composer\Autoload\ComposerStaticInit285dea4ecfc38f35aab901dfc1c31a29::$files; + $includeFiles = Composer\Autoload\ComposerStaticInit744d6860e21bf6592928b795556791fb::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { - composerRequire285dea4ecfc38f35aab901dfc1c31a29($fileIdentifier, $file); + composerRequire744d6860e21bf6592928b795556791fb($fileIdentifier, $file); } return $loader; } } -function composerRequire285dea4ecfc38f35aab901dfc1c31a29($fileIdentifier, $file) +function composerRequire744d6860e21bf6592928b795556791fb($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 4c1e1750001400edfbbe141300f8f42bda5774f2..d90e78db72d4ff45e9a91b7ddb2acf6e5b2a9eec 100644 --- a/civicrm/vendor/composer/autoload_static.php +++ b/civicrm/vendor/composer/autoload_static.php @@ -4,7 +4,7 @@ namespace Composer\Autoload; -class ComposerStaticInit285dea4ecfc38f35aab901dfc1c31a29 +class ComposerStaticInit744d6860e21bf6592928b795556791fb { public static $files = array ( '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', @@ -435,11 +435,11 @@ class ComposerStaticInit285dea4ecfc38f35aab901dfc1c31a29 public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { - $loader->prefixLengthsPsr4 = ComposerStaticInit285dea4ecfc38f35aab901dfc1c31a29::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInit285dea4ecfc38f35aab901dfc1c31a29::$prefixDirsPsr4; - $loader->prefixesPsr0 = ComposerStaticInit285dea4ecfc38f35aab901dfc1c31a29::$prefixesPsr0; - $loader->fallbackDirsPsr0 = ComposerStaticInit285dea4ecfc38f35aab901dfc1c31a29::$fallbackDirsPsr0; - $loader->classMap = ComposerStaticInit285dea4ecfc38f35aab901dfc1c31a29::$classMap; + $loader->prefixLengthsPsr4 = ComposerStaticInit744d6860e21bf6592928b795556791fb::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit744d6860e21bf6592928b795556791fb::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInit744d6860e21bf6592928b795556791fb::$prefixesPsr0; + $loader->fallbackDirsPsr0 = ComposerStaticInit744d6860e21bf6592928b795556791fb::$fallbackDirsPsr0; + $loader->classMap = ComposerStaticInit744d6860e21bf6592928b795556791fb::$classMap; }, null, ClassLoader::class); } diff --git a/civicrm/xml/version.xml b/civicrm/xml/version.xml index d0517d8f95108c6fd3f0d5885f7d9446621d4040..cdc26c377d065dae5ce7d2859b569f3d389a8bb1 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.13.2</version_no> + <version_no>5.13.3</version_no> </version>