api3 api4 OptionValue.create and OptionValue.update: Setting the default value (is_default=1) ignores domain ID
Overview
It seems that when you call either OptionValue.create
or OptionValue.update
using either api3
or api4
and you set "is default" to "true" (is_default = 1
), this ignores the civicrm_option_value.domain_id
column even when multisite is enabled. It sets the is_default
field value to 1
for the new or updated record, but forces is_default
to 0
(zero) for all other records in the Option Group, regardless of domain_id
.
I noticed this because I was trying to script a multisite deployment on WordPress using the wp cv
and cv.phar
CLI tools. CiviCRM wants each site (domain ID) in the multisite to have its own default "From Email Address" (DB option_group_name = "from_email_address"
), and it complains via an alert message if that is not configured. The Civi admin UI allows you to set a separate default "From Email Address" for each domain ID. (SQL query shows multiple records with "civicrm_option_value.is_default = 1", one per domain ID.) But when you invoke OptionValue.create
or OptionValue.update
specifying is_default = 1
, then the "last update wins." Only the last added or updated record will have is_default = 1
.
Reproduction steps
- Set up a multi-domain Civi installation with, say, three different sites. (I created mine on WordPress.)
- Via the UI, navigate to the Civi dashboard for each site. You should see the prompt to set a default "From Email Address".
- Using the UI, add a default email address for each site.
- Inspect the
civicrm_option_value
table with a query like the following to verify there are multiple default values (one per domain ID). Also take note if thecivicrm_option_value.id
values for use in the subsequent API calls below.
select y.id, y.name, y.is_default, y.domain_id, y.label, y.option_group_id, x.name as option_group_name
from civicrm_option_value y
inner join civicrm_option_group x on x.id = y.option_group_id
where x.name='from_email_address'
- At the command prompt, try any combination of or variations of the following API calls to add or update an option value record as the default.
- After each of the API calls, re-inspect the
civicrm_option_value
table to verify that there is now only onecivicrm_option_value
record withis_default = 1
for the Option Group 'from_email_address' corresponding to the most recent add/update, regardless of domain ID.
STDEMAIL="webmaster@a_great_nonprofit.org"
DOMAIN1=site1.agreatnonprofit.org
DOMAINID1=1
DOMAIN2=site2.agreatnonprofit.org
DOMAINID2=20
DOMAIN3=site3.agreatnonprofit.org
DOMAINID3=30
wp cv api OptionValue.create option_group_id=from_email_address label="$STDEMAIL" name="$STDEMAIL" is_default=1 --url=$DOMAIN1
wp cv api OptionValue.create option_group_id=from_email_address label="$STDEMAIL" name="$STDEMAIL" is_default=1 --url=$DOMAIN2
wp cv api OptionValue.create option_group_id=from_email_address label="$STDEMAIL" name="$STDEMAIL" is_default=1 domain_id=$DOMAINID3 --url=$DOMAIN3
wp cv api OptionValue.get sequential=1 limit=1 option_group_id="from_email_address" options='{"sort":"is_default DESC"}' domain_id=$DOMAINID1 --url=$DOMAIN1
# Using the output from the above, get the record ID from the civicrm_option_value table (RECORDID)
RECORDID1={record_id_from_above}
export SITEURL="$DOMAIN1"
cv api4 OptionValue.update +w "id = $RECORDID1" +w "domain_id = $DOMAINID1" +v is_default=true +v "label=\"$STDEMAIL\"" +v "name=\"$STDEMAIL\""
# for simplicity here assume that for DOMAIN2, the civicrm_option_value.id = 1020
export SITEURL="$DOMAIN2"
cv api4 OptionValue.update '{"where":[["id","=",1020],["domain_id","=",20]],"values":{"is_default":true,"label":"webmaster@a_great_nonprofit.org","name":"webmaster@a_great_nonprofit.org"}}'
Current behavior
Any OptionValue.create
or OptionValue.update
API call with either api3
or api4
that has is_default = 1
will set is_default = 1
for the target DB record, but will force all other civicrm_option_value
records with the same option_group_id
to is_default = 0
regardless of the domain ID.
Expected behavior
OptionValue.create
or OptionValue.update
should:
- Enforce one
is_default = 1
default record for eachoption_group_id, domain_id
tuple. - Or: if some Option Groups should always ignore the domain ID, then the API should be smart enough to know which option group names (like 'from_email_address') should allow a default record for each domain ID and which ones should not.
- Or: it should respect the domain ID when CiviCRM multisite is "enabled" and ignore it otherwise. However, in this last case, it seems that there should be only one domain ID
1
, so this perhaps reduces to #1 (closed) above.
Environment information
- Browser: 89.0.4389.90
- CiviCRM: 5.35.1
- PHP: 7.2
- CMS: WordPress 5.4.4
- Database: MariaDB 10.3
- Web Server: Apache 2.4
For command-line use of cv
my civicrm.settings.php
file inspects the SITEURL
environment variable and sets the correct Civi domain ID, domain group ID, and domain organization ID.
Comments
It seems I keep crashing on the dangerous shoals of CiviCRM multisite/multi-domain! ;-) It would be helpful to have an up-to-date summary of the limitations, known issues, and potential gotchas of working with multisite.