$tp->addMessage('body_html', $html_message, 'text/html');
foreach ($activityIds as $activityId) {
- $tp->addRow()->context('activity_id', $activityId);
+ $tp->addRow()->context('activityId', $activityId);
}
$tp->evaluate();
return new TokenProcessor(\Civi::dispatcher(), [
'controller' => get_class(),
'smarty' => FALSE,
- 'schema' => ['activity_id'],
+ 'schema' => ['activityId'],
]);
}
if (!empty($params['assigneeContactIds'])) {
$assigneeContacts = array_unique(explode(',', $params['assigneeContactIds']));
}
- foreach ($assigneeContacts as $key => $value) {
+ foreach ($assigneeContacts as $value) {
$assigneeParams = [
'activity_id' => $mainActivityId,
'contact_id' => $value,
}
/**
- * Get the name of the field which holds the ID of the given entity.
- *
* @return string
*/
- private function getEntityIDFieldName(): string {
- return 'activity_id';
+ private function getEntityContextSchema(): string {
+ return 'activityId';
}
/**
// Find all the entity IDs
$entityIds
= $e->getTokenProcessor()->getContextValues('actionSearchResult', 'entityID')
- + $e->getTokenProcessor()->getContextValues($this->getEntityIDFieldName());
+ + $e->getTokenProcessor()->getContextValues($this->getEntityContextSchema());
if (!$entityIds) {
return NULL;
/**
* @inheritDoc
- *
- * @throws \CRM_Core_Exception
*/
public function evaluateToken(\Civi\Token\TokenRow $row, $entity, $field, $prefetch = NULL) {
// maps token name to api field
];
// Get ActivityID either from actionSearchResult (for scheduled reminders) if exists
- $activityId = $row->context['actionSearchResult']->entityID ?? $row->context[$this->getEntityIDFieldName()];
+ $activityId = $row->context['actionSearchResult']->entityID ?? $row->context[$this->getEntityContextSchema()];
$activity = (object) $prefetch['activity'][$activityId];
'dedupe_email_default' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME,
'hash_mailing_url' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME,
'auto_recipient_rebuild' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME,
+ 'url_tracking_default' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME,
+ 'open_tracking_default' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME,
];
public function postProcess() {
// Fetch block for a specific action
else {
$action = strtolower($action);
- $fnName = 'civicrm_api3_' . _civicrm_api_get_entity_name_from_camel($entity) . '_' . $action;
+ $fnName = 'civicrm_api3_' . CRM_Core_DAO_AllCoreTables::convertEntityNameToLower($entity) . '_' . $action;
// Support the alternate "1 file per action" structure
$actionFile = "api/v3/$entity/" . ucfirst($action) . '.php';
$actionFileContents = file_get_contents("api/v3/$entity/" . ucfirst($action) . '.php', FILE_USE_INCLUDE_PATH);
/**
* Format a docblock to be a bit more readable
- * Not a proper doc parser... patches welcome :)
+ *
+ * FIXME: APIv4 uses markdown in code docs. Switch to that.
*
* @param string $text
* @return string
// Extract code blocks - save for later to skip html conversion
$code = [];
- preg_match_all('#@code(.*?)@endcode#is', $text, $code);
- $text = preg_replace('#@code.*?@endcode#is', '<pre></pre>', $text);
+ preg_match_all('#(@code|```)(.*?)(@endcode|```)#is', $text, $code);
+ $text = preg_replace('#(@code|```)(.*?)(@endcode|```)#is', '<pre></pre>', $text);
// Convert @annotations to titles
$text = preg_replace_callback(
$text = nl2br($text);
// Add unformatted code blocks back in
- if ($code && !empty($code[1])) {
- foreach ($code[1] as $block) {
+ if ($code && !empty($code[2])) {
+ foreach ($code[2] as $block) {
$text = preg_replace('#<pre></pre>#', "<pre>$block</pre>", $text, 1);
}
}
}
$surveysData[$sid]['isActive'] = $isActive;
- $surveysData[$sid]['is_default'] = self::crmIcon('fa-check', ts('Default'), $surveysData[$sid]['is_default']);
+ // For some reason, 'is_default' is coming as a string.
+ $surveysData[$sid]['is_default'] = boolval($surveysData[$sid]['is_default']);
if ($surveysData[$sid]['result_id']) {
$resultSet = '<a href= "javascript:displayResultSet( ' . $sid . ',' . "'" . $surveysData[$sid]['title'] . "'" . ', ' . $surveysData[$sid]['result_id'] . ' )" title="' . ts('view result set') . '">' . ts('Result Set') . '</a>';
$isActive = ts('Yes');
}
$petitionsData[$pid]['isActive'] = $isActive;
- $petitionsData[$pid]['is_default'] = self::crmIcon('fa-check', ts('Default'), $petitionsData[$pid]['is_default']);
+
+ // For some reason, 'is_default' is coming as a string.
+ $petitionsData[$pid]['is_default'] = boolval($petitionsData[$pid]['is_default']);
$petitionsData[$pid]['action'] = CRM_Core_Action::formLink(self::petitionActionLinks(),
$action,
];
CRM_Case_BAO_Case::processCaseActivity($caseParams);
$followupStatus = ts("A followup activity has been scheduled.") . '<br /><br />';
+
+ //dev/core#1721
+ if (Civi::settings()->get('activity_assignee_notification') &&
+ !in_array($followupActivity->activity_type_id,
+ Civi::settings()->get('do_not_notify_assignees_for'))
+ ) {
+ $followupActivityIDs = [$followupActivity->id];
+ $followupAssigneeContacts = CRM_Activity_BAO_ActivityAssignment::getAssigneeNames($followupActivityIDs, TRUE, FALSE);
+
+ if (!empty($followupAssigneeContacts)) {
+ $mailToFollowupContacts = [];
+ foreach ($followupAssigneeContacts as $facValues) {
+ $mailToFollowupContacts[$facValues['email']] = $facValues;
+ }
+
+ $facParams['case_id'] = $vval['case_id'];
+ $sentFollowup = CRM_Activity_BAO_Activity::sendToAssignee($followupActivity, $mailToFollowupContacts, $facParams);
+ if ($sentFollowup) {
+ $mailStatus .= '<br />' . ts("A copy of the follow-up activity has also been sent to follow-up assignee contacts(s).");
+ }
+ }
+ }
}
}
$title = ts("%1 Saved", [1 => $this->_activityTypeName]);
* List of component names.
* @param array $metadata
* Specification of the setting (per *.settings.php).
+ *
+ * @throws \CRM_Core_Exception.
*/
public static function onToggleComponents($oldValue, $newValue, $metadata) {
if (
$pathToCaseSampleTpl = __DIR__ . '/xml/configuration.sample/';
self::loadCaseSampleData($pathToCaseSampleTpl . 'case_sample.mysql.tpl');
if (!CRM_Case_BAO_Case::createCaseViews()) {
- $msg = ts("Could not create the MySQL views for CiviCase. Your mysql user needs to have the 'CREATE VIEW' permission");
- CRM_Core_Error::fatal($msg);
+ throw new CRM_Core_Exception(ts("Could not create the MySQL views for CiviCase. Your mysql user needs to have the 'CREATE VIEW' permission"));
}
}
}
}
else {
if ($this->_action & CRM_Core_Action::VIEW) {
- CRM_Core_Error::fatal('Contact Id is required for view action.');
+ CRM_Core_Error::statusBounce('Contact Id is required for view action.');
}
}
* @param string $caseType
* @param array $params
*
- * @return bool
- * @throws Exception
+ * @throws CRM_Core_Exception
*/
public function run($caseType, &$params) {
$xml = $this->retrieve($caseType);
if ($xml === FALSE) {
$docLink = CRM_Utils_System::docURL2("user/case-management/set-up");
- CRM_Core_Error::fatal(ts("Configuration file could not be retrieved for case type = '%1' %2.",
+ throw new CRM_Core_Exception(ts("Configuration file could not be retrieved for case type = '%1' %2.",
[1 => $caseType, 2 => $docLink]
));
- return FALSE;
}
$xmlProcessorProcess = new CRM_Case_XMLProcessor_Process();
$xml = $this->retrieve($caseType);
if ($xml === FALSE) {
$docLink = CRM_Utils_System::docURL2("user/case-management/set-up");
- CRM_Core_Error::fatal(ts("Unable to load configuration file for the referenced case type: '%1' %2.",
+ throw new CRM_Core_Exception(ts("Unable to load configuration file for the referenced case type: '%1' %2.",
[1 => $caseType, 2 => $docLink]
));
- return FALSE;
}
switch ($fieldSet) {
$params
)
) {
- CRM_Core_Error::fatal();
- return FALSE;
+ throw new CRM_Core_Exception('Unable to create case relationships');
}
}
}
* @param array $params
*
* @return bool
- * @throws Exception
+ * @throws CRM_Core_Exception
*/
public function createRelationships($relationshipTypeXML, &$params) {
list($relationshipType, $relationshipTypeName) = $this->locateNameOrLabel($relationshipTypeXML);
if ($relationshipType === FALSE) {
$docLink = CRM_Utils_System::docURL2("user/case-management/set-up");
- CRM_Core_Error::fatal(ts('Relationship type %1, found in case configuration file, is not present in the database %2',
+ throw new CRM_Core_Exception(ts('Relationship type %1, found in case configuration file, is not present in the database %2',
[1 => $relationshipTypeName, 2 => $docLink]
));
- return FALSE;
}
$client = $params['clientID'];
}
if (!$this->createRelationship($relationshipParams)) {
- CRM_Core_Error::fatal();
- return FALSE;
+ throw new CRM_Core_Exception('Unable to create case relationship');
}
}
return TRUE;
if (!$activityTypeInfo) {
$docLink = CRM_Utils_System::docURL2("user/case-management/set-up");
- CRM_Core_Error::fatal(ts('Activity type %1, found in case configuration file, is not present in the database %2',
+ throw new CRM_Core_Exception(ts('Activity type %1, found in case configuration file, is not present in the database %2',
[1 => $activityTypeName, 2 => $docLink]
));
- return FALSE;
}
$activityTypeID = $activityTypeInfo['id'];
$activity = CRM_Activity_BAO_Activity::create($activityParams);
if (!$activity) {
- CRM_Core_Error::fatal();
- return FALSE;
+ throw new CRM_Core_Exception('Unable to create Activity');
}
// create case activity record
/**
* @param $caseType
- * @param null $activityTypeName
+ * @param string|null $activityTypeName
*
* @return array|bool|mixed
- * @throws Exception
+ * @throws CRM_Core_Exception
*/
public function getMaxInstance($caseType, $activityTypeName = NULL) {
$xml = $this->retrieve($caseType);
if ($xml === FALSE) {
- CRM_Core_Error::fatal();
- return FALSE;
+ throw new CRM_Core_Exception('Unable to locate xml definition for case type ' . $caseType);
}
$activityInstances = $this->activityTypes($xml->ActivityTypes, TRUE);
*/
//protected $_groupTree;
- /**
- * Which blocks should we show and hide.
- *
- * @var CRM_Core_ShowHideBlocks
- */
- protected $_showHide;
-
/**
* Array group titles.
*
'objectExists',
['CRM_Contact_DAO_Contact', $form->_contactId, 'external_identifier']
);
- CRM_Core_ShowHideBlocks::links($form, 'demographics', '', '');
}
}
foreach ($groupDetails as $key => $group) {
$_groupTitle[$key] = $group['name'];
- CRM_Core_ShowHideBlocks::links($form, $group['name'], '', '');
foreach ($group['fields'] as $field) {
$fieldId = $field['id'];
$this->formatLocationBlock($value, $formatted);
}
else {
+ // @todo - this is still reachable - e.g. import with related contact info like firstname,lastname,spouse-first-name,spouse-last-name,spouse-home-phone
CRM_Core_Error::deprecatedFunctionWarning('this is not expected to be reachable now');
$this->formatContactParameters($value, $formatted);
}
/* validate the data against the CF type */
if ($value) {
- if ($customFields[$customFieldID]['data_type'] == 'Date') {
+ $dataType = $customFields[$customFieldID]['data_type'];
+ $htmlType = $customFields[$customFieldID]['html_type'];
+ $isSerialized = CRM_Core_BAO_CustomField::isSerialized($customFields[$customFieldID]);
+ if ($dataType == 'Date') {
if (array_key_exists($customFieldID, $addressCustomFields) && CRM_Utils_Date::convertToDefaultDate($params[$key][0], $dateType, $key)) {
$value = $params[$key][0][$key];
}
self::addToErrorMsg($customFields[$customFieldID]['label'], $errorMessage);
}
}
- elseif ($customFields[$customFieldID]['data_type'] == 'Boolean') {
+ elseif ($dataType == 'Boolean') {
if (CRM_Utils_String::strtoboolstr($value) === FALSE) {
self::addToErrorMsg($customFields[$customFieldID]['label'] . '::' . $customFields[$customFieldID]['groupTitle'], $errorMessage);
}
}
// need not check for label filed import
- $htmlType = [
+ $selectHtmlTypes = [
'CheckBox',
- 'Multi-Select',
'Select',
'Radio',
- 'Multi-Select State/Province',
- 'Multi-Select Country',
];
- if (!in_array($customFields[$customFieldID]['html_type'], $htmlType) || $customFields[$customFieldID]['data_type'] == 'Boolean' || $customFields[$customFieldID]['data_type'] == 'ContactReference') {
- $valid = CRM_Core_BAO_CustomValue::typecheck($customFields[$customFieldID]['data_type'], $value);
+ if ((!$isSerialized && !in_array($htmlType, $selectHtmlTypes)) || $dataType == 'Boolean' || $dataType == 'ContactReference') {
+ $valid = CRM_Core_BAO_CustomValue::typecheck($dataType, $value);
if (!$valid) {
self::addToErrorMsg($customFields[$customFieldID]['label'], $errorMessage);
}
}
// check for values for custom fields for checkboxes and multiselect
- if ($customFields[$customFieldID]['html_type'] == 'CheckBox' || $customFields[$customFieldID]['html_type'] == 'Multi-Select') {
+ if ($isSerialized) {
$value = trim($value);
$value = str_replace('|', ',', $value);
$mulValues = explode(',', $value);
}
}
}
- elseif ($customFields[$customFieldID]['html_type'] == 'Select' || ($customFields[$customFieldID]['html_type'] == 'Radio' && $customFields[$customFieldID]['data_type'] != 'Boolean')) {
+ elseif ($htmlType == 'Select' || ($htmlType == 'Radio' && $dataType != 'Boolean')) {
$customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE);
$flag = FALSE;
foreach ($customOption as $v2) {
self::addToErrorMsg($customFields[$customFieldID]['label'], $errorMessage);
}
}
- elseif ($customFields[$customFieldID]['html_type'] == 'Multi-Select State/Province') {
+ elseif ($isSerialized && $dataType === 'StateProvince') {
$mulValues = explode(',', $value);
foreach ($mulValues as $stateValue) {
if ($stateValue) {
}
}
}
- elseif ($customFields[$customFieldID]['html_type'] == 'Multi-Select Country') {
+ elseif ($isSerialized && $dataType == 'Country') {
$mulValues = explode(',', $value);
foreach ($mulValues as $countryValue) {
if ($countryValue) {
if (empty($resultDAO->payment_processor_id) && CRM_Core_Permission::check('edit contributions')) {
$links = [
CRM_Core_Action::UPDATE => [
- 'name' => "<i class='crm-i fa-pencil'></i>",
+ 'name' => ts('Edit Payment'),
+ 'icon' => 'fa-pencil',
'url' => 'civicrm/payment/edit',
'class' => 'medium-popup',
'qs' => "reset=1&id=%%id%%&contribution_id=%%contribution_id%%",
* @return bool
* @throws Exception
*/
- public function loadRelatedObjects(&$input, &$ids, $loadAll = FALSE) {
+ public function loadRelatedObjects($input, &$ids, $loadAll = FALSE) {
// @todo deprecate this function - the steps should be
// 1) add additional functions like 'getRelatedMemberships'
// 2) switch all calls that refer to ->_relatedObjects to
*
* Generated from xml/schema/CRM/Contribute/ContributionRecur.xml
* DO NOT EDIT. Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:d0c493bb1a98e2b75ebaceb330a40fb4)
+ * (GenCodeChecksum:1c6a0e2a296ffbb530eee381975b9d17)
*/
/**
public $contact_id;
/**
- * Amount to be contributed or charged each recurrence.
+ * Amount to be collected (including any sales tax) by payment processor each recurrence.
*
* @var float
*/
'name' => 'amount',
'type' => CRM_Utils_Type::T_MONEY,
'title' => ts('Amount'),
- 'description' => ts('Amount to be contributed or charged each recurrence.'),
+ 'description' => ts('Amount to be collected (including any sales tax) by payment processor each recurrence.'),
'required' => TRUE,
'precision' => [
20,
$postProfileType = CRM_Core_BAO_UFField::getProfileType($this->_values['custom_post_id']);
}
- if (((isset($postProfileType) && $postProfileType == 'Membership') ||
- (isset($preProfileType) && $preProfileType == 'Membership')
+ if (((isset($postProfileType) && $postProfileType === 'Membership') ||
+ (isset($preProfileType) && $preProfileType === 'Membership')
) &&
!$this->_membershipBlock['is_active']
) {
- CRM_Core_Error::fatal(ts('This page includes a Profile with Membership fields - but the Membership Block is NOT enabled. Please notify the site administrator.'));
+ CRM_Core_Error::statusBounce(ts('This page includes a Profile with Membership fields - but the Membership Block is NOT enabled. Please notify the site administrator.'));
}
$pledgeBlock = CRM_Pledge_BAO_PledgeBlock::getPledgeBlock($this->_id);
!$this->_membershipBlock['is_active'] &&
!$this->_priceSetId
) {
- CRM_Core_Error::fatal(ts('The requested online contribution page is missing a required Contribution Amount section or Membership section or Price Set. Please check with the site administrator for assistance.'));
+ CRM_Core_Error::statusBounce(ts('The requested online contribution page is missing a required Contribution Amount section or Membership section or Price Set. Please check with the site administrator for assistance.'));
}
if ($this->_values['amount_block_is_active']) {
//Hence, assign the existing location type email by iterating through the params.
if ($this->_emailExists && empty($this->_params["email-{$this->_bltID}"])) {
foreach ($this->_params as $key => $val) {
- if (substr($key, 0, 6) == 'email-') {
+ if (substr($key, 0, 6) === 'email-') {
$this->assign('email', $this->_params[$key]);
break;
}
CRM_Utils_ReCAPTCHA::enableCaptchaOnForm($this);
}
+ /**
+ * Assign payment field information to the template.
+ *
+ * @throws \CRM_Core_Exception
+ * @throws \CiviCRM_API3_Exception
+ */
public function assignPaymentFields() {
//fix for CRM-3767
$isMonetary = FALSE;
// The concept of contributeMode is deprecated.
// The payment processor object can provide info about the fields it shows.
if ($isMonetary && is_a($this->_paymentProcessor['object'], 'CRM_Core_Payment')) {
- /** @var $paymentProcessorObject \CRM_Core_Payment */
+ /** @var \CRM_Core_Payment $paymentProcessorObject */
$paymentProcessorObject = $this->_paymentProcessor['object'];
$paymentFields = $paymentProcessorObject->getPaymentFormFields();
*
* @param int $id
* @param CRM_Core_Form $form
+ *
+ * @throws \CRM_Core_Exception
*/
public function buildComponentForm($id, $form) {
if (empty($id)) {
$contactID = $this->getContactID();
foreach (['soft_credit', 'on_behalf'] as $module) {
- if ($module == 'soft_credit') {
+ if ($module === 'soft_credit') {
if (empty($form->_values['honoree_profile_id'])) {
continue;
}
if (!CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', $form->_values['honoree_profile_id'], 'is_active')) {
- CRM_Core_Error::fatal(ts('This contribution page has been configured for contribution on behalf of honoree and the selected honoree profile is either disabled or not found.'));
+ CRM_Core_Error::statusBounce(ts('This contribution page has been configured for contribution on behalf of honoree and the selected honoree profile is either disabled or not found.'));
}
$profileContactType = CRM_Core_BAO_UFGroup::getContactType($form->_values['honoree_profile_id']);
];
$validProfile = CRM_Core_BAO_UFGroup::checkValidProfile($form->_values['honoree_profile_id'], $requiredProfileFields[$profileContactType]);
if (!$validProfile) {
- CRM_Core_Error::fatal(ts('This contribution page has been configured for contribution on behalf of honoree and the required fields of the selected honoree profile are disabled or doesn\'t exist.'));
+ CRM_Core_Error::statusBounce(ts('This contribution page has been configured for contribution on behalf of honoree and the required fields of the selected honoree profile are disabled or doesn\'t exist.'));
}
foreach (['honor_block_title', 'honor_block_text'] as $name) {
}
if (!CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', $form->_values['onbehalf_profile_id'], 'is_active')) {
- CRM_Core_Error::fatal(ts('This contribution page has been configured for contribution on behalf of an organization and the selected onbehalf profile is either disabled or not found.'));
+ CRM_Core_Error::statusBounce(ts('This contribution page has been configured for contribution on behalf of an organization and the selected onbehalf profile is either disabled or not found.'));
}
$member = CRM_Member_BAO_Membership::getMembershipBlock($form->_id);
in_array('Contribution', $onBehalfProfile)
)
) {
- CRM_Core_Error::fatal($msg);
+ CRM_Core_Error::statusBounce($msg);
}
}
}
*/
public function getTemplateFileName() {
$fileName = $this->checkTemplateFileExists();
- return $fileName ? $fileName : parent::getTemplateFileName();
+ return $fileName ?: parent::getTemplateFileName();
}
/**
/**
* Authenticate pledge user during online payment.
+ *
+ * @throws \CRM_Core_Exception
*/
public function authenticatePledgeUser() {
//get the userChecksum and contact id
}
if (!$validUser) {
- CRM_Core_Error::fatal(ts("Oops. It looks like you have an incorrect or incomplete link (URL). Please make sure you've copied the entire link, and try again. Contact the site administrator if this error persists."));
+ CRM_Core_Error::statusBounce(ts("Oops. It looks like you have an incorrect or incomplete link (URL). Please make sure you've copied the entire link, and try again. Contact the site administrator if this error persists."));
}
//check for valid pledge status.
if (!in_array($pledgeValues['status_id'], $validStatus)) {
- CRM_Core_Error::fatal(ts('Oops. You cannot make a payment for this pledge - pledge status is %1.', [1 => CRM_Utils_Array::value($pledgeValues['status_id'], $allStatus)]));
+ CRM_Core_Error::statusBounce(ts('Oops. You cannot make a payment for this pledge - pledge status is %1.', [1 => CRM_Utils_Array::value($pledgeValues['status_id'], $allStatus)]));
}
}
* In case user cancel recurring contribution,
* When we get the control back from payment gate way
* lets delete the recurring and related contribution.
+ *
+ * @throws \CRM_Core_Exception
*/
public function cancelRecurring() {
$isCancel = CRM_Utils_Request::retrieve('cancel', 'Boolean');
*
* @return bool
* Is this a separate membership payment
+ *
+ * @throws \CiviCRM_API3_Exception
+ * @throws \CRM_Core_Exception
*/
protected function buildMembershipBlock(
$cid,
$statusID = $params['contribution_status_id'] ?? NULL;
$baseIPN = new CRM_Core_Payment_BaseIPN();
- $transaction = new CRM_Core_Transaction();
-
// get the missing pieces for each contribution
$contribIDs = implode(',', $form->_contributionIds);
$details = self::getDetails($contribIDs);
);
if ($statusID == array_search('Cancelled', $contributionStatuses)) {
+ $transaction = new CRM_Core_Transaction();
$baseIPN->cancelled($objects, $transaction);
$transaction->commit();
continue;
}
elseif ($statusID == array_search('Failed', $contributionStatuses)) {
+ $transaction = new CRM_Core_Transaction();
$baseIPN->failed($objects, $transaction);
$transaction->commit();
continue;
$contributionStatuses
)
) {
- $transaction->commit();
continue;
}
$input['trxn_date'] = $params["trxn_date_{$row['contribution_id']}"] . ' ' . date('H:i:s');
$input['is_email_receipt'] = !empty($params['is_email_receipt']);
- // @todo calling baseIPN like this is a pattern in it's last gasps. Call contribute.completetransaction api.
- $baseIPN->completeTransaction($input, $ids, $objects, $transaction, FALSE);
+ // @todo calling CRM_Contribute_BAO_Contribution::completeOrder like this is a pattern in it's last gasps. Call contribute.completetransaction api.
+ CRM_Contribute_BAO_Contribution::completeOrder($input, $ids, $objects);
// reset template values before processing next transactions
$template->clearTemplateVars();
$urlPath,
$classes,
!empty($link['title']) ? "title='{$link['title']}' " : '',
- $link['name']
+ empty($link['icon']) ? $link['name'] : CRM_Core_Page::crmIcon($link['icon'], $link['name'], TRUE, ['title' => ''])
);
}
}
* @param int $id
* ID of the Reminder to be deleted.
*
+ * @throws CRM_Core_Exception
*/
public static function del($id) {
if ($id) {
return;
}
}
- CRM_Core_Error::fatal(ts('Invalid value passed to delete function.'));
+ throw new CRM_Core_Exception(ts('Invalid value passed to delete function.'));
}
/**
$relTypeId = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_RelationshipType', 'Household Member of', 'id', 'name_a_b');
if (!$relTypeId) {
- CRM_Core_Error::fatal(ts("You seem to have deleted the relationship type 'Household Member of'"));
+ throw new CRM_Core_Exception(ts("You seem to have deleted the relationship type 'Household Member of'"));
}
$relParam = [
*
* @return array
* Array of $block objects.
+ * @throws CRM_Core_Exception
*/
public static function &getValues($blockName, $params) {
if (empty($params)) {
if (!isset($params['entity_table'])) {
$block->contact_id = $params['contact_id'];
if (!$block->contact_id) {
- CRM_Core_Error::fatal();
+ throw new CRM_Core_Exception('Invalid Contact ID parameter passed');
}
$blocks = self::retrieveBlock($block, $blockName);
}
* @param int $componentID
* The optional component ID (so componenets can share the same name space).
* @deprecated
+ * @throws CRM_Core_Exception
*/
public static function setItem(&$data, $group, $path, $componentID = NULL) {
CRM_Core_Error::deprecatedFunctionWarning(
// CRM-11234
$lock = Civi::lockManager()->acquire("cache.{$group}_{$path}._{$componentID}");
if (!$lock->isAcquired()) {
- CRM_Core_Error::fatal();
+ throw new CRM_Core_Exception('Cannot acquire database lock');
}
$table = self::getTableName();
return $options;
}
+ /**
+ * @inheritDoc
+ */
+ public static function buildOptions($fieldName, $context = NULL, $props = []) {
+ $options = parent::buildOptions($fieldName, $context, $props);
+ // This provides legacy support for APIv3, allowing no-longer-existent html types
+ if ($fieldName == 'html_type' && isset($props['version']) && $props['version'] == 3) {
+ $options['Multi-Select'] = 'Multi-Select';
+ $options['Multi-Select Country'] = 'Multi-Select Country';
+ $options['Multi-Select State/Province'] = 'Multi-Select State/Province';
+ }
+ return $options;
+ }
+
/**
* Store and return an array of all active custom fields.
*
$cfTable.date_format,
$cfTable.time_format,
$cgTable.is_multiple,
+ $cfTable.serialize,
$cgTable.table_name,
og.name as option_group_name
FROM $cfTable
$fields[$dao->id]['is_required'] = $dao->is_required;
$fields[$dao->id]['table_name'] = $dao->table_name;
$fields[$dao->id]['column_name'] = $dao->column_name;
+ $fields[$dao->id]['serialize'] = $dao->serialize;
$fields[$dao->id]['where'] = $dao->table_name . '.' . $dao->column_name;
// Probably we should use a different fn to get the extends tables but this is a refactor so not changing that now.
$fields[$dao->id]['extends_table'] = array_key_exists($dao->extends, CRM_Core_BAO_CustomQuery::$extendsMap) ? CRM_Core_BAO_CustomQuery::$extendsMap[$dao->extends] : '';
*
* @return CRM_Core_BAO_CustomField
* The field object.
+ * @throws CRM_Core_Exception
*/
public static function getFieldObject($fieldID) {
$field = new CRM_Core_BAO_CustomField();
if (empty($fieldValues)) {
$field->id = $fieldID;
if (!$field->find(TRUE)) {
- CRM_Core_Error::fatal();
+ throw new CRM_Core_Exception('Cannot find Custom Field');
}
$fieldValues = [];
// FIXME: Why are select state/country separate widget types?
$isSelect = (in_array($widget, [
'Select',
- 'Multi-Select',
'Select State/Province',
- 'Multi-Select State/Province',
'Select Country',
- 'Multi-Select Country',
'CheckBox',
'Radio',
]));
$selectAttributes = ['class' => 'crm-select2'];
// Search field is always multi-select
- if ($search || strpos($field->html_type, 'Multi') !== FALSE) {
+ if ($search || (self::isSerialized($field) && $widget === 'Select')) {
$selectAttributes['class'] .= ' huge';
$selectAttributes['multiple'] = 'multiple';
$selectAttributes['placeholder'] = $placeholder;
case 'Select Country':
case 'Select State/Province':
case 'CheckBox':
- case 'Multi-Select':
- case 'Multi-Select State/Province':
- case 'Multi-Select Country':
if ($field['data_type'] == 'ContactReference' && $value) {
if (is_numeric($value)) {
$display = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $value, 'display_name');
if ($customField->data_type == 'Money' && isset($value)) {
$value = number_format($value, 2);
}
- if (self::isSerialized($customField)) {
+ if (self::isSerialized($customField) && $value) {
$customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldId, FALSE);
$defaults[$elementName] = [];
$checkedValue = CRM_Utils_Array::explodePadded($value);
* FK to civicrm_custom_field.
* @param int $newGroupID
* FK to civicrm_custom_group.
+ *
+ * @throws CRM_Core_Exception
*/
public static function moveField($fieldID, $newGroupID) {
$validation = self::_moveFieldValidate($fieldID, $newGroupID);
if (TRUE !== $validation) {
- CRM_Core_Error::fatal(implode(' ', $validation));
+ throw new CRM_Core_Exception(implode(' ', $validation));
}
$field = new CRM_Core_DAO_CustomField();
$field->id = $fieldID;
$params['date_format'] = Civi::settings()->get('dateInputFormat');
}
- if ($htmlType === 'CheckBox' || $htmlType === 'Multi-Select') {
+ // Checkboxes are always serialized in current schema
+ if ($htmlType == 'CheckBox') {
+ $params['serialize'] = CRM_Core_DAO::SERIALIZE_SEPARATOR_BOOKEND;
+ }
+
+ if (!empty($params['serialize'])) {
if (isset($params['default_checkbox_option'])) {
$defaultArray = [];
foreach (array_keys($params['default_checkbox_option']) as $k => $v) {
// retrieve it from one of the other custom fields which use this option group
if (empty($params['default_value'])) {
//don't insert only value separator as default value, CRM-4579
- $defaultValue = self::getOptionGroupDefault($params['option_group_id'], $htmlType);
+ $defaultValue = self::getOptionGroupDefault($params['option_group_id'], !empty($params['serialize']));
if (!CRM_Utils_System::isNull(explode(CRM_Core_DAO::VALUE_SEPARATOR, $defaultValue))) {
$params['default_value'] = $defaultValue;
*
* @return array
* fatal is fieldID does not exists, else array of tableName, columnName
- * @throws \Exception
+ * @throws \CRM_Core_Exception
*/
public static function getTableColumnGroup($fieldID, $force = FALSE) {
$cacheKey = "CRM_Core_DAO_CustomField_CustomGroup_TableColumn_{$fieldID}";
$dao = CRM_Core_DAO::executeQuery($query, $params);
if (!$dao->fetch()) {
- CRM_Core_Error::fatal();
+ throw new CRM_Core_Exception("Cannot find table and column information for Custom Field " . $fieldID);
}
$fieldValues = [$dao->table_name, $dao->column_name, $dao->id];
$cache->set($cacheKey, $fieldValues);
* Get option group default.
*
* @param int $optionGroupId
- * @param string $htmlType
+ * @param bool $serialize
*
* @return null|string
*/
- public static function getOptionGroupDefault($optionGroupId, $htmlType) {
+ public static function getOptionGroupDefault($optionGroupId, $serialize) {
$query = "
-SELECT default_value, html_type
+SELECT default_value, serialize
FROM civicrm_custom_field
WHERE option_group_id = {$optionGroupId}
-AND default_value IS NOT NULL
-ORDER BY html_type";
+AND default_value IS NOT NULL";
$dao = CRM_Core_DAO::executeQuery($query);
- $defaultValue = NULL;
- $defaultHTMLType = NULL;
while ($dao->fetch()) {
- if ($dao->html_type == $htmlType) {
+ if ($dao->serialize == $serialize) {
return $dao->default_value;
}
- if ($defaultValue == NULL) {
- $defaultValue = $dao->default_value;
- $defaultHTMLType = $dao->html_type;
- }
+ $defaultValue = $dao->default_value;
}
- // some conversions are needed if either the old or new has a html type which has potential
- // multiple default values.
- if (($htmlType == 'CheckBox' || $htmlType == 'Multi-Select') &&
- ($defaultHTMLType != 'CheckBox' && $defaultHTMLType != 'Multi-Select')
- ) {
- $defaultValue = CRM_Core_DAO::VALUE_SEPARATOR . $defaultValue . CRM_Core_DAO::VALUE_SEPARATOR;
+ // Convert serialization
+ if (isset($defaultValue) && $serialize) {
+ return CRM_Utils_Array::implodePadded([$defaultValue]);
}
- elseif (($defaultHTMLType == 'CheckBox' || $defaultHTMLType == 'Multi-Select') &&
- ($htmlType != 'CheckBox' && $htmlType != 'Multi-Select')
- ) {
- $defaultValue = substr($defaultValue, 1, -1);
- $values = explode(CRM_Core_DAO::VALUE_SEPARATOR,
- substr($defaultValue, 1, -1)
- );
- $defaultValue = $values[0];
+ elseif (isset($defaultValue)) {
+ return CRM_Utils_Array::explodePadded($defaultValue)[0];
}
-
- return $defaultValue;
+ return NULL;
}
/**
/**
* Does this field store a serialized string?
*
- * @param array|object $field
+ * @param CRM_Core_DAO_CustomField|array $field
*
* @return bool
*/
public static function isSerialized($field) {
// Fields retrieved via api are an array, or from the dao are an object. We'll accept either.
$html_type = is_object($field) ? $field->html_type : $field['html_type'];
- // FIXME: Currently the only way to know if data is serialized is by looking at the html_type. It would be cleaner to decouple this.
- return ($html_type === 'CheckBox' || strpos($html_type, 'Multi') !== FALSE);
+ // APIv3 has a "legacy" mode where it returns old-style html_type of "Multi-Select"
+ // If anyone is using this function in conjunction with legacy api output, we'll accomodate:
+ if ($html_type === 'CheckBox' || strpos($html_type, 'Multi') !== FALSE) {
+ return TRUE;
+ }
+ // Otherwise this is the new standard as of 5.26
+ return is_object($field) ? !empty($field->serialize) : !empty($field['serialize']);
}
/**
'time_format',
'option_group_id',
'in_selector',
+ 'serialize',
],
'custom_group' => [
'id',
$form->assign_by_ref("{$prefix}groupTree", $groupTree);
foreach ($groupTree as $id => $group) {
- CRM_Core_ShowHideBlocks::links($form, $group['title'], '', '');
foreach ($group['fields'] as $field) {
$required = $field['is_required'] ?? NULL;
//fix for CRM-1620
}
if (in_array($field->html_type, ['CheckBox', 'Multi-Select'])) {
- $isDefault = (isset($defVal) && in_array($dao->value, $defVal));
+ $options[$dao->id]['is_default'] = (isset($defVal) && in_array($dao->value, $defVal));
}
else {
- $isDefault = ($field->default_value == $dao->value);
+ $options[$dao->id]['is_default'] = ($field->default_value == $dao->value);
}
- $options[$dao->id]['is_default'] = CRM_Core_Page::crmIcon('fa-check', ts('Default'), $isDefault);
$options[$dao->id]['description'] = $dao->description;
$options[$dao->id]['class'] = $dao->id . ',' . $class;
$options[$dao->id]['is_active'] = empty($dao->is_active) ? ts('No') : ts('Yes');
break;
default:
- CRM_Core_Error::fatal();
+ throw new CRM_Core_Exception('Invalid HTML Type');
}
$dao = CRM_Core_DAO::executeQuery($query, $queryParams);
}
case 'File':
if (!$field['file_id']) {
- CRM_Core_Error::fatal();
+ throw new CRM_Core_Exception('Missing parameter file_id');
}
// need to add/update civicrm_entity_file
return 'datetime';
default:
- CRM_Core_Error::fatal();
+ throw new CRM_Core_Exception('Invalid Field Type');
}
}
* Array of custom values for the entity with key=>value
* pairs specified as civicrm_custom_field.id => custom value.
* Empty array if no custom values found.
+ * @throws CRM_Core_Exception
*/
public static function &getEntityValues($entityID, $entityType = NULL, $fieldIDs = NULL, $formatMultiRecordField = FALSE, $DTparams = NULL) {
if (!$entityID) {
// adding this here since an empty contact id could have serious repurcussions
// like looping forever
- CRM_Core_Error::fatal('Please file an issue with the backtrace');
+ throw new CRM_Core_Exception('Please file an issue with the backtrace');
return NULL;
}
* @return int
* $dao->id discount id of the set which matches
* the date criteria
+ * @throws CRM_Core_Exception
*/
public static function findSet($entityID, $entityTable) {
if (empty($entityID) || empty($entityTable)) {
// adding this here, to trap errors if values are not sent
- CRM_Core_Error::fatal();
+ throw new CRM_Core_Exception('Invalid parameters passed to findSet function');
return NULL;
}
/**
* Delete a file attachment from an entity table / entity ID
- *
+ * @throws CRM_Core_Exception
*/
public static function deleteAttachment() {
$params = [];
$signer = new CRM_Utils_Signer(CRM_Core_Key::privateKey(), self::$_signableFields);
if (!$signer->validate($signature, $params)) {
- CRM_Core_Error::fatal('Request signature is invalid');
+ throw new CRM_Core_Exception('Request signature is invalid');
}
self::deleteEntityFile($params['entityTable'], $params['entityID'], NULL, $params['fileID']);
* ID of the job to be deleted.
*
* @return bool|null
+ * @throws CRM_Core_Exception
*/
public static function del($jobID) {
if (!$jobID) {
- CRM_Core_Error::fatal(ts('Invalid value passed to delete function.'));
+ throw new CRM_Core_Exception(ts('Invalid value passed to delete function.'));
}
$dao = new CRM_Core_DAO_Job();
// make sure serialized array will fit in the 'value' column
$attribute = CRM_Core_DAO::getAttribute('CRM_Core_BAO_LabelFormat', 'value');
if (strlen($this->value) > $attribute['maxlength']) {
- CRM_Core_Error::fatal(ts('Label Format does not fit in database.'));
+ throw new CRM_Core_Exception(ts('Label Format does not fit in database.'));
}
$this->save();
* Contact id.
* @param int $locationTypeId
* Id of the location to delete.
+ * @throws CRM_Core_Exception
*/
public static function deleteLocationBlocks($contactId, $locationTypeId) {
// ensure that contactId has a value
if (empty($contactId) ||
!CRM_Utils_Rule::positiveInteger($contactId)
) {
- CRM_Core_Error::fatal();
+ throw new CRM_Core_Exception('Incorrect contact id parameter passed to deleteLocationBlocks');
}
if (empty($locationTypeId) ||
*
* @return array
* formatted associated array of elements
+ * @throws CRM_Core_Exception
*/
public static function formattedFields(&$params, $row = FALSE) {
$fields = [];
foreach ($value as $k => $v) {
if (in_array($v[0], $types)) {
if ($contactType && $contactType != $v[0]) {
- CRM_Core_Error::fatal(ts("Cannot have two clauses with different types: %1, %2",
+ throw new CRM_Core_Exception(ts("Cannot have two clauses with different types: %1, %2",
[1 => $contactType, 2 => $v[0]]
));
}
*
* @return int
* Group ID (null if Group ID doesn't exist)
+ * @throws CRM_Core_Exception
*/
private static function _getGid() {
if (!self::$_gid) {
self::$_gid = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', 'paper_size', 'id', 'name');
if (!self::$_gid) {
- CRM_Core_Error::fatal(ts('Paper Size Option Group not found in database.'));
+ throw new CRM_Core_Exception(ts('Paper Size Option Group not found in database.'));
}
}
return self::$_gid;
* @param array $values associative array of name/value pairs
* @param int $id
* Id of the database record (null = new record).
+ * @throws CRM_Core_Exception
*/
public function savePaperSize(&$values, $id) {
// get the Option Group ID for Paper Sizes (create one if it doesn't exist)
// make sure serialized array will fit in the 'value' column
$attribute = CRM_Core_DAO::getAttribute('CRM_Core_BAO_PaperSize', 'value');
if (strlen($this->value) > $attribute['maxlength']) {
- CRM_Core_Error::fatal(ts('Paper Size does not fit in database.'));
+ throw new CRM_Core_Exception(ts('Paper Size does not fit in database.'));
}
$this->save();
*
* @param int $id
* ID of the Paper Size to be deleted.
- *
+ * @throws CRM_Core_Exception
*/
public static function del($id) {
if ($id) {
}
}
}
- CRM_Core_Error::fatal(ts('Invalid value passed to delete function.'));
+ throw new CRM_Core_Exception(ts('Invalid value passed to delete function.'));
}
}
*
* @return int
* Group ID (null if Group ID doesn't exist)
+ * @throws CRM_Core_Exception
*/
private static function _getGid() {
if (!self::$_gid) {
self::$_gid = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', 'pdf_format', 'id', 'name');
if (!self::$_gid) {
- CRM_Core_Error::fatal(ts('PDF Format Option Group not found in database.'));
+ throw new CRM_Core_Exception(ts('PDF Format Option Group not found in database.'));
}
}
return self::$_gid;
* @param array $values associative array of name/value pairs
* @param int $id
* Id of the database record (null = new record).
+ * @throws CRM_Core_Exception
*/
public function savePdfFormat(&$values, $id = NULL) {
// get the Option Group ID for PDF Page Formats (create one if it doesn't exist)
// make sure serialized array will fit in the 'value' column
$attribute = CRM_Core_DAO::getAttribute('CRM_Core_BAO_PdfFormat', 'value');
if (strlen($this->value) > $attribute['maxlength']) {
- CRM_Core_Error::fatal(ts('PDF Page Format does not fit in database.'));
+ throw new CRM_Core_Exception(ts('PDF Page Format does not fit in database.'));
}
$this->save();
*
* @param int $id
* ID of the PDF Page Format to be deleted.
- *
+ * @throws CRM_Core_Exception
*/
public static function del($id) {
if ($id) {
}
}
}
- CRM_Core_Error::fatal(ts('Invalid value passed to delete function.'));
+ throw new CRM_Core_Exception(ts('Invalid value passed to delete function.'));
}
}
* Id of the database record.
* @param bool $is_active
* Value we want to set the is_active field.
+ * @throws CRM_Core_Exception
*/
public static function setIsActive($id, $is_active) {
- CRM_Core_Error::fatal();
+ throw new CRM_Core_Exception('Cannot call setIsActive function');
}
/**
* Delete preference dates.
*
* @param int $id
+ * @throws CRM_Core_Exception
*/
public static function del($id) {
- CRM_Core_Error::fatal();
+ throw new CRM_Core_Exception('Cannot call del function');
}
/**
* Generate new DAOs and along with entries in civicrm_recurring_entity table.
*
* @return array
+ * @throws CRM_Core_Exception
*/
public function generateEntities() {
self::setStatus(self::RUNNING);
}
}
if (empty($findCriteria)) {
- CRM_Core_Error::fatal("Find criteria missing to generate form. Make sure entity_id and table is set.");
+ throw new CRM_Core_Exception("Find criteria missing to generate form. Make sure entity_id and table is set.");
}
$count = 0;
*
*
* @return object
+ * @throws new CRM_Core_Exception
*/
public static function copyCreateEntity($entityTable, $fromCriteria, $newParams, $createRecurringEntity = TRUE) {
$daoName = self::$_tableDAOMapper[$entityTable];
if (!$daoName) {
- CRM_Core_Error::fatal("DAO Mapper missing for $entityTable.");
+ throw new CRM_Core_Exception("DAO Mapper missing for $entityTable.");
}
$newObject = CRM_Core_DAO::copyGeneric($daoName, $fromCriteria, $newParams);
$updateDAO = CRM_Core_DAO::cascadeUpdate($daoName, $obj->id, $entityID, $skipData);
}
else {
- CRM_Core_Error::fatal("DAO Mapper missing for $entityTable.");
+ throw new CRM_Core_Exception("DAO Mapper missing for $entityTable.");
}
}
// done with processing. lets unset static var.
foreach (self::$_linkedEntitiesInfo as $linkedTable => $linfo) {
$daoName = self::$_tableDAOMapper[$linkedTable];
if (!$daoName) {
- CRM_Core_Error::fatal("DAO Mapper missing for $linkedTable.");
+ throw new CRM_Core_Exception("DAO Mapper missing for $linkedTable.");
}
$linkedDao = new $daoName();
* @param string $columnName
* @param $length
*
- * @throws Exception
+ * @throws CRM_Core_Exception
*/
public static function alterFieldLength($customFieldID, $tableName, $columnName, $length) {
// first update the custom field tables
CRM_Core_DAO::executeQuery($sql);
}
else {
- CRM_Core_Error::fatal(ts('Could Not Find Custom Field Details for %1, %2, %3',
+ throw new CRM_Core_Exception(ts('Could Not Find Custom Field Details for %1, %2, %3',
[
1 => $tableName,
2 => $columnName,
* @param array $params
*
* @return array
+ * @throws CRM_Core_Exception
*/
public static function create($params) {
$statusPreference = new CRM_Core_BAO_StatusPreference();
$params['ignore_severity'] = CRM_Utils_Check::severityMap($params['ignore_severity']);
}
if ($params['ignore_severity'] > 7) {
- CRM_Core_Error::fatal(ts('You can not pass a severity level higher than 7.'));
+ throw new CRM_Core_Exception(ts('You can not pass a severity level higher than 7.'));
}
// If severity is now blank, you have an invalid severity string.
if (is_null($params['ignore_severity'])) {
- CRM_Core_Error::fatal(ts('Invalid string passed as severity level.'));
+ throw new CRM_Core_Exception(ts('Invalid string passed as severity level.'));
}
// Check if this StatusPreference already exists.
// if field is not present in customFields, that means the user
// DOES NOT HAVE permission to access that field
if (array_key_exists($field->field_name, $customFields)) {
+ $formattedField['serialize'] = !empty($customFields[$field->field_name]['serialize']);
$formattedField['is_search_range'] = $customFields[$field->field_name]['is_search_range'];
// fix for CRM-1994
$formattedField['options_per_line'] = $customFields[$field->field_name]['options_per_line'];
if (!$domainEmailAddress || $domainEmailAddress == 'info@EXAMPLE.ORG') {
$fixUrl = CRM_Utils_System::url('civicrm/admin/domain', 'action=update&reset=1');
- CRM_Core_Error::fatal(ts('The site administrator needs to enter a valid \'FROM Email Address\' in <a href="%1">Administer CiviCRM » Communications » FROM Email Addresses</a>. The email address used may need to be a valid mail account with your email service provider.', [1 => $fixUrl]));
+ CRM_Core_Error::statusBounce(ts('The site administrator needs to enter a valid \'FROM Email Address\' in <a href="%1">Administer CiviCRM » Communications » FROM Email Addresses</a>. The email address used may need to be a valid mail account with your email service provider.', [1 => $fixUrl]));
}
foreach ($emailList as $emailTo) {
*
* @param $ctype
* @param bool $isLogin
+ *
+ * @throws CRM_Core_Exception
*/
public static function synchronize(&$user, $update, $uf, $ctype, $isLogin = FALSE) {
$userSystem = CRM_Core_Config::singleton()->userSystem;
$session = CRM_Core_Session::singleton();
if (!is_object($session)) {
- CRM_Core_Error::fatal('wow, session is not an object?');
+ throw new CRM_Core_Exception('wow, session is not an object?');
return;
}
* @param null $reset
*
* @return null|CRM_Core_BAO_WordReplacement
+ * @throws CRM_Core_Exception
*/
public static function getWordReplacement($reset = NULL) {
static $wordReplacement = NULL;
$wordReplacement = new CRM_Core_BAO_WordReplacement();
$wordReplacement->id = CRM_Core_Config::wordReplacementID();
if (!$wordReplacement->find(TRUE)) {
- CRM_Core_Error::fatal();
+ throw new CRM_Core_Exception('Unable to find word replacement');
}
}
return $wordReplacement;
* @param bool $force
*
* @return CRM_Core_Component_Info[]
- * @throws Exception
+ * @throws CRM_Core_Exception
*/
public static function &getComponents($force = FALSE) {
if (!isset(Civi::$statics[__CLASS__]['all']) || $force) {
require_once $infoClassFile;
$infoObject = new $infoClass($cr->name, $cr->namespace, $cr->id);
if ($infoObject->info['name'] !== $cr->name) {
- CRM_Core_Error::fatal("There is a discrepancy between name in component registry and in info file ({$cr->name}).");
+ throw new CRM_Core_Exception("There is a discrepancy between name in component registry and in info file ({$cr->name}).");
}
Civi::$statics[__CLASS__]['all'][$cr->name] = $infoObject;
unset($infoObject);
if (!$blockCopyofCustomValues) {
$newObject->copyCustomFields($object->id, $newObject->id);
}
- CRM_Utils_Hook::post('create', CRM_Core_DAO_AllCoreTables::getBriefName(str_replace('_BAO_', '_DAO_', $daoName)), $newObject->id, $newObject);
+ CRM_Utils_Hook::post('create', CRM_Core_DAO_AllCoreTables::getBriefName($daoName), $newObject->id, $newObject);
}
return $newObject;
* Generates acl clauses suitable for adding to WHERE or ON when doing an api.get for this entity
*
* Return format is in the form of fieldname => clauses starting with an operator. e.g.:
- * @code
+ * ```
* array(
* 'location_type_id' => array('IS NOT NULL', 'IN (1,2,3)')
* )
- * @endcode
+ * ```
*
* Note that all array keys must be actual field names in this entity. Use subqueries to filter on other tables e.g. custom values.
*
return class_exists($baoName) ? $baoName : $daoName;
}
+ /**
+ * Convert possibly underscore separated words to camel case with special handling for 'UF'
+ * e.g membership_payment returns MembershipPayment
+ *
+ * @param string $name
+ * @param bool $legacyV3
+ * @return string
+ */
+ public static function convertEntityNameToCamel(string $name, $legacyV3 = FALSE): string {
+ // This map only applies to APIv3
+ $map = [
+ 'acl' => 'Acl',
+ 'ACL' => 'Acl',
+ 'im' => 'Im',
+ 'IM' => 'Im',
+ ];
+ if ($legacyV3 && isset($map[$name])) {
+ return $map[$name];
+ }
+
+ $fragments = explode('_', $name);
+ foreach ($fragments as & $fragment) {
+ $fragment = ucfirst($fragment);
+ // Special case: UFGroup, UFJoin, UFMatch, UFField (if passed in without underscores)
+ if (strpos($fragment, 'Uf') === 0 && strlen($name) > 2) {
+ $fragment = 'UF' . ucfirst(substr($fragment, 2));
+ }
+ }
+ // Special case: UFGroup, UFJoin, UFMatch, UFField (if passed in underscore-separated)
+ if ($fragments[0] === 'Uf') {
+ $fragments[0] = 'UF';
+ }
+ return implode('', $fragments);
+ }
+
+ /**
+ * Convert CamelCase to snake_case, with special handling for some entity names.
+ *
+ * Eg. Activity returns activity
+ * UFGroup returns uf_group
+ * OptionValue returns option_value
+ *
+ * @param string $name
+ *
+ * @return string
+ */
+ public static function convertEntityNameToLower(string $name): string {
+ if ($name === strtolower($name)) {
+ return $name;
+ }
+ if ($name === 'PCP' || $name === 'IM' || $name === 'ACL') {
+ return strtolower($name);
+ }
+ return strtolower(ltrim(str_replace('U_F',
+ 'uf',
+ // That's CamelCase, beside an odd UFCamel that is expected as uf_camel
+ preg_replace('/(?=[A-Z])/', '_$0', $name)
+ ), '_'));
+ }
+
/**
* Get a list of all DAO classes.
*
* Ex: 'Contact'.
*/
public static function getBriefName($className) {
- return CRM_Utils_Array::value($className, array_flip(self::daoToClass()));
+ $className = self::getCanonicalClassName($className);
+ return array_search($className, self::daoToClass(), TRUE) ?: NULL;
}
/**
*
* Generated from xml/schema/CRM/Core/CustomField.xml
* DO NOT EDIT. Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:492b1be45dc41c15b35371410a074393)
+ * (GenCodeChecksum:0b21a2a1f1cba7a76fd8830db1626513)
*/
/**
*/
public $option_group_id;
+ /**
+ * Serialization method - a non-null value indicates a multi-valued field.
+ *
+ * @var int
+ */
+ public $serialize;
+
/**
* Stores Contact Get API params contact reference custom fields. May be used for other filters in the future.
*
'labelColumn' => 'title',
],
],
+ 'serialize' => [
+ 'name' => 'serialize',
+ 'type' => CRM_Utils_Type::T_INT,
+ 'title' => ts('Serialize'),
+ 'description' => ts('Serialization method - a non-null value indicates a multi-valued field.'),
+ 'where' => 'civicrm_custom_field.serialize',
+ 'table_name' => 'civicrm_custom_field',
+ 'entity' => 'CustomField',
+ 'bao' => 'CRM_Core_BAO_CustomField',
+ 'localizable' => 0,
+ 'pseudoconstant' => [
+ 'callback' => 'CRM_Core_SelectValues::fieldSerialization',
+ ],
+ ],
'filter' => [
'name' => 'filter',
'type' => CRM_Utils_Type::T_STRING,
* @param string $className
*
* @return mixed
- * @throws Exception
+ * @throws CRM_Core_Exception
*/
public static function create($className) {
$type = self::$_classes[$className] ?? NULL;
if (!$type) {
- CRM_Core_Error::fatal("class $className not found");
+ throw new CRM_Core_Exception("class $className not found");
}
$class = self::$_prefix[$type] . $className;
}
}
+ // Use the custom fatalErrorHandler if defined
+ if ($config->fatalErrorHandler && function_exists($config->fatalErrorHandler)) {
+ $name = $config->fatalErrorHandler;
+ $vars = [
+ 'pearError' => $pearError,
+ ];
+ $ret = $name($vars);
+ if ($ret) {
+ // the call has been successfully handled so we just exit
+ self::abend(CRM_Core_Error::FATAL_ERROR);
+ }
+ }
+
$template->assign_by_ref('error', $error);
$errorDetails = CRM_Core_Error::debug('', $error, FALSE);
$template->assign_by_ref('errorDetails', $errorDetails);
exit;
}
$runOnce = TRUE;
- self::abend(1);
+ self::abend(CRM_Core_Error::FATAL_ERROR);
}
/**
}
$file_log->close();
+ // Use the custom fatalErrorHandler if defined
+ if (in_array($priority, [PEAR_LOG_EMERG, PEAR_LOG_ALERT, PEAR_LOG_CRIT, PEAR_LOG_ERR])) {
+ if ($config->fatalErrorHandler && function_exists($config->fatalErrorHandler)) {
+ $name = $config->fatalErrorHandler;
+ $vars = [
+ 'debugLogMessage' => $message,
+ 'priority' => $priority,
+ ];
+ $name($vars);
+ }
+ }
+
if (!isset(\Civi::$statics[__CLASS__]['userFrameworkLogging'])) {
// Set it to FALSE first & then try to set it. This is to prevent a loop as calling
// $config->userFrameworkLogging can trigger DB queries & under log mode this
$element = $this->addElement($type, $name, CRM_Utils_String::purifyHTML($label), $attributes, $extra);
if (HTML_QuickForm::isError($element)) {
- CRM_Core_Error::fatal(HTML_QuickForm::errorMessage($element));
+ CRM_Core_Error::statusBounce(HTML_QuickForm::errorMessage($element));
}
if ($inputType == 'color') {
$error = $this->addRule($name, ts('%1 is a required field.', [1 => $label]), 'required');
}
if (HTML_QuickForm::isError($error)) {
- CRM_Core_Error::fatal(HTML_QuickForm::errorMessage($element));
+ CRM_Core_Error::statusBounce(HTML_QuickForm::errorMessage($element));
}
}
public function addEntityRef($name, $label = '', $props = [], $required = FALSE) {
// Default properties
$props['api'] = CRM_Utils_Array::value('api', $props, []);
- $props['entity'] = CRM_Utils_String::convertStringToCamel(CRM_Utils_Array::value('entity', $props, 'Contact'));
+ $props['entity'] = CRM_Core_DAO_AllCoreTables::convertEntityNameToCamel(CRM_Utils_Array::value('entity', $props, 'Contact'));
$props['class'] = ltrim(CRM_Utils_Array::value('class', $props, '') . ' crm-form-entityref');
if (array_key_exists('create', $props) && empty($props['create'])) {
public static $SQL_ESCAPER = NULL;
/**
- * Encode a string for use in SQL.
+ * Escape a string if a mode is specified, otherwise return string unmodified.
*
* @param string $text
+ * @param string $mode
* @return string
*/
- protected static function escapeSql($text) {
- if (self::$SQL_ESCAPER == NULL) {
- return CRM_Core_DAO::escapeString($text);
- }
- else {
- return call_user_func(self::$SQL_ESCAPER, $text);
+ protected static function escape($text, $mode) {
+ switch ($mode) {
+ case 'sql':
+ if (self::$SQL_ESCAPER == NULL) {
+ return CRM_Core_DAO::escapeString($text);
+ }
+ else {
+ return call_user_func(self::$SQL_ESCAPER, $text);
+ }
+
+ case 'js':
+ return substr(json_encode($text, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT), 1, -1);
}
+ return $text;
}
/**
* the translated string
*/
public function crm_translate($text, $params = []) {
- if (isset($params['escape'])) {
- $escape = $params['escape'];
- unset($params['escape']);
- }
+ $escape = $params['escape'] ?? NULL;
+ unset($params['escape']);
// sometimes we need to {ts}-tag a string, but don’t want to
// translate it in the template (like civicrm_navigation.tpl),
// because we handle the translation in a different way (CRM-6998)
// in such cases we return early, only doing SQL/JS escaping
if (isset($params['skip']) and $params['skip']) {
- if (isset($escape) and ($escape == 'sql')) {
- $text = self::escapeSql($text);
- }
- if (isset($escape) and ($escape == 'js')) {
- $text = addcslashes($text, "'");
- }
- return $text;
+ return self::escape($text, $escape);
}
$plural = $count = NULL;
$text = $this->strarg($text, $params);
}
- // escape SQL if we were asked for it
- if (isset($escape) and ($escape == 'sql')) {
- $text = self::escapeSql($text);
- }
-
- // escape for JavaScript (if requested)
- if (isset($escape) and ($escape == 'js')) {
- $text = addcslashes($text, "'");
- }
-
- return $text;
+ return self::escape($text, $escape);
}
/**
$id = CRM_Utils_Request::retrieve('id', 'Int', $this);
$this->_structure = CRM_Core_I18n_SchemaStructure::columns();
if (!isset($this->_structure[$table][$field])) {
- CRM_Core_Error::fatal("$table.$field is not internationalized.");
+ CRM_Core_Error::statusBounce("$table.$field is not internationalized.");
}
$this->addElement('hidden', 'table', $table);
// validate table and field
if (!isset($this->_structure[$table][$field])) {
- CRM_Core_Error::fatal("$table.$field is not internationalized.");
+ CRM_Core_Error::statusBounce("$table.$field is not internationalized.");
}
$cols = [];
return CRM_Utils_System::redirect();
}
else {
- CRM_Core_Error::fatal('You do not have permission to execute this url');
+ CRM_Core_Error::statusBounce('You do not have permission to execute this url');
}
}
}
if (!array_key_exists('page_callback', $item)) {
CRM_Core_Error::debug('Bad item', $item);
- CRM_Core_Error::fatal(ts('Bad menu record in database'));
+ CRM_Core_Error::statusBounce(ts('Bad menu record in database'));
}
// check that we are permissioned to access this page
$object = new $item['page_callback']($title, TRUE, $mode, NULL, $addSequence);
}
else {
- CRM_Core_Error::fatal();
+ throw new CRM_Core_Exception('Execute supplied menu action');
}
$result = $object->run($newArgs, $pageArgs);
}
* An XML document defining a list of menu items.
* @param array $menu
* An alterable list of menu items.
+ *
+ * @throws CRM_Core_Exception
*/
public static function readXML($xml, &$menu) {
$config = CRM_Core_Config::singleton();
foreach ($xml->item as $item) {
if (!(string ) $item->path) {
CRM_Core_Error::debug('i', $item);
- CRM_Core_Error::fatal();
+ throw new CRM_Core_Exception('Unable to read XML file');
}
$path = (string ) $item->path;
$menu[$path] = array();
* @param array $menu
* @param string $path
*
- * @throws Exception
+ * @throws CRM_Core_Exception
*/
public static function fillMenuValues(&$menu, $path) {
$fieldsToPropagate = array(
return;
}
- $messages = array();
+ $messages = [];
foreach ($fieldsToPropagate as $field) {
if (!$fieldsPresent[$field]) {
$messages[] = ts("Could not find %1 in path tree",
- array(1 => $field)
+ [1 => $field]
);
}
}
- CRM_Core_Error::fatal("'$path': " . implode(', ', $messages));
+ throw new CRM_Core_Exception("'$path': " . implode(', ', $messages));
}
/**
* @param bool $condition
* Whether to display anything at all. This helps simplify code when a
* checkmark should appear if something is true.
+ * @param array $attribs
+ * Attributes to set or override on the icon element. Any standard
+ * attribute can be unset by setting the value to an empty string.
*
* @return string
* The whole bit to drop in.
*/
- public static function crmIcon($icon, $text = NULL, $condition = TRUE) {
+ public static function crmIcon($icon, $text = NULL, $condition = TRUE, $attribs = []) {
if (!$condition) {
return '';
}
+
+ // Add icon classes to any that might exist in $attribs
+ $classes = array_key_exists('class', $attribs) ? explode(' ', $attribs['class']) : [];
+ $classes[] = 'crm-i';
+ $classes[] = $icon;
+ $attribs['class'] = implode(' ', array_unique($classes));
+
+ $standardAttribs = ['aria-hidden' => 'true'];
if ($text === NULL || $text === '') {
$title = $sr = '';
}
else {
- $text = htmlspecialchars($text);
- $title = " title=\"$text\"";
+ $standardAttribs['title'] = $text;
$sr = "<span class=\"sr-only\">$text</span>";
}
- return "<i class=\"crm-i $icon\"$title></i>$sr";
+
+ // Assemble attribs
+ $attribString = '';
+ // Strip out title if $attribs specifies a blank title
+ $attribs = array_merge($standardAttribs, $attribs);
+ foreach ($attribs as $attrib => $val) {
+ if (strlen($val)) {
+ $val = htmlspecialchars($val);
+ $attribString .= " $attrib=\"$val\"";
+ }
+ }
+
+ return "<i$attribString></i>$sr";
}
}
/**
* Placeholder page which generates a redirect
*
- * @code
+ * ```
* <item>
* <path>civicrm/admin/options/case_type</path>
* <page_callback>CRM_Core_Page_Redirect</page_callback>
* <page_arguments>url=civicrm/foo/bar?whiz=bang&passthru=%%passthru%%</page_arguments>
* </item>
- * @endcoe
+ * ```
*/
class CRM_Core_Page_Redirect extends CRM_Core_Page {
*
* @return bool
*/
- public function validateData(&$input, &$ids, &$objects, $required = TRUE, $paymentProcessorID = NULL) {
+ public function validateData($input, &$ids, &$objects, $required = TRUE, $paymentProcessorID = NULL) {
// Check if the contribution exists
// make sure contribution exists and is valid
*
* @return bool|array
*/
- public function loadObjects(&$input, &$ids, &$objects, $required, $paymentProcessorID, $error_handling = NULL) {
+ public function loadObjects($input, &$ids, &$objects, $required, $paymentProcessorID, $error_handling = NULL) {
if (empty($error_handling)) {
// default options are that we log an error & echo it out
// note that we should refactor this error handling into error code @ some point
// & suspec main function may be a victom of copy & paste
// membership would be an easy add - but not relevant to my customer...
$this->_component = $input['component'] = 'contribute';
- $input['trxn_date'] = date('Y-m-d-H-i-s', strtotime(self::retrieve('time_created', 'String')));
+ $input['trxn_date'] = date('Y-m-d H:i:s', strtotime(self::retrieve('time_created', 'String')));
$paymentProcessorID = $contributionRecur['payment_processor_id'];
if (!$this->validateData($input, $ids, $objects, TRUE, $paymentProcessorID)) {
* @param string $permissionName
* Name of the permission we are interested in.
*
+ * @throws CRM_Core_Exception.
*/
public function permissionEmails($permissionName) {
- CRM_Core_Error::fatal("this function only works in Drupal 6 at the moment");
+ throw new CRM_Core_Exception("this function only works in Drupal 6 at the moment");
}
/**
* @param string $roleName
* Name of the role we are interested in.
*
+ * @throws CRM_Core_Exception.
*/
public function roleEmails($roleName) {
- CRM_Core_Error::fatal("this function only works in Drupal 6 at the moment");
+ throw new CRM_Core_Exception("this function only works in Drupal 6 at the moment");
}
/**
'fresh' => FALSE,
'context' => $context,
];
- $entity = CRM_Core_DAO_AllCoreTables::getBriefName(CRM_Core_DAO_AllCoreTables::getCanonicalClassName($daoName));
+ $entity = CRM_Core_DAO_AllCoreTables::getBriefName($daoName);
// Custom fields are not in the schema
if (strpos($fieldName, 'custom_') === 0 && is_numeric($fieldName[7])) {
/**
* Add a snippet of content to a region.
*
- * @code
+ * ```
* CRM_Core_Region::instance('page-header')->add(array(
* 'markup' => '<div style="color:red">Hello!</div>',
* ));
* CRM_Core_Region::instance('page-header')->add(array(
* 'callback' => 'myextension_callback_function',
* ));
- * @endcode
+ * ```
*
* Note: This function does not perform any extra encoding of markup, script code, or etc. If
* you're passing in user-data, you must clean it yourself.
'Select Date' => ts('Select Date'),
'File' => ts('File'),
'Select State/Province' => ts('Select State/Province'),
- 'Multi-Select State/Province' => ts('Multi-Select State/Province'),
'Select Country' => ts('Select Country'),
- 'Multi-Select Country' => ts('Multi-Select Country'),
'RichTextEditor' => ts('Rich Text Editor'),
'Autocomplete-Select' => ts('Autocomplete-Select'),
- 'Multi-Select' => ts('Multi-Select'),
'Link' => ts('Link'),
'ContactReference' => ts('Autocomplete-Select'),
];
return $ret;
}
+ public static function fieldSerialization() {
+ return [
+ CRM_Core_DAO::SERIALIZE_SEPARATOR_BOOKEND => 'separator_bookend',
+ CRM_Core_DAO::SERIALIZE_SEPARATOR_TRIMMED => 'separator_trimmed',
+ CRM_Core_DAO::SERIALIZE_JSON => 'json',
+ CRM_Core_DAO::SERIALIZE_PHP => 'php',
+ CRM_Core_DAO::SERIALIZE_COMMA => 'comma',
+ ];
+ }
+
}
*/
class CRM_Core_ShowHideBlocks {
- /**
- * The icons prefixed to block show and hide links.
- *
- * @var string
- */
- public static $_showIcon;
- public static $_hideIcon;
-
/**
* The array of ids of blocks that will be shown.
*
}
}
- /**
- * Load icon vars used in hide and show links.
- */
- public static function setIcons() {
- if (!isset(self::$_showIcon)) {
- $config = CRM_Core_Config::singleton();
- self::$_showIcon = '<img src="' . $config->resourceBase . 'i/TreePlus.gif" class="action-icon" alt="' . ts('show field or section') . '"/>';
- self::$_hideIcon = '<img src="' . $config->resourceBase . 'i/TreeMinus.gif" class="action-icon" alt="' . ts('hide field or section') . '"/>';
- }
- }
-
/**
* Add the values from this class to the template.
*/
}
}
- /**
- * Create a well formatted html link from the smaller pieces.
- *
- * @param string $name
- * Name of the link.
- * @param string $href
- * @param string $text
- * @param string $js
- *
- * @return string
- * the formatted html link
- */
- public static function linkHtml($name, $href, $text, $js) {
- return '<a name="' . $name . '" id="' . $name . '" href="' . $href . '" ' . $js . ">$text</a>";
- }
-
- /**
- * Create links that we can use in the form.
- *
- * @param CRM_Core_Form $form
- * The form object.
- * @param string $prefix
- * The attribute that we are referencing.
- * @param string $showLinkText
- * The text to be shown for the show link.
- * @param string $hideLinkText
- * The text to be shown for the hide link.
- *
- * @param bool $assign
- *
- * @return array
- */
- public static function links(&$form, $prefix, $showLinkText, $hideLinkText, $assign = TRUE) {
- $showCode = "if(event.preventDefault) event.preventDefault(); else event.returnValue = false; cj('#id_{$prefix}').show(); cj('#id_{$prefix}_show').hide();";
- $hideCode = "if(event.preventDefault) event.preventDefault(); else event.returnValue = false; cj('#id_{$prefix}').hide(); cj('#id_{$prefix}_show').show();";
-
- self::setIcons();
- $values = [];
- $values['show'] = self::linkHtml("${prefix}_show", "#${prefix}_hide", self::$_showIcon . $showLinkText, "onclick=\"$showCode\"");
- $values['hide'] = self::linkHtml("${prefix}_hide", "#${prefix}", self::$_hideIcon . $hideLinkText, "onclick=\"$hideCode\"");
-
- if ($assign) {
- $form->assign($prefix, $values);
- }
- else {
- return $values;
- }
- }
-
- /**
- * Create html link elements that we can use in the form.
- *
- * @param CRM_Core_Form $form
- * The form object.
- * @param int $index
- * The current index of the element being processed.
- * @param int $maxIndex
- * The max number of elements that will be processed.
- * @param string $prefix
- * The attribute that we are referencing.
- * @param string $showLinkText
- * The text to be shown for the show link.
- * @param string $hideLinkText
- * The text to be shown for the hide link.
- * @param string $elementType
- * The set the class.
- * @param string $hideLink
- * The hide block string.
- */
- public function linksForArray(&$form, $index, $maxIndex, $prefix, $showLinkText, $hideLinkText, $elementType = NULL, $hideLink = NULL) {
- $showHidePrefix = str_replace(["]", "["], ["", "_"], $prefix);
- $showHidePrefix = "id_" . $showHidePrefix;
- if ($index == $maxIndex) {
- $showCode = $hideCode = "return false;";
- }
- else {
- $next = $index + 1;
- if ($elementType) {
- $showCode = "cj('#${prefix}_${next}_show').show(); return false;";
- if ($hideLink) {
- $hideCode = $hideLink;
- }
- else {
- $hideCode = "cj('#${prefix}_${next}_show, #${prefix}_${next}').hide(); return false;";
- }
- }
- else {
- $showCode = "cj('#{$showHidePrefix}_{$next}_show').show(); return false;";
- $hideCode = "cj('#{$showHidePrefix}_{$next}_show, #{$showHidePrefix}_{$next}').hide(); return false;";
- }
- }
-
- self::setIcons();
- if ($elementType) {
- $form->addElement('link', "${prefix}[${index}][show]", NULL, "#${prefix}_${index}", self::$_showIcon . $showLinkText,
- ['onclick' => "cj('#${prefix}_${index}_show').hide(); cj('#${prefix}_${index}').show();" . $showCode]
- );
- $form->addElement('link', "${prefix}[${index}][hide]", NULL, "#${prefix}_${index}", self::$_hideIcon . $hideLinkText,
- ['onclick' => "cj('#${prefix}_${index}').hide(); cj('#${prefix}_${index}_show').show();" . $hideCode]
- );
- }
- else {
- $form->addElement('link', "${prefix}[${index}][show]", NULL, "#${prefix}_${index}", self::$_showIcon . $showLinkText,
- ['onclick' => "cj('#{$showHidePrefix}_{$index}_show').hide(); cj('#{$showHidePrefix}_{$index}').show();" . $showCode]
- );
- $form->addElement('link', "${prefix}[${index}][hide]", NULL, "#${prefix}_${index}", self::$_hideIcon . $hideLinkText,
- ['onclick' => "cj('#{$showHidePrefix}_{$index}').hide(); cj('#{$showHidePrefix}_{$index}_show').show();" . $hideCode]
- );
- }
- }
-
}
/**
* Temporarily assign a list of variables.
*
- * @code
+ * ```
* $smarty->pushScope(array(
* 'first_name' => 'Alice',
* 'last_name' => 'roberts',
* ));
* $html = $smarty->fetch('view-contact.tpl');
* $smarty->popScope();
- * @endcode
+ * ```
*
* @param array $vars
* (string $name => mixed $value).
// Always add class 'button' - fixme probably should be crm-button
$params['class'] = empty($params['class']) ? 'button' : 'button ' . $params['class'];
// Any FA icon works
- $icon = CRM_Utils_Array::value('icon', $params, 'pencil');
+ if (array_key_exists('icon', $params) && !$params['icon']) {
+ // icon=0 should produce a button with no icon
+ $iconMarkup = '';
+ }
+ else {
+ $icon = $params['icon'] ?? 'fa-pencil';
+ // Assume for now that all icons are Font Awesome v4.x but handle if it's
+ // specified
+ if (strpos($icon, 'fa-') !== 0) {
+ $icon = "fa-$icon";
+ }
+ $iconMarkup = "<i class='crm-i $icon'></i> ";
+ }
// All other params are treated as html attributes
CRM_Utils_Array::remove($params, 'icon', 'p', 'q', 'a', 'f', 'h', 'fb', 'fe');
$attributes = CRM_Utils_String::htmlAttributes($params);
- return "<a $attributes><span><i class='crm-i fa-$icon'></i> $text</span></a>";
+ return "<a $attributes><span>$iconMarkup$text</span></a>";
}
*
* Example:
*
- * @code
+ * ```
* {tsScope x=1}
* Expect {$x}==1
* {tsScope x=2}
* {/tsScope}
* Expect {$x}==1
* {/tsScope}
- * @endcode
+ * ```
*
* @param array $params
* Must define 'name'.
* @param $params
* - condition: if present and falsey, return empty
* - icon: the icon class to display instead of fa-check
+ * - anything else is passed along as attributes for the icon
*
* @param $text
* The translated text to include in the icon's title and screen-reader text.
function smarty_block_icon($params, $text, &$smarty) {
$condition = array_key_exists('condition', $params) ? $params['condition'] : 1;
$icon = $params['icon'] ?? 'fa-check';
- return CRM_Core_Page::crmIcon($icon, $text, $condition);
+ $dontPass = [
+ 'condition' => 1,
+ 'icon' => 1,
+ ];
+ return CRM_Core_Page::crmIcon($icon, $text, $condition, array_diff_key($params, $dontPass));
}
/**
* Display the CiviCRM version
*
- * @code
+ * ```
* The version is {crmVersion}.
*
* {crmVersion redact=auto assign=ver}The version is {$ver}.
- * @endcode
+ * ```
*
* @param $params
* @param $smarty
*
* @package CRM
* @author Andrew Hunt, AGH Strategies
- * $Id$
*
*/
* @param $params
* - field: the applicable privacy field
* (one of CRM_Core_SelectValues::privacy() or `on_hold`)
+ * - condition: if present and falsey, return empty
*
* @param $smarty
*
* @return string
*/
function smarty_function_privacyFlag($params, &$smarty) {
+ if (array_key_exists('condition', $params) && !$params['condition']) {
+ return '';
+ }
$icons = [
'do_not_phone' => 'fa-phone',
'do_not_email' => 'fa-paper-plane',
*
* To ensure that they throw exceptions, use:
*
- * @code
+ * ```
* $errorScope = CRM_Core_TemporaryErrorScope::useException();
- * @endcode
+ * ```
*
* Note that relying on this is a code-smell: it can be
* safe to temporarily switch to exception
* @inheritDoc
*/
public function checkActive(\Civi\Token\TokenProcessor $processor) {
- return in_array($this->getEntityIDFieldName(), $processor->context['schema'], TRUE) ||
+ return in_array($this->getEntityContextSchema(), $processor->context['schema']) ||
(!empty($processor->context['actionMapping'])
&& $processor->context['actionMapping']->getEntity() === $this->getEntityTableName());
}
/**
* Find the fields that we need to get to construct the tokens requested.
- *
* @param array $tokens list of tokens
* @return array list of fields needed to generate those tokens
*/
*
* Examples:
*
- * @code
+ * ```
* // Some business logic using the helper functions
* function my_business_logic() {
* CRM_Core_Transaction::create()->run(function($tx) {
* }
* }
*
- * @endcode
+ * ```
*
* Note: As of 4.6, the transaction manager supports both reference-counting and nested
* transactions (SAVEPOINTs). In the past, it only supported reference-counting. The two cases
'Select' => 'Select',
'Radio' => 'Radio',
'CheckBox' => 'CheckBox',
- 'Multi-Select' => 'Multi-Select',
'Autocomplete-Select' => 'Autocomplete-Select',
],
['Text' => 'Text', 'Select' => 'Select', 'Radio' => 'Radio'],
['TextArea' => 'TextArea', 'RichTextEditor' => 'RichTextEditor'],
['Date' => 'Select Date'],
['Radio' => 'Radio'],
- ['StateProvince' => 'Select State/Province', 'Multi-Select' => 'Multi-Select State/Province'],
- ['Country' => 'Select Country', 'Multi-Select' => 'Multi-Select Country'],
+ ['StateProvince' => 'Select State/Province'],
+ ['Country' => 'Select Country'],
['File' => 'File'],
['Link' => 'Link'],
['ContactReference' => 'Autocomplete-Select'],
'Select' => ts('Select'),
'Radio' => ts('Radio'),
'CheckBox' => ts('CheckBox'),
- 'Multi-Select' => ts('Multi-Select'),
'Autocomplete-Select' => ts('Autocomplete-Select'),
],
[
['TextArea' => ts('TextArea'), 'RichTextEditor' => ts('Rich Text Editor')],
['Date' => ts('Select Date')],
['Radio' => ts('Radio')],
- ['StateProvince' => ts('Select State/Province'), 'Multi-Select' => ts('Multi-Select State/Province')],
- ['Country' => ts('Select Country'), 'Multi-Select' => ts('Multi-Select Country')],
+ ['StateProvince' => ts('Select State/Province')],
+ ['Country' => ts('Select Country')],
['File' => ts('Select File')],
['Link' => ts('Link')],
['ContactReference' => ts('Autocomplete-Select')],
'return' => ['title'],
];
+ $this->add('checkbox', 'serialize', ts('Multi-Select'));
+
if ($this->_action == CRM_Core_Action::UPDATE) {
$this->freeze('data_type');
if (!empty($this->_values['option_group_id'])) {
if (isset($fields['data_type'][1])) {
$dataField = $fields['data_type'][1];
}
- $optionFields = ['Select', 'Multi-Select', 'CheckBox', 'Radio'];
+ $optionFields = ['Select', 'CheckBox', 'Radio'];
if (isset($fields['option_type']) && $fields['option_type'] == 1) {
//capture duplicate Custom option values
$params['is_search_range'] = 0;
}
+ // Serialization cannot be changed on update
+ if ($this->_id) {
+ unset($params['serialize']);
+ }
+ elseif (strpos($params['html_type'], 'Select') === 0) {
+ $params['serialize'] = $params['serialize'] ? CRM_Core_DAO::SERIALIZE_SEPARATOR_BOOKEND : 'null';
+ }
+ else {
+ $params['serialize'] = $params['html_type'] == 'CheckBox' ? CRM_Core_DAO::SERIALIZE_SEPARATOR_BOOKEND : 'null';
+ }
+
$filter = 'null';
if ($dataTypeKey == 11 && !empty($params['filter_selected'])) {
if ($params['filter_selected'] == 'Advance' && trim(CRM_Utils_Array::value('filter', $params))) {
return [$cFields, $submitted];
}
$htmlType = $cFields[$fid]['attributes']['html_type'];
- switch ($htmlType) {
- case 'File':
- // Handled in CustomField->move(). Tested in testMergeCustomFields.
- unset($submitted["custom_$fid"]);
- break;
-
- case 'Select Country':
- // @todo Test in testMergeCustomFields disabled as this does not work, Handle in CustomField->move().
- case 'Select State/Province':
- $submitted[$key] = CRM_Core_BAO_CustomField::displayValue($value, $fid);
- break;
+ $isSerialized = CRM_Core_BAO_CustomField::isSerialized($cFields[$fid]['attributes']);
- case 'Select Date':
- if ($cFields[$fid]['attributes']['is_view']) {
- $submitted[$key] = date('YmdHis', strtotime($submitted[$key]));
- }
- break;
-
- case 'CheckBox':
- case 'Multi-Select':
- case 'Multi-Select Country':
- case 'Multi-Select State/Province':
- // Merge values from both contacts for multivalue fields, CRM-4385
- // get the existing custom values from db.
- $customParams = ['entityID' => $mainId, $key => TRUE];
- $customfieldValues = CRM_Core_BAO_CustomValueTable::getValues($customParams);
- if (!empty($customfieldValues[$key])) {
- $existingValue = explode(CRM_Core_DAO::VALUE_SEPARATOR, $customfieldValues[$key]);
- if (is_array($existingValue) && !empty($existingValue)) {
- $mergeValue = $submittedCustomFields = [];
- if ($value == 'null') {
- // CRM-19074 if someone has deliberately chosen to overwrite with 'null', respect it.
- $submitted[$key] = $value;
+ if ($htmlType === 'File') {
+ // Handled in CustomField->move(). Tested in testMergeCustomFields.
+ unset($submitted["custom_$fid"]);
+ }
+ elseif (!$isSerialized && ($htmlType === 'Select Country' || $htmlType === 'Select State/Province')) {
+ // @todo Test in testMergeCustomFields disabled as this does not work, Handle in CustomField->move().
+ $submitted[$key] = CRM_Core_BAO_CustomField::displayValue($value, $fid);
+ }
+ elseif ($htmlType === 'Select Date') {
+ if ($cFields[$fid]['attributes']['is_view']) {
+ $submitted[$key] = date('YmdHis', strtotime($submitted[$key]));
+ }
+ }
+ elseif ($isSerialized) {
+ // Merge values from both contacts for multivalue fields, CRM-4385
+ // get the existing custom values from db.
+ $customParams = ['entityID' => $mainId, $key => TRUE];
+ $customfieldValues = CRM_Core_BAO_CustomValueTable::getValues($customParams);
+ if (!empty($customfieldValues[$key])) {
+ $existingValue = explode(CRM_Core_DAO::VALUE_SEPARATOR, $customfieldValues[$key]);
+ if (is_array($existingValue) && !empty($existingValue)) {
+ $mergeValue = $submittedCustomFields = [];
+ if ($value == 'null') {
+ // CRM-19074 if someone has deliberately chosen to overwrite with 'null', respect it.
+ $submitted[$key] = $value;
+ }
+ else {
+ if ($value) {
+ $submittedCustomFields = explode(CRM_Core_DAO::VALUE_SEPARATOR, $value);
}
- else {
- if ($value) {
- $submittedCustomFields = explode(CRM_Core_DAO::VALUE_SEPARATOR, $value);
- }
- // CRM-19653: overwrite or add the existing custom field value with dupicate contact's
- // custom field value stored at $submittedCustomValue.
- foreach ($submittedCustomFields as $k => $v) {
- if ($v != '' && !in_array($v, $mergeValue)) {
- $mergeValue[] = $v;
- }
+ // CRM-19653: overwrite or add the existing custom field value with dupicate contact's
+ // custom field value stored at $submittedCustomValue.
+ foreach ($submittedCustomFields as $k => $v) {
+ if ($v != '' && !in_array($v, $mergeValue)) {
+ $mergeValue[] = $v;
}
+ }
- //keep state and country as array format.
- //for checkbox and m-select format w/ VALUE_SEPARATOR
- if (in_array($htmlType, [
- 'CheckBox',
- 'Multi-Select',
- ])) {
- $submitted[$key] = CRM_Core_DAO::VALUE_SEPARATOR . implode(CRM_Core_DAO::VALUE_SEPARATOR,
- $mergeValue
- ) . CRM_Core_DAO::VALUE_SEPARATOR;
- }
- else {
- $submitted[$key] = $mergeValue;
- }
+ //keep state and country as array format.
+ //for checkbox and m-select format w/ VALUE_SEPARATOR
+ if (in_array($htmlType, ['CheckBox', 'Select'])) {
+ $submitted[$key] = CRM_Utils_Array::implodePadded($mergeValue);
+ }
+ else {
+ $submitted[$key] = $mergeValue;
}
}
}
- elseif (in_array($htmlType, [
- 'Multi-Select Country',
- 'Multi-Select State/Province',
- ])) {
- //we require submitted values should be in array format
- if ($value) {
- $mergeValueArray = explode(CRM_Core_DAO::VALUE_SEPARATOR, $value);
- //hack to remove null values from array.
- $mergeValue = [];
- foreach ($mergeValueArray as $k => $v) {
- if ($v != '') {
- $mergeValue[] = $v;
- }
+ }
+ elseif (in_array($htmlType, ['Select Country', 'Select State/Province'])) {
+ //we require submitted values should be in array format
+ if ($value) {
+ $mergeValueArray = explode(CRM_Core_DAO::VALUE_SEPARATOR, $value);
+ //hack to remove null values from array.
+ $mergeValue = [];
+ foreach ($mergeValueArray as $k => $v) {
+ if ($v != '') {
+ $mergeValue[] = $v;
}
- $submitted[$key] = $mergeValue;
}
+ $submitted[$key] = $mergeValue;
}
- break;
-
- default:
- break;
+ }
}
}
return [$cFields, $submitted];
$fields = [];
foreach ($params as $key => $value) {
CRM_Event_Form_Registration_Confirm::fixLocationFields($value, $fields, $this);
- //unset the billing parameters if it is pay later mode
- //to avoid creation of billing location
- // @todo - the reasoning for this is unclear - elsewhere we check what fields are provided by
- // the form & if billing fields exist we create the address, relying on the form to collect
- // only information we intend to store.
if ($this->_allowWaitlist
|| $this->_requireApproval
|| (!empty($value['is_pay_later']) && !$this->_isBillingAddressRequiredForPayLater)
|| empty($value['is_primary'])
) {
- $billingFields = [
- "email-{$this->_bltID}",
- 'billing_first_name',
- 'billing_middle_name',
- 'billing_last_name',
- "billing_street_address-{$this->_bltID}",
- "billing_city-{$this->_bltID}",
- "billing_state_province-{$this->_bltID}",
- "billing_state_province_id-{$this->_bltID}",
- "billing_postal_code-{$this->_bltID}",
- "billing_country-{$this->_bltID}",
- "billing_country_id-{$this->_bltID}",
- "address_name-{$this->_bltID}",
- ];
- foreach ($billingFields as $field) {
- unset($value[$field]);
- }
+ // This is confusing because unnecessary code around it has been removed. It is not
+ // clear why we do this / whether we should.
if (!empty($value['is_pay_later'])) {
$this->_values['params']['is_pay_later'] = TRUE;
}
// change status to refunded.
self::updateContributionStatus($contribution['id'], 'Refunded');
}
+ self::updateRelatedContribution($params, $params['contribution_id']);
CRM_Contribute_BAO_Contribution::recordPaymentActivity($params['contribution_id'], CRM_Utils_Array::value('participant_id', $params), $params['total_amount'], $trxn->currency, $trxn->trxn_date);
return $trxn;
}
+ /**
+ * Function to update contribution's check_number and trxn_id by
+ * concatenating values from financial trxn's check_number and trxn_id respectively
+ *
+ * @param array $params
+ * @param int $contributionID
+ */
+ public static function updateRelatedContribution($params, $contributionID) {
+ $contributionDAO = new CRM_Contribute_DAO_Contribution();
+ $contributionDAO->id = $contributionID;
+ $contributionDAO->find(TRUE);
+
+ foreach (['trxn_id', 'check_number'] as $fieldName) {
+ if (!empty($params[$fieldName])) {
+ $values = [];
+ if (!empty($contributionDAO->$fieldName)) {
+ $values = explode(',', $contributionDAO->$fieldName);
+ }
+ // if submitted check_number or trxn_id value is
+ // already present then ignore else add to $values array
+ if (!in_array($params[$fieldName], $values)) {
+ $values[] = $params[$fieldName];
+ }
+ $contributionDAO->$fieldName = implode(',', $values);
+ }
+ }
+
+ $contributionDAO->save();
+ }
+
/**
* Send an email confirming a payment that has been received.
*
civicrm_api3('FinancialTrxn', 'create', $submittedValues);
}
- self::updateRelatedContribution($submittedValues, $this->_contributionID);
+ CRM_Financial_BAO_Payment::updateRelatedContribution($submittedValues, $this->_contributionID);
}
/**
$this->submit($params);
}
- /**
- * Function to update contribution's check_number and trxn_id by
- * concatenating values from financial trxn's check_number and trxn_id respectively
- *
- * @param array $params
- * @param int $contributionID
- */
- public static function updateRelatedContribution($params, $contributionID) {
- $contributionDAO = new CRM_Contribute_DAO_Contribution();
- $contributionDAO->id = $contributionID;
- $contributionDAO->find(TRUE);
-
- foreach (['trxn_id', 'check_number'] as $fieldName) {
- if (!empty($params[$fieldName])) {
- if (!empty($contributionDAO->$fieldName)) {
- $values = explode(',', $contributionDAO->$fieldName);
- // if submitted check_number or trxn_id value is
- // already present then ignore else add to $values array
- if (!in_array($params[$fieldName], $values)) {
- $values[] = $params[$fieldName];
- }
- $contributionDAO->$fieldName = implode(',', $values);
- }
- }
- }
-
- $contributionDAO->save();
- }
-
/**
* Get payment fields
*/
// do not log civicrm_mailing_event* tables, CRM-12300
$this->tables = preg_grep('/^civicrm_mailing_event_/', $this->tables, PREG_GREP_INVERT);
+ // dev/core#1762 Don't log subscription_history
+ $this->tables = preg_grep('/^civicrm_subscription_history/', $this->tables, PREG_GREP_INVERT);
+
// do not log civicrm_mailing_recipients table, CRM-16193
$this->tables = array_diff($this->tables, ['civicrm_mailing_recipients']);
$this->logTableSpec = array_fill_keys($this->tables, []);
// correct template IDs here
'override_verp' => TRUE,
'forward_replies' => FALSE,
- 'open_tracking' => TRUE,
- 'url_tracking' => TRUE,
+ 'open_tracking' => Civi::settings()->get('open_tracking_default'),
+ 'url_tracking' => Civi::settings()->get('url_tracking_default'),
'visibility' => 'Public Pages',
'replyto_email' => $domain_email,
'header_id' => CRM_Mailing_PseudoConstant::defaultComponent('header_id', ''),
* Form values.
*
* @param array $params
- * @param array $ids
*
- * @return object
- * $mailingab The new mailingab object
+ * @return CRM_Mailing_DAO_MailingAB
*/
- public static function create(&$params, $ids = []) {
+ public static function create(&$params) {
$transaction = new CRM_Core_Transaction();
- $mailingab = self::add($params, $ids);
+ $mailingab = self::writeRecord($params);
if (is_a($mailingab, 'CRM_Core_Error')) {
$transaction->rollback();
return $mailingab;
}
- /**
- * function to add the mailings.
- *
- * @param array $params
- * Reference array contains the values submitted by the form.
- * @param array $ids
- * Reference array contains the id.
- *
- *
- * @return object
- */
- public static function add(&$params, $ids = []) {
- $id = CRM_Utils_Array::value('mailingab_id', $ids, CRM_Utils_Array::value('id', $params));
-
- if ($id) {
- CRM_Utils_Hook::pre('edit', 'MailingAB', $id, $params);
- }
- else {
- CRM_Utils_Hook::pre('create', 'MailingAB', NULL, $params);
- }
-
- $mailingab = new CRM_Mailing_DAO_MailingAB();
- $mailingab->id = $id;
- if (!$id) {
- $mailingab->domain_id = CRM_Utils_Array::value('domain_id', $params, CRM_Core_Config::domainID());
- }
-
- $mailingab->copyValues($params);
-
- $result = $mailingab->save();
-
- if ($id) {
- CRM_Utils_Hook::post('edit', 'MailingAB', $mailingab->id, $mailingab);
- }
- else {
- CRM_Utils_Hook::post('create', 'MailingAB', $mailingab->id, $mailingab);
- }
-
- return $result;
- }
-
/**
* Delete MailingAB and all its associated records.
*
*
* Generated from xml/schema/CRM/Mailing/MailingAB.xml
* DO NOT EDIT. Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:74ff2df50144a54a2c5a740187f6a8ca)
+ * (GenCodeChecksum:808ef560b5f6c959cb4f3ceea87f5e38)
*/
/**
'type' => CRM_Utils_Type::T_INT,
'title' => ts('Domain ID'),
'description' => ts('Which site is this mailing for'),
+ 'required' => TRUE,
'where' => 'civicrm_mailing_abtest.domain_id',
'table_name' => 'civicrm_mailing_abtest',
'entity' => 'MailingAB',
if (!empty($results)) {
$results['label'] = $results['name'] = $params['name'];
$results['amount'] = empty($params['minimum_fee']) ? 0 : $params['minimum_fee'];
- $optionsIds['id'] = $results['id'];
}
else {
$results = [
if ($previousID) {
CRM_Member_Form_MembershipType::checkPreviousPriceField($previousID, $priceSetId, $membershipTypeId, $optionsIds);
if (!empty($optionsIds['option_id'])) {
- $optionsIds['id'] = current(CRM_Utils_Array::value('option_id', $optionsIds));
+ $results['id'] = current($optionsIds['option_id']);
}
}
$results['financial_type_id'] = $params['financial_type_id'] ?? NULL;
$results['description'] = $params['description'] ?? NULL;
- CRM_Price_BAO_PriceFieldValue::add($results, $optionsIds);
+ CRM_Price_BAO_PriceFieldValue::add($results);
}
}
$opt['label'] = !empty($opt['label']) ? $opt['label'] . '<span class="crm-price-amount-label-separator"> - </span>' : '';
$preHelpText = $postHelpText = '';
if (!empty($opt['help_pre'])) {
- $preHelpText = '<span class="crm-price-amount-help-pre description">' . $opt['help_pre'] . '</span>: ';
+ $preHelpText = '<span class="crm-price-amount-help-pre description">' . $opt['help_pre'] . '</span><span class="crm-price-amount-help-pre-separator">: </span>';
}
if (!empty($opt['help_post'])) {
- $postHelpText = ': <span class="crm-price-amount-help-post description">' . $opt['help_post'] . '</span>';
+ $postHelpText = '<span class="crm-price-amount-help-post-separator">: </span><span class="crm-price-amount-help-post description">' . $opt['help_post'] . '</span>';
}
if (isset($taxAmount) && $invoicing) {
if ($displayOpt == 'Do_not_show') {
*
* @param array $params
*
- * @param array $ids
- * Deprecated variable.
- *
* @return CRM_Price_DAO_PriceFieldValue
*/
- public static function add(&$params, $ids = []) {
- $hook = empty($params['id']) ? 'create' : 'edit';
- CRM_Utils_Hook::pre($hook, 'PriceFieldValue', CRM_Utils_Array::value('id', $params), $params);
-
- $fieldValueBAO = new CRM_Price_BAO_PriceFieldValue();
- $fieldValueBAO->copyValues($params);
-
- // CRM-16189
- $priceFieldID = $params['price_field_id'] ?? NULL;
+ public static function add($params) {
+ $fieldValueBAO = self::writeRecord($params);
- $id = CRM_Utils_Array::value('id', $ids, CRM_Utils_Array::value('id', $params));
-
- if (!$priceFieldID) {
- $priceFieldID = CRM_Core_DAO::getFieldValue('CRM_Price_BAO_PriceFieldValue', $id, 'price_field_id');
- }
if (!empty($params['is_default'])) {
+ $priceFieldID = $params['price_field_id'] ?? CRM_Core_DAO::getFieldValue('CRM_Price_BAO_PriceFieldValue', $fieldValueBAO->id, 'price_field_id');
$query = 'UPDATE civicrm_price_field_value SET is_default = 0 WHERE price_field_id = %1';
- $p = [1 => [$params['price_field_id'], 'Integer']];
+ $p = [1 => [$priceFieldID, 'Integer']];
CRM_Core_DAO::executeQuery($query, $p);
}
- $fieldValueBAO->save();
- CRM_Utils_Hook::post($hook, 'PriceFieldValue', $fieldValueBAO->id, $fieldValueBAO);
-
// Reset the cached values in this function.
CRM_Price_BAO_PriceField::getOptions(CRM_Utils_Array::value('price_field_id', $params), FALSE, TRUE);
return $fieldValueBAO;
if (!empty($financialType) && !array_key_exists($financialType, $financialTypes) && $params['is_active']) {
throw new CRM_Core_Exception("Financial Type for Price Field Option is either disabled or does not exist");
}
- return self::add($params, $ids);
+ $params['id'] = $id;
+ return self::add($params);
}
/**
$action -= CRM_Core_Action::DISABLE;
}
}
- $customOption[$id]['is_default'] = CRM_Core_Page::crmIcon('fa-check', ts('Default'), !empty($customOption[$id]['is_default']));
$customOption[$id]['order'] = $customOption[$id]['weight'];
$customOption[$id]['action'] = CRM_Core_Action::formLink(self::actionLinks(), $action,
[
* To ensure that PHP errors or unhandled exceptions are reported in JSON
* format, wrap this around your code. For example:
*
- * @code
+ * ```
* $errorContainer = new CRM_Queue_ErrorPolicy();
* $errorContainer->call(function() {
* ...include some files, do some work, etc...
* });
- * @endcode
+ * ```
*
* Note: Most of the code in this class is pretty generic vis-a-vis error
* handling -- except for 'reportError', whose message format is only
* different queue-providers may store the queue content in different
* ways (in memory, in SQL, or in an external service).
*
- * @code
+ * ```
* $queue = CRM_Queue_Service::singleton()->create(array(
* 'type' => 'interactive',
* 'name' => 'upgrade-tasks',
* $queue->releaseItem($item);
* }
* }
- * @endcode
+ * ```
*/
class CRM_Queue_Service {
$contributionTypes = CRM_Contribute_PseudoConstant::financialType();
$contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'label');
$paymentInstruments = CRM_Contribute_PseudoConstant::paymentInstrument();
- $contributionPages = CRM_Contribute_PseudoConstant::contributionPage();
+ // We pass in TRUE as 2nd param so that even disabled contribution page titles are returned and replaced in the report
+ $contributionPages = CRM_Contribute_PseudoConstant::contributionPage(NULL, TRUE);
$batches = CRM_Batch_BAO_Batch::getBatches();
foreach ($rows as $rowNum => $row) {
if (!empty($this->_noRepeats) && $this->_outputMode != 'csv') {
*/
const MIN_INSTALL_PHP_VER = '7.1.0';
+ /**
+ * The minimum recommended MySQL/MariaDB version.
+ *
+ * A site running an earlier version will be told to upgrade.
+ */
+ const MIN_RECOMMENDED_MYSQL_VER = '5.7';
+
+ /**
+ * The minimum MySQL/MariaDB version required to install Civi.
+ *
+ * @see install/index.php
+ */
+ const MIN_INSTALL_MYSQL_VER = '5.5';
+
/**
* Compute any messages which should be displayed before upgrade.
*
* (change the x in the function name):
*/
- // /**
- // * Upgrade function.
- // *
- // * @param string $rev
- // */
- // public function upgrade_5_0_x($rev) {
- // $this->addTask(ts('Upgrade DB to %1: SQL', [1 => $rev]), 'runSql', $rev);
- // $this->addTask('Do the foo change', 'taskFoo', ...);
- // // Additional tasks here...
- // // Note: do not use ts() in the addTask description because it adds unnecessary strings to transifex.
- // // The above is an exception because 'Upgrade DB to %1: SQL' is generic & reusable.
- // }
-
- // public static function taskFoo(CRM_Queue_TaskContext $ctx, ...) {
- // return TRUE;
- // }
+ /**
+ * Upgrade function.
+ *
+ * @param string $rev
+ */
+ public function upgrade_5_27_alpha1($rev) {
+ // Add column before running sql which populates the column's values
+ $this->addTask('Add serialize column to civicrm_custom_field', 'addColumn',
+ 'civicrm_custom_field', 'serialize', "int unsigned DEFAULT NULL COMMENT 'Serialization method - a non-null value indicates a multi-valued field.'"
+ );
+ $this->addTask(ts('Upgrade DB to %1: SQL', [1 => $rev]), 'runSql', $rev);
+ }
}
{* file to handle db changes in 5.27.alpha1 during upgrade *}
+
+UPDATE civicrm_custom_field SET serialize = 1, html_type = REPLACE(html_type, 'Multi-', '')
+WHERE html_type LIKE 'Multi-%' OR html_type = 'CheckBox';
+
+ALTER TABLE `civicrm_contribution_recur` CHANGE `amount` `amount` DECIMAL( 20,2 ) COMMENT 'Amount to be collected (including any sales tax) by payment processor each recurrence.';
* - "match-mandatory" will generate an error
* - "match" will allow action to proceed -- thus inserting a new record
*
- * @code
+ * ```
* $result = civicrm_api('contact', 'create', array(
* 'options' => array(
* 'match' => array('last_name', 'first_name')
* 'last_name' => 'Lebowski',
* 'nick_name' => 'The Dude',
* ));
- * @endcode
+ * ```
*
* @package CRM
* @copyright CiviCRM LLC https://civicrm.org/licensing
* Implement the "reload" option. This option can be used with "create" to force
* the API to reload a clean copy of the entity before returning the result.
*
- * @code
+ * ```
* $clean = civicrm_api('myentity', 'create', array(
* 'options' => array(
* 'reload' => 1
* ),
* ));
- * @endcode
+ * ```
*
* @package CRM
* @copyright CiviCRM LLC https://civicrm.org/licensing
/**
* Call a cleanup function when the current context shuts down.
*
- * @code
+ * ```
* function doStuff() {
* $ac = CRM_Utils_AutoClean::with(function(){
* MyCleanup::doIt();
* });
* ...
* }
- * @endcode
+ * ```
*
* @param mixed $callback
* @return CRM_Utils_AutoClean
* Temporarily swap values using callback functions, and cleanup
* when the current context shuts down.
*
- * @code
+ * ```
* function doStuff() {
* $ac = CRM_Utils_AutoClean::swap('My::get', 'My::set', 'tmpValue');
* ...
* }
- * @endcode
+ * ```
*
* @param mixed $getter
* Function to lookup current value.
return $messages;
}
+ public function checkMysqlVersion() {
+ $messages = [];
+ $version = CRM_Utils_SQL::getDatabaseVersion();
+ $minInstallVersion = CRM_Upgrade_Incremental_General::MIN_INSTALL_MYSQL_VER;
+ $minRecommendedVersion = CRM_Upgrade_Incremental_General::MIN_RECOMMENDED_MYSQL_VER;
+ if (version_compare(CRM_Utils_SQL::getDatabaseVersion(), $minInstallVersion, '<')) {
+ $messages[] = new CRM_Utils_Check_Message(
+ __FUNCTION__,
+ ts('This system uses MySQL/MariaDB version %1. To ensure the continued operation of CiviCRM, upgrade your server now. At least MySQL version %2 or MariaDB version %3 is recommended', [
+ 1 => $version,
+ 2 => $minRecommendedVersion,
+ 3 => '10.1',
+ ]),
+ ts('MySQL Out of date'),
+ \Psr\Log\LogLevel::WARNING,
+ 'fa-server'
+ );
+ }
+ return $messages;
+ }
+
}
/**
* Capture the output from the console, copy it to a file, and pass it on.
*
- * @code
+ * ```
* $tee = CRM_Utils_ConsoleTee::create()->start();
* echo "hello world";
* $tee->stop();
* assertEquals("hello world", file_get_contents($tee->getFileName()));
- * @endCode
+ * ```
*
* Loosely speaking, it serves a similar purpose to Unix `tee`.
*
* This is a quick-and-dirty way to define a vaguely-class-ish structure. It's non-performant, abnormal,
* and not a complete OOP system. Only use for testing/mocking.
*
- * @code
+ * ```
* $object = new CRM_Utils_FakeObject(array(
* 'doIt' => function() { print "It!\n"; }
* ));
* $object->doIt();
- * @endcode
+ * ```
*/
class CRM_Utils_FakeObject {
* @param string $mimeType the mime-type we want extensions for
* @return array
*/
- public static function getAcceptableExtensionsForMimeType($mimeType = NULL) {
+ public static function getAcceptableExtensionsForMimeType($mimeType = []) {
$mimeRepostory = new \MimeTyper\Repository\ExtendedRepository();
return $mimeRepostory->findExtensions($mimeType);
}
/**
* Temporarily change a global variable.
*
- * @code
+ * ```
* $globals = CRM_Utils_GlobalStack::singleton();
* $globals->push(array(
* '_GET' => array(
* ));
* ...do stuff...
* $globals->pop();
- * @endcode
+ * ```
*
* Note: for purposes of this class, we'll refer to the array passed into
* push() as a frame.
* If omitted, default to "array('civicrm/a')" for backward compat.
* For a utility that should only be loaded on-demand, use "array()".
* For a utility that should be loaded in all pages use, "array('*')".
- * @return null
- * the return value is ignored
*
- * @code
+ * ```
* function mymod_civicrm_angularModules(&$angularModules) {
* $angularModules['myAngularModule'] = array(
* 'ext' => 'org.example.mymod',
* 'basePages' => array('civicrm/a'),
* );
* }
- * @endcode
+ * ```
+ *
+ * @return null
+ * the return value is ignored
*/
public static function angularModules(&$angularModules) {
return self::singleton()->invoke(['angularModules'], $angularModules,
*
* @param \Civi\Angular\Manager $angular
*
- * @code
+ * ```
* function example_civicrm_alterAngular($angular) {
* $changeSet = \Civi\Angular\ChangeSet::create('mychanges')
* ->alterHtml('~/crmMailing/EditMailingCtrl/2step.html', function(phpQueryObject $doc) {
* );
* $angular->add($changeSet);
* }
- * @endCode
+ * ```
*/
public static function alterAngular($angular) {
$event = \Civi\Core\Event\GenericHookEvent::create([
/**
* Modify the CiviCRM container - add new services, parameters, extensions, etc.
*
- * @code
+ * ```
* use Symfony\Component\Config\Resource\FileResource;
* use Symfony\Component\DependencyInjection\Definition;
*
* $container->addResource(new FileResource(__FILE__));
* $container->setDefinition('mysvc', new Definition('My\Class', array()));
* }
- * @endcode
+ * ```
*
* Tip: The container configuration will be compiled/cached. The default cache
* behavior is aggressive. When you first implement the hook, be sure to
* @var array
* Description of export field mapping
*
- * @code
+ * ```
* 'exampleEntityMappingName' => array(
* 'data' => array(), // placeholder; this will get filled-in during execution
* 'name' => 'CustomGroup', // per-item XML tag name
* 'idNameFields' => array('id', 'name'), // name of the (local/autogenerated) "id" and (portable) "name" columns
* 'idNameMap' => array(), // placeholder; this will get filled-in during execution
* ),
- * @endcode
+ * ```
*/
protected $_xml;
/**
* Get the value of a SQL parameter.
*
- * @code
+ * ```
* $select['cid'] = 123;
* $select->where('contact.id = #cid');
* echo $select['cid'];
- * @endCode
+ * ```
*
* @param string $offset
* @return mixed
/**
* Set the value of a SQL parameter.
*
- * @code
+ * ```
* $select['cid'] = 123;
* $select->where('contact.id = #cid');
* echo $select['cid'];
- * @endCode
+ * ```
*
* @param string $offset
* @param mixed $value
* Dear God Why Do I Have To Write This (Dumb SQL Builder)
*
* Usage:
- * @code
+ * ```
* $del = CRM_Utils_SQL_Delete::from('civicrm_activity act')
* ->where('activity_type_id = #type', array('type' => 234))
* ->where('status_id IN (#statuses)', array('statuses' => array(1,2,3))
* 'value' => $form['foo']
* ))
* echo $del->toSQL();
- * @endcode
+ * ```
*
* Design principles:
* - Portable
* xor output. The notations for input and output interpolation are a bit different,
* and they may not be mixed.
*
- * @code
+ * ```
* // Interpolate on input. Set params when using them.
* $select->where('activity_type_id = #type', array(
* 'type' => 234,
* $select
* ->where('activity_type_id = #type')
* ->param('type', 234),
- * @endcode
+ * ```
*
* @package CRM
* @copyright CiviCRM LLC https://civicrm.org/licensing
* Dear God Why Do I Have To Write This (Dumb SQL Builder)
*
* Usage:
- * @code
+ * ```
* $select = CRM_Utils_SQL_Select::from('civicrm_activity act')
* ->join('absence', 'inner join civicrm_activity absence on absence.id = act.source_record_id')
* ->where('activity_type_id = #type', array('type' => 234))
* 'value' => $form['foo']
* ))
* echo $select->toSQL();
- * @endcode
+ * ```
*
* Design principles:
* - Portable
* xor output. The notations for input and output interpolation are a bit different,
* and they may not be mixed.
*
- * @code
+ * ```
* // Interpolate on input. Set params when using them.
* $select->where('activity_type_id = #type', array(
* 'type' => 234,
* $select
* ->where('activity_type_id = #type')
* ->param('type', 234),
- * @endcode
+ * ```
*
* @package CRM
* @copyright CiviCRM LLC https://civicrm.org/licensing
*
* FIXME: Add TTL support?
*
- * @code
+ * ```
* $signer = new CRM_Utils_Signer('myprivatekey', array('param1','param2'));
* $params = array(
* 'param1' => 'hello',
* $token = $signer->sign($params);
* ...
* assertTrue($signer->validate($token, $params));
- * @endcode
+ * ```
*/
class CRM_Utils_Signer {
/**
}
/**
- * Convert possibly underscore separated words to camel case with special handling for 'UF'
- * e.g membership_payment returns MembershipPayment
- *
- * @param string $string
+ * Convert possibly underscore separated words to camel case.
*
+ * @param string $str
+ * @param bool $ucFirst
+ * Should the first letter be capitalized like `CamelCase` or lower like `camelCase`
* @return string
*/
- public static function convertStringToCamel($string) {
- $map = [
- 'acl' => 'Acl',
- 'ACL' => 'Acl',
- 'im' => 'Im',
- 'IM' => 'Im',
- ];
- if (isset($map[$string])) {
- return $map[$string];
- }
-
- $fragments = explode('_', $string);
- foreach ($fragments as & $fragment) {
- $fragment = ucfirst($fragment);
- // Special case: UFGroup, UFJoin, UFMatch, UFField (if passed in without underscores)
- if (strpos($fragment, 'Uf') === 0 && strlen($string) > 2) {
- $fragment = 'UF' . ucfirst(substr($fragment, 2));
- }
- }
- // Special case: UFGroup, UFJoin, UFMatch, UFField (if passed in underscore-separated)
- if ($fragments[0] === 'Uf') {
- $fragments[0] = 'UF';
- }
- return implode('', $fragments);
+ public static function convertStringToCamel($str, $ucFirst = TRUE) {
+ $fragments = explode('_', $str);
+ $camel = implode('', array_map('ucfirst', $fragments));
+ return $ucFirst ? $camel : lcfirst($camel);
}
/**
return $form_state['user']->uid;
}
+ /**
+ * Appends a Drupal 7 Javascript file when the CRM Menubar Javascript file has
+ * been included. The file is added before the menu bar so we can properly listen
+ * for the menu bar ready event.
+ */
+ public function appendCoreResources(\Civi\Core\Event\GenericHookEvent $event) {
+ $menuBarFileIndex = array_search('js/crm.menubar.js', $event->list);
+
+ if ($menuBarFileIndex !== FALSE) {
+ array_splice($event->list, $menuBarFileIndex, 0, ['js/crm.drupal7.js']);
+ }
+ }
+
/**
* @inheritDoc
*/
*
* For example, 'civicrm/contact/view?reset=1&cid=66' will be returned as:
*
- * @code
+ * ```
* array(
* 'path' => 'civicrm/contact/view',
* 'route' => 'civicrm.civicrm_contact_view',
* 'query' => array('reset' => '1', 'cid' => '66'),
* );
- * @endcode
+ * ```
*
* @param string $url
* The url to parse.
// pre-existing logic
if (isset($path)) {
- $queryParts[] = 'page=CiviCRM';
+ // Admin URLs still need "page=CiviCRM", front-end URLs do not.
+ if ((is_admin() && !$frontend) || $forceBackend) {
+ $queryParts[] = 'page=CiviCRM';
+ }
+ else {
+ $queryParts[] = 'civiwp=CiviCRM';
+ }
$queryParts[] = 'q=' . rawurlencode($path);
}
if ($wpPageParam) {
*
* @param string $url
*
- * @return \GuzzleHttp\Psr7\UriInterface
+ * @return \Psr\Http\Message\UriInterface
*/
public static function parseUrl($url) {
return new Uri($url);
/**
* Unparse url back to a string.
*
- * @param \GuzzleHttp\Psr7\UriInterface $parsed
+ * @param \Psr\Http\Message\UriInterface $parsed
*
* @return string
*/
/**
* A central location for static variable storage.
* @var array
- * @code
+ * ```
* `Civi::$statics[__CLASS__]['foo'] = 'bar';
- * @endcode
+ * ```
*/
public static $statics = array();
* @return string
*/
public static function normalizeEntityName($entity) {
- return \CRM_Utils_String::convertStringToCamel(\CRM_Utils_String::munge($entity));
+ return \CRM_Core_DAO_AllCoreTables::convertEntityNameToCamel(\CRM_Utils_String::munge($entity), TRUE);
}
/**
* Get acl clause for an entity
*
* @param string $tableAlias
- * @param string $baoName
+ * @param \CRM_Core_DAO|string $baoName
* @param array $stack
* @return array
*/
* The ChainSubscriber looks for API parameters which specify a nested or
* chained API call. For example:
*
- * @code
+ * ```
* $result = civicrm_api('Contact', 'create', array(
* 'version' => 3,
* 'first_name' => 'Amy',
* 'location_type_id' => 123,
* ),
* ));
- * @endcode
+ * ```
*
* The ChainSubscriber looks for any parameters of the form "api.Email.create";
* if found, it issues the nested API call (and passes some extra context --
namespace Civi\API\Subscriber;
use Civi\API\Events;
+use CRM_Core_DAO_AllCoreTables as AllCoreTables;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
*/
public function __construct($kernel, $entityName, $actions, $lookupDelegateSql, $lookupCustomFieldSql, $allowedDelegates = NULL) {
$this->kernel = $kernel;
- $this->entityName = \CRM_Utils_String::convertStringToCamel($entityName);
+ $this->entityName = AllCoreTables::convertEntityNameToCamel($entityName, TRUE);
$this->actions = $actions;
$this->lookupDelegateSql = $lookupDelegateSql;
$this->lookupCustomFieldSql = $lookupCustomFieldSql;
*/
public function onApiAuthorize(\Civi\API\Event\AuthorizeEvent $event) {
$apiRequest = $event->getApiRequest();
- if ($apiRequest['version'] == 3 && \CRM_Utils_String::convertStringToCamel($apiRequest['entity']) == $this->entityName && in_array(strtolower($apiRequest['action']), $this->actions)) {
+ if ($apiRequest['version'] == 3 && AllCoreTables::convertEntityNameToCamel($apiRequest['entity'], TRUE) == $this->entityName && in_array(strtolower($apiRequest['action']), $this->actions)) {
if (isset($apiRequest['params']['field_name'])) {
$fldIdx = \CRM_Utils_Array::index(['field_name'], $this->getCustomFields());
if (empty($fldIdx[$apiRequest['params']['field_name']])) {
* A WhitelistRule is used to determine if an API call is authorized.
* For example:
*
- * @code
+ * ```
* new WhitelistRule(array(
* 'entity' => 'Contact',
* 'actions' => array('get','getsingle'),
* 'required' => array('contact_type' => 'Organization'),
* 'fields' => array('id', 'display_name', 'sort_name', 'created_date'),
* ));
- * @endcode
+ * ```
*
* This rule would allow API requests that attempt to get contacts of type "Organization",
* but only a handful of fields ('id', 'display_name', 'sort_name', 'created_date')
*
* The basic mailing query looks a bit like this (depending on configuration):
*
- * @code
+ * ```
* SELECT reminder.id AS reminderID, reminder.contact_id as contactID, ...
* FROM `civicrm_action_log` reminder
* ... JOIN `target_entity` e ON e.id = reminder.entity_id ...
* WHERE reminder.action_schedule_id = #casActionScheduleId
- * @endcode
+ * ```
*
* Listeners may modify the query. For example, suppose we want to load
* additional fields from the related 'foo' entity:
*
- * @code
+ * ```
* $event->query->join('foo', '!casMailingJoinType civicrm_foo foo ON foo.myentity_id = e.id')
* ->select('foo.bar_value AS bar');
- * @endcode
+ * ```
*
* There are several parameters pre-set for use in queries:
* - 'casActionScheduleId'
* to fire the reminders X days after the registration date. The
* MappingInterface::createQuery() could return a query like:
*
- * @code
+ * ```
* CRM_Utils_SQL_Select::from('civicrm_participant e')
* ->join('event', 'INNER JOIN civicrm_event event ON e.event_id = event.id')
* ->where('e.is_pay_later = 1')
* ->param('casDateField', 'e.register_date')
* ->param($defaultParams)
* ...etc...
- * @endcode
+ * ```
*
* In the RELATION_FIRST phase, RecipientBuilder adds a LEFT-JOIN+WHERE to find
* participants who have *not* yet received any reminder, and filters those
* The AngularLoader stops short of bootstrapping AngularJS. You may
* need to `<div ng-app="..."></div>` or `angular.bootstrap(...)`.
*
- * @code
+ * ```
* $loader = new AngularLoader();
* $loader->setPageName('civicrm/case/a');
* $loader->setModules(array('crmApp'));
* $loader->load();
- * @endCode
+ * ```
*
* @link https://docs.angularjs.org/guide/bootstrap
*/
+--------------------------------------------------------------------+
*/
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
namespace Civi\Api4\Action\CustomValue;
use Civi\Api4\Service\Spec\SpecFormatter;
+--------------------------------------------------------------------+
*/
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
-
namespace Civi\Api4\Action;
use Civi\API\Exception\NotImplementedException;
+--------------------------------------------------------------------+
*/
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
-
namespace Civi\Api4\Action\GroupContact;
/**
+--------------------------------------------------------------------+
*/
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
namespace Civi\Api4\Action\Relationship;
/**
+--------------------------------------------------------------------+
*/
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
namespace Civi\Api4\Action\Setting;
use Civi\Api4\Domain;
+--------------------------------------------------------------------+
*/
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
namespace Civi\Api4\Action\Setting;
use Civi\Api4\Generic\Result;
+--------------------------------------------------------------------+
*/
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
namespace Civi\Api4\Action\Setting;
/**
+--------------------------------------------------------------------+
*/
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
namespace Civi\Api4\Action\Setting;
use Civi\Api4\Generic\Result;
class Events {
- /**
- * Prepare the specification for a request. Fired from within a request to
- * get fields.
- *
- * @see \Civi\Api4\Event\GetSpecEvent
- */
- const GET_SPEC = 'civi.api.get_spec';
-
/**
* Build the database schema, allow adding of custom joins and tables.
*/
const SCHEMA_MAP_BUILD = 'api.schema_map.build';
- /**
- * Alter query results of APIv4 select query
- */
- const POST_SELECT_QUERY = 'api.select_query.post';
-
}
+++ /dev/null
-<?php
-
-/*
- +--------------------------------------------------------------------+
- | Copyright CiviCRM LLC. All rights reserved. |
- | |
- | This work is published under the GNU AGPLv3 license with some |
- | permitted exceptions and without any warranty. For full license |
- | and copyright information, see https://civicrm.org/licensing |
- +--------------------------------------------------------------------+
- */
-
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
-
-namespace Civi\Api4\Event;
-
-use Civi\Api4\Generic\AbstractAction;
-use Symfony\Component\EventDispatcher\Event as BaseEvent;
-
-class GetSpecEvent extends BaseEvent {
- /**
- * @var \Civi\Api4\Generic\AbstractAction
- */
- protected $request;
-
- /**
- * @param \Civi\Api4\Generic\AbstractAction $request
- */
- public function __construct(AbstractAction $request) {
- $this->request = $request;
- }
-
- /**
- * @return \Civi\Api4\Generic\AbstractAction
- */
- public function getRequest() {
- return $this->request;
- }
-
- /**
- * @param $request
- */
- public function setRequest(AbstractAction $request) {
- $this->request = $request;
- }
-
-}
+++ /dev/null
-<?php
-
-/*
- +--------------------------------------------------------------------+
- | Copyright CiviCRM LLC. All rights reserved. |
- | |
- | This work is published under the GNU AGPLv3 license with some |
- | permitted exceptions and without any warranty. For full license |
- | and copyright information, see https://civicrm.org/licensing |
- +--------------------------------------------------------------------+
- */
-
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
-
-namespace Civi\Api4\Event\Subscriber;
-
-use Civi\Api4\Event\Events;
-use Civi\Api4\Event\SchemaMapBuildEvent;
-use Civi\Api4\Service\Schema\Joinable\Joinable;
-use Symfony\Component\EventDispatcher\EventSubscriberInterface;
-
-class ContactSchemaMapSubscriber implements EventSubscriberInterface {
-
- /**
- * @return array
- */
- public static function getSubscribedEvents() {
- return [
- Events::SCHEMA_MAP_BUILD => 'onSchemaBuild',
- ];
- }
-
- /**
- * @param \Civi\Api4\Event\SchemaMapBuildEvent $event
- */
- public function onSchemaBuild(SchemaMapBuildEvent $event) {
- $schema = $event->getSchemaMap();
- $table = $schema->getTableByName('civicrm_contact');
- $this->addCreatedActivitiesLink($table);
- $this->fixPreferredLanguageAlias($table);
- }
-
- /**
- * @param \Civi\Api4\Service\Schema\Table $table
- */
- private function addCreatedActivitiesLink($table) {
- $alias = 'created_activities';
- $joinable = new Joinable('civicrm_activity_contact', 'contact_id', $alias);
- $joinable->addCondition($alias . '.record_type_id = 1');
- $joinable->setJoinType($joinable::JOIN_TYPE_ONE_TO_MANY);
- $table->addTableLink('id', $joinable);
- }
-
- /**
- * @param \Civi\Api4\Service\Schema\Table $table
- */
- private function fixPreferredLanguageAlias($table) {
- foreach ($table->getExternalLinks() as $link) {
- if ($link->getAlias() === 'languages') {
- $link->setAlias('preferred_language');
- return;
- }
- }
- }
-
-}
+++ /dev/null
-<?php
-
-/*
- +--------------------------------------------------------------------+
- | Copyright CiviCRM LLC. All rights reserved. |
- | |
- | This work is published under the GNU AGPLv3 license with some |
- | permitted exceptions and without any warranty. For full license |
- | and copyright information, see https://civicrm.org/licensing |
- +--------------------------------------------------------------------+
- */
-
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
-
-namespace Civi\Api4\Event\Subscriber;
-
-use Civi\Api4\Event\Events;
-use Civi\Api4\Event\PostSelectQueryEvent;
-use Civi\Api4\Query\Api4SelectQuery;
-use Civi\Api4\Utils\ArrayInsertionUtil;
-use Civi\Api4\Utils\FormattingUtil;
-use Symfony\Component\EventDispatcher\EventSubscriberInterface;
-
-/**
- * Changes the results of a select query, doing 1-n joins and unserializing data
- */
-class PostSelectQuerySubscriber implements EventSubscriberInterface {
-
- /**
- * @inheritDoc
- */
- public static function getSubscribedEvents() {
- return [
- Events::POST_SELECT_QUERY => 'onPostQuery',
- ];
- }
-
- /**
- * @param \Civi\Api4\Event\PostSelectQueryEvent $event
- */
- public function onPostQuery(PostSelectQueryEvent $event) {
- $results = $event->getResults();
- $event->setResults($this->postRun($results, $event->getQuery()));
- }
-
- /**
- * @param array $results
- * @param \Civi\Api4\Query\Api4SelectQuery $query
- *
- * @return array
- */
- protected function postRun(array $results, Api4SelectQuery $query) {
- if (empty($results)) {
- return $results;
- }
-
- FormattingUtil::formatOutputValues($results, $query->getApiFieldSpec(), $query->getEntity());
-
- // Group the selects to avoid queries for each field
- $groupedSelects = $this->getNtoManyJoinSelects($query);
- foreach ($groupedSelects as $finalAlias => $selects) {
- $joinPath = $query->getPathJoinTypes($selects[0]);
- $selects = $this->formatSelects($finalAlias, $selects, $query);
- $joinResults = $this->getJoinResults($query, $finalAlias, $selects);
- $this->formatJoinResults($joinResults, $query, $finalAlias);
-
- // Insert join results into original result
- foreach ($results as &$primaryResult) {
- $baseId = $primaryResult['id'];
- $filtered = array_filter($joinResults, function ($res) use ($baseId) {
- return ($res['_base_id'] == $baseId);
- });
- $filtered = array_values($filtered);
- ArrayInsertionUtil::insert($primaryResult, $joinPath, $filtered);
- }
- }
-
- return array_values($results);
- }
-
- /**
- * @param array $joinResults
- * @param \Civi\Api4\Query\Api4SelectQuery $query
- * @param string $alias
- */
- private function formatJoinResults(&$joinResults, $query, $alias) {
- $join = $query->getJoinedTable($alias);
- $fields = [];
- foreach ($join->getEntityFields() as $field) {
- $name = explode('.', $field->getName());
- $fields[array_pop($name)] = $field->toArray();
- }
- if ($fields) {
- FormattingUtil::formatOutputValues($joinResults, $fields, $join->getEntity());
- }
- }
-
- /**
- * Find only those joins that need to be handled by a separate query and weren't done in the main query.
- *
- * @param \Civi\Api4\Query\Api4SelectQuery $query
- *
- * @return array
- */
- private function getNtoManyJoinSelects(Api4SelectQuery $query) {
- $joinedDotSelects = array_filter(
- $query->getSelect(),
- function ($select) use ($query) {
- return strpos($select, '.') && array_filter($query->getPathJoinTypes($select));
- }
- );
-
- $selects = [];
- // group related selects by alias so they can be executed in one query
- foreach ($joinedDotSelects as $select) {
- $parts = explode('.', $select);
- $finalAlias = $parts[count($parts) - 2];
- $selects[$finalAlias][] = $select;
- }
-
- // sort by depth, e.g. email selects should be done before email.location
- uasort($selects, function ($a, $b) {
- $aFirst = $a[0];
- $bFirst = $b[0];
- return substr_count($aFirst, '.') > substr_count($bFirst, '.');
- });
-
- return $selects;
- }
-
- /**
- * @param array $selects
- * @param $serializationType
- * @param \Civi\Api4\Query\Api4SelectQuery $query
- *
- * @return array
- */
- private function getResultsForSerializedField(
- array $selects,
- $serializationType,
- Api4SelectQuery $query
- ) {
- // Get the alias (Selects are grouped and all target the same table)
- $sampleField = current($selects);
- $alias = strstr($sampleField, '.', TRUE);
-
- // Fetch the results with the serialized field
- $selects['serialized'] = $query::MAIN_TABLE_ALIAS . '.' . $alias;
- $serializedResults = $this->runWithNewSelects($selects, $query);
- $newResults = [];
-
- // Create a new results array, with a separate entry for each option value
- foreach ($serializedResults as $result) {
- $optionValues = \CRM_Core_DAO::unSerializeField(
- $result['serialized'],
- $serializationType
- );
- unset($result['serialized']);
- foreach ($optionValues as $value) {
- $newResults[] = array_merge($result, ['value' => $value]);
- }
- }
-
- $optionValueValues = array_unique(array_column($newResults, 'value'));
- $optionValues = $this->getOptionValuesFromValues(
- $selects,
- $query,
- $optionValueValues
- );
- $valueField = $alias . '.value';
-
- // Index by value
- foreach ($optionValues as $key => $subResult) {
- $optionValues[$subResult['value']] = $subResult;
- unset($subResult[$key]);
-
- // Exclude 'value' if not in original selects
- if (!in_array($valueField, $selects)) {
- unset($optionValues[$subResult['value']]['value']);
- }
- }
-
- // Replace serialized with the sub-select results
- foreach ($newResults as &$result) {
- $result = array_merge($result, $optionValues[$result['value']]);
- unset($result['value']);
- }
-
- return $newResults;
- }
-
- /**
- * Prepares selects for the subquery to fetch join results
- *
- * @param string $alias
- * @param array $selects
- * @param \Civi\Api4\Query\Api4SelectQuery $query
- *
- * @return array
- */
- private function formatSelects($alias, $selects, Api4SelectQuery $query) {
- $mainAlias = $query::MAIN_TABLE_ALIAS;
- $selectFields = [];
-
- foreach ($selects as $select) {
- $selectAlias = str_replace('`', '', $query->getField($select)['sql_name']);
- $fieldAlias = substr($select, strrpos($select, '.') + 1);
- $selectFields[$fieldAlias] = $selectAlias;
- }
-
- $firstSelect = $selects[0];
- $pathParts = explode('.', $firstSelect);
- $numParts = count($pathParts);
- $parentAlias = $numParts > 2 ? $pathParts[$numParts - 3] : $mainAlias;
-
- $selectFields['id'] = sprintf('%s.id', $alias);
- $selectFields['_parent_id'] = $parentAlias . '.id';
- $selectFields['_base_id'] = $mainAlias . '.id';
-
- return $selectFields;
- }
-
- /**
- * @param array $selects
- * @param \Civi\Api4\Query\Api4SelectQuery $query
- *
- * @return array
- */
- private function runWithNewSelects(array $selects, Api4SelectQuery $query) {
- $aliasedSelects = array_map(function ($field, $alias) {
- return sprintf('%s as "%s"', $field, $alias);
- }, $selects, array_keys($selects));
-
- $newSelect = sprintf('SELECT DISTINCT %s', implode(", ", $aliasedSelects));
- $sql = $query->getQuery()->toSQL();
- // Replace the "SELECT" clause
- $sql = $newSelect . substr($sql, strpos($sql, "\nFROM"));
-
- if (is_array($query->debugOutput)) {
- $query->debugOutput['sql'][] = $sql;
- }
-
- $relatedResults = [];
- $resultDAO = \CRM_Core_DAO::executeQuery($sql);
- while ($resultDAO->fetch()) {
- $relatedResult = [];
- foreach ($selects as $alias => $column) {
- $returnName = $alias;
- $alias = str_replace('.', '_', $alias);
- if (property_exists($resultDAO, $alias)) {
- $relatedResult[$returnName] = $resultDAO->$alias;
- }
- };
- $relatedResults[] = $relatedResult;
- }
-
- return $relatedResults;
- }
-
- /**
- * @param \Civi\Api4\Query\Api4SelectQuery $query
- * @param $alias
- * @param $selects
- * @return array
- */
- protected function getJoinResults(Api4SelectQuery $query, $alias, $selects) {
- $apiFieldSpec = $query->getApiFieldSpec();
- if (!empty($apiFieldSpec[$alias]['serialize'])) {
- $type = $apiFieldSpec[$alias]['serialize'];
- $joinResults = $this->getResultsForSerializedField($selects, $type, $query);
- }
- else {
- $joinResults = $this->runWithNewSelects($selects, $query);
- }
-
- // Remove results with no matching entries
- $joinResults = array_filter($joinResults, function ($result) {
- return !empty($result['id']);
- });
-
- return $joinResults;
- }
-
- /**
- * Get all the option_value values required in the query
- *
- * @param array $selects
- * @param \Civi\Api4\Query\Api4SelectQuery $query
- * @param array $values
- *
- * @return array
- */
- private function getOptionValuesFromValues(
- array $selects,
- Api4SelectQuery $query,
- array $values
- ) {
- $sampleField = current($selects);
- $alias = strstr($sampleField, '.', TRUE);
-
- // Get the option value table that was joined
- $relatedTable = NULL;
- foreach ($query->getJoinedTables() as $joinedTable) {
- if ($joinedTable->getAlias() === $alias) {
- $relatedTable = $joinedTable;
- }
- }
-
- // We only want subselects related to the joined table
- $subSelects = array_filter($selects, function ($select) use ($alias) {
- return strpos($select, $alias) === 0;
- });
-
- // Fetch all related option_value entries
- $valueField = $alias . '.value';
- $subSelects[] = $valueField;
- $tableName = $relatedTable->getTargetTable();
- $conditions = $relatedTable->getExtraJoinConditions();
- $conditions[] = $valueField . ' IN ("' . implode('", "', $values) . '")';
- $subQuery = new \CRM_Utils_SQL_Select($tableName . ' ' . $alias);
- $subQuery->where($conditions);
- $subQuery->select($subSelects);
- $subResults = $subQuery->execute()->fetchAll();
-
- return $subResults;
- }
-
-}
$permissions = \CRM_Core_Permission::getEntityActionPermissions();
// For legacy reasons the permissions are keyed by lowercase entity name
- // Note: Convert to camel & back in order to circumvent all the api3 naming oddities
- $lcentity = _civicrm_api_get_entity_name_from_camel(\CRM_Utils_String::convertStringToCamel(self::getEntityName()));
+ $lcentity = \CRM_Core_DAO_AllCoreTables::convertEntityNameToLower(self::getEntityName());
// Merge permissions for this entity with the defaults
- return \CRM_Utils_Array::value($lcentity, $permissions, []) + $permissions['default'];
+ return ($permissions[$lcentity] ?? []) + $permissions['default'];
}
/**
+--------------------------------------------------------------------+
*/
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
-
namespace Civi\Api4\Generic;
/**
/**
* @return DAOGetAction
+ *
+ * @throws \API_Exception
*/
public static function get() {
return new DAOGetAction(static::class, __FUNCTION__);
/**
* @return DAOCreateAction
+ *
+ * @throws \API_Exception
*/
public static function create() {
return new DAOCreateAction(static::class, __FUNCTION__);
*/
protected $select = [];
+ /**
+ * Joins to other entities.
+ *
+ * @var array
+ */
+ protected $join = [];
+
/**
* Field(s) by which to group the results.
*
return $this;
}
+ /**
+ * @param string $entity
+ * @param bool $required
+ * @param array ...$conditions
+ * @return DAOGetAction
+ */
+ public function addJoin(string $entity, bool $required = FALSE, ...$conditions): DAOGetAction {
+ array_unshift($conditions, $entity, $required);
+ $this->join[] = $conditions;
+ return $this;
+ }
+
+ /**
+ * @param array $join
+ * @return DAOGetAction
+ */
+ public function setJoin(array $join): DAOGetAction {
+ $this->join = $join;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getJoin(): array {
+ return $this->join;
+ }
+
}
+--------------------------------------------------------------------+
*/
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
namespace Civi\Api4\Generic\Traits;
use Civi\Api4\Utils\FormattingUtil;
/**
* @method string getLanguage()
- * @method setLanguage(string $language)
+ * @method $this setLanguage(string $language)
*/
trait DAOActionTrait {
*
* @param array $items
* The records to write to the DB.
+ *
* @return array
* The records after being written to the DB (e.g. including newly assigned "id").
* @throws \API_Exception
+ * @throws \CRM_Core_Exception
*/
protected function writeObjects($items) {
$baoName = $this->getBaoName();
$item['check_permissions'] = $this->getCheckPermissions();
// For some reason the contact bao requires this
- if ($entityId && $this->getEntityName() == 'Contact') {
+ if ($entityId && $this->getEntityName() === 'Contact') {
$item['contact_id'] = $entityId;
}
$this->checkContactPermissions($baoName, $item);
}
- if ($this->getEntityName() == 'Address') {
+ if ($this->getEntityName() === 'Address') {
$createResult = $baoName::add($item, $this->fixAddress);
}
elseif (method_exists($baoName, $method)) {
/**
* @param array $params
* @param int $entityId
+ *
* @return mixed
+ *
+ * @throws \API_Exception
+ * @throws \CRM_Core_Exception
*/
protected function formatCustomParams(&$params, $entityId) {
$customParams = [];
$value = FormattingUtil::replacePseudoconstant($options, $value, TRUE);
}
- if ($customFieldType == 'CheckBox') {
+ if ($customFieldType === 'CheckBox') {
// this function should be part of a class
formatCheckBoxField($value, 'custom_' . $customFieldId, $this->getEntityName());
}
/**
* Check edit/delete permissions for contacts and related entities.
*
- * @param $baoName
- * @param $item
+ * @param string $baoName
+ * @param array $item
+ *
* @throws \Civi\API\Exception\UnauthorizedException
*/
protected function checkContactPermissions($baoName, $item) {
- if ($baoName == 'CRM_Contact_BAO_Contact' && !empty($item['id'])) {
- $permission = $this->getActionName() == 'delete' ? \CRM_Core_Permission::DELETE : \CRM_Core_Permission::EDIT;
+ if ($baoName === 'CRM_Contact_BAO_Contact' && !empty($item['id'])) {
+ $permission = $this->getActionName() === 'delete' ? \CRM_Core_Permission::DELETE : \CRM_Core_Permission::EDIT;
if (!\CRM_Contact_BAO_Contact_Permission::allow($item['id'], $permission)) {
throw new \Civi\API\Exception\UnauthorizedException('Permission denied to modify contact record');
}
namespace Civi\Api4\Query;
use Civi\API\SelectQuery;
-use Civi\Api4\Event\Events;
-use Civi\Api4\Event\PostSelectQueryEvent;
use Civi\Api4\Service\Schema\Joinable\CustomGroupJoinable;
-use Civi\Api4\Service\Schema\Joinable\Joinable;
-use Civi\Api4\Service\Schema\Joinable\OptionValueJoinable;
use Civi\Api4\Utils\FormattingUtil;
use Civi\Api4\Utils\CoreUtil;
use Civi\Api4\Utils\SelectUtil;
*/
protected $apiVersion = 4;
- /**
- * @var \Civi\Api4\Service\Schema\Joinable\Joinable[]
- * The joinable tables that have been joined so far
- */
- protected $joinedTables = [];
-
/**
* @var array
* [alias => expr][]
// Add ACLs first to avoid redundant subclauses
$this->query->where($this->getAclClause(self::MAIN_TABLE_ALIAS, $baoName));
+
+ // Add explicit joins. Other joins implied by dot notation may be added later
+ $this->addExplicitJoins($apiGet->getJoin());
}
/**
$this->debugOutput['sql'][] = $sql;
}
$query = \CRM_Core_DAO::executeQuery($sql);
- $i = 0;
while ($query->fetch()) {
- $id = $query->id ?? $i++;
if (in_array('row_count', $this->select)) {
$results[]['row_count'] = (int) $query->c;
break;
}
- $results[$id] = [];
+ $result = [];
foreach ($this->selectAliases as $alias => $expr) {
$returnName = $alias;
$alias = str_replace('.', '_', $alias);
- $results[$id][$returnName] = property_exists($query, $alias) ? $query->$alias : NULL;
+ $result[$returnName] = property_exists($query, $alias) ? $query->$alias : NULL;
}
+ $results[] = $result;
}
- $event = new PostSelectQueryEvent($results, $this);
- \Civi::dispatcher()->dispatch(Events::POST_SELECT_QUERY, $event);
-
- return $event->getResults();
+ FormattingUtil::formatOutputValues($results, $this->getApiFieldSpec(), $this->getEntity());
+ return $results;
}
protected function buildSelectClause() {
});
foreach ($wildFields as $item) {
$pos = array_search($item, array_values($this->select));
- $this->joinFK($item);
+ $this->autoJoinFK($item);
$matches = SelectUtil::getMatchingFields($item, array_keys($this->apiFieldSpec));
array_splice($this->select, $pos, 1, $matches);
}
}
$valid = FALSE;
}
- elseif ($field['is_many']) {
- $valid = FALSE;
- }
}
if ($valid) {
$alias = $expr->getAlias();
if ($dir !== 'ASC' && $dir !== 'DESC') {
throw new \API_Exception("Invalid sort direction. Cannot order by $item $dir");
}
- $expr = SqlExpression::convert($item);
- foreach ($expr->getFields() as $fieldName) {
- $this->getField($fieldName, TRUE);
- }
- $this->query->orderBy($expr->render($this->apiFieldSpec) . " $dir");
+ $this->query->orderBy($this->renderExpression($item) . " $dir");
}
}
*/
protected function buildGroupBy() {
foreach ($this->groupBy as $item) {
- $expr = SqlExpression::convert($item);
- foreach ($expr->getFields() as $fieldName) {
- $this->getField($fieldName, TRUE);
- }
- $this->query->groupBy($expr->render($this->apiFieldSpec));
+ $this->query->groupBy($this->renderExpression($item));
}
}
*
* @param array $clause
* @param string $type
- * WHERE|HAVING
+ * WHERE|HAVING|ON
* @return string SQL where clause
*
* @throws \API_Exception
* Validate and transform a leaf clause array to SQL.
* @param array $clause [$fieldName, $operator, $criteria]
* @param string $type
- * WHERE|HAVING
+ * WHERE|HAVING|ON
* @return string SQL
* @throws \API_Exception
* @throws \Exception
protected function composeClause(array $clause, string $type) {
// Pad array for unary operators
list($expr, $operator, $value) = array_pad($clause, 3, NULL);
+ if (!in_array($operator, \CRM_Core_DAO::acceptedSQLOperators(), TRUE)) {
+ throw new \API_Exception('Illegal operator');
+ }
// For WHERE clause, expr must be the name of a field.
if ($type === 'WHERE') {
$fieldAlias = $field['sql_name'];
}
// For HAVING, expr must be an item in the SELECT clause
- else {
+ elseif ($type === 'HAVING') {
// Expr references a fieldName or alias
if (isset($this->selectAliases[$expr])) {
$fieldAlias = $expr;
}
$fieldAlias = '`' . $fieldAlias . '`';
}
+ elseif ($type === 'ON') {
+ $expr = $this->getExpression($expr);
+ $fieldName = count($expr->getFields()) === 1 ? $expr->getFields()[0] : NULL;
+ $fieldAlias = $expr->render($this->apiFieldSpec);
+ if (is_string($value)) {
+ $valExpr = $this->getExpression($value);
+ if ($fieldName && $valExpr->getType() === 'SqlString') {
+ FormattingUtil::formatInputValue($valExpr->expr, $fieldName, $this->apiFieldSpec[$fieldName]);
+ }
+ return sprintf('%s %s %s', $fieldAlias, $operator, $valExpr->render($this->apiFieldSpec));
+ }
+ elseif ($fieldName) {
+ FormattingUtil::formatInputValue($value, $fieldName, $this->apiFieldSpec[$fieldName]);
+ }
+ }
$sql_clause = \CRM_Core_DAO::createSQLFilter($fieldAlias, [$operator => $value]);
if ($sql_clause === NULL) {
return $sql_clause;
}
+ /**
+ * @param string $expr
+ * @return SqlExpression
+ * @throws \API_Exception
+ */
+ protected function getExpression(string $expr) {
+ $sqlExpr = SqlExpression::convert($expr);
+ foreach ($sqlExpr->getFields() as $fieldName) {
+ $this->getField($fieldName, TRUE);
+ }
+ return $sqlExpr;
+ }
+
+ /**
+ * @param string $expr
+ * @return string
+ * @throws \API_Exception
+ */
+ protected function renderExpression(string $expr) {
+ $sqlExpr = $this->getExpression($expr);
+ return $sqlExpr->render($this->apiFieldSpec);
+ }
+
/**
* @inheritDoc
*/
$fieldName = $col ? substr($expr, 0, $col) : $expr;
// Perform join if field not yet available - this will add it to apiFieldSpec
if (!isset($this->apiFieldSpec[$fieldName]) && strpos($fieldName, '.')) {
- $this->joinFK($fieldName);
+ $this->autoJoinFK($fieldName);
}
$field = $this->apiFieldSpec[$fieldName] ?? NULL;
if ($strict && !$field) {
}
/**
- * Joins a path and adds all fields in the joined eneity to apiFieldSpec
+ * Join onto other entities as specified by the api call.
+ *
+ * @param $joins
+ * @throws \API_Exception
+ * @throws \Civi\API\Exception\NotImplementedException
+ */
+ private function addExplicitJoins($joins) {
+ foreach ($joins as $join) {
+ // First item in the array is the entity name
+ $entity = array_shift($join);
+ // Which might contain an alias. Split on the keyword "AS"
+ list($entity, $alias) = array_pad(explode(' AS ', $entity), 2, NULL);
+ // Ensure alias is a safe string, and supply default if not given
+ $alias = $alias ? \CRM_Utils_String::munge($alias) : strtolower($entity);
+ // First item in the array is a boolean indicating if the join is required (aka INNER or LEFT).
+ // The rest are join conditions.
+ $side = array_shift($join) ? 'INNER' : 'LEFT';
+ $joinEntityGet = \Civi\API\Request::create($entity, 'get', ['version' => 4, 'checkPermissions' => $this->checkPermissions]);
+ foreach ($joinEntityGet->entityFields() as $field) {
+ $field['sql_name'] = '`' . $alias . '`.`' . $field['column_name'] . '`';
+ $field['is_join'] = TRUE;
+ $this->addSpecField($alias . '.' . $field['name'], $field);
+ }
+ $conditions = [];
+ foreach (array_merge($join, $this->getJoinConditions($entity, $alias)) as $clause) {
+ $conditions[] = $this->treeWalkClauses($clause, 'ON');
+ }
+ $tableName = AllCoreTables::getTableForEntityName($entity);
+ $this->join($side, $tableName, $alias, $conditions);
+ }
+ }
+
+ /**
+ * Supply conditions for an explicit join.
+ *
+ * @param $entity
+ * @param $alias
+ * @return array
+ */
+ private function getJoinConditions($entity, $alias) {
+ $conditions = [];
+ // getAclClause() expects a stack of 1-to-1 join fields to help it dedupe, but this is more flexible,
+ // so unless this is a direct 1-to-1 join with the main entity, we'll just hack it
+ // with a padded empty stack to bypass its deduping.
+ $stack = [NULL, NULL];
+ foreach ($this->apiFieldSpec as $name => $field) {
+ if ($field['entity'] !== $entity && $field['fk_entity'] === $entity) {
+ $conditions[] = [$name, '=', "$alias.id"];
+ $stack = [$name];
+ }
+ elseif (strpos($name, "$alias.") === 0 && substr_count($name, '.') === 1 && $field['fk_entity'] === $this->entity) {
+ $conditions[] = [$name, '=', 'id'];
+ }
+ }
+ // Hmm, if we came up with > 1 condition, then it's ambiguous how it should be joined so we won't return anything but the generic ACLs
+ if (count($conditions) > 1) {
+ return $this->getAclClause($alias, AllCoreTables::getFullName($entity), [NULL, NULL]);
+ }
+ $acls = $this->getAclClause($alias, AllCoreTables::getFullName($entity), $stack);
+ return array_merge($acls, $conditions);
+ }
+
+ /**
+ * Joins a path and adds all fields in the joined entity to apiFieldSpec
*
* @param $key
* @throws \API_Exception
* @throws \Exception
*/
- protected function joinFK($key) {
+ protected function autoJoinFK($key) {
if (isset($this->apiFieldSpec[$key])) {
return;
}
array_pop($pathArray);
$pathString = implode('.', $pathArray);
- if (!$joiner->canJoin($this, $pathString)) {
+ if (!$joiner->canAutoJoin($this->getFrom(), $pathString)) {
return;
}
$joinPath = $joiner->join($this, $pathString);
- $isMany = FALSE;
- foreach ($joinPath as $joinable) {
- if ($joinable->getJoinType() === Joinable::JOIN_TYPE_ONE_TO_MANY) {
- $isMany = TRUE;
- }
- if ($joinable instanceof OptionValueJoinable) {
- \Civi::log()->warning('Use API pseudoconstant suffix like :name or :label instead of join.', ['civi.tag' => 'deprecated']);
- }
- }
-
- /** @var \Civi\Api4\Service\Schema\Joinable\Joinable $lastLink */
$lastLink = array_pop($joinPath);
// Custom field names are already prefixed
$fieldArray['sql_name'] = '`' . $lastLink->getAlias() . '`.`' . $fieldArray['column_name'] . '`';
$fieldArray['is_custom'] = $isCustom;
$fieldArray['is_join'] = TRUE;
- $fieldArray['is_many'] = $isMany;
$this->addSpecField($prefix . $fieldArray['name'], $fieldArray);
}
}
- /**
- * @param \Civi\Api4\Service\Schema\Joinable\Joinable $joinable
- *
- * @return $this
- */
- public function addJoinedTable(Joinable $joinable) {
- $this->joinedTables[] = $joinable;
-
- return $this;
- }
-
/**
* @return FALSE|string
*/
return $this->selectFields;
}
- /**
- * @return bool
- */
- public function isFillUniqueFields() {
- return $this->isFillUniqueFields;
- }
-
/**
* @return \CRM_Utils_SQL_Select
*/
return $this->apiVersion;
}
- /**
- * @return \Civi\Api4\Service\Schema\Joinable\Joinable[]
- */
- public function getJoinedTables() {
- return $this->joinedTables;
- }
-
- /**
- * @return \Civi\Api4\Service\Schema\Joinable\Joinable
- */
- public function getJoinedTable($alias) {
- foreach ($this->joinedTables as $join) {
- if ($join->getAlias() == $alias) {
- return $join;
- }
- }
- }
-
/**
* Get table name on basis of entity
*
}
}
- /**
- * Checks if a field either belongs to the main entity or is joinable 1-to-1.
- *
- * Used to determine if a field can be added to the SELECT of the main query,
- * or if it must be fetched post-query.
- *
- * @param string $fieldPath
- * @return bool
- */
- public function isOneToOneField(string $fieldPath) {
- return strpos($fieldPath, '.') === FALSE || !array_filter($this->getPathJoinTypes($fieldPath));
- }
-
- /**
- * Separates a string like 'emails.location_type.label' into an array, where
- * each value in the array tells whether it is 1-1 or 1-n join type
- *
- * @param string $pathString
- * Dot separated path to the field
- *
- * @return array
- * Index is table alias and value is boolean whether is 1-to-many join
- */
- public function getPathJoinTypes($pathString) {
- $pathParts = explode('.', $pathString);
- // remove field
- array_pop($pathParts);
- $path = [];
- $query = $this;
- $isMultipleChecker = function($alias) use ($query) {
- foreach ($query->getJoinedTables() as $table) {
- if ($table->getAlias() === $alias) {
- return $table->getJoinType() === Joinable::JOIN_TYPE_ONE_TO_MANY;
- }
- }
- return FALSE;
- };
-
- foreach ($pathParts as $part) {
- $path[$part] = $isMultipleChecker($part);
- }
-
- return $path;
- }
-
/**
* @param $path
* @param $field
return;
}
$defaults = [];
- $defaults['is_custom'] = $defaults['is_join'] = $defaults['is_many'] = FALSE;
+ $defaults['is_custom'] = $defaults['is_join'] = FALSE;
$field += $defaults;
$this->apiFieldSpec[$path] = $field;
}
* The raw expression, minus the alias.
* @var string
*/
- protected $expr = '';
+ public $expr = '';
/**
* SqlFunction constructor.
return $this->alias ?? $this->fields[0] ?? \CRM_Utils_String::munge($this->expr);
}
+ /**
+ * Returns the name of this sql expression class.
+ *
+ * @return string
+ */
+ public function getType(): string {
+ $className = get_class($this);
+ return substr($className, strrpos($className, '\\') + 1);
+ }
+
}
+++ /dev/null
-<?php
-
-/*
- +--------------------------------------------------------------------+
- | Copyright CiviCRM LLC. All rights reserved. |
- | |
- | This work is published under the GNU AGPLv3 license with some |
- | permitted exceptions and without any warranty. For full license |
- | and copyright information, see https://civicrm.org/licensing |
- +--------------------------------------------------------------------+
- */
-
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
-
-namespace Civi\Api4\Service\Schema\Joinable;
-
-class OptionValueJoinable extends Joinable {
- /**
- * @var string
- */
- protected $optionGroupName;
-
- /**
- * @param string $optionGroup
- * Can be either the option group name or ID
- * @param string|null $alias
- * The join alias
- * @param string $keyColumn
- * Which column to use to join, defaults to "value"
- */
- public function __construct($optionGroup, $alias = NULL, $keyColumn = 'value') {
- $this->optionGroupName = $optionGroup;
- $optionValueTable = 'civicrm_option_value';
-
- // default join alias to option group name, e.g. activity_type
- if (!$alias && !is_numeric($optionGroup)) {
- $alias = $optionGroup;
- }
-
- parent::__construct($optionValueTable, $keyColumn, $alias);
-
- if (!is_numeric($optionGroup)) {
- $subSelect = 'SELECT id FROM civicrm_option_group WHERE name = "%s"';
- $subQuery = sprintf($subSelect, $optionGroup);
- $condition = sprintf('%s.option_group_id = (%s)', $alias, $subQuery);
- }
- else {
- $condition = sprintf('%s.option_group_id = %d', $alias, $optionGroup);
- }
-
- $this->addCondition($condition);
- }
-
- /**
- * The existing condition must also be re-aliased
- *
- * @param string $alias
- *
- * @return $this
- */
- public function setAlias($alias) {
- foreach ($this->conditions as $index => $condition) {
- $search = $this->alias . '.';
- $replace = $alias . '.';
- $this->conditions[$index] = str_replace($search, $replace, $condition);
- }
-
- parent::setAlias($alias);
-
- return $this;
- }
-
-}
$conditions = $link->getConditionsForJoin($baseTable);
$query->join($side, $target, $alias, $conditions);
- $query->addJoinedTable($link);
$baseTable = $link->getAlias();
}
}
/**
- * @param \Civi\Api4\Query\Api4SelectQuery $query
+ * Determines if path string points to a simple n-1 join that can be automatically added
+ *
+ * @param string $baseTable
* @param $joinPath
*
* @return bool
*/
- public function canJoin(Api4SelectQuery $query, $joinPath) {
- return !empty($this->getPath($query->getFrom(), $joinPath));
+ public function canAutoJoin($baseTable, $joinPath) {
+ try {
+ $path = $this->getPath($baseTable, $joinPath);
+ foreach ($path as $joinable) {
+ if ($joinable->getJoinType() === $joinable::JOIN_TYPE_ONE_TO_MANY) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+ }
+ catch (\Exception $e) {
+ return FALSE;
+ }
}
/**
* @param string $baseTable
* @param string $joinPath
*
- * @return array
+ * @return \Civi\Api4\Service\Schema\Joinable\Joinable[]
* @throws \Exception
*/
protected function getPath($baseTable, $joinPath) {
use Civi\Api4\Service\Schema\Joinable\CustomGroupJoinable;
use Civi\Api4\Service\Schema\Joinable\Joinable;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
-use Civi\Api4\Service\Schema\Joinable\OptionValueJoinable;
use CRM_Core_DAO_AllCoreTables as AllCoreTables;
class SchemaMapBuilder {
$joinable->setJoinType($joinable::JOIN_TYPE_MANY_TO_ONE);
$table->addTableLink($field, $joinable);
}
- elseif (!empty($data['pseudoconstant'])) {
- $this->addPseudoConstantJoin($table, $field, $data);
- }
- }
-
- /**
- * @param Table $table
- * @param string $field
- * @param array $data
- */
- private function addPseudoConstantJoin(Table $table, $field, array $data) {
- $pseudoConstant = $data['pseudoconstant'] ?? NULL;
- $tableName = $pseudoConstant['table'] ?? NULL;
- $optionGroupName = $pseudoConstant['optionGroupName'] ?? NULL;
- $keyColumn = $pseudoConstant['keyColumn'] ?? 'id';
-
- if ($tableName) {
- $alias = str_replace('civicrm_', '', $tableName);
- $joinable = new Joinable($tableName, $keyColumn, $alias);
- $condition = $pseudoConstant['condition'] ?? NULL;
- if ($condition) {
- $joinable->addCondition($condition);
- }
- $table->addTableLink($field, $joinable);
- }
- elseif ($optionGroupName) {
- $keyColumn = $pseudoConstant['keyColumn'] ?? 'value';
- $joinable = new OptionValueJoinable($optionGroupName, NULL, $keyColumn);
-
- if (!empty($data['serialize'])) {
- $joinable->setJoinType($joinable::JOIN_TYPE_ONE_TO_MANY);
- }
-
- $table->addTableLink($field, $joinable);
- }
}
/**
private function addBackReferences(SchemaMap $map) {
foreach ($map->getTables() as $table) {
foreach ($table->getTableLinks() as $link) {
- // there are too many possible joins from option value so skip
- if ($link instanceof OptionValueJoinable) {
- continue;
- }
-
$target = $map->getTableByName($link->getTargetTable());
$tableName = $link->getBaseTable();
$plural = str_replace('civicrm_', '', $this->getPlural($tableName));
$customTable = new Table($tableName);
}
- if (!empty($fieldData->option_group_id)) {
- $optionValueJoinable = new OptionValueJoinable($fieldData->option_group_id, $fieldData->label);
- $customTable->addTableLink($fieldData->column_name, $optionValueJoinable);
- }
-
$map->addTable($customTable);
$alias = $fieldData->custom_group_name;
+--------------------------------------------------------------------+
*/
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
-
namespace Civi\Api4\Service\Spec\Provider;
use Civi\Api4\Service\Spec\RequestSpec;
$field->setHelpPre($data['help_pre'] ?? NULL);
$field->setHelpPost($data['help_post'] ?? NULL);
$field->setOptions(self::customFieldHasOptions($data));
- if (\CRM_Core_BAO_CustomField::isSerialized($data)) {
- $field->setSerialize(\CRM_Core_DAO::SERIALIZE_SEPARATOR_BOOKEND);
- }
}
else {
$name = $data['name'] ?? NULL;
$field->setRequired(!empty($data['required']));
$field->setTitle($data['title'] ?? NULL);
$field->setOptions(!empty($data['pseudoconstant']));
- $field->setSerialize($data['serialize'] ?? NULL);
}
-
+ $field->setSerialize($data['serialize'] ?? NULL);
$field->setDefaultValue($data['default'] ?? NULL);
$field->setDescription($data['description'] ?? NULL);
self::setInputTypeAndAttrs($field, $data, $dataTypeName);
$inputAttrs = $data['html'] ?? [];
unset($inputAttrs['type']);
- if (strstr($inputType, 'Multi-Select') || ($inputType == 'Select' && !empty($data['serialize']))) {
- $inputAttrs['multiple'] = TRUE;
- $inputType = 'Select';
- }
$map = [
'Select State/Province' => 'Select',
'Select Country' => 'Select',
'Link' => 'Url',
];
$inputType = $map[$inputType] ?? $inputType;
+ if ($inputType == 'Select' && !empty($data['serialize'])) {
+ $inputAttrs['multiple'] = TRUE;
+ }
if ($inputType == 'Date' && !empty($inputAttrs['formatType'])) {
self::setLegacyDateFormat($inputAttrs);
}
*
* @package CRM
* @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
*
*/
*/
protected $specProviders = [];
- /**
- * A cache of DAOs based on entity
- *
- * @var \CRM_Core_DAO[]
- */
- protected $DAONames;
-
/**
* Returns a RequestSpec with all the fields available. Uses spec providers
* to add or modify field specifications.
if ($suffix) {
$options = self::getPseudoconstantList($fieldSpec['entity'], $fieldSpec['name'], substr($fieldName, $suffix + 1));
$value = self::replacePseudoconstant($options, $value, TRUE);
+ return;
}
elseif (is_array($value)) {
foreach ($value as &$val) {
* named "api-fields.json" which lists all the fields of
* all the API entities.
*
- * @code
+ * ```
* // Build a URL to `api-fields.json`.
* $url = \Civi::service('asset_builder')->getUrl('api-fields.json');
*
* $mimeType = 'application/json';
* $content = json_encode($fields);
* }
- * @endCode
+ * ```
*
* Assets can be parameterized. Each combination of ($asset,$params)
* will be cached separately. For example, we might want a copy of
* Simply pass the chosen entities into `getUrl()`, then update
* the definition to use `$params['entities']`, as in:
*
- * @code
+ * ```
* // Build a URL to `api-fields.json`.
* $url = \Civi::service('asset_builder')->getUrl('api-fields.json', array(
* 'entities' => array('Contact', 'Phone', 'Email', 'Address'),
* $mimeType = 'application/json';
* $content = json_encode($fields);
* }
- * @endCode
+ * ```
*
* Note: These assets are designed to hold non-sensitive data, such as
* aggregated JS or common metadata. There probably are ways to
* The event inspector is a development tool which provides metadata about events.
* It can be used for code-generators and documentation-generators.
*
- * @code
+ * ```
* $i = new CiviEventInspector();
* print_r(CRM_Utils_Array::collect('name', $i->getAll()));
- * @endCode
+ * ```
*
* An event definition includes these fields:
* - type: string, required. Ex: 'hook' or 'object'
* and methods. This requires some kind of mapping. `GenericHookEvent`
* maps each parameter to a field (using magic methods):
*
- * @code
+ * ```
* // Creating an event object.
* $event = GenericHookEvent::create(array(
* 'bar' => 'abc',
*
* // Dispatching an event.
* Civi::dispatcher()->dispatch('hook_civicrm_foo', $event);
- * @endCode
+ * ```
*
* Design Discussion:
*
* as an array, and all the returned values are merged into one big array.
* You can add and retrieve return-values using these methods:
*
- * @code
+ * ```
* $event->addReturnValues(array(...));
* foreach ($event->getReturnValues() as $retVal) { ... }
- * @endCode
+ * ```
*/
class GenericHookEvent extends \Symfony\Component\EventDispatcher\Event {
/**
* Recursively interpolate values.
*
- * @code
+ * ```
* $params = array('foo' => '@1');
* $this->interpolate($params, array('@1'=> $object))
* assert $data['foo'] == $object;
- * @endcode
+ * ```
*
* @param array $array
* Array which may or many not contain a mix of tokens.
* @return array
*/
public function checkMysqlVersion(array $db_config) {
- $min = '5.1';
+ $min = \CRM_Upgrade_Incremental_General::MIN_INSTALL_MYSQL_VER;
$results = [
'title' => 'CiviCRM MySQL Version',
'severity' => $this::REQUIREMENT_OK,
/**
* Create a builder for the headless environment.
*
- * @return \Civi\Test\CiviEnvBuilder
- *
- * @code
+ * ```
* \Civi\Test::headless()->apply();
* \Civi\Test::headless()->sqlFile('ex.sql')->apply();
- * @endCode
+ * ```
+ *
+ * @return \Civi\Test\CiviEnvBuilder
*/
public static function headless() {
$civiRoot = dirname(__DIR__);
/**
* Create a builder for end-to-end testing on the live environment.
*
- * @return \Civi\Test\CiviEnvBuilder
- *
- * @code
+ * ```
* \Civi\Test::e2e()->apply();
* \Civi\Test::e2e()->install('foo.bar')->apply();
- * @endCode
+ * ```
+ *
+ * @return \Civi\Test\CiviEnvBuilder
*/
public static function e2e() {
$builder = new \Civi\Test\CiviEnvBuilder('CiviEnvBuilder');
* @throws \Exception
*/
public function runApi4Legacy($v3Entity, $v3Action, $v3Params = []) {
- $v4Entity = self::convertEntityNameToApi4($v3Entity);
+ $v4Entity = \CRM_Core_DAO_AllCoreTables::convertEntityNameToCamel($v3Entity);
$v4Action = $v3Action = strtolower($v3Action);
$v4Params = ['checkPermissions' => isset($v3Params['check_permissions']) ? (bool) $v3Params['check_permissions'] : FALSE];
$sequential = !empty($v3Params['sequential']);
// Handle single api call
list(, $chainEntity, $chainAction) = explode('.', $key);
- $lcChainEntity = \_civicrm_api_get_entity_name_from_camel($chainEntity);
- $chainEntity = self::convertEntityNameToApi4($chainEntity);
- $lcMainEntity = \_civicrm_api_get_entity_name_from_camel($mainEntity);
+ $lcChainEntity = \CRM_Core_DAO_AllCoreTables::convertEntityNameToLower($chainEntity);
+ $chainEntity = \CRM_Core_DAO_AllCoreTables::convertEntityNameToCamel($chainEntity);
+ $lcMainEntity = \CRM_Core_DAO_AllCoreTables::convertEntityNameToLower($mainEntity);
$params = is_array($params) ? $params : [];
// Api3 expects this to be inherited
return $this->runApi4Legacy($chainEntity, $chainAction, $params);
}
- /**
- * Fix the naming differences between api3 & api4 entities.
- *
- * @param string $legacyName
- * @return string
- */
- public static function convertEntityNameToApi4($legacyName) {
- $api4Name = \CRM_Utils_String::convertStringToCamel($legacyName);
- $map = [
- 'Im' => 'IM',
- 'Acl' => 'ACL',
- ];
- return $map[$api4Name] ?? $api4Name;
- }
-
}
* This interface allows you to subscribe to hooks as part of the test.
* Simply create an eponymous hook function (e.g. `hook_civicrm_post()`).
*
- * @code
+ * ```
* class MyTest extends \PHPUnit_Framework_TestCase implements \Civi\Test\HookInterface {
* public function hook_civicrm_post($op, $objectName, $objectId, &$objectRef) {
* echo "Running hook_civicrm_post\n";
* }
* }
- * @endCode
+ * ```
*
* At time of writing, there are a few limitations in how HookInterface is handled
* by CiviTestListener:
* The TokenRegisterEvent is fired when constructing a list of available
* tokens. Listeners may register by specifying the entity/field/label for the token.
*
- * @code
+ * ```
* $ev->entity('profile')
* ->register('viewUrl', ts('Default Profile URL (View Mode)')
* ->register('editUrl', ts('Default Profile URL (Edit Mode)');
* 'field' => 'viewUrl',
* 'label' => ts('Default Profile URL (View Mode)'),
* ));
- * @endcode
+ * ```
*
* Event name: 'civi.token.list'
*/
* A TokenValueEvent is fired to convert raw query data into mergeable
* tokens. For example:
*
- * @code
+ * ```
* $event = new TokenValueEvent($myContext, 'text/html', array(
* array('contact_id' => 123),
* array('contact_id' => 456),
* - schema: array, a list of fields that will be provided for each row.
* This is automatically populated with any general context
* keys, but you may need to add extra keys for token-row data.
- * ex: ['contactId', 'activity_id']. (Note we are standardising on the latter).
+ * ex: ['contactId', 'activityId'].
*/
public $context;
* (1) When setting up a job, you may specify general/baseline info.
* This is called the "context" data. Here, we create two rows:
*
- * @code
+ * ```
* $proc->addRow()->context('contact_id', 123);
* $proc->addRow()->context('contact_id', 456);
- * @endCode
+ * ```
*
* (2) When defining a token (eg `{profile.viewUrl}`), you might read the
* context-data (`contact_id`) and set the token-data (`profile => viewUrl`):
*
- * @code
+ * ```
* foreach ($proc->getRows() as $row) {
* $row->tokens('profile', [
* 'viewUrl' => 'http://example.com/profile?cid=' . urlencode($row->context['contact_id'];
* ]);
* }
- * @endCode
+ * ```
*
* The context and tokens can be accessed using either methods or attributes.
*
- * @code
+ * ```
* # Setting context data
* $row->context('contact_id', 123);
* $row->context(['contact_id' => 123]);
*
* # Reading token data
* echo $row->tokens['profile']['viewUrl'];
- * @endCode
+ * ```
*
* Note: The methods encourage a "fluent" style. They were written for PHP 5.3
* (eg before short-array syntax was supported) and are fairly flexible about
<input class="collapsible-optgroups form-control huge" ng-model="controls.select" crm-ui-select="{data: fieldsAndJoinsAndFunctionsAndWildcards}" placeholder="Add select" />
</div>
</fieldset>
+ <fieldset class="api4-input form-inline" ng-mouseenter="help('join', availableParams.join)" ng-mouseleave="help()" ng-if="::availableParams.join">
+ <legend>join<span class="crm-marker" ng-if="::availableParams.join.required"> *</span></legend>
+ <div ng-model="params.join" ui-sortable="{axis: 'y'}">
+ <div class="api4-input form-inline" ng-repeat="item in params.join track by $index">
+ <i class="crm-i fa-arrows"></i>
+ <input class="form-control twenty" type="text" ng-model="params.join[$index][0]" />
+ <select class="form-control" ng-model="params.join[$index][1]" ng-options="o.k as o.v for o in ::joinTypes" ></select>
+ <input class="form-control twenty" type="text" ng-model="params.join[$index][2]" />
+ <a href class="crm-hover-button" title="Clear" ng-click="clearParam('join', $index)"><i class="crm-i fa-times"></i></a>
+ </div>
+ </div>
+ <div class="api4-input form-inline">
+ <input class="collapsible-optgroups form-control huge" ng-model="controls.join" crm-ui-select="{data: entities}" placeholder="Add join" />
+ </div>
+ </fieldset>
<div class="api4-input form-inline" ng-mouseenter="help('fields', availableParams.fields)" ng-mouseleave="help()" ng-if="::availableParams.fields">
<label for="api4-param-fields">fields<span class="crm-marker" ng-if="::availableParams.fields.required"> *</span></label>
<input class="form-control" ng-list crm-ui-select="::{data: fields, multiple: true}" id="api4-param-fields" ng-model="params.fields" style="width: 85%;"/>
$scope.loading = false;
$scope.controls = {};
$scope.langs = ['php', 'js', 'ang', 'cli'];
+ $scope.joinTypes = [{k: false, v: ts('Optional')}, {k: true, v: ts('Required')}];
$scope.code = {
php: [
{name: 'oop', label: ts('OOP Style'), code: ''},
}
fields.push({
text: link.alias,
- description: 'Join to ' + link.entity,
+ description: 'Implicit join to ' + link.entity,
children: wildCard.concat(formatForSelect2(linkFields, [], 'name', ['description'], link.alias + '.'))
});
}
if (_.isEmpty($scope.availableParams)) {
return;
}
- var specialParams = ['select', 'fields', 'action', 'where', 'values', 'defaults', 'orderBy', 'chain', 'groupBy', 'having'];
+ var specialParams = ['select', 'fields', 'action', 'where', 'values', 'defaults', 'orderBy', 'chain', 'groupBy', 'having', 'join'];
if ($scope.availableParams.limit && $scope.availableParams.offset) {
specialParams.push('limit', 'offset');
}
});
});
}
- if (typeof objectParams[name] !== 'undefined' || name === 'groupBy' || name === 'select') {
+ if (typeof objectParams[name] !== 'undefined' || name === 'groupBy' || name === 'select' || name === 'join') {
$scope.$watch('controls.' + name, function(value) {
var field = value;
$timeout(function() {
if (field) {
- if (typeof objectParams[name] === 'undefined') {
+ if (name === 'join') {
+ $scope.params[name].push([field + ' AS ' + _.snakeCase(field), false, '[]']);
+ }
+ else if (typeof objectParams[name] === 'undefined') {
$scope.params[name].push(field);
} else {
var defaultOp = _.cloneDeep(objectParams[name]);
<div class="crm-submit-buttons">
<br>
- <a ng-href="#/abtest/new" class="button"><span><i class="crm-i fa-bar-chart"></i> {{:: ts('New A/B Test') }}</span></a>
+ <a ng-href="#/abtest/new" class="button"><span><i class="crm-i fa-flask"></i> {{:: ts('New A/B Test') }}</span></a>
</div>
*
* @return string
* Entity name in underscore separated format.
+ *
+ * @deprecated
*/
function _civicrm_api_get_entity_name_from_camel($entity) {
- if (!$entity || $entity === strtolower($entity)) {
- return $entity;
- }
- elseif ($entity == 'PCP') {
- return 'pcp';
- }
- else {
- $entity = ltrim(strtolower(str_replace('U_F',
- 'uf',
- // That's CamelCase, beside an odd UFCamel that is expected as uf_camel
- preg_replace('/(?=[A-Z])/', '_$0', $entity)
- )), '_');
+ if (!$entity) {
+ // @todo - this should not be called when empty.
+ return '';
}
- return $entity;
+ return CRM_Core_DAO_AllCoreTables::convertEntityNameToLower($entity);
}
/**
* Having a DAO object find the entity name.
*
- * @param object $bao
+ * @param CRM_Core_DAO $bao
* DAO being passed in.
*
* @return string
*/
function _civicrm_api_get_entity_name_from_dao($bao) {
- $daoName = str_replace("BAO", "DAO", get_class($bao));
- return CRM_Core_DAO_AllCoreTables::getBriefName($daoName);
+ return CRM_Core_DAO_AllCoreTables::getBriefName(get_class($bao));
}
*
* This class allows to consume the API, either from within a module that knows civicrm already:
*
- * @code
+ * ```
* require_once('api/class.api.php');
* $api = new civicrm_api3();
- * @endcode
+ * ```
*
* or from any code on the same server as civicrm
*
- * @code
+ * ```
* require_once('/your/civi/folder/api/class.api.php');
* // the path to civicrm.settings.php
* $api = new civicrm_api3 (array('conf_path'=> '/your/path/to/your/civicrm/or/joomla/site));
- * @endcode
+ * ```
*
* or to query a remote server via the rest api
*
- * @code
+ * ```
* $api = new civicrm_api3 (array ('server' => 'http://example.org',
* 'api_key'=>'theusersecretkey',
* 'key'=>'thesitesecretkey'));
- * @endcode
+ * ```
*
* No matter how initialised and if civicrm is local or remote, you use the class the same way.
*
- * @code
+ * ```
* $api->{entity}->{action}($params);
- * @endcode
+ * ```
*
* So, to get the individual contacts:
*
- * @code
+ * ```
* if ($api->Contact->Get(array('contact_type'=>'Individual','return'=>'sort_name,current_employer')) {
* // each key of the result array is an attribute of the api
* echo "\n contacts found " . $api->count;
* } else {
* echo $api->errorMsg();
* }
- * @endcode
+ * ```
*
* Or, to create an event:
*
- * @code
+ * ```
* if ($api->Event->Create(array('title'=>'Test','event_type_id' => 1,'is_public' => 1,'start_date' => 19430429))) {
* echo "created event id:". $api->id;
* } else {
* echo $api->errorMsg();
* }
- * @endcode
+ * ```
*
* To make it easier, the Actions can either take for input an
* associative array $params, or simply an id. The following two lines
* are equivalent.
*
- * @code
+ * ```
* $api->Activity->Get (42);
* $api->Activity->Get (array('id'=>42));
- * @endcode
+ * ```
*
*
* You can also get the result like civicrm_api does, but as an object
* instead of an array (eg $entity->attribute instead of
* $entity['attribute']).
*
- * @code
+ * ```
* $result = $api->result;
* // is the json encoded result
* echo $api;
- * @endcode
+ * ```
*/
class civicrm_api3 {
* file content.
* For core fields use "entity_table", for custom fields use "field_name"
*
- * @code
+ * ```
* // Create an attachment for a core field
* $result = civicrm_api3('Attachment', 'create', array(
* 'entity_table' => 'civicrm_activity',
* ));
* $attachment = $result['values'][$result['id']];
* echo sprintf("<a href='%s'>View %s</a>", $attachment['url'], $attachment['name']);
- * @endcode
+ * ```
*
- * @code
+ * ```
* // Create an attachment for a custom file field
* $result = civicrm_api3('Attachment', 'create', array(
* 'field_name' => 'custom_6',
* ));
* $attachment = $result['values'][$result['id']];
* echo sprintf("<a href='%s'>View %s</a>", $attachment['url'], $attachment['name']);
- * @endcode
+ * ```
*
- * @code
+ * ```
* // Move an existing file and save as an attachment
* $result = civicrm_api3('Attachment', 'create', array(
* 'entity_table' => 'civicrm_activity',
* ));
* $attachment = $result['values'][$result['id']];
* echo sprintf("<a href='%s'>View %s</a>", $attachment['url'], $attachment['name']);
- * @endcode
+ * ```
*
* Notes:
* - File content is not returned by default. One must specify 'return => content'.
*
* @param array $params
*
- * @code
+ * ```
* // REQUIRED for create:
* 'case_type_id' => int OR
* 'case_type' => str (provide one or the other)
* 'start_date' => str datestamp // defaults to: date('YmdHis')
* 'duration' => int // in minutes
* 'details' => str // html format
- * @endcode
+ * ```
*
* @throws API_Exception
* @return array
*
* @param array $params
*
- * @code
+ * ```
* //REQUIRED:
* 'id' => int
*
* //OPTIONAL
* 'move_to_trash' => bool (defaults to false)
- * @endcode
+ * ```
*
* @throws API_Exception
* @return mixed
*/
function civicrm_api3_custom_field_create($params) {
+ // Legacy handling for old way of naming serialized fields
+ if (!empty($params['html_type']) && ($params['html_type'] == 'CheckBox' || strpos($params['html_type'], 'Multi-') === 0)) {
+ $params['serialize'] = 1;
+ $params['html_type'] = str_replace('Multi-', '', $params['html_type']);
+ }
+
// Array created for passing options in params.
if (isset($params['option_values']) && is_array($params['option_values'])) {
$weight = 0;
* @return array
*/
function civicrm_api3_custom_field_get($params) {
- return _civicrm_api3_basic_get(_civicrm_api3_get_BAO(__FUNCTION__), $params);
+ if (($params['legacy_html_type'] ?? TRUE) && !empty($params['return'])) {
+ if (is_array($params['return'])) {
+ $params['return'][] = 'serialize';
+ }
+ elseif (is_string($params['return'])) {
+ $params['return'] .= ',serialize';
+ }
+ }
+
+ $results = _civicrm_api3_basic_get(_civicrm_api3_get_BAO(__FUNCTION__), $params);
+
+ if (($params['legacy_html_type'] ?? TRUE) && !empty($results['values']) && is_array($results['values'])) {
+ foreach ($results['values'] as $id => $result) {
+ if (!empty($result['serialize']) && !empty($result['html_type'])) {
+ $results['values'][$id]['html_type'] = str_replace('Select', 'Multi-Select', $result['html_type']);
+ }
+ }
+ }
+
+ return $results;
}
/**
* Expected keys are in format custom_fieldID:recordID or custom_groupName:fieldName:recordID.
*
* @example:
- * @code
+ * ```
* // entity ID. You do not need to specify entity type, we figure it out based on the fields you're using
* 'entity_id' => 123,
* // (omitting :id) inserts or updates a field in a single-valued group
* 'custom_some_group:my_field' => 'myinfo',
* // updates record ID 8 in my_other_field in multi-valued some_big_group
* 'custom_some_big_group:my_other_field:8' => 'myinfo',
- * @endcode
+ * ```
*
* @throws Exception
* @return array
*
* This api has a legacy/nonstandard signature.
* On success, the return array will be structured as follows:
- * @code
+ * ```
* array(
* "is_error" => 0,
* "version" => 3,
* "total_count" => integer
* )
* )
- * @endcode
+ * ```
*
* On failure, the return array will be structured as follows:
- * @code
+ * ```
* array(
* 'is_error' => 1,
* 'error_message' = string,
* 'error_data' = mixed or undefined
* )
- * @endcode
+ * ```
*
* @param array $params
* Input parameters:
$params['forward_replies']['api.default'] = FALSE;
$params['auto_responder']['api.default'] = FALSE;
- $params['open_tracking']['api.default'] = TRUE;
- $params['url_tracking']['api.default'] = TRUE;
+ $params['open_tracking']['api.default'] = Civi::settings()->get('open_tracking_default');
+ $params['url_tracking']['api.default'] = Civi::settings()->get('url_tracking_default');
$params['header_id']['api.default'] = CRM_Mailing_PseudoConstant::defaultComponent('Header', '');
$params['footer_id']['api.default'] = CRM_Mailing_PseudoConstant::defaultComponent('Footer', '');
$spec['created_date']['api.default'] = 'now';
$spec['created_id']['api.required'] = 1;
$spec['created_id']['api.default'] = 'user_contact_id';
+ $spec['domain_id']['api.default'] = CRM_Core_Config::domainID();
}
/**
* @return array
*/
function _civicrm_api3_basic_get($bao_name, $params, $returnAsSuccess = TRUE, $entity = "", $sql = NULL, $uniqueFields = FALSE) {
- $entity = $entity ?: CRM_Core_DAO_AllCoreTables::getBriefName(str_replace('_BAO_', '_DAO_', $bao_name));
+ $entity = $entity ?: CRM_Core_DAO_AllCoreTables::getBriefName($bao_name);
$options = _civicrm_api3_get_options_from_params($params);
$query = new \Civi\API\Api3SelectQuery($entity, CRM_Utils_Array::value('check_permissions', $params, FALSE));
if (strpos($fieldName, 'custom_') === 0 && is_numeric($fieldName[7])) {
return $fieldName;
}
- if ($fieldName == _civicrm_api_get_entity_name_from_camel($entity) . '_id') {
+ if ($fieldName === (CRM_Core_DAO_AllCoreTables::convertEntityNameToLower($entity) . '_id')) {
return 'id';
}
$result = civicrm_api($entity, 'getfields', [
/* accordion styles */
-.crm-container .crm-accordion-header {
- background-image: url("../i/TreeMinusWhite.gif");
- background-repeat: no-repeat;
- background-position: 2px center;
+.crm-container .crm-accordion-header,
+.crm-container .crm-collapsible .collapsible-title,
+.crm-container span.collapsed,
+.crm-container a.collapsed,
+.crm-container .crm-expand-row {
cursor: pointer;
+}
+
+.crm-container .crm-accordion-wrapper {
+ margin-bottom: 4px;
+}
+
+/* Specific types of headers */
+
+#crm-container .widget-content .crm-accordion-header {
+ background-color: #EFEFE5;
+ color: #080808;
+}
+
+.crm-container a.crm-expand-row:before,
+.crm-container a.crm-expand-row:link::before,
+.crm-container a.crm-expand-row:visited::before {
+ color: #3E3E3E;
+}
+
+.crm-container .crm-accordion-header {
color: #F5F6F1;
font-weight: normal;
- padding: 4px 8px 4px 20px;
+ padding: 4px 8px;
background-color: #5D677B;
+ border-radius: 4px 4px 0 0;
}
-.crm-container .crm-accordion-header:hover {
- background-color: #32414f;
+.crm-container .collapsed .crm-accordion-header {
+ border-radius: 4px;
}
-.crm-container .collapsed .crm-accordion-header {
- background-image: url("../i/TreePlusWhite.gif");
+.crm-container .crm-accordion-header.active {
+ font-weight: bold;
+ background-color: #3E3E3E;
}
-.crm-container .collapsed .crm-accordion-body,
-.crm-container .crm-collapsible.collapsed .collapsible-title + * {
- display: none;
+.crm-container .crm-accordion-header:hover {
+ background-color: #2F2F2E;
}
-.crm-container .crm-expand-row {
- min-width: 16px;
- min-height: 16px;
- display: inline-block;
+#crm-container .widget-content .crm-accordion-header:hover {
+ background-color: #e8e8de;
}
-.crm-container .crm-accordion-inner .crm-accordion-header,
-.crm-container .crm-accordion-wrapper .crm-master-accordion-header,
-.crm-container .crm-collapsible .collapsible-title {
- background-image: url("../i/TreeMinus.gif");
+.crm-container .crm-accordion-wrapper .crm-master-accordion-header {
background-color: transparent;
color: #3E3E3E;
}
-.crm-container .crm-accordion-inner.collapsed .crm-accordion-header,
-.crm-container .crm-accordion-wrapper.collapsed .crm-master-accordion-header,
-.crm-container .crm-collapsible.collapsed .collapsible-title {
- background-image: url("../i/TreePlus.gif");
-}
-
.crm-container .crm-accordion-wrapper .crm-master-accordion-header {
- background-color: transparent;
font-size: 16px;
- color: #3e3e3e;
- margin-bottom: 0;
}
-.crm-container .crm-accordion-inner .crm-accordion-header {
- font-size: 13px;
+.crm-container .crm-master-accordion-header.crm-accordion-header:hover,
+.crm-container .crm-collapsible .collapsible-title:hover {
+ color: #121A2D;
}
-.crm-container .crm-accordion-wrapper {
- margin-bottom: 4px;
+.crm-container .collapsed .crm-accordion-body,
+.crm-container .crm-collapsible.collapsed .collapsible-title + * {
+ display: none;
}
-.crm-container .crm-accordion-header {
- border-radius: 4px 4px 0 0;
+/* Collapse icon */
+
+/* General icon settings for all collapsible things */
+.crm-container .crm-accordion-header:before,
+.crm-container .crm-collapsible .collapsible-title:before,
+.crm-container span.collapsed:before,
+.crm-container a.collapsed:before,
+.crm-container .crm-expand-row:before {
+ font-family: "FontAwesome";
+ display: inline-block;
+ width: 1em;
+ content: "\f0da";
+ font-size: 13px;
}
-.crm-container .collapsed .crm-accordion-header {
- border-radius: 4px;
+/* Collapsed icon */
+.crm-container .collapsed .crm-accordion-header:before,
+.crm-container .crm-collapsible.collapsed .collapsible-title:before,
+.crm-container span.collapsed:before,
+.crm-container a.collapsed:before,
+.crm-container .crm-expand-row:before {
+ content: "\f0da";
+}
+
+/* Expanded icon */
+.crm-container .crm-accordion-header:before,
+.crm-container .crm-collapsible .collapsible-title:before,
+.crm-container span.expanded:before,
+.crm-container a.expanded:before,
+.crm-container .crm-expand-row.expanded:before {
+ content: "\f0d7";
}
+/* Accordion bodies */
+
.crm-container .crm-accordion-body {
border-radius: 0 0 4px 4px;
border: 1px solid #70716B;
padding: 4px 0;
}
-.crm-container .crm-collapsible .collapsible-title {
- padding-left: 19px;
- text-decoration: none;
- background-repeat: no-repeat;
- background-position: 0 center;
- cursor: pointer;
+#crm-container .widget-content .crm-accordion-body {
+ border-color: #e8e8de;
}
.crm-container .crm-master-accordion-header+.crm-accordion-body {
padding: 0;
}
-.crm-container .crm-accordion-header.active {
- font-weight: bold;
- background-color: #41477E;
-}
-
-.crm-container .crm-accordion-header.active:hover {
- background-color: #2E3471;
-}
-
-.crm-container .crm-master-accordion-header.crm-accordion-header:hover,
-.crm-container .crm-collapsible .collapsible-title:hover {
- background-color: transparent;
- color: #0200A0;
+#crm-container .widget-content .crm-accordion-body,
+.crm-container .crm-accordion-body.padded {
+ padding-left: .5em;
+ padding-right: .5em;
}
.crm-container .crm-child-row > td {
padding-left: 20px;
}
-.crm-container span.collapsed,
-.crm-container a.collapsed,
-.crm-container .crm-expand-row {
- background: url("../i/TreePlus.gif") no-repeat 0 0;
- padding-left: 19px;
- cursor: pointer;
-}
-
-.crm-container span.expanded,
-.crm-container a.expanded {
- background: url("../i/TreeMinus.gif") no-repeat 0 0;
- padding-left: 19px;
- cursor: pointer;
-}
-
/* Notifications */
#crm-notification-container {
width: 350px;
min-height: 10em;
}
-#crm-container .widget-content .crm-accordion-header {
- background-color: #EFEFE5;
- background-image: url("../i/TreeMinus.gif");
- color: #080808;
-}
-
-#crm-container .widget-content .crm-accordion-wrapper.collapsed .crm-accordion-header {
- background-image: url("../i/TreePlus.gif");
-}
-
-#crm-container .widget-content .crm-accordion-header:hover {
- background-color: #e8e8de;
-}
-
-#crm-container .widget-content .crm-accordion-body {
- border-color: #e8e8de;
- padding: 4px .5em;
-}
-
#crm-container .widget-controls {
background-color: #5D677B;
/* Standards-browsers do this anyway because all inner block elements are floated. IE doesn't because it's crap. */
)
)
) {
- @$this->requireMySQLVersion("5.1",
+ @$this->requireMySQLVersion(CRM_Upgrade_Incremental_General::MIN_INSTALL_MYSQL_VER,
array(
ts("MySQL %1 Configuration", array(1 => $dbName)),
- ts("MySQL version at least %1", array(1 => '5.1')),
- ts("MySQL version %1 or higher is required, you are running MySQL %2.", array(1 => '5.1', 2 => mysqli_get_server_info($this->conn))),
+ ts("MySQL version at least %1", array(1 => CRM_Upgrade_Incremental_General::MIN_INSTALL_MYSQL_VER)),
+ ts("MySQL version %1 or higher is required, you are running MySQL %2.", array(1 => CRM_Upgrade_Incremental_General::MIN_INSTALL_MYSQL_VER, 2 => mysqli_get_server_info($this->conn))),
ts("MySQL %1", array(1 => mysqli_get_server_info($this->conn))),
)
);
description = row.description || $(row.element).data('description'),
ret = '';
if (icon) {
- ret += '<i class="crm-i ' + icon + '"></i> ';
+ ret += '<i class="crm-i ' + icon + '" aria-hidden="true"></i> ';
}
if (color) {
ret += '<span class="crm-select-item-color" style="background-color: ' + color + '"></span> ';
return ret + _.escape(row.text) + (description ? '<div class="crm-select2-row-description"><p>' + _.escape(description) + '</p></div>' : '');
}
+ /**
+ * Helper to generate an icon with alt text.
+ *
+ * See also smarty `{icon}` and CRM_Core_Page::crmIcon() functions
+ *
+ * @param string icon
+ * The Font Awesome icon class to use.
+ * @param string text
+ * Alt text to display.
+ * @param mixed condition
+ * This will only display if this is truthy.
+ *
+ * @return string
+ * The formatted icon markup.
+ */
+ CRM.utils.formatIcon = function (icon, text, condition) {
+ if (typeof condition !== 'undefined' && !condition) {
+ return '';
+ }
+ var title = '';
+ var sr = '';
+ if (text) {
+ text = _.escape(text);
+ title = ' title="' + text + '"';
+ sr = '<span class="sr-only">' + text + '</span>';
+ }
+ return '<i class="crm-i ' + icon + '"' + title + ' aria-hidden="true"></i>' + sr;
+ };
+
/**
* Wrapper for select2 initialization function; supplies defaults
* @param options object
placeholder = settings.placeholder || $el.data('placeholder') || $el.attr('placeholder') || $('option[value=""]', $el).text();
if (m.length && placeholder === m) {
iconClass = $el.attr('class').match(/(fa-\S*)/)[1];
- out = '<i class="crm-i ' + iconClass + '"></i> ' + out;
+ out = '<i class="crm-i ' + iconClass + '" aria-hidden="true"></i> ' + out;
}
return out;
};
}
_.each(createLinks, function(link) {
markup += ' <a class="crm-add-entity crm-hover-button" href="' + link.url + '">' +
- '<i class="crm-i ' + (link.icon || 'fa-plus-circle') + '"></i> ' +
+ '<i class="crm-i ' + (link.icon || 'fa-plus-circle') + '" aria-hidden="true"></i> ' +
_.escape(link.label) + '</a>';
});
markup += '</div>';
--- /dev/null
+// https://civicrm.org/licensing
+(function($) {
+ "use strict";
+
+ $(document).on('crmLoad', '#civicrm-menu', hideMenuToggleButtonForNonAdminUsers);
+
+ /**
+ * Hides the Menu Toggle Button when the Admin Menu is not available for the user.
+ */
+ function hideMenuToggleButtonForNonAdminUsers() {
+ $(document).ready(function() {
+ if (!$('#toolbar').length) {
+ CRM.menubar.removeToggleButton();
+ }
+ });
+ }
+
+})(CRM.$);
data: null,
settings: {collapsibleBehavior: 'accordion'},
position: 'over-cms-menu',
+ toggleButton: true,
attachTo: (CRM.menubar && CRM.menubar.position === 'above-crm-container') ? '#crm-container' : 'body',
initialize: function() {
var cache = CRM.cache.get('menubar');
}
},
initializePosition: function() {
- if (CRM.menubar.position === 'over-cms-menu' || CRM.menubar.position === 'below-cms-menu') {
+ if (CRM.menubar.toggleButton && (CRM.menubar.position === 'over-cms-menu' || CRM.menubar.position === 'below-cms-menu')) {
$('#civicrm-menu')
.on('click', 'a[href="#toggle-position"]', function(e) {
e.preventDefault();
}
$('body').addClass('crm-menubar-visible crm-menubar-' + CRM.menubar.position);
},
+ removeToggleButton: function() {
+ $('#crm-menubar-toggle-position').remove();
+ CRM.menubar.toggleButton = false;
+ if (CRM.menubar.position === 'below-cms-menu') {
+ CRM.menubar.togglePosition();
+ }
+ },
initializeResponsive: function() {
var $mainMenuState = $('#crm-menubar-state');
// hide mobile menu beforeunload
'description' => ts('Allow sending email from the logged in contact\'s email address.'),
'help_text' => 'CiviCRM allows you to send email from the domain from email addresses and the logged in contact id addresses by default. Disable this if you only want to allow the domain from addresses to be used.',
],
+ 'url_tracking_default' => [
+ 'group_name' => 'Mailing Preferences',
+ 'group' => 'mailing',
+ 'name' => 'url_tracking_default',
+ 'type' => 'Boolean',
+ 'html_type' => 'checkbox',
+ 'quick_form_type' => 'CheckBox',
+ 'default' => '1',
+ 'title' => ts('Enable click-through tracking by default'),
+ 'is_domain' => 1,
+ 'is_contact' => 0,
+ 'description' => ts('If checked, mailings will have click-through tracking enabled by default.'),
+ 'help_text' => NULL,
+ ],
+ 'open_tracking_default' => [
+ 'group_name' => 'Mailing Preferences',
+ 'group' => 'mailing',
+ 'name' => 'open_tracking_default',
+ 'type' => 'Boolean',
+ 'html_type' => 'checkbox',
+ 'quick_form_type' => 'CheckBox',
+ 'default' => '1',
+ 'title' => ts('Enable open tracking by default'),
+ 'is_domain' => 1,
+ 'is_contact' => 0,
+ 'description' => ts('If checked, mailings will have open tracking enabled by default.'),
+ 'help_text' => NULL,
+ ],
];
CRM.$(function($) {
var mailSetting = $("input[name='outBound_option']:checked").val( );
- var archiveWarning = "{/literal}{ts escape='js'}WARNING: You are switching from a testing mode (Redirect to Database) to a live mode. Check Mailings > Archived Mailings, and delete any test mailings that are not in Completed status prior to running the mailing cron job for the first time. This will ensure that test mailings are not actually sent out.{/ts}{literal}"
+ var archiveWarning = "{/literal}{ts escape='js'}WARNING: You are switching from a testing mode (Redirect to Database) to a live mode. Check Mailings > Archived Mailings, and delete any test mailings that are not in Completed status prior to running the mailing cron job for the first time. This will ensure that test mailings are not actually sent out.{/ts}{literal}";
showHideMailOptions( $("input[name='outBound_option']:checked").val( ) ) ;
//add id for yes/no column.
CRM.$(nRow).children().eq(8).attr( 'id', rowId + '_status' );
+ CRM.$(nRow).children().eq(6).html(CRM.utils.formatIcon('fa-check', ts('Default'), nRow.cells[6].innerText));
return nRow;
},
//add id for yes/no column.
CRM.$(nRow).children().eq(11).attr( 'id', rowId + '_status' );
+ CRM.$(nRow).children().eq(9).html(CRM.utils.formatIcon('fa-check', ts('Default'), nRow.cells[9].innerText));
return nRow;
},
<tr id="optionField_{$index}" class="form-item {cycle values="odd-row,even-row"}">
<td>
{if $index GT 1}
- <a onclick="hideRow({$index}); return false;" name="orderBy_{$index}" href="#" class="form-link"><img src="{$config->resourceBase}i/TreeMinus.gif" class="action-icon" alt="{ts}hide field or section{/ts}"/></a>
+ <a onclick="hideRow({$index}); return false;" name="orderBy_{$index}" href="#" class="form-link">{icon icon="fa-trash"}{ts}hide field or section{/ts}{/icon}</a>
{/if}
</td>
<td> {$form.order_bys.$index.column.html}</td>
{/section}
</table>
<div id="optionFieldLink" class="add-remove-link">
- <a onclick="showHideRow(); return false;" name="optionFieldLink" href="#" class="form-link"><img src="{$config->resourceBase}i/TreePlus.gif" class="action-icon" alt="{ts}show field or section{/ts}"/>{ts}another column{/ts}</a>
+ <a onclick="showHideRow(); return false;" name="optionFieldLink" href="#" class="form-link"><i class="crm-i fa-plus action-icon" aria-hidden="true"></i> {ts}another column{/ts}</a>
</div>
<script type="text/javascript">
if (interview.errors[error]) errorList = errorList + '<li>' + interview.errors[error] + '</li>';
}
if ( errorList ) {
- var allErrors = '<i class="crm-i fa-exclamation-triangle crm-i-red"></i> {/literal}{ts}Please correct the following errors in the survey fields below:{/ts}{literal}<ul>' + errorList + '</ul>';
+ var allErrors = '<i class="crm-i fa-exclamation-triangle crm-i-red" aria-hidden="true"></i> {/literal}{ts}Please correct the following errors in the survey fields below:{/ts}{literal}<ul>' + errorList + '</ul>';
CRM.$('#responseErrors').show( ).html(allErrors);
}
}
{ts}Activities{/ts}
</div>
- <div id="activities" class="crm-accordion-body">
- <div class="crm-accordion-wrapper crm-accordion-inner crm-search_filters-accordion collapsed">
- <div class="crm-accordion-header">
+ <div id="activities" class="crm-accordion-body padded">
+ <div class="crm-collapsible crm-search_filters-accordion collapsed">
+ <div class="collapsible-title">
{ts}Search Filters{/ts}
- </div><!-- /.crm-accordion-header -->
- <div class="crm-accordion-body">
- <table class="no-border form-layout-compressed" id="searchOptions">
- <tr>
- <td class="crm-case-caseview-form-block-repoter_id"colspan="2"><label for="reporter_id">{ts}Reporter/Role{/ts}</label><br />
- {$form.reporter_id.html|crmAddClass:twenty}
- </td>
- <td class="crm-case-caseview-form-block-status_id"><label for="status_id">{$form.status_id.label}</label><br />
- {$form.status_id.html}
- </td>
- </tr>
- <tr>
- <td class="crm-case-caseview-form-block-activity_date_low">
- {assign var=activitylow value=activity_date_low_$caseID}
- {$form.$activitylow.label}<br />
- {$form.$activitylow.html}
- </td>
- <td class="crm-case-caseview-form-block-activity_date_high">
- {assign var=activityhigh value=activity_date_high_$caseID}
- {$form.$activityhigh.label}<br />
- {$form.$activityhigh.html}
- </td>
- <td class="crm-case-caseview-form-block-activity_type_filter_id">
- {$form.activity_type_filter_id.label}<br />
- {$form.activity_type_filter_id.html}
+ </div>
+ <table class="no-border form-layout-compressed" id="searchOptions">
+ <tr>
+ <td class="crm-case-caseview-form-block-repoter_id"colspan="2"><label for="reporter_id">{ts}Reporter/Role{/ts}</label><br />
+ {$form.reporter_id.html|crmAddClass:twenty}
+ </td>
+ <td class="crm-case-caseview-form-block-status_id"><label for="status_id">{$form.status_id.label}</label><br />
+ {$form.status_id.html}
+ </td>
+ </tr>
+ <tr>
+ <td class="crm-case-caseview-form-block-activity_date_low">
+ {assign var=activitylow value=activity_date_low_$caseID}
+ {$form.$activitylow.label}<br />
+ {$form.$activitylow.html}
+ </td>
+ <td class="crm-case-caseview-form-block-activity_date_high">
+ {assign var=activityhigh value=activity_date_high_$caseID}
+ {$form.$activityhigh.label}<br />
+ {$form.$activityhigh.html}
+ </td>
+ <td class="crm-case-caseview-form-block-activity_type_filter_id">
+ {$form.activity_type_filter_id.label}<br />
+ {$form.activity_type_filter_id.html}
+ </td>
+ </tr>
+ {if $form.activity_deleted}
+ <tr class="crm-case-caseview-form-block-activity_deleted">
+ <td>
+ {$form.activity_deleted.html}{$form.activity_deleted.label}
</td>
</tr>
- {if $form.activity_deleted}
- <tr class="crm-case-caseview-form-block-activity_deleted">
- <td>
- {$form.activity_deleted.html}{$form.activity_deleted.label}
- </td>
- </tr>
- {/if}
- </table>
- </div><!-- /.crm-accordion-body -->
+ {/if}
+ </table>
</div><!-- /.crm-accordion-wrapper -->
{/if}
var caseUrl = destUrl + selectedCaseId + '&cid=' + contactId + context;
var statusMsg = {/literal}'{ts escape='js' 1='%1'}Activity has been filed to %1 case.{/ts}'{literal};
- CRM.alert(ts(statusMsg, {1: '<a href="' + caseUrl + '">' + caseTitle + '</a>'}), '{/literal}{ts escape="js"}Saved{/ts}{literal}', 'success');
+ CRM.alert(ts(statusMsg, {1: '<a href="' + caseUrl + '">' + CRM._.escape(caseTitle) + '</a>'}), '{/literal}{ts escape="js"}Saved{/ts}{literal}', 'success');
CRM.refreshParent(a);
}
}
| and copyright information, see https://civicrm.org/licensing |
+--------------------------------------------------------------------+
*}
-{capture assign=expandIconURL}<img src="{$config->resourceBase}i/TreePlus.gif" alt="{ts}open section{/ts}"/>{/capture}
{strip}
<table class="case-selector-{$list} crm-ajax-table" data-page-length='10'>
<thead>
{assign var="showBlock" value="'searchForm'"}
{assign var="hideBlock" value="'searchForm_show','searchForm_hide'"}
<div class="crm-block crm-form-block crm-search-form-block">
-<div id="searchForm_show" class="form-item">
- <a href="#" onclick="cj('#searchForm_show').hide(); cj('#searchForm').show(); return false;"><img src="{$config->resourceBase}i/TreePlus.gif" class="action-icon" alt="{ts}open section{/ts}" /></a>
- <label>{ts}Edit Search Criteria{/ts}</label>
-</div>
-
-<div id="searchForm" class="crm-block crm-form-block crm-contact-custom-search-eventDetails-form-block">
- <fieldset>
- <legend><span id="searchForm_hide"><a href="#" onclick="cj('#searchForm').hide(); cj('#searchForm_show').show(); return false;"><img src="{$config->resourceBase}i/TreeMinus.gif" class="action-icon" alt="{ts}close section{/ts}" /></a></span>{ts}Search Criteria{/ts}</legend>
- <div class="crm-submit-buttons">{include file="CRM/common/formButtons.tpl" location="top"}</div>
- <table class="form-layout-compressed">
- {* Loop through all defined search criteria fields (defined in the buildForm() function). *}
- {foreach from=$elements item=element}
- <tr class="crm-contact-custom-search-eventDetails-form-block-{$element}">
- <td class="label">{$form.$element.label}</td>
- <td>{$form.$element.html}</td>
+ <div class="crm-accordion-wrapper crm-eventDetails_search-accordion {if $rows}collapsed{/if}">
+ <div class="crm-accordion-header crm-master-accordion-header">
+ {ts}Edit Search Criteria{/ts}
+ </div><!-- /.crm-accordion-header -->
+ <div class="crm-accordion-body">
+ <div id="searchForm" class="crm-block crm-form-block crm-contact-custom-search-eventDetails-form-block">
+ <div class="crm-submit-buttons">{include file="CRM/common/formButtons.tpl" location="top"}</div>
+ <table class="form-layout-compressed">
+ {* Loop through all defined search criteria fields (defined in the buildForm() function). *}
+ {foreach from=$elements item=element}
+ <tr class="crm-contact-custom-search-eventDetails-form-block-{$element}">
+ <td class="label">{$form.$element.label}</td>
+ <td>{$form.$element.html}</td>
+ </tr>
+ {/foreach}
+ <tr class="crm-contact-custom-search-eventDetails-form-block-event_type">
+ <td class="label">{ts}Event Type{/ts}</td>
+ <td>
+ <div class="listing-box">
+ {foreach from=$form.event_type_id item="event_val"}
+ <div class="{cycle values="odd-row,even-row"}">
+ {$event_val.html}
+ </div>
+ {/foreach}
+ </div>
+ <div class="spacer"></div>
+ </td>
</tr>
- {/foreach}
- <tr class="crm-contact-custom-search-eventDetails-form-block-event_type">
- <td class="label">{ts}Event Type{/ts}</td>
- <td>
- <div class="listing-box">
- {foreach from=$form.event_type_id item="event_val"}
- <div class="{cycle values="odd-row,even-row"}">
- {$event_val.html}
- </div>
- {/foreach}
- </div>
- <div class="spacer"></div>
- </td>
- </tr>
- </table>
- <div class="crm-submit-buttons">{include file="CRM/common/formButtons.tpl" location="bottom"}</div>
- </fieldset>
-</div>
+ </table>
+ <div class="crm-submit-buttons">{include file="CRM/common/formButtons.tpl" location="bottom"}</div>
+ </div>
+ </div>
+ </div>
{if $rowsEmpty}
{include file="CRM/Contact/Form/Search/Custom/EmptyResults.tpl"}
</fieldset>
{* END Actions/Results section *}
{/if}
-
-<script type="text/javascript">
- var showBlock = new Array({$showBlock});
- var hideBlock = new Array({$hideBlock});
-
- {* hide and display the appropriate blocks *}
- on_load_init_blocks( showBlock, hideBlock );
-</script>
</div>
<td><a href="{crmURL p='civicrm/contact/view' q="reset=1&cid=`$row.contact_id`&key=`$qfKey`&context=`$context`"}">{if $row.is_deleted}<del>{/if}{$row.sort_name}{if $row.is_deleted}</del>{/if}</a></td>
{if $action eq 512 or $action eq 256}
{if !empty($columnHeaders.street_address)}
- <td><span title="{$row.street_address|escape}">{$row.street_address|mb_truncate:22:"...":true}{if $row.do_not_mail} {privacyFlag field=do_not_mail}{/if}</span></td>
+ <td><span title="{$row.street_address|escape}">{$row.street_address|mb_truncate:22:"...":true}{privacyFlag field=do_not_mail condition=$row.do_not_mail}</span></td>
{/if}
{if !empty($columnHeaders.city)}
<td>{$row.city}</td>
{if $row.email}
<span title="{$row.email|escape}">
{$row.email|mb_truncate:17:"...":true}
- {if $row.on_hold}
- {privacyFlag field=on_hold}
- {elseif $row.do_not_email}
- {privacyFlag field=do_not_email}
- {/if}
+ {privacyFlag field=do_not_email condition=$row.do_not_email}
+ {privacyFlag field=on_hold condition=$row.on_hold}
</span>
{/if}
</td>
<td>
{if $row.phone}
{$row.phone}
- {if $row.do_not_phone}
- {privacyFlag field=do_not_phone}
- {/if}
- {if $row.do_not_sms}
- {privacyFlag field=do_not_sms}
- {/if}
+ {privacyFlag field=do_not_phone condition=$row.do_not_phone}
+ {privacyFlag field=do_not_sms condition=$row.do_not_sms}
{/if}
</td>
{else}
<div class="crm-summary-row {if $add.is_primary eq 1} primary{/if}">
<div class="crm-label">
{ts 1=$add.location_type}%1 Address{/ts}
- {if $privacy.do_not_mail}{privacyFlag field=do_not_mail}{/if}
+ {privacyFlag field=do_not_mail condition=$privacy.do_not_mail}
{if $config->mapProvider AND
!empty($add.geo_code_1) AND
is_numeric($add.geo_code_1) AND
<div class="crm-summary-row {if $item.is_primary eq 1}primary{/if}">
<div class="crm-label">
{$item.location_type} {ts}Email{/ts}
- {if $privacy.do_not_email}{privacyFlag field=do_not_email}{elseif $item.on_hold}{privacyFlag field=on_hold}{/if}
+ {privacyFlag field=do_not_email condition=$privacy.do_not_email}{privacyFlag field=on_hold condition=$item.on_hold}
</div>
<div class="crm-content crm-contact_email">
{if !$item.on_hold and !$privacy.do_not_email}
<div class="crm-summary-row">
<div class="crm-label">
{ts}Phone{/ts}
- {if $privacy.do_not_sms}{privacyFlag field=do_not_sms}{/if}
- {if $privacy.do_not_phone}{privacyFlag field=do_not_phone}{/if}
+ {privacyFlag field=do_not_sms condition=$privacy.do_not_sms}
+ {privacyFlag field=do_not_phone condition=$privacy.do_not_phone}
</div>
<div class="crm-content"></div>
</div>
{if $item.phone || $item.phone_ext}
<div class="crm-summary-row {if $item.is_primary eq 1}primary{/if}">
<div class="crm-label">
- {if $privacy.do_not_sms}{privacyFlag field=do_not_sms}{/if}
- {if $privacy.do_not_phone}{privacyFlag field=do_not_phone}{/if}
+ {privacyFlag field=do_not_sms condition=$privacy.do_not_sms}
+ {privacyFlag field=do_not_phone condition=$privacy.do_not_phone}
{$item.location_type} {$item.phone_type}
</div>
<div class="crm-content crm-contact_phone">
{/if}
</td>
</tr>
+ <tr class="crm-custom-field-form-block-serialize">
+ <td class="label">{$form.serialize.label}</td>
+ <td class="html-adjust">{$form.serialize.html}</td>
+ </tr>
{if $form.in_selector}
<tr class='crm-custom-field-form-block-in_selector'>
<td class='label'>{$form.in_selector.label}</td>
$("#textLength", $form).toggle(dataType === 'String');
$("#noteColumns, #noteRows, #noteLength", $form).toggle(dataType === 'Memo');
+
+ $(".crm-custom-field-form-block-serialize", $form).toggle(htmlType === 'Select' || htmlType === 'Country' || htmlType === 'StateProvince');
}
$('[name^="data_type"]', $form).change(customOptionHtmlType);
"aoColumns" : [
{sClass:'crm-custom_option-label'},
{sClass:'crm-custom_option-value'},
+ {sClass:'crm-custom_option-description'},
{sClass:'crm-custom_option-default_value'},
- {sClass:'crm-custom_option-default_description'},
{sClass:'crm-custom_option-is_active'},
{sClass:'crm-custom_option-links'},
{sClass:'hiddenElement'}
$(nRow).addClass(cl).attr({id: 'OptionValue-' + id});
$('td:eq(0)', nRow).wrapInner('<span class="crm-editable crmf-label" />');
$('td:eq(0)', nRow).prepend('<span class="crm-i fa-arrows crm-grip" />');
- $('td:eq(2)', nRow).addClass('crmf-default_value');
+ $('td:eq(3)', nRow).addClass('crmf-default_value').html(CRM.utils.formatIcon('fa-check', ts('Default'), nRow.cells[3].innerText));
return nRow;
},
"fnDrawCallback": function() {
{section name=rowLoop start=1 loop=6}
{assign var=index value=$smarty.section.rowLoop.index}
<tr id="discount_{$index}" class=" crm-event-manage-fee-form-block-discount_{$index} {if $index GT 1 AND empty( $form.discount_name[$index].value) } hiddenElement {/if} form-item {cycle values="odd-row,even-row"}">
- <td>{if $index GT 1} <a onclick="showHideDiscountRow('discount_{$index}', false, {$index}); return false;" name="discount_{$index}" href="#" class="form-link"><img src="{$config->resourceBase}i/TreeMinus.gif" class="action-icon" alt="{ts}hide field or section{/ts}"/></a>{/if}
+ <td>{if $index GT 1} <a onclick="showHideDiscountRow('discount_{$index}', false, {$index}); return false;" name="discount_{$index}" href="#" class="form-link">{icon icon="fa-trash"}{ts}remove discount set{/ts}{/icon}</span></a>{/if}
</td>
<td class="crm-event-manage-fee-form-block-discount_name"> {$form.discount_name.$index.html}</td>
<td class="crm-event-manage-fee-form-block-discount_start_date"> {$form.discount_start_date.$index.html} </td>
{/section}
</table>
<div id="discountLink" class="add-remove-link">
- <a onclick="showHideDiscountRow( 'discount', true);return false;" id="discountLink" href="#" class="form-link"><img src="{$config->resourceBase}i/TreePlus.gif" class="action-icon" alt="{ts}show field or section{/ts}"/>{ts}another discount set{/ts}</a>
+ <a onclick="showHideDiscountRow( 'discount', true);return false;" id="discountLink" href="#" class="form-link"><i class="crm-i fa-plus action-icon" aria-hidden="true"></i> {ts}another discount set{/ts}</a>
</div>
{$form._qf_Fee_submit.html}
</div><div class="spacer"></div>
<div id="comment" style="display:none">
<table class="form-layout">
- <tr class="crm-mailing-forward-form-block-forward_comment"><td><a href="#" onclick="cj('#comment').hide(); cj('#comment_show').show(); return false;"><img src="{$config->resourceBase}i/TreeMinus.gif" class="action-icon" alt="{ts}close section{/ts}"/></a>
+ <tr class="crm-mailing-forward-form-block-forward_comment"><td><a href="#" onclick="cj('#comment').hide(); cj('#comment_show').show(); return false;">{icon icon="fa-caret-down action-icon"}{ts}close section{/ts}{/icon}</a>
<label>{$form.forward_comment.label}</label></td>
<td>{$form.forward_comment.html}<br /><br />
{$form.html_comment.html}<br /></td>
alert = CRM.alert(
// Mixing client-side variables with a translated string in smarty is awkward!
- ts({/literal}'{ts escape='js' 1='%1' 2='%2' 3='%3' 4='%4'}This contact has an existing %1 membership at %2 with %3 status%4.{/ts}'{literal}, {1:memberorgs[selectedorg].membership_type, 2: org, 3: memberorgs[selectedorg].membership_status, 4: andEndDate})
+ ts({/literal}'{ts escape='js'}This contact has an existing %1 membership at %2 with %3 status%4.{/ts}'{literal}, {1:memberorgs[selectedorg].membership_type, 2: org, 3: memberorgs[selectedorg].membership_status, 4: andEndDate})
+ '<ul><li><a href="' + memberorgs[selectedorg].renewUrl + '">'
+ {/literal}'{ts escape='js'}Renew the existing membership instead{/ts}'
+ '</a></li><li><a href="' + memberorgs[selectedorg].membershipTab + '">'
<td class="crm-membership-source">{$activeMember.source}</td>
<td class="crm-membership-auto_renew">
{if $activeMember.auto_renew eq 1}
- <i class="crm-i fa-check" aria-hidden="true" title="{ts}Auto-renew active{/ts}"></i>
+ {icon icon="fa-check"}{ts}Auto-renew active{/ts}{/icon}
{elseif $activeMember.auto_renew eq 2}
- <i class="crm-i fa-ban" aria-hidden="true" title="{ts}Auto-renew error{/ts}"></i>
+ {icon icon="fa-exclamation-triangle"}{ts}Auto-renew error{/ts}{/icon}
{/if}
</td>
<td class="crm-membership-related_count">{$activeMember.related_count}</td>
<td class="crm-membership-source">{$inActiveMember.source}</td>
<td class="crm-membership-auto_renew">
{if $inActiveMember.auto_renew eq 1}
- <i class="crm-i fa-check" aria-hidden="true" title="{ts}Auto-renew active{/ts}"></i>
+ {icon icon="fa-check"}{ts}Auto-renew active{/ts}{/icon}
{elseif $inActiveMember.auto_renew eq 2}
- <i class="crm-i fa-ban" aria-hidden="true" title="{ts}Auto-renew error{/ts}"></i>
+ {icon icon="fa-exclamation-triangle"}{ts}Auto-renew error{/ts}{/icon}
{/if}
</td>
<td>{$inActiveMember.action|replace:'xx':$inActiveMember.id}
(function($) {
$('#groups').val('').change(function() {
CRM.confirm({
- message: ts({/literal}'{ts escape='js' 1='<em>%1</em>'}Add all contacts to %1 group?{/ts}'{literal}, {1: $('option:selected', '#groups').text()})
+ message: ts({/literal}'{ts escape='js' 1='<em>%1</em>'}Add all contacts to %1 group?{/ts}'{literal}, {1: CRM._.escape($('option:selected', '#groups').text())})
})
.on({
'crmConfirm:yes': function() {
<tr id="optionField_{$index}" class="form-item {cycle values="odd-row,even-row"}">
<td>
{if $index GT 1}
- <a onclick="hideRow({$index}); return false;" name="orderBy_{$index}" href="#" class="form-link"><img src="{$config->resourceBase}i/TreeMinus.gif" class="action-icon" alt="{ts}hide field or section{/ts}"/></a>
+ <a onclick="hideRow({$index}); return false;" name="orderBy_{$index}" href="#" class="form-link">{icon icon="fa-trash"}{ts}remove sort by column{/ts}{/icon}</a>
{/if}
</td>
<td> {$form.order_bys.$index.column.html}</td>
{/section}
</table>
<div id="optionFieldLink" class="add-remove-link">
- <a onclick="showHideRow(); return false;" name="optionFieldLink" href="#" class="form-link"><img src="{$config->resourceBase}i/TreePlus.gif" class="action-icon" alt="{ts}show field or section{/ts}"/>{ts}another column{/ts}</a>
+ <a onclick="showHideRow(); return false;" name="optionFieldLink" href="#" class="form-link"><i class="crm-i fa-plus action-icon" aria-hidden="true"></i> {ts}another column{/ts}</a>
</div>
<script type="text/javascript">
var showRows = new Array({$showBlocks});
--- /dev/null
+<?php
+
+/**
+ * @group headless
+ */
+class CRM_Activity_Page_AJAXTest extends CiviUnitTestCase {
+
+ public function setUp() {
+ parent::setUp();
+
+ $this->loadAllFixtures();
+
+ CRM_Core_BAO_ConfigSetting::enableComponent('CiviCase');
+
+ $this->loggedInUser = $this->createLoggedInUser();
+ $this->target = $this->individualCreate();
+ }
+
+ /**
+ * Test the underlying function that implements file-on-case.
+ *
+ * The UI is a quickform but it's only realized as a popup ajax form that
+ * doesn't have its own postProcess. Instead the values are ultimately
+ * passed to the function this test is testing. So there's no form or ajax
+ * being tested here, just the final act of filing the activity.
+ */
+ public function testConvertToCaseActivity() {
+ $activity = $this->callAPISuccess('Activity', 'create', [
+ 'source_contact_id' => $this->loggedInUser,
+ 'activity_type_id' => 'Meeting',
+ 'subject' => 'test file on case',
+ 'status_id' => 'Completed',
+ 'target_id' => $this->target,
+ ]);
+
+ $case = $this->callAPISuccess('Case', 'create', [
+ 'contact_id' => $this->target,
+ 'case_type_id' => 'housing_support',
+ 'subject' => 'Needs housing',
+ ]);
+
+ $params = [
+ 'activityID' => $activity['id'],
+ 'caseID' => $case['id'],
+ 'mode' => 'file',
+ 'targetContactIds' => $this->target,
+ ];
+ $result = CRM_Activity_Page_AJAX::_convertToCaseActivity($params);
+
+ $this->assertEmpty($result['error_msg']);
+ $newActivityId = $result['newId'];
+
+ $caseActivities = $this->callAPISuccess('Activity', 'get', [
+ 'case_id' => $case['id'],
+ ])['values'];
+ $this->assertEquals('test file on case', $caseActivities[$newActivityId]['subject']);
+ // This should be a different physical activity, not the same db record as the original.
+ $this->assertNotEquals($activity['id'], $caseActivities[$newActivityId]['id']);
+ }
+
+}
'extends' => 'Contact',
'title' => 'ABC',
]);
- $customField = $this->customFieldOptionValueCreate($customGroup, 'fieldABC', ['html_type' => 'Multi-Select']);
+ $customField = $this->customFieldOptionValueCreate($customGroup, 'fieldABC', ['html_type' => 'Select', 'serialize' => 1]);
$params = [
'custom_' . $customField['id'] => 'Label1|Label2',
];
'where' => 'civicrm_value_custom_group_' . $customGroupID . '.' . $this->getCustomFieldColumnName('country'),
'extends_table' => 'civicrm_contact',
'search_table' => 'contact_a',
+ 'serialize' => NULL,
'pseudoconstant' => [
'table' => 'civicrm_country',
'keyColumn' => 'id',
'options_per_line' => NULL,
'text_length' => NULL,
'data_type' => 'Country',
- 'html_type' => 'Multi-Select Country',
+ 'html_type' => 'Select Country',
'is_search_range' => '0',
'id' => $this->getCustomFieldID('multi_country'),
'label' => 'Country-multi',
'where' => 'civicrm_value_custom_group_' . $customGroupID . '.' . $this->getCustomFieldColumnName('multi_country'),
'extends_table' => 'civicrm_contact',
'search_table' => 'contact_a',
+ 'serialize' => 1,
'pseudoconstant' => [
'table' => 'civicrm_country',
'keyColumn' => 'id',
'where' => 'civicrm_value_custom_group_' . $customGroupID . '.my_file_' . $this->getCustomFieldID('file'),
'extends_table' => 'civicrm_contact',
'search_table' => 'contact_a',
+ 'serialize' => NULL,
],
$this->getCustomFieldName('text') => [
'name' => $this->getCustomFieldName('text'),
'extends_table' => 'civicrm_contact',
'search_table' => 'contact_a',
'maxlength' => 300,
+ 'serialize' => NULL,
],
$this->getCustomFieldName('select_string') => [
'name' => $this->getCustomFieldName('select_string'),
'where' => 'civicrm_value_custom_group_' . $customGroupID . '.pick_color_' . $this->getCustomFieldID('select_string'),
'extends_table' => 'civicrm_contact',
'search_table' => 'contact_a',
+ 'serialize' => NULL,
'pseudoconstant' => [
'optionGroupName' => $this->callAPISuccessGetValue('CustomField', ['id' => $this->getCustomFieldID('select_string'), 'return' => 'option_group_id.name']),
'optionEditPath' => 'civicrm/admin/options/' . $this->callAPISuccessGetValue('CustomField', ['id' => $this->getCustomFieldID('select_string'), 'return' => 'option_group_id.name']),
'where' => 'civicrm_value_custom_group_' . $customGroupID . '.test_date_' . $this->getCustomFieldID('select_date'),
'extends_table' => 'civicrm_contact',
'search_table' => 'contact_a',
+ 'serialize' => NULL,
],
$this->getCustomFieldName('link') => [
'name' => $this->getCustomFieldName('link'),
'where' => 'civicrm_value_custom_group_' . $customGroupID . '.test_link_' . $this->getCustomFieldID('link'),
'extends_table' => 'civicrm_contact',
'search_table' => 'contact_a',
+ 'serialize' => NULL,
],
$this->getCustomFieldName('int') => [
'name' => $this->getCustomFieldName('int'),
'where' => 'civicrm_value_custom_group_' . $customGroupID . '.' . $this->getCustomFieldColumnName('int'),
'extends_table' => 'civicrm_contact',
'search_table' => 'contact_a',
+ 'serialize' => NULL,
],
$this->getCustomFieldName('contact_reference') => [
'name' => $this->getCustomFieldName('contact_reference'),
'where' => 'civicrm_value_custom_group_' . $customGroupID . '.' . $this->getCustomFieldColumnName('contact_reference'),
'extends_table' => 'civicrm_contact',
'search_table' => 'contact_a',
+ 'serialize' => NULL,
],
$this->getCustomFieldName('state') => [
'name' => $this->getCustomFieldName('state'),
'where' => 'civicrm_value_custom_group_' . $customGroupID . '.' . $this->getCustomFieldColumnName('state'),
'extends_table' => 'civicrm_contact',
'search_table' => 'contact_a',
+ 'serialize' => NULL,
'pseudoconstant' => [
'table' => 'civicrm_state_province',
'keyColumn' => 'id',
'where' => 'civicrm_value_custom_group_' . $customGroupID . '.' . $this->getCustomFieldColumnName('multi_state'),
'extends_table' => 'civicrm_contact',
'search_table' => 'contact_a',
+ 'serialize' => 1,
'pseudoconstant' => [
'table' => 'civicrm_state_province',
'keyColumn' => 'id',
'data_type' => 'StateProvince',
'name' => $this->getCustomFieldName('multi_state'),
'type' => 1,
- 'html_type' => 'Multi-Select State/Province',
+ 'html_type' => 'Select State/Province',
'text_length' => NULL,
'options_per_line' => NULL,
'is_search_range' => '0',
'text_length' => NULL,
'options_per_line' => NULL,
'is_search_range' => '0',
+ 'serialize' => NULL,
'pseudoconstant' => [
'callback' => 'CRM_Core_SelectValues::boolean',
],
'type' => 4,
'where' => 'civicrm_value_testsearchcus_' . $ids['custom_group_id'] . '.date_field_' . $dateCustomField['id'],
'import' => 1,
+ 'serialize' => NULL,
], $queryObj->getFieldSpec('custom_' . $dateCustomField['id']));
}
trim($queryObject->_where[0][0])
);
$this->assertEquals(
- 'FROM civicrm_contact contact_a LEFT JOIN civicrm_address ON ( contact_a.id = civicrm_address.contact_id AND civicrm_address.is_primary = 1 ) LEFT JOIN civicrm_country ON ( civicrm_address.country_id = civicrm_country.id ) LEFT JOIN civicrm_email ON (contact_a.id = civicrm_email.contact_id AND civicrm_email.is_primary = 1) LEFT JOIN civicrm_phone ON (contact_a.id = civicrm_phone.contact_id AND civicrm_phone.is_primary = 1) LEFT JOIN civicrm_im ON (contact_a.id = civicrm_im.contact_id AND civicrm_im.is_primary = 1) LEFT JOIN civicrm_worldregion ON civicrm_country.region_id = civicrm_worldregion.id
-LEFT JOIN ' . $this->getCustomGroupTable() . ' ON ' . $this->getCustomGroupTable() . '.entity_id = `contact_a`.id',
- trim($queryObject->_fromClause)
+ 'FROM civicrm_contact contact_a LEFT JOIN civicrm_address ON ( contact_a.id = civicrm_address.contact_id AND civicrm_address.is_primary = 1 ) LEFT JOIN civicrm_country ON ( civicrm_address.country_id = civicrm_country.id ) LEFT JOIN civicrm_email ON (contact_a.id = civicrm_email.contact_id AND civicrm_email.is_primary = 1) LEFT JOIN civicrm_phone ON (contact_a.id = civicrm_phone.contact_id AND civicrm_phone.is_primary = 1) LEFT JOIN civicrm_im ON (contact_a.id = civicrm_im.contact_id AND civicrm_im.is_primary = 1) LEFT JOIN civicrm_worldregion ON civicrm_country.region_id = civicrm_worldregion.id' .
+ ' LEFT JOIN ' . $this->getCustomGroupTable() . ' ON ' . $this->getCustomGroupTable() . '.entity_id = `contact_a`.id',
+ preg_replace('/\s+/', ' ', trim($queryObject->_fromClause))
);
$this->assertEquals('Test Date - greater than or equal to "June 6th, 2014 12:00 AM"', $queryObject->_qill[0][0]);
$this->assertEquals(1, $queryObject->_whereTables['civicrm_contact']);
$this->assertFalse(CRM_Core_DAO_AllCoreTables::isCoreTable('civicrm_invalid_table'), 'civicrm_invalid_table should NOT be a core table');
}
+ public function testGetBriefName() {
+ $this->assertEquals('Contact', CRM_Core_DAO_AllCoreTables::getBriefName('CRM_Contact_BAO_Contact'));
+ $this->assertEquals('Contact', CRM_Core_DAO_AllCoreTables::getBriefName('CRM_Contact_DAO_Contact'));
+ }
+
}
--- /dev/null
+<?php
+
+/**
+ * Class CRM_Core_PageTest
+ * @group headless
+ */
+class CRM_Core_PageTest extends CiviUnitTestCase {
+
+ /**
+ * Test data for testMakeIcons
+ *
+ * @return array
+ */
+ public function iconTestData() {
+ // first item is icon, text, condition, and attribs, second is expected markup
+ return [
+ [
+ '<i aria-hidden="true" title="We have a winner" class="crm-i fa-trophy"></i><span class="sr-only">We have a winner</span>',
+ ['fa-trophy', 'We have a winner', TRUE, []],
+ '{icon icon="fa-trophy"}We have a winner{/icon}',
+ ],
+ [
+ '',
+ ['fa-trophy', 'We have a winner', 0, []],
+ '{icon icon="fa-trophy" condition=0}We have a winner{/icon}',
+ ],
+ [
+ '<i aria-hidden="true" title="Favorite" class="action-icon test-icon crm-i fa-heart"></i><span class="sr-only">Favorite</span>',
+ ['fa-heart', 'Favorite', TRUE, ['class' => 'action-icon test-icon']],
+ '{icon icon="fa-heart" class="action-icon test-icon"}Favorite{/icon}',
+ ],
+ [
+ '<i aria-hidden="true" title="I "choo-choo" choose you" class="crm-i fa-train"></i><span class="sr-only">I "choo-choo" choose you</span>',
+ ['fa-train', 'I "choo-choo" choose you', TRUE, []],
+ '{icon icon="fa-train"}I "choo-choo" choose you{/icon}',
+ ],
+ [
+ '<i aria-hidden="true" class="crm-i fa-trash"></i><span class="sr-only">Trash</span>',
+ ['fa-trash', 'Trash', TRUE, ['title' => '']],
+ '{icon icon="fa-trash" title=""}Trash{/icon}',
+ ],
+ [
+ '<i title="It\'s bedtime" class="crm-i fa-bed"></i><span class="sr-only">It\'s bedtime</span>',
+ ['fa-bed', "It's bedtime", TRUE, ['aria-hidden' => '']],
+ // Ye olde Smarty 2 doesn't support hyphenated function parameters
+ ],
+ [
+ '<i aria-hidden="true" class="crm-i fa-snowflake-o"></i>',
+ ['fa-snowflake-o', NULL, TRUE, []],
+ '{icon icon="fa-snowflake-o"}{/icon}',
+ ],
+ ];
+ }
+
+ /**
+ * Test that icons are formed properly
+ *
+ * @param string $expectedMarkup
+ * @param array $params
+ * @param string $smartyFunc
+ * @dataProvider iconTestData
+ */
+ public function testMakeIcons($expectedMarkup, $params, $smartyFunc = '') {
+ list($icon, $text, $condition, $attribs) = $params;
+ $this->assertEquals($expectedMarkup, CRM_Core_Page::crmIcon($icon, $text, $condition, $attribs));
+ if (!empty($smartyFunc)) {
+ $smarty = CRM_Core_Smarty::singleton();
+ $actual = $smarty->fetch('string:' . $smartyFunc);
+ $this->assertEquals($expectedMarkup, $actual, "Process input=[$smartyFunc]");
+ }
+ }
+
+}
*/
class CRM_Logging_SchemaTest extends CiviUnitTestCase {
+ protected $databaseVersion;
+
public function setUp() {
+ $this->databaseVersion = CRM_Utils_SQL::getDatabaseVersion();
parent::setUp();
}
public function tearDown() {
$schema = new CRM_Logging_Schema();
$schema->disableLogging();
+ $this->databaseVersion = NULL;
parent::tearDown();
$this->quickCleanup(['civicrm_contact'], TRUE);
$schema->dropAllLogTables();
$this->assertEquals('int', $ci['test_id']['DATA_TYPE']);
$this->assertEquals('NO', $ci['test_id']['IS_NULLABLE']);
$this->assertEquals('auto_increment', $ci['test_id']['EXTRA']);
- $this->assertEquals('10', $ci['test_id']['LENGTH']);
+ if (!$this->isMySQL8()) {
+ $this->assertEquals('10', $ci['test_id']['LENGTH']);
+ }
$this->assertEquals('varchar', $ci['test_varchar']['DATA_TYPE']);
$this->assertEquals('42', $ci['test_varchar']['LENGTH']);
$this->assertEquals('int', $ci['test_integer']['DATA_TYPE']);
- $this->assertEquals('8', $ci['test_integer']['LENGTH']);
+ if (!$this->isMySQL8()) {
+ $this->assertEquals('8', $ci['test_integer']['LENGTH']);
+ }
$this->assertEquals('YES', $ci['test_integer']['IS_NULLABLE']);
$this->assertEquals('decimal', $ci['test_decimal']['DATA_TYPE']);
$schema->fixSchemaDifferences();
$ci = \Civi::$statics['CRM_Logging_Schema']['columnSpecs'];
// length should increase
- $this->assertEquals(6, $ci['log_civicrm_test_length_change']['test_integer']['LENGTH']);
+ if (!$this->isMySQL8()) {
+ $this->assertEquals(6, $ci['log_civicrm_test_length_change']['test_integer']['LENGTH']);
+ }
$this->assertEquals('22,2', $ci['log_civicrm_test_length_change']['test_decimal']['LENGTH']);
CRM_Core_DAO::executeQuery(
"ALTER TABLE civicrm_test_length_change
$schema->fixSchemaDifferences();
$ci = \Civi::$statics['CRM_Logging_Schema']['columnSpecs'];
// length should not decrease
- $this->assertEquals(6, $ci['log_civicrm_test_length_change']['test_integer']['LENGTH']);
+ if (!$this->isMySQL8()) {
+ $this->assertEquals(6, $ci['log_civicrm_test_length_change']['test_integer']['LENGTH']);
+ }
$this->assertEquals('22,2', $ci['log_civicrm_test_length_change']['test_decimal']['LENGTH']);
}
$this->assertEquals("'A','B','C','D'", $ci['civicrm_test_enum_change']['test_enum']['ENUM_VALUES']);
}
+ /**
+ * Determine if we are running on MySQL 8 version 8.0.19 or later.
+ *
+ * @return bool
+ */
+ protected function isMySQL8() {
+ return (bool) (version_compare($this->databaseVersion, '8.0.19', '>=') && stripos($this->databaseVersion, 'mariadb') === FALSE);
+ }
+
}
return TRUE;
}
- public function fromToData() {
+ /**
+ * Used by testGetFromTo
+ */
+ private function fromToData() {
$cases = [];
// Absolute dates
- $cases[] = ['20170901000000', '20170913235959', 0, '09/01/2017', '09/13/2017'];
+ $cases['absolute'] = [
+ 'expectedFrom' => '20170901000000',
+ 'expectedTo' => '20170913235959',
+ 'relative' => 0,
+ 'from' => '09/01/2017',
+ 'to' => '09/13/2017',
+ ];
// "Today" relative date filter
$date = new DateTime();
- $expectedFrom = $date->format('Ymd') . '000000';
- $expectedTo = $date->format('Ymd') . '235959';
- $cases[] = [$expectedFrom, $expectedTo, 'this.day', '', ''];
+ $cases['today'] = [
+ 'expectedFrom' => $date->format('Ymd') . '000000',
+ 'expectedTo' => $date->format('Ymd') . '235959',
+ 'relative' => 'this.day',
+ 'from' => '',
+ 'to' => '',
+ ];
// "yesterday" relative date filter
$date = new DateTime();
$date->sub(new DateInterval('P1D'));
- $expectedFrom = $date->format('Ymd') . '000000';
- $expectedTo = $date->format('Ymd') . '235959';
- $cases[] = [$expectedFrom, $expectedTo, 'previous.day', '', ''];
+ $cases['yesterday'] = [
+ 'expectedFrom' => $date->format('Ymd') . '000000',
+ 'expectedTo' => $date->format('Ymd') . '235959',
+ 'relative' => 'previous.day',
+ 'from' => '',
+ 'to' => '',
+ ];
return $cases;
}
/**
* Test that getFromTo returns the correct dates.
- *
- * @dataProvider fromToData
- *
- * @param string $expectedFrom
- * @param string $expectedTo
- * @param string $relative
- * @param string $from
- * @param string $to
*/
- public function testGetFromTo($expectedFrom, $expectedTo, $relative, $from, $to) {
- $obj = new CRM_Report_Form();
- if (date('Hi') === '0000') {
- $this->markTestIncomplete('The date might have changed since the dataprovider was called. Skip to avoid flakiness');
+ public function testGetFromTo() {
+ $cases = $this->fromToData();
+ foreach ($cases as $caseDescription => $case) {
+ $obj = new CRM_Report_Form();
+ list($calculatedFrom, $calculatedTo) = $obj->getFromTo($case['relative'], $case['from'], $case['to']);
+ $this->assertEquals([$case['expectedFrom'], $case['expectedTo']], [$calculatedFrom, $calculatedTo], "fail on data set '{$caseDescription}'. Local php time is " . date('Y-m-d H:i:s') . ' and mysql time is ' . CRM_Core_DAO::singleValueQuery('SELECT NOW()'));
}
- list($calculatedFrom, $calculatedTo) = $obj->getFromTo($relative, $from, $to);
- $this->assertEquals([$expectedFrom, $expectedTo], [$calculatedFrom, $calculatedTo], "fail on data set [ $relative , $from , $to ]. Local php time is " . date('Y-m-d H:i:s') . ' and mysql time is ' . CRM_Core_DAO::singleValueQuery('SELECT NOW()'));
}
}
return TRUE;
}
- public function fromToData() {
+ /**
+ * Used by testGetFromTo
+ */
+ private function fromToData() {
$cases = [];
// Absolute dates
- $cases[] = ['20170901000000', '20170913235959', 0, '09/01/2017', '09/13/2017'];
+ $cases['absolute'] = [
+ 'expectedFrom' => '20170901000000',
+ 'expectedTo' => '20170913235959',
+ 'relative' => 0,
+ 'from' => '09/01/2017',
+ 'to' => '09/13/2017',
+ ];
// "Today" relative date filter
$date = new DateTime();
- $expectedFrom = $date->format('Ymd') . '000000';
- $expectedTo = $date->format('Ymd') . '235959';
- $cases[] = [$expectedFrom, $expectedTo, 'this.day', '', ''];
+ $cases['today'] = [
+ 'expectedFrom' => $date->format('Ymd') . '000000',
+ 'expectedTo' => $date->format('Ymd') . '235959',
+ 'relative' => 'this.day',
+ 'from' => '',
+ 'to' => '',
+ ];
// "yesterday" relative date filter
$date = new DateTime();
$date->sub(new DateInterval('P1D'));
- $expectedFrom = $date->format('Ymd') . '000000';
- $expectedTo = $date->format('Ymd') . '235959';
- $cases[] = [$expectedFrom, $expectedTo, 'previous.day', '', ''];
+ $cases['yesterday'] = [
+ 'expectedFrom' => $date->format('Ymd') . '000000',
+ 'expectedTo' => $date->format('Ymd') . '235959',
+ 'relative' => 'previous.day',
+ 'from' => '',
+ 'to' => '',
+ ];
return $cases;
}
/**
* Test that getFromTo returns the correct dates.
- *
- * @dataProvider fromToData
- * @param $expectedFrom
- * @param $expectedTo
- * @param $relative
- * @param $from
- * @param $to
*/
- public function testGetFromTo($expectedFrom, $expectedTo, $relative, $from, $to) {
- $obj = new CRM_Utils_Date();
- list($calculatedFrom, $calculatedTo) = $obj->getFromTo($relative, $from, $to);
- $this->assertEquals($expectedFrom, $calculatedFrom);
- $this->assertEquals($expectedTo, $calculatedTo);
+ public function testGetFromTo() {
+ $cases = $this->fromToData();
+ foreach ($cases as $caseDescription => $case) {
+ $obj = new CRM_Utils_Date();
+ list($calculatedFrom, $calculatedTo) = $obj->getFromTo($case['relative'], $case['from'], $case['to']);
+ $this->assertEquals($case['expectedFrom'], $calculatedFrom, "Expected From failed for case $caseDescription");
+ $this->assertEquals($case['expectedTo'], $calculatedTo, "Expected To failed for case $caseDescription");
+ }
}
/**
* Create a batch of external API calls which can
* be executed concurrently.
*
- * @code
+ * ```
* $calls = $this->createExternalAPI()
* ->addCall('Contact', 'get', ...)
* ->addCall('Contact', 'get', ...)
* ...
* ->run()
* ->getResults();
- * @endcode
+ * ```
*
* @return \Civi\API\ExternalBatch
* @throws PHPUnit_Framework_SkippedTestError
'contribution_status_id' => 2,
];
$contribution = $this->callAPISuccess('Contribution', 'create', $contributionParams);
+ $checkNumber1 = 'C111';
$this->callAPISuccess('Payment', 'create', [
'contribution_id' => $contribution['id'],
'total_amount' => 50,
- 'payment_instrument_id' => 'Cash',
+ 'payment_instrument_id' => 'Check',
+ 'check_number' => $checkNumber1,
]);
$payments = $this->callAPISuccess('Payment', 'get', ['contribution_id' => $contribution['id']])['values'];
$this->assertCount(1, $payments);
$this->validateAllPayments();
+
+ $checkNumber2 = 'C222';
+ $this->callAPISuccess('Payment', 'create', [
+ 'contribution_id' => $contribution['id'],
+ 'total_amount' => 20,
+ 'payment_instrument_id' => 'Check',
+ 'check_number' => $checkNumber2,
+ ]);
+ $expectedConcatanatedCheckNumbers = implode(',', [$checkNumber1, $checkNumber2]);
+ //Assert check number is concatenated on the main contribution.
+ $contributionValues = $this->callAPISuccess('Contribution', 'getsingle', ['id' => $contribution['id']]);
+ $this->assertEquals($expectedConcatanatedCheckNumbers, $contributionValues['check_number']);
}
/**
use api\v4\UnitTestCase;
use Civi\Api4\Activity;
use Civi\Api4\Contact;
+use Civi\Api4\Email;
+use Civi\Api4\Phone;
/**
* @group headless
$this->assertCount(1, $results);
}
- public function testActivityContactJoin() {
- $results = Activity::get()
+ public function testOptionalJoin() {
+ // DefaultDataSet includes 2 phones for contact 1, 0 for contact 2.
+ // We'll add one for contact 2 as a red herring to make sure we only get back the correct ones.
+ Phone::create()->setCheckPermissions(FALSE)
+ ->setValues(['contact_id' => $this->getReference('test_contact_2')['id'], 'phone' => '123456'])
+ ->execute();
+ $contacts = Contact::get()
->setCheckPermissions(FALSE)
- ->addSelect('assignees.id')
- ->addSelect('assignees.first_name')
- ->addSelect('assignees.display_name')
- ->addWhere('assignees.first_name', '=', 'Phoney')
+ ->addJoin('Phone', FALSE)
+ ->addSelect('id', 'phone.phone')
+ ->addWhere('id', 'IN', [$this->getReference('test_contact_1')['id']])
+ ->addOrderBy('phone.id')
->execute();
+ $this->assertCount(2, $contacts);
+ $this->assertEquals($this->getReference('test_contact_1')['id'], $contacts[0]['id']);
+ $this->assertEquals($this->getReference('test_contact_1')['id'], $contacts[1]['id']);
+ }
- $firstResult = $results->first();
-
- $this->assertCount(1, $results);
- $this->assertTrue(is_array($firstResult['assignees']));
+ public function testRequiredJoin() {
+ // Joining with no condition
+ $contacts = Contact::get()
+ ->setCheckPermissions(FALSE)
+ ->addSelect('id', 'phone.phone')
+ ->addJoin('Phone', TRUE)
+ ->addWhere('id', 'IN', [$this->getReference('test_contact_1')['id'], $this->getReference('test_contact_2')['id']])
+ ->addOrderBy('phone.id')
+ ->execute();
+ $this->assertCount(2, $contacts);
+ $this->assertEquals($this->getReference('test_contact_1')['id'], $contacts[0]['id']);
+ $this->assertEquals($this->getReference('test_contact_1')['id'], $contacts[1]['id']);
- $firstAssignee = array_shift($firstResult['assignees']);
- $this->assertEquals($firstAssignee['first_name'], 'Phoney');
+ // Add is_primary condition, should result in only one record
+ $contacts = Contact::get()
+ ->setCheckPermissions(FALSE)
+ ->addSelect('id', 'phone.phone', 'phone.location_type_id')
+ ->addJoin('Phone', TRUE, ['phone.is_primary', '=', TRUE])
+ ->addWhere('id', 'IN', [$this->getReference('test_contact_1')['id'], $this->getReference('test_contact_2')['id']])
+ ->addOrderBy('phone.id')
+ ->execute();
+ $this->assertCount(1, $contacts);
+ $this->assertEquals($this->getReference('test_contact_1')['id'], $contacts[0]['id']);
+ $this->assertEquals('+35355439483', $contacts[0]['phone.phone']);
+ $this->assertEquals('1', $contacts[0]['phone.location_type_id']);
}
- public function testContactPhonesJoin() {
- $testContact = $this->getReference('test_contact_1');
- $testPhone = $this->getReference('test_phone_1');
+ public function testJoinToTheSameTableTwice() {
+ $cid1 = Contact::create()->setCheckPermissions(FALSE)
+ ->addValue('first_name', 'Aaa')
+ ->addChain('email1', Email::create()->setValues(['email' => 'yoohoo@yahoo.test', 'contact_id' => '$id', 'location_type_id:name' => 'Home']))
+ ->addChain('email2', Email::create()->setValues(['email' => 'yahoo@yoohoo.test', 'contact_id' => '$id', 'location_type_id:name' => 'Work']))
+ ->execute()
+ ->first()['id'];
- $results = Contact::get()
- ->setCheckPermissions(FALSE)
- ->addSelect('phones.phone')
- ->addWhere('id', '=', $testContact['id'])
- ->addWhere('phones.location_type.name', '=', 'Home')
+ $cid2 = Contact::create()->setCheckPermissions(FALSE)
+ ->addValue('first_name', 'Bbb')
+ ->addChain('email1', Email::create()->setValues(['email' => '1@test.test', 'contact_id' => '$id', 'location_type_id:name' => 'Home']))
+ ->addChain('email2', Email::create()->setValues(['email' => '2@test.test', 'contact_id' => '$id', 'location_type_id:name' => 'Work']))
+ ->addChain('email3', Email::create()->setValues(['email' => '3@test.test', 'contact_id' => '$id', 'location_type_id:name' => 'Other']))
->execute()
- ->first();
+ ->first()['id'];
- $this->assertArrayHasKey('phones', $results);
- $this->assertCount(1, $results['phones']);
- $firstPhone = array_shift($results['phones']);
- $this->assertEquals($testPhone['phone'], $firstPhone['phone']);
+ $cid3 = Contact::create()->setCheckPermissions(FALSE)
+ ->addValue('first_name', 'Ccc')
+ ->execute()
+ ->first()['id'];
+
+ $contacts = Contact::get()
+ ->setCheckPermissions(FALSE)
+ ->addSelect('id', 'first_name', 'any_email.email', 'any_email.location_type_id:name', 'any_email.is_primary', 'primary_email.email')
+ ->addJoin('Email AS any_email', TRUE)
+ ->addJoin('Email AS primary_email', FALSE, ['primary_email.is_primary', '=', TRUE])
+ ->addWhere('id', 'IN', [$cid1, $cid2, $cid3])
+ ->addOrderBy('any_email.id')
+ ->setDebug(TRUE)
+ ->execute();
+ $this->assertCount(5, $contacts);
+ $this->assertEquals('Home', $contacts[0]['any_email.location_type_id:name']);
+ $this->assertEquals('yoohoo@yahoo.test', $contacts[1]['primary_email.email']);
+ $this->assertEquals('1@test.test', $contacts[2]['primary_email.email']);
+ $this->assertEquals('1@test.test', $contacts[3]['primary_email.email']);
+ $this->assertEquals('1@test.test', $contacts[4]['primary_email.email']);
}
}
+++ /dev/null
-<?php
-
-/*
- +--------------------------------------------------------------------+
- | Copyright CiviCRM LLC. All rights reserved. |
- | |
- | This work is published under the GNU AGPLv3 license with some |
- | permitted exceptions and without any warranty. For full license |
- | and copyright information, see https://civicrm.org/licensing |
- +--------------------------------------------------------------------+
- */
-
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
-
-namespace api\v4\Query;
-
-use Civi\Api4\Query\Api4SelectQuery;
-use api\v4\UnitTestCase;
-
-/**
- * @group headless
- */
-class Api4SelectQueryComplexJoinTest extends UnitTestCase {
-
- public function setUpHeadless() {
- $relatedTables = [
- 'civicrm_address',
- 'civicrm_email',
- 'civicrm_phone',
- 'civicrm_openid',
- 'civicrm_im',
- 'civicrm_website',
- 'civicrm_activity',
- 'civicrm_activity_contact',
- ];
- $this->cleanup(['tablesToTruncate' => $relatedTables]);
- $this->loadDataSet('SingleContact');
- return parent::setUpHeadless();
- }
-
- public function testWithComplexRelatedEntitySelect() {
- $api = \Civi\API\Request::create('Contact', 'get', ['version' => 4, 'checkPermissions' => FALSE]);
- $query = new Api4SelectQuery($api); $query->select[] = 'id';
- $query->select[] = 'display_name';
- $query->select[] = 'phones.*_id';
- $query->select[] = 'emails.email';
- $query->select[] = 'emails.location_type.name';
- $query->select[] = 'created_activities.contact_id';
- $query->select[] = 'created_activities.activity.subject';
- $query->select[] = 'created_activities.activity.activity_type_id:name';
- $query->where[] = ['first_name', '=', 'Single'];
- $query->where[] = ['id', '=', $this->getReference('test_contact_1')['id']];
- $results = $query->run();
-
- $testActivities = [
- $this->getReference('test_activity_1'),
- $this->getReference('test_activity_2'),
- ];
- $activitySubjects = array_column($testActivities, 'subject');
-
- $this->assertCount(1, $results);
- $firstResult = array_shift($results);
- $this->assertArrayHasKey('created_activities', $firstResult);
- $firstCreatedActivity = array_shift($firstResult['created_activities']);
- $this->assertArrayHasKey('activity', $firstCreatedActivity);
- $firstActivity = $firstCreatedActivity['activity'];
- $this->assertContains($firstActivity['subject'], $activitySubjects);
- $this->assertArrayHasKey('activity_type_id:name', $firstActivity);
-
- $this->assertArrayHasKey('name', $firstResult['emails'][0]['location_type']);
- $this->assertArrayHasKey('location_type_id', $firstResult['phones'][0]);
- $this->assertArrayHasKey('id', $firstResult['phones'][0]);
- $this->assertArrayNotHasKey('phone', $firstResult['phones'][0]);
- }
-
- public function testWithSelectOfOrphanDeepValues() {
- $api = \Civi\API\Request::create('Contact', 'get', ['version' => 4, 'checkPermissions' => FALSE]);
- $query = new Api4SelectQuery($api); $query->select[] = 'id';
- $query->select[] = 'first_name';
- // emails not selected
- $query->select[] = 'emails.location_type.name';
- $results = $query->run();
- $firstResult = array_shift($results);
-
- $this->assertEmpty($firstResult['emails']);
- }
-
- public function testOrderDoesNotMatter() {
- $api = \Civi\API\Request::create('Contact', 'get', ['version' => 4, 'checkPermissions' => FALSE]);
- $query = new Api4SelectQuery($api);
- $query->select[] = 'id';
- $query->select[] = 'first_name';
- // before emails selection
- $query->select[] = 'emails.location_type.name';
- $query->select[] = 'emails.email';
- $query->where[] = ['emails.email', 'IS NOT NULL'];
- $results = $query->run();
- $firstResult = array_shift($results);
-
- $this->assertNotEmpty($firstResult['emails'][0]['location_type']['name']);
- }
-
-}
return parent::setUpHeadless();
}
- public function testWithSingleWhereJoin() {
- $phoneNum = $this->getReference('test_phone_1')['phone'];
-
- $api = \Civi\API\Request::create('Contact', 'get', ['version' => 4, 'checkPermissions' => FALSE]);
- $query = new Api4SelectQuery($api);
- $query->where[] = ['phones.phone', '=', $phoneNum];
- $results = $query->run();
-
- $this->assertCount(1, $results);
- }
-
- public function testOneToManyJoin() {
- $phoneNum = $this->getReference('test_phone_1')['phone'];
-
- $api = \Civi\API\Request::create('Contact', 'get', ['version' => 4, 'checkPermissions' => FALSE]);
- $query = new Api4SelectQuery($api);
- $query->select[] = 'id';
- $query->select[] = 'first_name';
- $query->select[] = 'phones.phone';
- $query->where[] = ['phones.phone', '=', $phoneNum];
- $results = $query->run();
-
- $this->assertCount(1, $results);
- $firstResult = array_shift($results);
- $this->assertArrayHasKey('phones', $firstResult);
- $firstPhone = array_shift($firstResult['phones']);
- $this->assertEquals($phoneNum, $firstPhone['phone']);
- }
-
public function testManyToOneJoin() {
$phoneNum = $this->getReference('test_phone_1')['phone'];
$contact = $this->getReference('test_contact_1');
$this->assertEquals($contact['display_name'], $firstResult['contact.display_name']);
}
- public function testOneToManyMultipleJoin() {
- $api = \Civi\API\Request::create('Contact', 'get', ['version' => 4, 'checkPermissions' => FALSE]);
- $query = new Api4SelectQuery($api);
- $query->select[] = 'id';
- $query->select[] = 'first_name';
- $query->select[] = 'phones.phone';
- $query->where[] = ['first_name', '=', 'Phoney'];
- $results = $query->run();
- $result = array_pop($results);
-
- $this->assertEquals('Phoney', $result['first_name']);
- $this->assertCount(2, $result['phones']);
- }
-
public function testInvaidSort() {
$api = \Civi\API\Request::create('Contact', 'get', ['version' => 4, 'checkPermissions' => FALSE]);
$query = new Api4SelectQuery($api);
*
* @package CRM
* @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
*
*/
->addSelect('preferred_language:label')
->addSelect('last_name')
->execute()
- ->indexBy('last_name')
- ->getArrayCopy();
+ ->indexBy('last_name');
$this->assertEquals($contacts['One']['preferred_language:label'], 'Armenian');
$this->assertEquals($contacts['Two']['preferred_language:label'], 'Basque');
namespace api\v4\Query;
-use Civi\Api4\Contact;
use Civi\Api4\Email;
use api\v4\UnitTestCase;
return parent::setUpHeadless();
}
- public function testOneToManySelect() {
- $results = Contact::get()
- ->addSelect('emails.email')
- ->execute()
- ->indexBy('id')
- ->getArrayCopy();
-
- $firstContactId = $this->getReference('test_contact_1')['id'];
- $secondContactId = $this->getReference('test_contact_2')['id'];
-
- $firstContact = $results[$firstContactId];
- $secondContact = $results[$secondContactId];
- $firstContactEmails = array_column($firstContact['emails'], 'email');
- $secondContactEmails = array_column($secondContact['emails'], 'email');
-
- $expectedFirstEmails = [
- 'test_contact_one_home@fakedomain.com',
- 'test_contact_one_work@fakedomain.com',
- ];
- $expectedSecondEmails = [
- 'test_contact_two_home@fakedomain.com',
- 'test_contact_two_work@fakedomain.com',
- ];
-
- $this->assertEquals($expectedFirstEmails, $firstContactEmails);
- $this->assertEquals($expectedSecondEmails, $secondContactEmails);
- }
-
public function testManyToOneSelect() {
$results = Email::get()
->addSelect('contact.display_name')
->execute()
- ->indexBy('id')
- ->getArrayCopy();
+ ->indexBy('id');
$firstEmail = $this->getReference('test_email_1');
$secondEmail = $this->getReference('test_email_2');
'id' => $customFieldId,
'name' => $name,
'data_type' => 'String',
- 'html_type' => 'Multi-Select',
+ 'html_type' => 'Select',
'column_name' => $name,
+ 'serialize' => 1,
];
/** @var \Civi\Api4\Service\Spec\CustomFieldSpec $field */
<title>Amount</title>
<type>decimal</type>
<required>true</required>
- <comment>Amount to be contributed or charged each recurrence.</comment>
+ <comment>Amount to be collected (including any sales tax) by payment processor each recurrence.</comment>
<add>1.6</add>
<html>
<type>Text</type>
<add>5.6</add>
<onDelete>SET NULL</onDelete>
</foreignKey>
+ <field>
+ <name>serialize</name>
+ <type>int unsigned</type>
+ <title>Serialize</title>
+ <length>255</length>
+ <comment>Serialization method - a non-null value indicates a multi-valued field.</comment>
+ <pseudoconstant>
+ <callback>CRM_Core_SelectValues::fieldSerialization</callback>
+ </pseudoconstant>
+ <add>5.26</add>
+ </field>
<field>
<name>filter</name>
<type>varchar</type>
<title>Domain ID</title>
<comment>Which site is this mailing for</comment>
<add>4.6</add>
+ <required>true</required>
</field>
<field>
<name>testing_criteria</name>
(1231, 1101, "DL", "Delhi"),
(1232, 1101, "LD", "Lakshadweep"),
(1233, 1101, "PY", "Pondicherry"),
+-- Note we believe all lower-case is correct for Poland. See https://github.com/civicrm/civicrm-core/pull/17107
(1300, 1172, "MZ", "mazowieckie"),
(1301, 1172, "PM", "pomorskie"),
(1302, 1172, "DS", "dolnośląskie"),