Merge pull request #17312 from totten/url3
authorEileen McNaughton <emcnaughton@wikimedia.org>
Thu, 21 May 2020 00:55:23 +0000 (12:55 +1200)
committerGitHub <noreply@github.com>
Thu, 21 May 2020 00:55:23 +0000 (12:55 +1200)
Migrate CiviMail "extern" scripts to conventional routes

221 files changed:
CRM/Activity/Form/Task/PDFLetterCommon.php
CRM/Activity/Page/AJAX.php
CRM/Activity/Tokens.php
CRM/Admin/Form/Preferences/Mailing.php
CRM/Admin/Page/APIExplorer.php
CRM/Campaign/Page/DashBoard.php
CRM/Case/Form/Activity.php
CRM/Case/Info.php
CRM/Case/Page/Tab.php
CRM/Case/XMLProcessor/Process.php
CRM/Contact/Form/CustomData.php
CRM/Contact/Form/Edit/Individual.php
CRM/Contact/Form/Search/Criteria.php
CRM/Contact/Import/Parser.php
CRM/Contact/Import/Parser/Contact.php
CRM/Contribute/BAO/Contribution.php
CRM/Contribute/DAO/ContributionRecur.php
CRM/Contribute/Form/ContributionBase.php
CRM/Contribute/Form/Task/Status.php
CRM/Core/Action.php
CRM/Core/BAO/ActionSchedule.php
CRM/Core/BAO/Address.php
CRM/Core/BAO/Block.php
CRM/Core/BAO/Cache.php
CRM/Core/BAO/CustomField.php
CRM/Core/BAO/CustomGroup.php
CRM/Core/BAO/CustomOption.php
CRM/Core/BAO/CustomValueTable.php
CRM/Core/BAO/Discount.php
CRM/Core/BAO/File.php
CRM/Core/BAO/Job.php
CRM/Core/BAO/LabelFormat.php
CRM/Core/BAO/Location.php
CRM/Core/BAO/Mapping.php
CRM/Core/BAO/PaperSize.php
CRM/Core/BAO/PdfFormat.php
CRM/Core/BAO/PreferencesDate.php
CRM/Core/BAO/RecurringEntity.php
CRM/Core/BAO/SchemaHandler.php
CRM/Core/BAO/StatusPreference.php
CRM/Core/BAO/UFGroup.php
CRM/Core/BAO/UFMatch.php
CRM/Core/BAO/WordReplacement.php
CRM/Core/Component.php
CRM/Core/DAO.php
CRM/Core/DAO/AllCoreTables.php
CRM/Core/DAO/CustomField.php
CRM/Core/DAO/Factory.php
CRM/Core/Error.php
CRM/Core/Form.php
CRM/Core/I18n.php
CRM/Core/I18n/Form.php
CRM/Core/Invoke.php
CRM/Core/Menu.php
CRM/Core/Page.php
CRM/Core/Page/Redirect.php
CRM/Core/Payment/BaseIPN.php
CRM/Core/Payment/PayPalProIPN.php
CRM/Core/Permission/Base.php
CRM/Core/PseudoConstant.php
CRM/Core/Region.php
CRM/Core/SelectValues.php
CRM/Core/ShowHideBlocks.php
CRM/Core/Smarty.php
CRM/Core/Smarty/plugins/block.crmButton.php
CRM/Core/Smarty/plugins/block.crmScope.php
CRM/Core/Smarty/plugins/block.icon.php
CRM/Core/Smarty/plugins/function.crmVersion.php
CRM/Core/Smarty/plugins/function.privacyFlag.php
CRM/Core/TemporaryErrorScope.php
CRM/Core/TokenTrait.php
CRM/Core/Transaction.php
CRM/Custom/Form/Field.php
CRM/Dedupe/Merger.php
CRM/Event/Form/Registration/Confirm.php
CRM/Financial/BAO/Payment.php
CRM/Financial/Form/PaymentEdit.php
CRM/Logging/Schema.php
CRM/Mailing/BAO/Mailing.php
CRM/Mailing/BAO/MailingAB.php
CRM/Mailing/DAO/MailingAB.php
CRM/Member/BAO/MembershipType.php
CRM/Price/BAO/PriceField.php
CRM/Price/BAO/PriceFieldValue.php
CRM/Price/Page/Option.php
CRM/Queue/ErrorPolicy.php
CRM/Queue/Service.php
CRM/Report/Form/Contribute/Detail.php
CRM/Upgrade/Incremental/General.php
CRM/Upgrade/Incremental/php/FiveTwentySeven.php
CRM/Upgrade/Incremental/sql/5.27.alpha1.mysql.tpl
CRM/Utils/API/MatchOption.php
CRM/Utils/API/ReloadOption.php
CRM/Utils/AutoClean.php
CRM/Utils/Check/Component/Env.php
CRM/Utils/ConsoleTee.php
CRM/Utils/FakeObject.php
CRM/Utils/File.php
CRM/Utils/GlobalStack.php
CRM/Utils/Hook.php
CRM/Utils/Migrate/Export.php
CRM/Utils/SQL/BaseParamQuery.php
CRM/Utils/SQL/Delete.php
CRM/Utils/SQL/Select.php
CRM/Utils/Signer.php
CRM/Utils/String.php
CRM/Utils/System/Drupal.php
CRM/Utils/System/Drupal8.php
CRM/Utils/System/WordPress.php
CRM/Utils/Url.php
Civi.php
Civi/API/Request.php
Civi/API/SelectQuery.php
Civi/API/Subscriber/ChainSubscriber.php
Civi/API/Subscriber/DynamicFKAuthorization.php
Civi/API/WhitelistRule.php
Civi/ActionSchedule/Event/MailingQueryEvent.php
Civi/ActionSchedule/RecipientBuilder.php
Civi/Angular/AngularLoader.php
Civi/Api4/Action/CustomValue/GetFields.php
Civi/Api4/Action/GetActions.php
Civi/Api4/Action/GroupContact/Save.php
Civi/Api4/Action/Relationship/Get.php
Civi/Api4/Action/Setting/AbstractSettingAction.php
Civi/Api4/Action/Setting/Get.php
Civi/Api4/Action/Setting/GetFields.php
Civi/Api4/Action/Setting/Revert.php
Civi/Api4/Event/Events.php
Civi/Api4/Event/GetSpecEvent.php [deleted file]
Civi/Api4/Event/Subscriber/ContactSchemaMapSubscriber.php [deleted file]
Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php [deleted file]
Civi/Api4/Generic/AbstractEntity.php
Civi/Api4/Generic/DAOEntity.php
Civi/Api4/Generic/DAOGetAction.php
Civi/Api4/Generic/Traits/DAOActionTrait.php
Civi/Api4/Query/Api4SelectQuery.php
Civi/Api4/Query/SqlExpression.php
Civi/Api4/Service/Schema/Joinable/OptionValueJoinable.php [deleted file]
Civi/Api4/Service/Schema/Joiner.php
Civi/Api4/Service/Schema/SchemaMapBuilder.php
Civi/Api4/Service/Spec/Provider/DomainCreationSpecProvider.php
Civi/Api4/Service/Spec/SpecFormatter.php
Civi/Api4/Service/Spec/SpecGatherer.php
Civi/Api4/Utils/FormattingUtil.php
Civi/Core/AssetBuilder.php
Civi/Core/CiviEventInspector.php
Civi/Core/Event/GenericHookEvent.php
Civi/Core/Resolver.php
Civi/Install/Requirements.php
Civi/Test.php
Civi/Test/Api3TestTrait.php
Civi/Test/HookInterface.php
Civi/Token/Event/TokenRegisterEvent.php
Civi/Token/Event/TokenValueEvent.php
Civi/Token/TokenProcessor.php
Civi/Token/TokenRow.php
ang/api4Explorer/Explorer.html
ang/api4Explorer/Explorer.js
ang/crmMailingAB/ListCtrl.html
api/api.php
api/class.api.php
api/v3/Attachment.php
api/v3/Case.php
api/v3/CustomField.php
api/v3/CustomValue.php
api/v3/GroupContact.php
api/v3/Mailing.php
api/v3/MailingAB.php
api/v3/utils.php
css/civicrm.css
css/dashboard.css
i/TreeMinus.gif [deleted file]
i/TreeMinusWhite.gif [deleted file]
i/TreePlus.gif [deleted file]
i/TreePlusWhite.gif [deleted file]
install/index.php
js/Common.js
js/crm.drupal7.js [new file with mode: 0644]
js/crm.menubar.js
settings/Mailing.setting.php
templates/CRM/Admin/Form/Setting/Smtp.tpl
templates/CRM/Campaign/Form/Search/Petition.tpl
templates/CRM/Campaign/Form/Search/Survey.tpl
templates/CRM/Campaign/Form/Task/Interview.tpl
templates/CRM/Case/Form/ActivityTab.tpl
templates/CRM/Case/Form/ActivityToCase.tpl
templates/CRM/Case/Page/DashboardSelector.tpl
templates/CRM/Contact/Form/Search/Custom/EventDetails.tpl
templates/CRM/Contact/Form/Selector.tpl
templates/CRM/Contact/Page/Inline/Address.tpl
templates/CRM/Contact/Page/Inline/Email.tpl
templates/CRM/Contact/Page/Inline/Phone.tpl
templates/CRM/Custom/Form/Field.tpl
templates/CRM/Custom/Page/Option.tpl
templates/CRM/Event/Form/ManageEvent/Fee.tpl
templates/CRM/Mailing/Form/ForwardMailing.tpl
templates/CRM/Member/Form/Membership.tpl
templates/CRM/Member/Page/Tab.tpl
templates/CRM/Report/Form/Actions.tpl
templates/CRM/Report/Form/Tabs/OrderBy.tpl
tests/phpunit/CRM/Activity/Page/AJAXTest.php [new file with mode: 0644]
tests/phpunit/CRM/Contact/Import/Parser/ContactTest.php
tests/phpunit/CRM/Core/BAO/CustomFieldTest.php
tests/phpunit/CRM/Core/BAO/CustomQueryTest.php
tests/phpunit/CRM/Core/DAO/AllCoreTablesTest.php
tests/phpunit/CRM/Core/PageTest.php [new file with mode: 0644]
tests/phpunit/CRM/Logging/SchemaTest.php
tests/phpunit/CRM/Report/FormTest.php
tests/phpunit/CRM/Utils/DateTest.php
tests/phpunit/CiviTest/CiviUnitTestCase.php
tests/phpunit/api/v3/PaymentTest.php
tests/phpunit/api/v4/Action/FkJoinTest.php
tests/phpunit/api/v4/Query/Api4SelectQueryComplexJoinTest.php [deleted file]
tests/phpunit/api/v4/Query/Api4SelectQueryTest.php
tests/phpunit/api/v4/Query/OneToOneJoinTest.php
tests/phpunit/api/v4/Query/SelectQueryMultiJoinTest.php
tests/phpunit/api/v4/Spec/SpecFormatterTest.php
xml/schema/Contribute/ContributionRecur.xml
xml/schema/Core/CustomField.xml
xml/schema/Mailing/MailingAB.xml
xml/templates/civicrm_state_province.tpl

index 7af6c006cfe0dbc9a0d956ca5fa62783a0224b6e..424249fb5c8a2e28ce12d651fee9fb5ad8e25a26 100644 (file)
@@ -53,7 +53,7 @@ class CRM_Activity_Form_Task_PDFLetterCommon extends CRM_Core_Form_Task_PDFLette
     $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();
 
@@ -69,7 +69,7 @@ class CRM_Activity_Form_Task_PDFLetterCommon extends CRM_Core_Form_Task_PDFLette
     return new TokenProcessor(\Civi::dispatcher(), [
       'controller' => get_class(),
       'smarty' => FALSE,
-      'schema' => ['activity_id'],
+      'schema' => ['activityId'],
     ]);
   }
 
index e652726a608d756508da8738964649b15401f588..c7fe505bc48a3fce3b4d7af2e075b87c2a799e79 100644 (file)
@@ -353,7 +353,7 @@ class CRM_Activity_Page_AJAX {
     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,
index e8d875c5e7ad852fc16f5d0b1fe9d3d9a2af19a3..fff8e6224a507fe684cf1465cbf0881d7365ca91 100644 (file)
@@ -48,12 +48,10 @@ class CRM_Activity_Tokens extends \Civi\Token\AbstractTokenSubscriber {
   }
 
   /**
-   * 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';
   }
 
   /**
@@ -90,7 +88,7 @@ class CRM_Activity_Tokens extends \Civi\Token\AbstractTokenSubscriber {
     // 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;
@@ -124,8 +122,6 @@ class CRM_Activity_Tokens extends \Civi\Token\AbstractTokenSubscriber {
 
   /**
    * @inheritDoc
-   *
-   * @throws \CRM_Core_Exception
    */
   public function evaluateToken(\Civi\Token\TokenRow $row, $entity, $field, $prefetch = NULL) {
     // maps token name to api field
@@ -134,7 +130,7 @@ class CRM_Activity_Tokens extends \Civi\Token\AbstractTokenSubscriber {
     ];
 
     // 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];
 
index d786297fe9296893349ddd659e4b0b0638f9b5d3..b2f4a975dc32a8317860a6e3b5a519604e1d1870 100644 (file)
@@ -33,6 +33,8 @@ class CRM_Admin_Form_Preferences_Mailing extends CRM_Admin_Form_Preferences {
     '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() {
index 13363ee23122a5dc8e84f61e87f7cc2713acc5b9..c769535be31717c3635ce8370a3e366faba915a8 100644 (file)
@@ -177,7 +177,7 @@ class CRM_Admin_Page_APIExplorer extends CRM_Core_Page {
     // 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);
@@ -205,7 +205,8 @@ class CRM_Admin_Page_APIExplorer extends CRM_Core_Page {
 
   /**
    * 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
@@ -224,8 +225,8 @@ class CRM_Admin_Page_APIExplorer extends CRM_Core_Page {
 
     // 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(
@@ -242,8 +243,8 @@ class CRM_Admin_Page_APIExplorer extends CRM_Core_Page {
     $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);
       }
     }
index 563f85654b9a02b09bef4ce4e0a6b681ed4c06bb..dfd6f3126cd7bcbd8b30cef56ad646260d5ddb11 100644 (file)
@@ -312,7 +312,8 @@ class CRM_Campaign_Page_DashBoard extends CRM_Core_Page {
         }
         $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>';
@@ -411,7 +412,9 @@ class CRM_Campaign_Page_DashBoard extends CRM_Core_Page {
           $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,
index d84717985ca2fe8bd68f357aa53f01238b60037c..01714e436b05a4dcb596827eb46b2c86688018c4 100644 (file)
@@ -639,6 +639,28 @@ class CRM_Case_Form_Activity extends CRM_Activity_Form_Activity {
           ];
           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]);
index 0f996ee0c890116dd80d19f72e9ea8fd796bfc33..bf9d4adca5d870e14a1dad5e104b0320f3d828f2 100644 (file)
@@ -228,6 +228,8 @@ class CRM_Case_Info extends CRM_Core_Component_Info {
    *   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 (
@@ -238,8 +240,7 @@ class CRM_Case_Info extends CRM_Core_Component_Info {
       $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"));
       }
     }
   }
index e89132c3dedb03b506d74f5d6676659fc1bc4237..12f6cd06714d236ee121602e3451e5eb7c47bf2f 100644 (file)
@@ -63,7 +63,7 @@ class CRM_Case_Page_Tab extends CRM_Core_Page {
     }
     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.');
       }
     }
 
index 4eb4e8826ef0321526fa62d964d115e18a2ae657..9307f2bb905a61f408e0121b175186bcd3200943 100644 (file)
@@ -23,18 +23,16 @@ class CRM_Case_XMLProcessor_Process extends CRM_Case_XMLProcessor {
    * @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();
@@ -56,10 +54,9 @@ class CRM_Case_XMLProcessor_Process extends CRM_Case_XMLProcessor {
     $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) {
@@ -93,8 +90,7 @@ class CRM_Case_XMLProcessor_Process extends CRM_Case_XMLProcessor {
               $params
             )
             ) {
-              CRM_Core_Error::fatal();
-              return FALSE;
+              throw new CRM_Core_Exception('Unable to create case relationships');
             }
           }
         }
@@ -190,7 +186,7 @@ class CRM_Case_XMLProcessor_Process extends CRM_Case_XMLProcessor {
    * @param array $params
    *
    * @return bool
-   * @throws Exception
+   * @throws CRM_Core_Exception
    */
   public function createRelationships($relationshipTypeXML, &$params) {
 
@@ -198,10 +194,9 @@ class CRM_Case_XMLProcessor_Process extends CRM_Case_XMLProcessor {
     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'];
@@ -228,8 +223,7 @@ class CRM_Case_XMLProcessor_Process extends CRM_Case_XMLProcessor {
       }
 
       if (!$this->createRelationship($relationshipParams)) {
-        CRM_Core_Error::fatal();
-        return FALSE;
+        throw new CRM_Core_Exception('Unable to create case relationship');
       }
     }
     return TRUE;
@@ -415,10 +409,9 @@ AND        a.is_deleted = 0
 
     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'];
@@ -549,8 +542,7 @@ AND        a.is_deleted = 0
     $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
@@ -731,17 +723,16 @@ AND        a.is_deleted = 0
 
   /**
    * @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);
index 86dc621b46548bad4166c8562c9c3117ad2373b9..87849e1b28eb8950ef60d64fe91be3881c91173d 100644 (file)
@@ -52,13 +52,6 @@ class CRM_Contact_Form_CustomData extends CRM_Core_Form {
    */
   //protected $_groupTree;
 
-  /**
-   * Which blocks should we show and hide.
-   *
-   * @var CRM_Core_ShowHideBlocks
-   */
-  protected $_showHide;
-
   /**
    * Array group titles.
    *
index 29d8dc0a5f4da8cb6f468fa16dd8619ea4e48968..f4d933933a3c30915c800db695d7b833e212d98d 100644 (file)
@@ -88,7 +88,6 @@ class CRM_Contact_Form_Edit_Individual {
         'objectExists',
         ['CRM_Contact_DAO_Contact', $form->_contactId, 'external_identifier']
       );
-      CRM_Core_ShowHideBlocks::links($form, 'demographics', '', '');
     }
   }
 
index d4d711296cb73d1808567d267e0b00c56a5e643c..024a8305ccfd6904188ab9c320635956b6318b43 100644 (file)
@@ -605,7 +605,6 @@ class CRM_Contact_Form_Search_Criteria {
 
     foreach ($groupDetails as $key => $group) {
       $_groupTitle[$key] = $group['name'];
-      CRM_Core_ShowHideBlocks::links($form, $group['name'], '', '');
 
       foreach ($group['fields'] as $field) {
         $fieldId = $field['id'];
index 80d10ff5865d2dd8c3a294e695f5717a8f7af5a3..260c45cfbd7d315ef3b4d046bb43d5cb78a4d656 100644 (file)
@@ -832,6 +832,7 @@ abstract class CRM_Contact_Import_Parser extends CRM_Import_Parser {
               $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);
             }
index bb4be04495888a35366838bcae94bfa870a9060c..760d16059b8df74c45976bc8d5615d2e74765e66 100644 (file)
@@ -1167,7 +1167,10 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Contact_Import_Parser {
         /* 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];
             }
@@ -1178,29 +1181,26 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Contact_Import_Parser {
               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);
@@ -1222,7 +1222,7 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Contact_Import_Parser {
               }
             }
           }
-          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) {
@@ -1234,7 +1234,7 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Contact_Import_Parser {
               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) {
@@ -1247,7 +1247,7 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Contact_Import_Parser {
               }
             }
           }
-          elseif ($customFields[$customFieldID]['html_type'] == 'Multi-Select Country') {
+          elseif ($isSerialized && $dataType == 'Country') {
             $mulValues = explode(',', $value);
             foreach ($mulValues as $countryValue) {
               if ($countryValue) {
index 48b01b3e6c1277fa54ba0ee0bccede7c2c7f2985..350bb0ff9211f542ae21c692f944d96e33403f2e 100644 (file)
@@ -1290,7 +1290,8 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution {
       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%%",
@@ -2755,7 +2756,7 @@ INNER JOIN civicrm_activity ON civicrm_activity_contact.activity_id = civicrm_ac
    * @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
index 060a304ed0eedf8dba24618ae4ae841f34fe6541..192721f6e66711024e83d3d64e504249f4158953 100644 (file)
@@ -6,7 +6,7 @@
  *
  * Generated from xml/schema/CRM/Contribute/ContributionRecur.xml
  * DO NOT EDIT.  Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:d0c493bb1a98e2b75ebaceb330a40fb4)
+ * (GenCodeChecksum:1c6a0e2a296ffbb530eee381975b9d17)
  */
 
 /**
@@ -43,7 +43,7 @@ class CRM_Contribute_DAO_ContributionRecur extends CRM_Core_DAO {
   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
    */
@@ -294,7 +294,7 @@ class CRM_Contribute_DAO_ContributionRecur extends CRM_Core_DAO {
           '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,
index db2870fa146a305f725d5a2fe4b7c224fad2bc5d..ca2805d551eb1e0849df356b4d6be2f3501cc2a3 100644 (file)
@@ -371,12 +371,12 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
         $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);
@@ -437,7 +437,7 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
       !$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']) {
@@ -577,7 +577,7 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
     //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;
         }
@@ -749,6 +749,12 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
     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;
@@ -765,7 +771,7 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
     // 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();
@@ -824,6 +830,8 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
    *
    * @param int $id
    * @param CRM_Core_Form $form
+   *
+   * @throws \CRM_Core_Exception
    */
   public function buildComponentForm($id, $form) {
     if (empty($id)) {
@@ -833,13 +841,13 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
     $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']);
@@ -850,7 +858,7 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
         ];
         $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) {
@@ -892,7 +900,7 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
         }
 
         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);
@@ -911,7 +919,7 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
                 in_array('Contribution', $onBehalfProfile)
               )
             ) {
-              CRM_Core_Error::fatal($msg);
+              CRM_Core_Error::statusBounce($msg);
             }
           }
         }
@@ -1031,7 +1039,7 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
    */
   public function getTemplateFileName() {
     $fileName = $this->checkTemplateFileExists();
-    return $fileName ? $fileName : parent::getTemplateFileName();
+    return $fileName ?: parent::getTemplateFileName();
   }
 
   /**
@@ -1049,6 +1057,8 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
 
   /**
    * Authenticate pledge user during online payment.
+   *
+   * @throws \CRM_Core_Exception
    */
   public function authenticatePledgeUser() {
     //get the userChecksum and contact id
@@ -1087,12 +1097,12 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
     }
 
     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)]));
     }
   }
 
@@ -1102,6 +1112,8 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
    * 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');
@@ -1135,6 +1147,9 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
    *
    * @return bool
    *   Is this a separate membership payment
+   *
+   * @throws \CiviCRM_API3_Exception
+   * @throws \CRM_Core_Exception
    */
   protected function buildMembershipBlock(
     $cid,
index 09f1285c99b43ffa64a42b0fb6396d172b6f6a45..c8acc1e8ff88e65a559b25f33d5f0a29e5a1e31c 100644 (file)
@@ -215,8 +215,6 @@ AND    co.id IN ( $contribIDs )";
     $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);
@@ -246,11 +244,13 @@ AND    co.id IN ( $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;
@@ -261,7 +261,6 @@ AND    co.id IN ( $contribIDs )";
           $contributionStatuses
         )
       ) {
-        $transaction->commit();
         continue;
       }
 
@@ -282,8 +281,8 @@ AND    co.id IN ( $contribIDs )";
       $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();
index 0e54b2445b738ebb6384987de42597380d3e3712..e24431a2ef3a9ccaebc2ff004da72566555ed91b 100644 (file)
@@ -247,7 +247,7 @@ class CRM_Core_Action {
           $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' => ''])
         );
       }
     }
index c1d7ec1a104270d55cf61ca6ab974523641da72c..a53e0e623278acb7ba7a1eb4b8f4df4aa58b8267 100644 (file)
@@ -226,6 +226,7 @@ FROM civicrm_action_schedule cas
    * @param int $id
    *   ID of the Reminder to be deleted.
    *
+   * @throws CRM_Core_Exception
    */
   public static function del($id) {
     if ($id) {
@@ -236,7 +237,7 @@ FROM civicrm_action_schedule cas
         return;
       }
     }
-    CRM_Core_Error::fatal(ts('Invalid value passed to delete function.'));
+    throw new CRM_Core_Exception(ts('Invalid value passed to delete function.'));
   }
 
   /**
index e22a25cecd2c1e61a94eb029b36528920b8f7956..c0f6561c8a41c624b622d2725403b33c9bbb304d 100644 (file)
@@ -1181,7 +1181,7 @@ SELECT is_primary,
     $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 = [
index a36b698e66d446923e0ffe0844ac2c7973d3f260..876cd323c5b2d8024ea3d2de1e68afba3bfc2b95 100644 (file)
@@ -40,6 +40,7 @@ class CRM_Core_BAO_Block {
    *
    * @return array
    *   Array of $block objects.
+   * @throws CRM_Core_Exception
    */
   public static function &getValues($blockName, $params) {
     if (empty($params)) {
@@ -52,7 +53,7 @@ class CRM_Core_BAO_Block {
     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);
     }
index ee678348dd60813e4ec3a6575532bbacf8647c9f..a13a6fee8588cfa91a32a185e93cc35e45477f6a 100644 (file)
@@ -149,6 +149,7 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
    * @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(
@@ -167,7 +168,7 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
     // 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();
index 128c2a8841502016b78e3c3081b29664f95d6a25..a682676e749a904aea770edfa666d7e5aafe13ca 100644 (file)
@@ -242,6 +242,20 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
     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.
    *
@@ -396,6 +410,7 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
                             $cfTable.date_format,
                             $cfTable.time_format,
                             $cgTable.is_multiple,
+                            $cfTable.serialize,
                             $cgTable.table_name,
                             og.name as option_group_name
                      FROM $cfTable
@@ -478,6 +493,7 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
           $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] : '';
@@ -592,6 +608,7 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
    *
    * @return CRM_Core_BAO_CustomField
    *   The field object.
+   * @throws CRM_Core_Exception
    */
   public static function getFieldObject($fieldID) {
     $field = new CRM_Core_BAO_CustomField();
@@ -603,7 +620,7 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_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 = [];
@@ -659,11 +676,8 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
     // 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',
     ]));
@@ -680,7 +694,7 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
       $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;
@@ -1053,9 +1067,6 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
       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');
@@ -1233,7 +1244,7 @@ class CRM_Core_BAO_CustomField extends CRM_Core_DAO_CustomField {
     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);
@@ -1767,11 +1778,13 @@ WHERE  id IN ( %1, %2 )
    *   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;
@@ -1881,7 +1894,12 @@ WHERE  id IN ( %1, %2 )
       $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) {
@@ -1935,7 +1953,7 @@ WHERE  id IN ( %1, %2 )
       // 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;
@@ -2055,7 +2073,7 @@ WHERE  id IN ( %1, %2 )
    *
    * @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}";
@@ -2072,7 +2090,7 @@ AND    cf.id = %1";
       $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);
@@ -2194,49 +2212,33 @@ WHERE  option_group_id = {$optionGroupId}";
    * 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;
   }
 
   /**
@@ -2523,15 +2525,20 @@ WHERE cf.id = %1 AND cg.is_multiple = 1";
   /**
    * 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']);
   }
 
   /**
index 4dc6d36f3fece9c2c004c78c2df2204ff23ce97c..0f08b2779845a96f62783cc979f69d4921524cd2 100644 (file)
@@ -399,6 +399,7 @@ class CRM_Core_BAO_CustomGroup extends CRM_Core_DAO_CustomGroup {
         'time_format',
         'option_group_id',
         'in_selector',
+        'serialize',
       ],
       'custom_group' => [
         'id',
@@ -1533,7 +1534,6 @@ ORDER BY civicrm_custom_group.weight,
     $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
index 7ada06678891739ceb6b36e1b6ca6fd39b7de8ab..e876436f75ff03a34da1d192348282ea46563921 100644 (file)
@@ -138,12 +138,11 @@ class CRM_Core_BAO_CustomOption {
       }
 
       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');
@@ -278,7 +277,7 @@ SET    {$dao->columnName} = REPLACE( {$dao->columnName}, %1, %2 )";
           break;
 
         default:
-          CRM_Core_Error::fatal();
+          throw new CRM_Core_Exception('Invalid HTML Type');
       }
       $dao = CRM_Core_DAO::executeQuery($query, $queryParams);
     }
index 1fa9a508a82df987e017fcb2d3b8e9bea2380330..0a724557b86e4a90eef27b879bf604b0fe3193df 100644 (file)
@@ -153,7 +153,7 @@ class CRM_Core_BAO_CustomValueTable {
 
             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
@@ -318,7 +318,7 @@ class CRM_Core_BAO_CustomValueTable {
         return 'datetime';
 
       default:
-        CRM_Core_Error::fatal();
+        throw new CRM_Core_Exception('Invalid Field Type');
     }
   }
 
@@ -415,12 +415,13 @@ class CRM_Core_BAO_CustomValueTable {
    *   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;
     }
 
index ce24c32e4e4888b3e9c601534f007a44dffa50b5..812747eb42466b68edfc2893c488e73535e688bd 100644 (file)
@@ -98,11 +98,12 @@ class CRM_Core_BAO_Discount extends CRM_Core_DAO_Discount {
    * @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;
     }
 
index 3e78e3ffeaa9a8be4466f13650fcf60269b068e1..e8c2ad60525a62f3fc4042c3fb2c86f7267f4e47 100644 (file)
@@ -677,7 +677,7 @@ AND       CEF.entity_id    = %2";
 
   /**
    * Delete a file attachment from an entity table / entity ID
-   *
+   * @throws CRM_Core_Exception
    */
   public static function deleteAttachment() {
     $params = [];
@@ -689,7 +689,7 @@ AND       CEF.entity_id    = %2";
 
     $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']);
index 5609b3075f0270f4133a8bcb1ccfc427782aad97..5cbcd121fda9316c245988c6cce3b00c65c6f730 100644 (file)
@@ -88,10 +88,11 @@ class CRM_Core_BAO_Job extends CRM_Core_DAO_Job {
    *   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();
index 175d599410bfe207560ab21264a09c271cd8fcf0..8678560c9cf30a693035e0d4ea34b5872fda3168 100644 (file)
@@ -527,7 +527,7 @@ class CRM_Core_BAO_LabelFormat extends CRM_Core_DAO_OptionValue {
     // 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();
 
index ec50cd15f04c81a462d77632c6e0af0750030625..e2d393976cf114715d0b1406dcd6bf0edfcfda6e 100644 (file)
@@ -260,13 +260,14 @@ WHERE e.id = %1";
    *   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) ||
index 19c6ec2290cd8cff27b851cdc0386c2522be4a1d..56c1d9e02034591b8e1c82a6404eab339f88058c 100644 (file)
@@ -1002,6 +1002,7 @@ class CRM_Core_BAO_Mapping extends CRM_Core_DAO_Mapping {
    *
    * @return array
    *   formatted associated array of elements
+   * @throws CRM_Core_Exception
    */
   public static function formattedFields(&$params, $row = FALSE) {
     $fields = [];
@@ -1016,7 +1017,7 @@ class CRM_Core_BAO_Mapping extends CRM_Core_DAO_Mapping {
       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]]
             ));
           }
index c14709e855258815f7aa39e74acf61e076ae5311..f7456bc50df0b5665ca5b919c8cb06137fdda573 100644 (file)
@@ -72,12 +72,13 @@ class CRM_Core_BAO_PaperSize extends CRM_Core_DAO_OptionValue {
    *
    * @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;
@@ -268,6 +269,7 @@ class CRM_Core_BAO_PaperSize extends CRM_Core_DAO_OptionValue {
    * @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)
@@ -311,7 +313,7 @@ class CRM_Core_BAO_PaperSize extends CRM_Core_DAO_OptionValue {
     // 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();
 
@@ -325,7 +327,7 @@ class CRM_Core_BAO_PaperSize extends CRM_Core_DAO_OptionValue {
    *
    * @param int $id
    *   ID of the Paper Size to be deleted.
-   *
+   * @throws CRM_Core_Exception
    */
   public static function del($id) {
     if ($id) {
@@ -340,7 +342,7 @@ class CRM_Core_BAO_PaperSize extends CRM_Core_DAO_OptionValue {
         }
       }
     }
-    CRM_Core_Error::fatal(ts('Invalid value passed to delete function.'));
+    throw new CRM_Core_Exception(ts('Invalid value passed to delete function.'));
   }
 
 }
index 0703108a7f0a60326c3486966b10b3a1f578ba09..33783d7306c4a87a091f3e4bf5e8e229278d18ab 100644 (file)
@@ -127,12 +127,13 @@ class CRM_Core_BAO_PdfFormat extends CRM_Core_DAO_OptionValue {
    *
    * @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;
@@ -325,6 +326,7 @@ class CRM_Core_BAO_PdfFormat extends CRM_Core_DAO_OptionValue {
    * @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)
@@ -364,7 +366,7 @@ class CRM_Core_BAO_PdfFormat extends CRM_Core_DAO_OptionValue {
     // 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();
 
@@ -378,7 +380,7 @@ class CRM_Core_BAO_PdfFormat extends CRM_Core_DAO_OptionValue {
    *
    * @param int $id
    *   ID of the PDF Page Format to be deleted.
-   *
+   * @throws CRM_Core_Exception
    */
   public static function del($id) {
     if ($id) {
@@ -393,7 +395,7 @@ class CRM_Core_BAO_PdfFormat extends CRM_Core_DAO_OptionValue {
         }
       }
     }
-    CRM_Core_Error::fatal(ts('Invalid value passed to delete function.'));
+    throw new CRM_Core_Exception(ts('Invalid value passed to delete function.'));
   }
 
 }
index 513bba5dd5174d8ded6b8f16cd1c454ddec12863..b05bbf4a15488ec5981828e45e496bda0868da3c 100644 (file)
@@ -57,18 +57,20 @@ class CRM_Core_BAO_PreferencesDate extends CRM_Core_DAO_PreferencesDate {
    *   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');
   }
 
   /**
index f4b1622de908feadb54a23f5d4a7937a15ad93d4..fceb1237b154f3e87b3518d2b1ff09c01cc7bf75 100644 (file)
@@ -224,6 +224,7 @@ class CRM_Core_BAO_RecurringEntity extends CRM_Core_DAO_RecurringEntity {
    * 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);
@@ -241,7 +242,7 @@ class CRM_Core_BAO_RecurringEntity extends CRM_Core_DAO_RecurringEntity {
         }
       }
       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;
@@ -547,11 +548,12 @@ class CRM_Core_BAO_RecurringEntity extends CRM_Core_DAO_RecurringEntity {
    *
    *
    * @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);
 
@@ -631,7 +633,7 @@ class CRM_Core_BAO_RecurringEntity extends CRM_Core_DAO_RecurringEntity {
         $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.
@@ -811,7 +813,7 @@ class CRM_Core_BAO_RecurringEntity extends CRM_Core_DAO_RecurringEntity {
         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();
index c6757015f02e7bca13c0fd03be7cb58a25b60257..48a820916ad21af0af5f1ef2dd77d5a0c5cea1cb 100644 (file)
@@ -502,7 +502,7 @@ ADD UNIQUE INDEX `unique_entity_id` ( `entity_id` )";
    * @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
@@ -543,7 +543,7 @@ MODIFY      {$columnName} varchar( $length )
       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,
index 2f225ee9fe2e7241d00cfed80cf3d2ff337f0d65..d4e182eadc31ad5f76900919f303299451233e39 100644 (file)
@@ -28,6 +28,7 @@ class CRM_Core_BAO_StatusPreference extends CRM_Core_DAO_StatusPreference {
    * @param array $params
    *
    * @return array
+   * @throws CRM_Core_Exception
    */
   public static function create($params) {
     $statusPreference = new CRM_Core_BAO_StatusPreference();
@@ -42,11 +43,11 @@ class CRM_Core_BAO_StatusPreference extends CRM_Core_DAO_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.
index d8a3d6c8ed43845c45a255a897d31fe59492baa9..5841ab0d72451a4c9b3585fb0e582a30fc7ce960 100644 (file)
@@ -516,6 +516,7 @@ class CRM_Core_BAO_UFGroup extends CRM_Core_DAO_UFGroup {
       // 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'];
@@ -2699,7 +2700,7 @@ AND    ( entity_id IS NULL OR entity_id <= 0 )
 
     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 &raquo; Communications &raquo; 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 &raquo; Communications &raquo; 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) {
index 116ebd141c874a9139e35fb7d86b6390d7457fc9..b7dd9b2c80187275e9022d123fe3c6f4d9038b07 100644 (file)
@@ -57,12 +57,14 @@ class CRM_Core_BAO_UFMatch extends CRM_Core_DAO_UFMatch {
    *
    * @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;
     }
 
index d8fe9df6b359fa05c4dcaa8629eaae7d5a35f058..ba4f09d7fa43d4d8dc3a3807728a8305eb4fb87d 100644 (file)
@@ -51,6 +51,7 @@ class CRM_Core_BAO_WordReplacement extends CRM_Core_DAO_WordReplacement {
    * @param null $reset
    *
    * @return null|CRM_Core_BAO_WordReplacement
+   * @throws CRM_Core_Exception
    */
   public static function getWordReplacement($reset = NULL) {
     static $wordReplacement = NULL;
@@ -58,7 +59,7 @@ class CRM_Core_BAO_WordReplacement extends CRM_Core_DAO_WordReplacement {
       $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;
index b96694a43d5a38c4f6cc74d24d9a085b99163b45..c594c3b6d8ab8edc725eaccd1e2da35193ccdf81 100644 (file)
@@ -70,7 +70,7 @@ class CRM_Core_Component {
    * @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) {
@@ -87,7 +87,7 @@ class CRM_Core_Component {
         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);
index a72e5cdf33df691715fd599909023168ddca2011..6e9995ff23a2bfa30a061bac0b1b279c72fa5cac 100644 (file)
@@ -1736,7 +1736,7 @@ FROM   civicrm_domain
       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;
@@ -2858,11 +2858,11 @@ SELECT contact_id
    * 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.
    *
index f6a8e2b657f0cd4b4b113010e2bc4f17fa809f40..9e292fdb5a4f6e9b372d183253527344c46c7a9d 100644 (file)
@@ -208,6 +208,66 @@ class CRM_Core_DAO_AllCoreTables {
     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.
    *
@@ -254,7 +314,8 @@ class CRM_Core_DAO_AllCoreTables {
    *   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;
   }
 
   /**
index 7b8ded42abbfa91cb479cb2d422d345a29f5f985..5c5e9a1036ed3234c76b5fdda16b04d637d937e7 100644 (file)
@@ -6,7 +6,7 @@
  *
  * Generated from xml/schema/CRM/Core/CustomField.xml
  * DO NOT EDIT.  Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:492b1be45dc41c15b35371410a074393)
+ * (GenCodeChecksum:0b21a2a1f1cba7a76fd8830db1626513)
  */
 
 /**
@@ -224,6 +224,13 @@ class CRM_Core_DAO_CustomField extends CRM_Core_DAO {
    */
   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.
    *
@@ -640,6 +647,20 @@ class CRM_Core_DAO_CustomField extends CRM_Core_DAO {
             '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,
index 66dd5a9d7495c7fc040beaf803c053c399160e91..1401d2ba1da9cce62416b9df818cf69aa02a7f9f 100644 (file)
@@ -24,12 +24,12 @@ class CRM_Core_DAO_Factory {
    * @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;
index 206570e51b93bce6f8201de15dee7fef28432a32..864996b057e667031036f87912afcbfcf45f2f1d 100644 (file)
@@ -184,6 +184,19 @@ class CRM_Core_Error extends PEAR_ErrorStack {
       }
     }
 
+    // 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);
@@ -204,7 +217,7 @@ class CRM_Core_Error extends PEAR_ErrorStack {
       exit;
     }
     $runOnce = TRUE;
-    self::abend(1);
+    self::abend(CRM_Core_Error::FATAL_ERROR);
   }
 
   /**
@@ -559,6 +572,18 @@ class CRM_Core_Error extends PEAR_ErrorStack {
     }
     $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
index 416085c733a52fb2fb08a4557d3b9eaeb7505575..a3c4023c0edb89e82936b4486cf82de9151d5042 100644 (file)
@@ -421,7 +421,7 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
 
     $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') {
@@ -436,7 +436,7 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
         $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));
       }
     }
 
@@ -2015,7 +2015,7 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
   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'])) {
index 2f0e3207f3525a6cb6deb99754375e236a99e8e2..357dc2f936715ea8d00db74362c962431a52e0a3 100644 (file)
@@ -38,18 +38,26 @@ class CRM_Core_I18n {
   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;
   }
 
   /**
@@ -312,23 +320,15 @@ class CRM_Core_I18n {
    *   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;
@@ -385,17 +385,7 @@ class CRM_Core_I18n {
       $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);
   }
 
   /**
index 2c36e246397c53381cfb3da7e3cda940b2b6d534..6beb1d669dafc6cf9c783d1e31944a3558206060 100644 (file)
@@ -27,7 +27,7 @@ class CRM_Core_I18n_Form extends CRM_Core_Form {
     $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);
@@ -106,7 +106,7 @@ class CRM_Core_I18n_Form extends CRM_Core_Form {
 
     // 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 = [];
index b0262dfda7f7bc9a0f86344dcd769e6f506a8689..780ae182fa8933155968e5feb193f2e11e5cc8af 100644 (file)
@@ -102,7 +102,7 @@ class CRM_Core_Invoke {
         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');
       }
     }
   }
@@ -224,7 +224,7 @@ class CRM_Core_Invoke {
 
       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
@@ -307,7 +307,7 @@ class CRM_Core_Invoke {
           $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);
       }
index 79e4f552f3d9fe6e6a6625c4819972e3aabb9ece..bd9900df2f984e7b47e71854520c3e39f6d156f4 100644 (file)
@@ -105,13 +105,15 @@ class CRM_Core_Menu {
    *   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();
@@ -204,7 +206,7 @@ class CRM_Core_Menu {
    * @param array $menu
    * @param string $path
    *
-   * @throws Exception
+   * @throws CRM_Core_Exception
    */
   public static function fillMenuValues(&$menu, $path) {
     $fieldsToPropagate = array(
@@ -240,15 +242,15 @@ class CRM_Core_Menu {
       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));
   }
 
   /**
index 5e7844367c50ed240782782796d7cd72f4c07c9b..f83b170f931f309f924b69f530665899213e4355 100644 (file)
@@ -431,23 +431,45 @@ class CRM_Core_Page {
    * @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";
   }
 
 }
index 90be62a20bc28528352935618d5850b7d299e0e2..15141ae4f021258de5af189e17af8310f6fc662c 100644 (file)
@@ -3,13 +3,13 @@
 /**
  * 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&amp;passthru=%%passthru%%</page_arguments>
  * </item>
- * @endcoe
+ * ```
  */
 class CRM_Core_Page_Redirect extends CRM_Core_Page {
 
index 2da96d6cc4926b0f150a3590afac2bc54af9cd9e..2ad490ac4da1ddd2f317c1c5b18c2f3ddd84246d 100644 (file)
@@ -85,7 +85,7 @@ class CRM_Core_Payment_BaseIPN {
    *
    * @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
@@ -158,7 +158,7 @@ class CRM_Core_Payment_BaseIPN {
    *
    * @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
index 885fac970f2edc8897597746bb0d21c16fbfc369..46c0949f8517eef1fb30573b81ca8736f01db683 100644 (file)
@@ -588,7 +588,7 @@ INNER JOIN civicrm_membership_payment mp ON m.id = mp.membership_id AND mp.contr
     // & 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)) {
index a91c126b8c113271110d79e4bbbb0aea66ab7808..b3f5a066f17114eeb6382021a20882e145dda3ea 100644 (file)
@@ -170,9 +170,10 @@ class CRM_Core_Permission_Base {
    * @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");
   }
 
   /**
@@ -181,9 +182,10 @@ class CRM_Core_Permission_Base {
    * @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");
   }
 
   /**
index afbb0b5fbb4120b2515db85bd44c6f5d191809dd..911189b5e12f9b4ba7f64cb7b9c0df5dcc01ab76 100644 (file)
@@ -194,7 +194,7 @@ class CRM_Core_PseudoConstant {
       '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])) {
index b1809af63629f209e129148ec10e6e2f5659782d..5d101d1d70ff0cc9e682fe5044c6a951cb2dd743 100644 (file)
@@ -68,7 +68,7 @@ class CRM_Core_Region {
   /**
    * Add a snippet of content to a region.
    *
-   * @code
+   * ```
    * CRM_Core_Region::instance('page-header')->add(array(
    *   'markup' => '<div style="color:red">Hello!</div>',
    * ));
@@ -81,7 +81,7 @@ class CRM_Core_Region {
    * 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.
index 6b451af1ebde9bf00baeec118eeb34d13ae187a8..3455d148493a3dd24eab52b044db53ab9f66ec90 100644 (file)
@@ -181,12 +181,9 @@ class CRM_Core_SelectValues {
       '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'),
     ];
@@ -1179,4 +1176,14 @@ class CRM_Core_SelectValues {
     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',
+    ];
+  }
+
 }
index 934eb317a3aa8f3a7e38f90ccb56f36512237ab6..1b4786987c947d969f8f3878f3104d76f22e727c 100644 (file)
  */
 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.
    *
@@ -64,17 +56,6 @@ class CRM_Core_ShowHideBlocks {
     }
   }
 
-  /**
-   * 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.
    */
@@ -130,115 +111,4 @@ class CRM_Core_ShowHideBlocks {
     }
   }
 
-  /**
-   * 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]
-      );
-    }
-  }
-
 }
index e45873d4dbbf55e53919283b75428c7e1e78ee72..86fcab419aa1565c46634f7cdddcc6ab71caabb0 100644 (file)
@@ -258,14 +258,14 @@ class CRM_Core_Smarty extends Smarty {
   /**
    * 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).
index b9869e20d312782210e547da09915394bc709e95..0e1c46a78ef2d791b487a3cd514f3bb8a2a66c73 100644 (file)
@@ -38,9 +38,21 @@ function smarty_block_crmButton($params, $text, &$smarty) {
   // 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>&nbsp; ";
+  }
   // 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>&nbsp; $text</span></a>";
+  return "<a $attributes><span>$iconMarkup$text</span></a>";
 }
index 66bb8bcb4fdac8098422820dd4d87ae1d74c588a..3e0e0280038625d4fbed68b8edbee3b8340e52df 100644 (file)
@@ -5,7 +5,7 @@
  *
  * Example:
  *
- * @code
+ * ```
  * {tsScope x=1}
  *   Expect {$x}==1
  *   {tsScope x=2}
@@ -13,7 +13,7 @@
  *   {/tsScope}
  *   Expect {$x}==1
  * {/tsScope}
- * @endcode
+ * ```
  *
  * @param array $params
  *   Must define 'name'.
index 0cdad70186ec367da3a5f16fe5a6a76b0839be87..8fb0213b497c68c2e5446f7ffc0eec0ccc5fa473 100644 (file)
@@ -25,6 +25,7 @@
  * @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.
@@ -36,5 +37,9 @@
 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));
 }
index e25693c93a56fec4216f073dde485613e4057036..6f9ab7c617055fffd29104b4418b12a8548098a2 100644 (file)
 /**
  * Display the CiviCRM version
  *
- * @code
+ * ```
  * The version is {crmVersion}.
  *
  * {crmVersion redact=auto assign=ver}The version is {$ver}.
- * @endcode
+ * ```
  *
  * @param $params
  * @param $smarty
index e4e3593291c7ce2e3a70d0beeaf879cbd0c15dab..a3b63e78cf6f8b09c6739cedc09314f45fb599ec 100644 (file)
@@ -13,7 +13,6 @@
  *
  * @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',
index d8853b8ef8cdff4b6ea0802a32e832b9d2d630d9..ab0cf4b3f800fed1bcbab1e5a1d246283c277cdd 100644 (file)
@@ -14,9 +14,9 @@
  *
  * 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
index bd77c7dc693ac1d6b307ae97008d820b4127a141..26110155eff60f3b96140e65f0cd8087da1df0b5 100644 (file)
@@ -19,7 +19,7 @@ trait CRM_Core_TokenTrait {
    * @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());
   }
@@ -51,7 +51,6 @@ trait CRM_Core_TokenTrait {
 
   /**
    * 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
    */
index 04be629fb36f7ce17b522544cc93b79f8f1cf7fd..a2297bee1f2db42c7164fe64ffc046bc5da6d340 100644 (file)
@@ -28,7 +28,7 @@
  *
  * Examples:
  *
- * @code
+ * ```
  * // Some business logic using the helper functions
  * function my_business_logic() {
  *   CRM_Core_Transaction::create()->run(function($tx) {
@@ -60,7 +60,7 @@
  *   }
  * }
  *
- * @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
index 4f5625d81a9252f3aade4ab2a7013113d82b0381..e61d23ed803ce27ae2468052c1eb234dfd82add6 100644 (file)
@@ -76,7 +76,6 @@ class CRM_Custom_Form_Field extends CRM_Core_Form {
       'Select' => 'Select',
       'Radio' => 'Radio',
       'CheckBox' => 'CheckBox',
-      'Multi-Select' => 'Multi-Select',
       'Autocomplete-Select' => 'Autocomplete-Select',
     ],
     ['Text' => 'Text', 'Select' => 'Select', 'Radio' => 'Radio'],
@@ -85,8 +84,8 @@ class CRM_Custom_Form_Field extends CRM_Core_Form {
     ['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'],
@@ -141,7 +140,6 @@ class CRM_Custom_Form_Field extends CRM_Core_Form {
           'Select' => ts('Select'),
           'Radio' => ts('Radio'),
           'CheckBox' => ts('CheckBox'),
-          'Multi-Select' => ts('Multi-Select'),
           'Autocomplete-Select' => ts('Autocomplete-Select'),
         ],
         [
@@ -162,8 +160,8 @@ class CRM_Custom_Form_Field extends CRM_Core_Form {
         ['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')],
@@ -329,6 +327,8 @@ class CRM_Custom_Form_Field extends CRM_Core_Form {
       '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'])) {
@@ -727,7 +727,7 @@ SELECT count(*)
     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
@@ -950,6 +950,17 @@ AND    option_group_id = %2";
       $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))) {
index 851a1287dd7b1ed6e16195745877313f5ff059b0..031568ea4a654d31d6680e52e3a3c42674e875d0 100644 (file)
@@ -1917,90 +1917,72 @@ INNER JOIN  civicrm_membership membership2 ON membership1.membership_type_id = m
         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];
index 4547e4c173de2faaa05f8ce4a0a5695cd680ea58..14afb06fe5ba23bdd9b3f502c2eddab132cd7b3e 100644 (file)
@@ -439,33 +439,13 @@ class CRM_Event_Form_Registration_Confirm extends CRM_Event_Form_Registration {
     $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;
         }
index 8a06f7f26cc39a1fd13fd0a7f66a8a316c22e062..049d675e71f900139d667393443781f5cbb5899f 100644 (file)
@@ -163,10 +163,41 @@ class CRM_Financial_BAO_Payment {
       //  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.
    *
index f131ef76776bf4fa1eb7cc0a5ec293d2df5c5b2f..b40f1325a344db138a3caf4fe3818a8d901f422a 100644 (file)
@@ -214,7 +214,7 @@ class CRM_Financial_Form_PaymentEdit extends CRM_Core_Form {
       civicrm_api3('FinancialTrxn', 'create', $submittedValues);
     }
 
-    self::updateRelatedContribution($submittedValues, $this->_contributionID);
+    CRM_Financial_BAO_Payment::updateRelatedContribution($submittedValues, $this->_contributionID);
   }
 
   /**
@@ -230,35 +230,6 @@ class CRM_Financial_Form_PaymentEdit extends CRM_Core_Form {
     $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
    */
index 5d4ae83f83933a0ef95cfccc71a259c38d3bd634..183af4100ac696a62e314f46dcfcf4a1c56747e4 100644 (file)
@@ -142,6 +142,9 @@ AND    TABLE_NAME LIKE 'civicrm_%'
     // 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, []);
index 46ee694de39a6fe71101dbb11a04b4b14faa4751..455a6a88248d5fa4f3a7a7072140ef28ab88d0bb 100644 (file)
@@ -1558,8 +1558,8 @@ ORDER BY   civicrm_email.is_bulkmail DESC
         // 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', ''),
index 02b305e4991101422a2b50c3553ad882c73b5496..7d26a2c1000a2f9f43e615dfdd0a7d5be3befe45 100644 (file)
@@ -34,15 +34,13 @@ class CRM_Mailing_BAO_MailingAB extends CRM_Mailing_DAO_MailingAB {
    *   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();
@@ -52,47 +50,6 @@ class CRM_Mailing_BAO_MailingAB extends CRM_Mailing_DAO_MailingAB {
     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.
    *
index 64aec05fb14c632b55d72d50c0d77e02ef44a863..37fa94d1b996f7735280955d1957e2957399d43f 100644 (file)
@@ -6,7 +6,7 @@
  *
  * Generated from xml/schema/CRM/Mailing/MailingAB.xml
  * DO NOT EDIT.  Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:74ff2df50144a54a2c5a740187f6a8ca)
+ * (GenCodeChecksum:808ef560b5f6c959cb4f3ceea87f5e38)
  */
 
 /**
@@ -227,6 +227,7 @@ class CRM_Mailing_DAO_MailingAB extends CRM_Core_DAO {
           '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',
index 8a56e9bde509f4f7577993002007d10d4a96ae09..09b3eef0c4d44b6c89843b7da7af3acf475fc79f 100644 (file)
@@ -740,7 +740,6 @@ class CRM_Member_BAO_MembershipType extends CRM_Member_DAO_MembershipType {
       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 = [
@@ -756,12 +755,12 @@ class CRM_Member_BAO_MembershipType extends CRM_Member_DAO_MembershipType {
       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);
     }
   }
 
index bd7b6df0b0313902855ac2a6ccb35be4d278cf42..8f7cc8e1b5583919da0cec82c60771db685bfc01 100644 (file)
@@ -386,10 +386,10 @@ class CRM_Price_BAO_PriceField extends CRM_Price_DAO_PriceField {
             $opt['label'] = !empty($opt['label']) ? $opt['label'] . '<span class="crm-price-amount-label-separator">&nbsp;-&nbsp;</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">:&nbsp;</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">:&nbsp;</span><span class="crm-price-amount-help-post description">' . $opt['help_post'] . '</span>';
             }
             if (isset($taxAmount) && $invoicing) {
               if ($displayOpt == 'Do_not_show') {
index 34d431210a9b6959da5e4b93d29701e9d5c5ff90..7806a6c4dcb9f86b9f885d57c7c235144d0594b7 100644 (file)
@@ -26,35 +26,18 @@ class CRM_Price_BAO_PriceFieldValue extends CRM_Price_DAO_PriceFieldValue {
    *
    * @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;
@@ -110,7 +93,8 @@ class CRM_Price_BAO_PriceFieldValue extends CRM_Price_DAO_PriceFieldValue {
     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);
   }
 
   /**
index 14dfa3f65f66ccc8d8f60e2dac3e4c7362c24d13..a8ea081f5004055436705270132fab44e85273b8 100644 (file)
@@ -160,7 +160,6 @@ class CRM_Price_Page_Option extends CRM_Core_Page {
           $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,
         [
index 110e07bc919987a9038a1ecb378ca9bfd99b8e04..f7bc774add4f107ae9ece1ce8d6864d90a7d0295 100644 (file)
  * 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
index c86f790ad4690abba7f467de7c754eefdfba3f35..60d2521cba1507852862a7cc55f2e201ea9a3777 100644 (file)
@@ -15,7 +15,7 @@
  * 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',
@@ -31,7 +31,7 @@
  *     $queue->releaseItem($item);
  *   }
  * }
- * @endcode
+ * ```
  */
 class CRM_Queue_Service {
 
index 9d4c12d22a02607edeaaa8e4f8a28d04b4704ece..73c1369b3a9f06be969dd9d0f954563ba163a136 100644 (file)
@@ -640,7 +640,8 @@ UNION ALL
     $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') {
index 013bd70b7363f8dfef5f3be153a197ce6b772a0f..3820347d65edad839f500d0b9fb224a154850924 100644 (file)
@@ -44,6 +44,20 @@ class CRM_Upgrade_Incremental_General {
    */
   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.
    *
index b4f153b3cae641de9c50ce074cdfbef6f2d939f3..b78bbb34225ac51dc0ab6f2fbe3245dfa83d097b 100644 (file)
@@ -52,21 +52,17 @@ class CRM_Upgrade_Incremental_php_FiveTwentySeven extends CRM_Upgrade_Incrementa
    * (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);
+  }
 
 }
index 0628764183675e09c2a40a54344a32333996cec7..9f3c4621460908a1262e1a0f14b9f44722376c8b 100644 (file)
@@ -1 +1,6 @@
 {* 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.';
index f92723c33726ff4845aeda86e1b5f8fd8fc708f1..fa87a1cf8b03de2b87eb7741b1e317ca4b6edfd1 100644 (file)
@@ -21,7 +21,7 @@
  *   - "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')
@@ -30,7 +30,7 @@
  *   'last_name' => 'Lebowski',
  *   'nick_name' => 'The Dude',
  * ));
- * @endcode
+ * ```
  *
  * @package CRM
  * @copyright CiviCRM LLC https://civicrm.org/licensing
index 764d81bf80c5cc153792e3ae14ce87dae2ae7ee0..3e320d5c1408d94c277b966765c4603f41fbafb4 100644 (file)
  * 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
index 48e0cf9e2df2da96e23b618e45fd25b7d4995099..1be38b0ece20ac11d069018901a3d69458914395 100644 (file)
@@ -29,14 +29,14 @@ class CRM_Utils_AutoClean {
   /**
    * 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
@@ -52,12 +52,12 @@ class 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.
index 63efaddef8261373bb051f18351eec0942afdb0e..8cc07c6e95aaade7a1fa8717684d25613af07160 100644 (file)
@@ -935,4 +935,25 @@ class CRM_Utils_Check_Component_Env extends CRM_Utils_Check_Component {
     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;
+  }
+
 }
index 75cb7eaec82df2092869cdc4cd54c5b259badfbc..f7adb10695b1c6a6272bfe0fec1d3cc707a91752 100644 (file)
 /**
  * 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`.
  *
index fef296edbcdcf0a548534ef565e97a66f2ec38bc..a20246fe6b200ecc87f11514c4e31c375de1e637 100644 (file)
  * 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 {
 
index b16bb28d8ae4e2437d33712619411ad376d1d4b7..3ad79207b6711b83df29cfa8e54449c9c3f6b2a2 100644 (file)
@@ -1082,7 +1082,7 @@ HTACCESS;
    * @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);
   }
index 047de04c3c7d1038f12fe87ea6408546468626c5..50403d9546ea2018b1ca2cd62b91dc849918c4fb 100644 (file)
@@ -17,7 +17,7 @@
 /**
  * Temporarily change a global variable.
  *
- * @code
+ * ```
  * $globals = CRM_Utils_GlobalStack::singleton();
  * $globals->push(array(
  *   '_GET' => array(
@@ -26,7 +26,7 @@
  * ));
  * ...do stuff...
  * $globals->pop();
- * @endcode
+ * ```
  *
  * Note: for purposes of this class, we'll refer to the array passed into
  * push() as a frame.
index 6e8a7b59100384c26592473f327d15b69daddd1a..edad19c9b58661b625dda6c4d413e307db6dcfd4 100644 (file)
@@ -2234,10 +2234,8 @@ abstract class CRM_Utils_Hook {
    *      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',
@@ -2252,7 +2250,10 @@ abstract class CRM_Utils_Hook {
    *     'basePages' => array('civicrm/a'),
    *   );
    * }
-   * @endcode
+   * ```
+   *
+   * @return null
+   *   the return value is ignored
    */
   public static function angularModules(&$angularModules) {
     return self::singleton()->invoke(['angularModules'], $angularModules,
@@ -2266,7 +2267,7 @@ abstract class CRM_Utils_Hook {
    *
    * @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) {
@@ -2275,7 +2276,7 @@ abstract class CRM_Utils_Hook {
    *   );
    *   $angular->add($changeSet);
    * }
-   * @endCode
+   * ```
    */
   public static function alterAngular($angular) {
     $event = \Civi\Core\Event\GenericHookEvent::create([
@@ -2365,7 +2366,7 @@ abstract class CRM_Utils_Hook {
   /**
    * Modify the CiviCRM container - add new services, parameters, extensions, etc.
    *
-   * @code
+   * ```
    * use Symfony\Component\Config\Resource\FileResource;
    * use Symfony\Component\DependencyInjection\Definition;
    *
@@ -2373,7 +2374,7 @@ abstract class CRM_Utils_Hook {
    *   $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
index 76a440e26a2cd2bbc6dc3e7b07701db8e9ee1337..7dc6b17d7f67b6babea222205437a2a21f9414bb 100644 (file)
@@ -22,7 +22,7 @@ class CRM_Utils_Migrate_Export {
    * @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
@@ -31,7 +31,7 @@ class CRM_Utils_Migrate_Export {
    *   '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;
 
index 159f56a5fad60f2832034fc54a100a36023f2d06..20125db2bdad4d4f45e991306069888f5655abd0 100644 (file)
@@ -191,11 +191,11 @@ class CRM_Utils_SQL_BaseParamQuery implements ArrayAccess {
   /**
    * 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
@@ -209,11 +209,11 @@ class CRM_Utils_SQL_BaseParamQuery implements ArrayAccess {
   /**
    * 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
index 6bbc076691c71146d757d848c3d1fdddd0ee7bdc..fcb5443030cc6afe153a3548f977cad9dba35cb3 100644 (file)
@@ -13,7 +13,7 @@
  * 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))
@@ -24,7 +24,7 @@
  *        'value' => $form['foo']
  *      ))
  * echo $del->toSQL();
- * @endcode
+ * ```
  *
  * Design principles:
  *  - Portable
@@ -48,7 +48,7 @@
  * 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,
@@ -58,7 +58,7 @@
  * $select
  *     ->where('activity_type_id = #type')
  *     ->param('type', 234),
- * @endcode
+ * ```
  *
  * @package CRM
  * @copyright CiviCRM LLC https://civicrm.org/licensing
index 47707a316fd8727a4d31f95eddcd6e064325e8e7..4aa6b7e5166974cd334df51fb6b1f27c0dd986b5 100644 (file)
@@ -13,7 +13,7 @@
  * 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))
@@ -25,7 +25,7 @@
  *        'value' => $form['foo']
  *      ))
  * echo $select->toSQL();
- * @endcode
+ * ```
  *
  * Design principles:
  *  - Portable
@@ -49,7 +49,7 @@
  * 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,
@@ -59,7 +59,7 @@
  * $select
  *     ->where('activity_type_id = #type')
  *     ->param('type', 234),
- * @endcode
+ * ```
  *
  * @package CRM
  * @copyright CiviCRM LLC https://civicrm.org/licensing
index eb902bc18949f199adac6968755bad5d42c4c706..2b6491f9d0b778b7d1bc931a045a1c3393cf68d6 100644 (file)
@@ -20,7 +20,7 @@
  *
  * FIXME: Add TTL support?
  *
- * @code
+ * ```
  * $signer = new CRM_Utils_Signer('myprivatekey', array('param1','param2'));
  * $params = array(
  *   'param1' => 'hello',
@@ -29,7 +29,7 @@
  * $token = $signer->sign($params);
  * ...
  * assertTrue($signer->validate($token, $params));
- * @endcode
+ * ```
  */
 class CRM_Utils_Signer {
   /**
index 18d4904b1f161c5c99df730b435eb535a86c57e4..e0cac9a763ba167433a6c45677831032d4f5e4cf 100644 (file)
@@ -86,37 +86,17 @@ class CRM_Utils_String {
   }
 
   /**
-   * 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);
   }
 
   /**
index f0c8cdbc7de24004bd2f7fcf594ca6a4ff0cee0f..bf4f8693cf5c72af1dbdeed7f5f3dd6121516c6e 100644 (file)
@@ -72,6 +72,19 @@ class CRM_Utils_System_Drupal extends CRM_Utils_System_DrupalBase {
     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
    */
index 82fe4d85e47fd4f8b6442937310e08d081b29574..6e42f703b8814c1f36bd5ecf6372bcea2ff57ae9 100644 (file)
@@ -654,13 +654,13 @@ class CRM_Utils_System_Drupal8 extends CRM_Utils_System_DrupalBase {
    *
    * 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.
index 715b05b28092b723f36143f47f5e65a560557c4c..873020ce537cd96b2fb6c4a5252ef89872be99c0 100644 (file)
@@ -253,7 +253,13 @@ class CRM_Utils_System_WordPress extends CRM_Utils_System_Base {
 
       // 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) {
index 1b73c1dc7e3cef38f4fcfb5530444f36e728949f..51340c3f723159843053f720f4ac01fd27236649 100644 (file)
@@ -19,7 +19,7 @@ class CRM_Utils_Url {
    *
    * @param string $url
    *
-   * @return \GuzzleHttp\Psr7\UriInterface
+   * @return \Psr\Http\Message\UriInterface
    */
   public static function parseUrl($url) {
     return new Uri($url);
@@ -28,7 +28,7 @@ class CRM_Utils_Url {
   /**
    * Unparse url back to a string.
    *
-   * @param \GuzzleHttp\Psr7\UriInterface $parsed
+   * @param \Psr\Http\Message\UriInterface $parsed
    *
    * @return string
    */
index ebd09e8afec737e387d067a398a7a6c79e51f439..e52dd1614849e230584e55ccfbdb6b1733d67e3a 100644 (file)
--- a/Civi.php
+++ b/Civi.php
@@ -19,9 +19,9 @@ class Civi {
   /**
    * A central location for static variable storage.
    * @var array
-   * @code
+   * ```
    * `Civi::$statics[__CLASS__]['foo'] = 'bar';
-   * @endcode
+   * ```
    */
   public static $statics = array();
 
index 22fa382a7104c953555db1fa02cde56b6e8dc1ca..5d0457ef7d2933bed191870593027c0349824ff5 100644 (file)
@@ -74,7 +74,7 @@ class Request {
    * @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);
   }
 
   /**
index 92e09ad8d1c977ecf446e7988ac464c6da6ded3b..22577b92c3c0dd05a4a91b07a2626b8b5613a6c7 100644 (file)
@@ -361,7 +361,7 @@ abstract class SelectQuery {
    * Get acl clause for an entity
    *
    * @param string $tableAlias
-   * @param string $baoName
+   * @param \CRM_Core_DAO|string $baoName
    * @param array $stack
    * @return array
    */
index ff4f79256e1a23456f107aa9c9febf708240991a..e76220ca0dc0785a4723b000073865fbb0760e93 100644 (file)
@@ -18,7 +18,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  * 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',
@@ -27,7 +27,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  *     '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 --
index 6a19cded035058c8a9d8ac931e7cfd27e95cda45..3aaa1a89c62ff064b2f8a74a01b7e03f6c87bc08 100644 (file)
@@ -12,6 +12,7 @@
 namespace Civi\API\Subscriber;
 
 use Civi\API\Events;
+use CRM_Core_DAO_AllCoreTables as AllCoreTables;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
 /**
@@ -123,7 +124,7 @@ class DynamicFKAuthorization implements 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;
@@ -138,7 +139,7 @@ class DynamicFKAuthorization implements EventSubscriberInterface {
    */
   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']])) {
index c17b41cb2e67ecc082c141589e44edb78cfee215..5150b362f82827bbb946d5dbe1bc8cc990aef636 100644 (file)
@@ -14,14 +14,14 @@ namespace Civi\API;
  * 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')
index 0158b41e7d43b2440d4f824b3104d010576aca9b..873951416995823b18a972b6d6f636c5f59d0e35 100644 (file)
@@ -13,20 +13,20 @@ use Symfony\Component\EventDispatcher\Event;
  *
  * 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'
index a54bca3b83ea1ccb150c94f8218498c7f14c4fd9..c8a32dd97e3756e8125fb2e31b881a12f0fe4b0d 100644 (file)
@@ -37,7 +37,7 @@ namespace Civi\ActionSchedule;
  * 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')
@@ -46,7 +46,7 @@ namespace Civi\ActionSchedule;
  *   ->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
index 58e69987004adee665fd985cc26657b44038c291..1dca6bcd9e1d9c61bb30e4620ee8381675620d1e 100644 (file)
@@ -8,12 +8,12 @@ namespace Civi\Angular;
  * 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
  */
index c2ca32a1a84296c3ff0e9de51c2409aef60ebaa9..9ba2981dd801a7a92a2fb73ec2942dc40e91f521 100644 (file)
  +--------------------------------------------------------------------+
  */
 
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
 namespace Civi\Api4\Action\CustomValue;
 
 use Civi\Api4\Service\Spec\SpecFormatter;
index bb82f93555cb32c0041265b57b53f985576ffe69..fbab1f057db8e3bc15749372b2b02e13dc50c241 100644 (file)
  +--------------------------------------------------------------------+
  */
 
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
-
 namespace Civi\Api4\Action;
 
 use Civi\API\Exception\NotImplementedException;
index b695ae63f709aa46e2fb00405590d0e6b064e4e9..bb19580b68de0570a024632f44cecec71ce2e7cc 100644 (file)
  +--------------------------------------------------------------------+
  */
 
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
-
 namespace Civi\Api4\Action\GroupContact;
 
 /**
index 71d51b4d830f1ca23694727aca42c86056d8b7b4..e9460283c44bb471c856cb2548b1fa560fce79ba 100644 (file)
  +--------------------------------------------------------------------+
  */
 
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
 namespace Civi\Api4\Action\Relationship;
 
 /**
index d4a7a7dbe5a96add9a8f7a6b1e1256bebbec4fb7..31e228968224a9a95282ddd8d6b07de28fddbb31 100644 (file)
  +--------------------------------------------------------------------+
  */
 
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
 namespace Civi\Api4\Action\Setting;
 
 use Civi\Api4\Domain;
index fd4a0ce3f0370124d61c07624b087d55f318bf68..9a6539de665fbdb292ea97d93f149a9922b6d380 100644 (file)
  +--------------------------------------------------------------------+
  */
 
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
 namespace Civi\Api4\Action\Setting;
 
 use Civi\Api4\Generic\Result;
index bc6c9d68a1ed64f2c80e7f4c2e39253d5c5ba8d6..44711ef4c82680fdce5e1b20e5c53c78c7274310 100644 (file)
  +--------------------------------------------------------------------+
  */
 
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
 namespace Civi\Api4\Action\Setting;
 
 /**
index 11abc64db40ff51a2259fb2ac0bea4e14e8628c2..f6a2f3800356c5879df9a959a7e0e78b25b0d58f 100644 (file)
  +--------------------------------------------------------------------+
  */
 
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
 namespace Civi\Api4\Action\Setting;
 
 use Civi\Api4\Generic\Result;
index bb866e287b636ca03b3b3b3ebc2f4d7b54b7b6aa..b42618db698710f9052aeea79bef01079de4deb4 100644 (file)
@@ -23,22 +23,9 @@ namespace Civi\Api4\Event;
 
 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';
-
 }
diff --git a/Civi/Api4/Event/GetSpecEvent.php b/Civi/Api4/Event/GetSpecEvent.php
deleted file mode 100644 (file)
index 16e1e84..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-<?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;
-  }
-
-}
diff --git a/Civi/Api4/Event/Subscriber/ContactSchemaMapSubscriber.php b/Civi/Api4/Event/Subscriber/ContactSchemaMapSubscriber.php
deleted file mode 100644 (file)
index d41ab9f..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-<?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;
-      }
-    }
-  }
-
-}
diff --git a/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php b/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php
deleted file mode 100644 (file)
index e21c2dd..0000000
+++ /dev/null
@@ -1,335 +0,0 @@
-<?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;
-  }
-
-}
index 74d28bfb33041ef5f1e90596131253369c08c52f..8a1ed4e86286838b887f5b35a54644ea97c34a4d 100644 (file)
@@ -66,10 +66,9 @@ abstract class AbstractEntity {
     $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'];
   }
 
   /**
index 38888227c6accfd44cc809a14d1e71ff364c7583..f63c87ba62199f9ae92248d2faec7956a33169c2 100644 (file)
  +--------------------------------------------------------------------+
  */
 
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
-
 namespace Civi\Api4\Generic;
 
 /**
@@ -28,6 +19,8 @@ abstract class DAOEntity extends AbstractEntity {
 
   /**
    * @return DAOGetAction
+   *
+   * @throws \API_Exception
    */
   public static function get() {
     return new DAOGetAction(static::class, __FUNCTION__);
@@ -49,6 +42,8 @@ abstract class DAOEntity extends AbstractEntity {
 
   /**
    * @return DAOCreateAction
+   *
+   * @throws \API_Exception
    */
   public static function create() {
     return new DAOCreateAction(static::class, __FUNCTION__);
index 73098f5a5cf0f6d809bc8758355b66752a155e1c..d922781ada2e049574a2d065aec703437733213e 100644 (file)
@@ -45,6 +45,13 @@ class DAOGetAction extends AbstractGetAction {
    */
   protected $select = [];
 
+  /**
+   * Joins to other entities.
+   *
+   * @var array
+   */
+  protected $join = [];
+
   /**
    * Field(s) by which to group the results.
    *
@@ -120,4 +127,32 @@ class DAOGetAction extends AbstractGetAction {
     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;
+  }
+
 }
index 45b572a2526e6ce3ff5ba5fe28d4801ab2aecc3d..85ac173f5e6ce96dfdb8fe98a9204958bacac5c2 100644 (file)
  +--------------------------------------------------------------------+
  */
 
-/**
- *
- * @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 {
 
@@ -103,9 +95,11 @@ 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();
@@ -130,7 +124,7 @@ trait DAOActionTrait {
       $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;
       }
 
@@ -138,7 +132,7 @@ trait DAOActionTrait {
         $this->checkContactPermissions($baoName, $item);
       }
 
-      if ($this->getEntityName() == 'Address') {
+      if ($this->getEntityName() === 'Address') {
         $createResult = $baoName::add($item, $this->fixAddress);
       }
       elseif (method_exists($baoName, $method)) {
@@ -162,7 +156,11 @@ trait DAOActionTrait {
   /**
    * @param array $params
    * @param int $entityId
+   *
    * @return mixed
+   *
+   * @throws \API_Exception
+   * @throws \CRM_Core_Exception
    */
   protected function formatCustomParams(&$params, $entityId) {
     $customParams = [];
@@ -204,7 +202,7 @@ trait DAOActionTrait {
           $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());
         }
@@ -232,13 +230,14 @@ trait DAOActionTrait {
   /**
    * 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');
       }
index d7e6da44c8138e75ead7e3248d389dce45ee1be1..80208522b4521c44199900dcd0a0689fee12f72a 100644 (file)
 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;
@@ -42,12 +38,6 @@ class Api4SelectQuery extends SelectQuery {
    */
   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][]
@@ -102,6 +92,9 @@ class Api4SelectQuery extends SelectQuery {
 
     // 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());
   }
 
   /**
@@ -134,24 +127,21 @@ class Api4SelectQuery extends SelectQuery {
       $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() {
@@ -174,7 +164,7 @@ class Api4SelectQuery extends SelectQuery {
       });
       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);
       }
@@ -193,9 +183,6 @@ class Api4SelectQuery extends SelectQuery {
           }
           $valid = FALSE;
         }
-        elseif ($field['is_many']) {
-          $valid = FALSE;
-        }
       }
       if ($valid) {
         $alias = $expr->getAlias();
@@ -236,11 +223,7 @@ class Api4SelectQuery extends SelectQuery {
       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");
     }
   }
 
@@ -259,11 +242,7 @@ class Api4SelectQuery extends SelectQuery {
    */
   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));
     }
   }
 
@@ -272,7 +251,7 @@ class Api4SelectQuery extends SelectQuery {
    *
    * @param array $clause
    * @param string $type
-   *   WHERE|HAVING
+   *   WHERE|HAVING|ON
    * @return string SQL where clause
    *
    * @throws \API_Exception
@@ -311,7 +290,7 @@ class Api4SelectQuery extends SelectQuery {
    * 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
@@ -319,6 +298,9 @@ class Api4SelectQuery extends SelectQuery {
   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') {
@@ -327,7 +309,7 @@ class Api4SelectQuery extends SelectQuery {
       $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;
@@ -357,6 +339,21 @@ class Api4SelectQuery extends SelectQuery {
       }
       $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) {
@@ -365,6 +362,29 @@ class Api4SelectQuery extends SelectQuery {
     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
    */
@@ -389,7 +409,7 @@ class Api4SelectQuery extends SelectQuery {
     $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) {
@@ -400,13 +420,76 @@ class Api4SelectQuery extends SelectQuery {
   }
 
   /**
-   * 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;
     }
@@ -419,23 +502,12 @@ class Api4SelectQuery extends SelectQuery {
     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
@@ -451,22 +523,10 @@ class Api4SelectQuery extends SelectQuery {
       $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
    */
@@ -523,13 +583,6 @@ class Api4SelectQuery extends SelectQuery {
     return $this->selectFields;
   }
 
-  /**
-   * @return bool
-   */
-  public function isFillUniqueFields() {
-    return $this->isFillUniqueFields;
-  }
-
   /**
    * @return \CRM_Utils_SQL_Select
    */
@@ -579,24 +632,6 @@ class Api4SelectQuery extends SelectQuery {
     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
    *
@@ -615,51 +650,6 @@ class Api4SelectQuery extends SelectQuery {
     }
   }
 
-  /**
-   * 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
@@ -671,7 +661,7 @@ class Api4SelectQuery extends SelectQuery {
       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;
   }
index e21f3880f4fb72d601ef070a24d7936b1b391dff..2759d3baff0a72b28302685f7f119815dc81cc79 100644 (file)
@@ -35,7 +35,7 @@ abstract class SqlExpression {
    * The raw expression, minus the alias.
    * @var string
    */
-  protected $expr = '';
+  public $expr = '';
 
   /**
    * SqlFunction constructor.
@@ -148,4 +148,14 @@ abstract class SqlExpression {
     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);
+  }
+
 }
diff --git a/Civi/Api4/Service/Schema/Joinable/OptionValueJoinable.php b/Civi/Api4/Service/Schema/Joinable/OptionValueJoinable.php
deleted file mode 100644 (file)
index 6b9b539..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-<?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;
-  }
-
-}
index 6333e14a779e743b0a0de847b0eae59692e9f527..8442da91129ab5ef9ac24eca13a5340bcd67db15 100644 (file)
@@ -63,7 +63,6 @@ class Joiner {
       $conditions = $link->getConditionsForJoin($baseTable);
 
       $query->join($side, $target, $alias, $conditions);
-      $query->addJoinedTable($link);
 
       $baseTable = $link->getAlias();
     }
@@ -72,20 +71,33 @@ class Joiner {
   }
 
   /**
-   * @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) {
index 74dc0f34536906561efd7fc71539554f9c8bda1c..684784f54fc3a97802897a523148ac7ebdf97e56 100644 (file)
@@ -27,7 +27,6 @@ use Civi\Api4\Event\SchemaMapBuildEvent;
 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 {
@@ -99,41 +98,6 @@ 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);
-    }
   }
 
   /**
@@ -144,11 +108,6 @@ class SchemaMapBuilder {
   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));
@@ -213,11 +172,6 @@ class SchemaMapBuilder {
         $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;
index 061595f6cfa07fa1973aa0c15df27326f1932d8c..b417dbad7297eb3df15de1fbb3a4cfe7741baee6 100644 (file)
  +--------------------------------------------------------------------+
  */
 
-/**
- *
- * @package CRM
- * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
- *
- */
-
-
 namespace Civi\Api4\Service\Spec\Provider;
 
 use Civi\Api4\Service\Spec\RequestSpec;
index c6d884f1bec9c16aa230d61278ab312fce8724a0..1da26d1520a984492b829f9ad8da81a70e3a1ec2 100644 (file)
@@ -67,9 +67,6 @@ class SpecFormatter {
       $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;
@@ -77,9 +74,8 @@ class SpecFormatter {
       $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);
@@ -145,10 +141,6 @@ class SpecFormatter {
     $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',
@@ -156,6 +148,9 @@ class SpecFormatter {
       'Link' => 'Url',
     ];
     $inputType = $map[$inputType] ?? $inputType;
+    if ($inputType == 'Select' && !empty($data['serialize'])) {
+      $inputAttrs['multiple'] = TRUE;
+    }
     if ($inputType == 'Date' && !empty($inputAttrs['formatType'])) {
       self::setLegacyDateFormat($inputAttrs);
     }
index d9909c477d2a784a51312a5de07c12733f1cc719..798779a8798978ce138a8a601a6fbce079f75e81 100644 (file)
@@ -14,7 +14,6 @@
  *
  * @package CRM
  * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
  *
  */
 
@@ -32,13 +31,6 @@ class SpecGatherer {
    */
   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.
index be91ce41e1991640d2de9e081895456ce9b099f2..b3cd5ecd0528b4d203dee505aae2fd2de4a42ab5 100644 (file)
@@ -88,6 +88,7 @@ class FormattingUtil {
     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) {
index 82e2141461b1e15e8ab74ce669683b9eac168a0a..155c32c77970df4d7c2462c046c23770009e2010 100644 (file)
@@ -20,7 +20,7 @@ use Civi\Core\Exception\UnknownAssetException;
  * 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');
  *
@@ -37,7 +37,7 @@ use Civi\Core\Exception\UnknownAssetException;
  *   $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
@@ -45,7 +45,7 @@ use Civi\Core\Exception\UnknownAssetException;
  * 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'),
@@ -63,7 +63,7 @@ use Civi\Core\Exception\UnknownAssetException;
  *   $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
index 77d91684e25b2b25426a10e243c8153bd673118b..34514f9bf750dffa23203502a5133a5a22e6d0ed 100644 (file)
@@ -7,10 +7,10 @@ namespace Civi\Core;
  * 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'
index 6c62ea498afdf49140190647915545626d45f4d8..1b29ff76e9a9db9e28ef8d1cf32a3e856d407f6f 100644 (file)
@@ -26,7 +26,7 @@ namespace Civi\Core\Event;
  * 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',
@@ -41,7 +41,7 @@ namespace Civi\Core\Event;
  *
  * // Dispatching an event.
  * Civi::dispatcher()->dispatch('hook_civicrm_foo', $event);
- * @endCode
+ * ```
  *
  * Design Discussion:
  *
@@ -56,10 +56,10 @@ namespace Civi\Core\Event;
  * 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 {
 
index 379f94d64c3766f609c800f2a8187d72307b045a..a1ebfe2b0fdde0156742fc8276184a5e36ac24db 100644 (file)
@@ -222,11 +222,11 @@ class ResolverApi {
   /**
    * 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.
index 63b6cb05522ea3965ece02e28a9f417b0527f8be..0381768218aca43d2adb5ac52b82ad088bf7930b 100644 (file)
@@ -291,7 +291,7 @@ class Requirements {
    * @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,
index 50d6ebf31c4a39ee1d97fa1071b29516757eec2d..1d9db12079521969e442e86d704ae96fc8fd90ff 100644 (file)
@@ -100,12 +100,12 @@ class Test {
   /**
    * 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__);
@@ -130,12 +130,12 @@ class Test {
   /**
    * 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');
index 6d5421053dcb75835c34c503a2007fad3a06a64f..fbe73cd0fe909038df590eceae14fdc313bdae40 100644 (file)
@@ -302,7 +302,7 @@ trait Api3TestTrait {
    * @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']);
@@ -641,9 +641,9 @@ trait Api3TestTrait {
 
     // 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
@@ -677,19 +677,4 @@ trait Api3TestTrait {
     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;
-  }
-
 }
index 53d5c8cd11226fbeb572ac879dd5a1e69bd872a3..4885cd06772525e0610ac326716ea60dd2c169c4 100644 (file)
@@ -9,13 +9,13 @@ namespace Civi\Test;
  * 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:
index ee867b949e895594206032a8223b045ddb7a992b..730ddb616c87c662f83d0be2d0b10eb15ec18264 100644 (file)
@@ -8,7 +8,7 @@ namespace Civi\Token\Event;
  * 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)');
@@ -17,7 +17,7 @@ namespace Civi\Token\Event;
  *   'field' => 'viewUrl',
  *   'label' => ts('Default Profile URL (View Mode)'),
  * ));
- * @endcode
+ * ```
  *
  * Event name: 'civi.token.list'
  */
index c9a251ccc752ae05c24770372f7c5a1e4ce6470e..ed7a3e2504bb55e0ea096b1f517b4d90ed8db481 100644 (file)
@@ -8,7 +8,7 @@ namespace Civi\Token\Event;
  * 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),
index 0d1ff138db5038887eabfea041e2678d365e6df0..e8ee1b8f73f5d65f8d331284ac67eff50b168512 100644 (file)
@@ -58,7 +58,7 @@ class TokenProcessor {
    *   - 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;
 
index cb38ead05b985a388b8458285c5deb2de0979d87..536b6ab237245674db3f83319a4b2ebd482e7e1d 100644 (file)
@@ -11,25 +11,25 @@ namespace Civi\Token;
  * (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]);
@@ -43,7 +43,7 @@ namespace Civi\Token;
  *
  * # 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
index abff9b0162f619fd98f292b6de49d6b85da113ed..173604ef8383b4ab71ef48bf24295a2473c9d0f9 100644 (file)
               <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%;"/>
index 97c6152e2355e3b0e5035d8e7fbe677853b35966..63cb5dedd5bf9ef682c307189e69c81dedddfbc0 100644 (file)
@@ -52,6 +52,7 @@
     $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]);
index 29fe00ed63085f3011be4569289f926b700c104e..61104cd8561f966c0856b8a1291da2bdfa0088f8 100644 (file)
@@ -59,5 +59,5 @@ Required vars: mailingABList
 
 <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>
index 8208af08db30d36f42b8a9e653f983f18bd69e62..be1946db3735796f65dd091d9d3aece9908d98e6 100644 (file)
@@ -281,33 +281,25 @@ function _civicrm_api_replace_variable($value, $parentResult, $separator) {
  *
  * @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));
 }
index 3e93b97a586b61c37b8e4254b5c9270c70fea061..4ba61dd30bde6c5d50e8e25d839a895f07d596fb 100644 (file)
@@ -4,36 +4,36 @@
  *
  * 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 {
 
index 1e78d6796bbae0faf4e7d805d300ea2cc5d30454..8583f0b8c5acab4aa7679a2d886b12eabf743e8c 100644 (file)
@@ -15,7 +15,7 @@
  * 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',
@@ -26,9 +26,9 @@
  * ));
  * $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',
@@ -39,9 +39,9 @@
  * ));
  * $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',
@@ -54,7 +54,7 @@
  * ));
  * $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'.
index 1f5b9b6ae46a9bf2cfe290692499795b25332b6c..5249010c45b372c98e6b31117e6af0cadf9cff09 100644 (file)
@@ -21,7 +21,7 @@
  *
  * @param array $params
  *
- * @code
+ * ```
  * // REQUIRED for create:
  * 'case_type_id' => int OR
  * 'case_type' => str (provide one or the other)
@@ -38,7 +38,7 @@
  * 'start_date' => str datestamp // defaults to: date('YmdHis')
  * 'duration' => int // in minutes
  * 'details' => str // html format
- * @endcode
+ * ```
  *
  * @throws API_Exception
  * @return array
@@ -556,13 +556,13 @@ function civicrm_api3_case_update($params) {
  *
  * @param array $params
  *
- * @code
+ * ```
  *   //REQUIRED:
  *   'id' => int
  *
  *   //OPTIONAL
  *   'move_to_trash' => bool (defaults to false)
- * @endcode
+ * ```
  *
  * @throws API_Exception
  * @return mixed
index 020ebe2bbfdae5b7437962b19e90b49645c12bf8..09907334c399cd7b5b7ed37a68dd65bf2d856a1d 100644 (file)
  */
 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;
@@ -116,7 +122,26 @@ function civicrm_api3_custom_field_delete($params) {
  * @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;
 }
 
 /**
index 0711224f0021e52fa9fa0d979a068caf86a01472..34f96a9f2daa350e2c3115255a74d9a0b3ccaf27 100644 (file)
@@ -22,7 +22,7 @@
  *   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
@@ -39,7 +39,7 @@
  *   '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
index 250438378f774e038337164192bc3a8e3c9cc9ae..ef1121ccb9f96766a9d49bd2e13c00a62767e079 100644 (file)
@@ -72,7 +72,7 @@ function _civicrm_api3_group_contact_create_spec(&$params) {
  *
  * This api has a legacy/nonstandard signature.
  * On success, the return array will be structured as follows:
- * @code
+ * ```
  * array(
  *   "is_error" => 0,
  *   "version"  => 3,
@@ -83,16 +83,16 @@ function _civicrm_api3_group_contact_create_spec(&$params) {
  *     "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:
index 185b3cd374ad063926d3d56efa66f56d2e1ad448..011a19f149ef88d2570850dbc7f3166d4ec59ce7 100644 (file)
@@ -134,8 +134,8 @@ function _civicrm_api3_mailing_create_spec(&$params) {
 
   $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', '');
index a8c96ab80a9abb7b09c4aa5f4a8dc3f05de63721..8ae6d7f89b5ab115a8c37ecd0df50a75d6d18203 100644 (file)
@@ -23,6 +23,7 @@ function _civicrm_api3_mailing_a_b_create_spec(&$spec) {
   $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();
 }
 
 /**
index 3101e29480174cb7c6b0bc9222d6f418b61c0c63..40f73f2871683e8eddf4f252af1b8ae74a1663e8 100644 (file)
@@ -1237,7 +1237,7 @@ function formatCheckBoxField(&$checkboxFieldValue, $customFieldLabel, $entity) {
  * @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));
@@ -2367,7 +2367,7 @@ function _civicrm_api3_api_resolve_alias($entity, $fieldName, $action = 'create'
   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', [
index 20a90b7d03188179bf6808ca380fca7af605b330..2befe291b2143522b29329aaf6b01bc819903cf1 100644 (file)
@@ -2226,73 +2226,110 @@ div.crm-master-accordion-header a.helpicon {
 
 /* 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;
@@ -2300,12 +2337,8 @@ div.crm-master-accordion-header a.helpicon {
   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 {
@@ -2313,19 +2346,10 @@ div.crm-master-accordion-header a.helpicon {
   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 {
@@ -3492,21 +3516,6 @@ span.crm-select-item-color {
   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;
index cf2f34e815617599fc20824c30841b054f68e858..7f783555fd5344f244c1f67dd602bd1cd116b641 100644 (file)
   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. */
diff --git a/i/TreeMinus.gif b/i/TreeMinus.gif
deleted file mode 100644 (file)
index 964739a..0000000
Binary files a/i/TreeMinus.gif and /dev/null differ
diff --git a/i/TreeMinusWhite.gif b/i/TreeMinusWhite.gif
deleted file mode 100644 (file)
index f5ae57e..0000000
Binary files a/i/TreeMinusWhite.gif and /dev/null differ
diff --git a/i/TreePlus.gif b/i/TreePlus.gif
deleted file mode 100644 (file)
index a07d4c5..0000000
Binary files a/i/TreePlus.gif and /dev/null differ
diff --git a/i/TreePlusWhite.gif b/i/TreePlusWhite.gif
deleted file mode 100644 (file)
index 69c21a3..0000000
Binary files a/i/TreePlusWhite.gif and /dev/null differ
index 49cf011ca46df80694b52b8f24e9ee37c530ddc7..c0e66c03b07e85394911c1962c9bf63f847f5698 100644 (file)
@@ -480,11 +480,11 @@ class InstallRequirements {
         )
       )
       ) {
-        @$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))),
           )
         );
index f4c2a60a6f09eeb0ed0b4eae89e90867f5001d19..0ed3e38c67b131ba9db029c452cbda00595042cc 100644 (file)
@@ -387,7 +387,7 @@ if (!CRM.vars) CRM.vars = {};
       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> ';
@@ -395,6 +395,35 @@ if (!CRM.vars) CRM.vars = {};
     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
@@ -434,7 +463,7 @@ if (!CRM.vars) CRM.vars = {};
             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;
         };
@@ -704,7 +733,7 @@ if (!CRM.vars) CRM.vars = {};
     }
     _.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>';
diff --git a/js/crm.drupal7.js b/js/crm.drupal7.js
new file mode 100644 (file)
index 0000000..8f172f2
--- /dev/null
@@ -0,0 +1,18 @@
+// 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.$);
index eba37ed3c46d6c90b6281b3be088900ce874018a..f2eed0531628fa8b6a04a2169fc220c3c9fa2a4d 100644 (file)
@@ -8,6 +8,7 @@
     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
index a2a5bc06c8e2e4328c0f13fd2d75afed16e172a5..2e2640956a7fc7bb5fc93b1ccb939007c0640a68 100644 (file)
@@ -341,4 +341,32 @@ return [
     '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,
+  ],
 ];
index bde961cb5a93597df711a38cc0e3be48eb522cc4..789a85bd570be41cb68fc7d137be15f45de09090 100644 (file)
@@ -93,7 +93,7 @@
     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( ) ) ;
 
index e14be7e538ff6a2caf5c3b88bb1bf44e0c2a3d92..35414668910eb632810b12329866e5d553edae60 100644 (file)
@@ -198,6 +198,7 @@ function loadPetitionList( )
 
          //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;
     },
index 1177d3a5ceccec75402be6bc9da2fb466e4dba5f..09a53a4ecea0fb293d4327f6d1ba3b51a0dff730 100644 (file)
@@ -210,6 +210,7 @@ function loadSurveyList( )
 
          //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;
     },
index f723097862d8a4e628e293020716895a551d01ba..9c927d7b4cc295242060c07da56debdf731fc616 100644 (file)
@@ -51,7 +51,7 @@
           <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>
@@ -65,7 +65,7 @@
         {/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">
@@ -311,7 +311,7 @@ var surveyActivityIds = {/literal}{$surveyActivityIds}{literal};
         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);
       }
     }
index b489a8037aad828c64601213d3022547b08a801d..32ee2e940932e4c554a6e6d9fa3ae07494c8849e 100644 (file)
       {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}
 
index 0bc1ad634b2079ef8b4a20aae2639796e27051fa..8d9b69525263d0d31157b7c6e3de8ba78470e1de 100644 (file)
@@ -73,7 +73,7 @@
             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);
           }
         }
index c9d46d9b467b276cdf356cb067d3e71500cb9b15..30392c8d53c46ecf68f002d655a3ba9f18d33cdb 100644 (file)
@@ -7,7 +7,6 @@
  | 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>
index 42a7bc69040474a8f43ee5ec19388413e765adf1..2ad2a0bea4aa83629253b7ed7d0e1511657f24e5 100644 (file)
 {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>
index 36cf635081419cd708b6871ea8f1cad81d917d9c..1f34763c9f8de05fe5bd851a2d2ab1eaf6cc7b2a 100644 (file)
@@ -81,7 +81,7 @@
             <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}
index 94cc45f2e7bc5b891b4b9eed71fa00def01b1d43..fc0bb61c5a00f0836772b29497194b6fd77c191c 100644 (file)
@@ -24,7 +24,7 @@
       <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
index afc29d4bebae6db774b168c4477131d5715d5eed..bb5323097dd9c87a8d2cc43a52f951bc2f4aa027 100644 (file)
@@ -29,7 +29,7 @@
     <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}
index 3ab90ab126e663ed999943284514b8aa34c106ed..8388f4aee3930ebaab3b438136c0c917d620e9b3 100644 (file)
@@ -19,8 +19,8 @@
       <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>
@@ -29,8 +29,8 @@
       {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">
index 040dc348cbb97927f1e32e5dcb92d2e9007d95f9..83a8dd57e338510ba22a6483c1dd995becb9dcfd 100644 (file)
         {/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);
index f47cafc27e4ea8fe7e0244f57424b14936864883..41b49ac784904ad6e6fca788dcbcc08ee15e53bc 100644 (file)
@@ -54,8 +54,8 @@
               "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'}
@@ -87,7 +87,7 @@
                 $(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() {
index 2e93012d0192d505a6eb86d87022a71062258c5d..e397febb8cc1fdd9c6637c0af207eee75225ccff 100644 (file)
   {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}
 
index 72c3fa34083fe99f1a285f46ea75817c22c7d9af..449d7a33d5b80a81e5210f44756a6cfd49c5a710 100644 (file)
@@ -25,7 +25,7 @@
 </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 />
               &nbsp;{$form.html_comment.html}<br /></td>
index bd99f32de27bec943673f9a4561054e6261f0b23..abe88c92a03d802c4c226216ccee6d294d3841cc 100644 (file)
 
           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 + '">'
index b4dd598370968a32396cadb83f2221ede8b0cbb1..14b96aa83a38c2591a8f4b99dc2a0e5e0a1ce659 100644 (file)
@@ -80,9 +80,9 @@
                 <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}
index 3e44fef13e1ac132ae3a1cdeb8c1dd25bd1340e6..7b00728147f94fb5c137b760080d7c203753e976 100644 (file)
@@ -42,7 +42,7 @@
                       (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() {
index 413d66e14e446c6934035d0af99246e8fc265315..f881c58d2ae3d4f993fcea4042acc9bad02f5c31 100644 (file)
@@ -24,7 +24,7 @@
         <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>
@@ -35,7 +35,7 @@
       {/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});
diff --git a/tests/phpunit/CRM/Activity/Page/AJAXTest.php b/tests/phpunit/CRM/Activity/Page/AJAXTest.php
new file mode 100644 (file)
index 0000000..f9e9f75
--- /dev/null
@@ -0,0 +1,61 @@
+<?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']);
+  }
+
+}
index afa56c8748c77342e0b140cb6629a08a2f28e498..e9dfc1b0420917d47fbfe33927a52ad3eb276d07 100644 (file)
@@ -548,7 +548,7 @@ class CRM_Contact_Import_Parser_ContactTest extends CiviUnitTestCase {
       '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',
     ];
index a92ae46126d5349027e76d9acdc4f3515454fb63..8b55cfe182e8f7f58975861e6ccf834e6fe61583 100644 (file)
@@ -467,6 +467,7 @@ class CRM_Core_BAO_CustomFieldTest extends CiviUnitTestCase {
         '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',
@@ -484,7 +485,7 @@ class CRM_Core_BAO_CustomFieldTest extends CiviUnitTestCase {
         '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',
@@ -505,6 +506,7 @@ class CRM_Core_BAO_CustomFieldTest extends CiviUnitTestCase {
         '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',
@@ -543,6 +545,7 @@ class CRM_Core_BAO_CustomFieldTest extends CiviUnitTestCase {
         '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'),
@@ -576,6 +579,7 @@ class CRM_Core_BAO_CustomFieldTest extends CiviUnitTestCase {
         'extends_table' => 'civicrm_contact',
         'search_table' => 'contact_a',
         'maxlength' => 300,
+        'serialize' => NULL,
       ],
       $this->getCustomFieldName('select_string') => [
         'name' => $this->getCustomFieldName('select_string'),
@@ -608,6 +612,7 @@ class CRM_Core_BAO_CustomFieldTest extends CiviUnitTestCase {
         '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']),
@@ -644,6 +649,7 @@ class CRM_Core_BAO_CustomFieldTest extends CiviUnitTestCase {
         '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'),
@@ -676,6 +682,7 @@ class CRM_Core_BAO_CustomFieldTest extends CiviUnitTestCase {
         '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'),
@@ -708,6 +715,7 @@ class CRM_Core_BAO_CustomFieldTest extends CiviUnitTestCase {
         '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'),
@@ -740,6 +748,7 @@ class CRM_Core_BAO_CustomFieldTest extends CiviUnitTestCase {
         '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'),
@@ -765,6 +774,7 @@ class CRM_Core_BAO_CustomFieldTest extends CiviUnitTestCase {
         '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',
@@ -801,6 +811,7 @@ class CRM_Core_BAO_CustomFieldTest extends CiviUnitTestCase {
         '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',
@@ -810,7 +821,7 @@ class CRM_Core_BAO_CustomFieldTest extends CiviUnitTestCase {
         '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',
@@ -846,6 +857,7 @@ class CRM_Core_BAO_CustomFieldTest extends CiviUnitTestCase {
         'text_length' => NULL,
         'options_per_line' => NULL,
         'is_search_range' => '0',
+        'serialize' => NULL,
         'pseudoconstant' => [
           'callback' => 'CRM_Core_SelectValues::boolean',
         ],
index b803394c2e1fcc8333933f8e0ff996dcfb72222f..acca0de169bff091a68c9a19c562c91baf1025b8 100644 (file)
@@ -85,6 +85,7 @@ class CRM_Core_BAO_CustomQueryTest extends CiviUnitTestCase {
       'type' => 4,
       'where' => 'civicrm_value_testsearchcus_' . $ids['custom_group_id'] . '.date_field_' . $dateCustomField['id'],
       'import' => 1,
+      'serialize' => NULL,
     ], $queryObj->getFieldSpec('custom_' . $dateCustomField['id']));
 
   }
@@ -153,9 +154,9 @@ class CRM_Core_BAO_CustomQueryTest extends CiviUnitTestCase {
       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']);
index e6998025be23f0d36f213d246e8aa029408c2c5f..1bf6542772ee61527e38c3432bb99e143244882e 100644 (file)
@@ -213,4 +213,9 @@ class CRM_Core_DAO_AllCoreTablesTest extends CiviUnitTestCase {
     $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'));
+  }
+
 }
diff --git a/tests/phpunit/CRM/Core/PageTest.php b/tests/phpunit/CRM/Core/PageTest.php
new file mode 100644 (file)
index 0000000..1566334
--- /dev/null
@@ -0,0 +1,73 @@
+<?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 &quot;choo-choo&quot; 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]");
+    }
+  }
+
+}
index ecfafeb53c21604c8fbeafebc5c8cdfd794dee01..63269ab3185b6868b1e2dcb4b4b345abceca470b 100644 (file)
@@ -6,7 +6,10 @@
  */
 class CRM_Logging_SchemaTest extends CiviUnitTestCase {
 
+  protected $databaseVersion;
+
   public function setUp() {
+    $this->databaseVersion = CRM_Utils_SQL::getDatabaseVersion();
     parent::setUp();
   }
 
@@ -18,6 +21,7 @@ class CRM_Logging_SchemaTest extends CiviUnitTestCase {
   public function tearDown() {
     $schema = new CRM_Logging_Schema();
     $schema->disableLogging();
+    $this->databaseVersion = NULL;
     parent::tearDown();
     $this->quickCleanup(['civicrm_contact'], TRUE);
     $schema->dropAllLogTables();
@@ -235,13 +239,17 @@ class CRM_Logging_SchemaTest extends CiviUnitTestCase {
     $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']);
@@ -282,7 +290,9 @@ class CRM_Logging_SchemaTest extends CiviUnitTestCase {
     $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
@@ -292,7 +302,9 @@ class CRM_Logging_SchemaTest extends CiviUnitTestCase {
     $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']);
   }
 
@@ -312,4 +324,13 @@ class CRM_Logging_SchemaTest extends CiviUnitTestCase {
     $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);
+  }
+
 }
index 60b3108e02a162b0c758ca30db51febe83905b72..0127615f6ebb216b135cb4aefbe450e9d81c446f 100644 (file)
@@ -36,42 +36,51 @@ class CRM_Report_FormTest extends CiviUnitTestCase {
     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()'));
   }
 
 }
index ba2403541868ecdf6f5397afce7d21fd8664a4fe..630e897edf22363279e5f92b8932dfeadc4757cb 100644 (file)
@@ -36,39 +36,52 @@ class CRM_Utils_DateTest extends CiviUnitTestCase {
     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");
+    }
   }
 
   /**
index af0f28dd4af099da91d801975ec85013c664b76e..83fff3e4185cae226db4a55ae446b90a74260078 100644 (file)
@@ -495,14 +495,14 @@ class CiviUnitTestCase extends PHPUnit\Framework\TestCase {
    * 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
index 927e11bb1657995e5cafdefe2211057a5200b3fd..52a15df535d4e61d55834ac08e86cc20d25cd2a7 100644 (file)
@@ -874,14 +874,28 @@ class api_v3_PaymentTest extends CiviUnitTestCase {
       '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']);
   }
 
   /**
index babd1cec0fabeb1dda14764ab839aef72eefa605..602942f93dc4762dab2f33ea123717f60e1834ea 100644 (file)
@@ -24,6 +24,8 @@ namespace api\v4\Action;
 use api\v4\UnitTestCase;
 use Civi\Api4\Activity;
 use Civi\Api4\Contact;
+use Civi\Api4\Email;
+use Civi\Api4\Phone;
 
 /**
  * @group headless
@@ -55,40 +57,87 @@ class FkJoinTest extends UnitTestCase {
     $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']);
   }
 
 }
diff --git a/tests/phpunit/api/v4/Query/Api4SelectQueryComplexJoinTest.php b/tests/phpunit/api/v4/Query/Api4SelectQueryComplexJoinTest.php
deleted file mode 100644 (file)
index e51ed6b..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-<?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']);
-  }
-
-}
index 3a11b5e8b0a51ff7f6b3fd93edac11cdd8d372f5..a628b89106fa8d52cc1c51bd4c0f091c4f221149 100644 (file)
@@ -48,35 +48,6 @@ class Api4SelectQueryTest extends UnitTestCase {
     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');
@@ -95,20 +66,6 @@ class Api4SelectQueryTest extends UnitTestCase {
     $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);
index 138034c77b69bfdd6975c27ba2e74ea2396e2a90..416289db2c1ec76949bfb2317ff3f4e203245dbb 100644 (file)
@@ -14,7 +14,6 @@
  *
  * @package CRM
  * @copyright CiviCRM LLC https://civicrm.org/licensing
- * $Id$
  *
  */
 
@@ -53,8 +52,7 @@ class OneToOneJoinTest extends UnitTestCase {
       ->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');
index 4f0f22416d0bc986042ea7bf1b2e21f7d5602315..d8af222c684f324f6667e47c7716fa1bba441ec9 100644 (file)
@@ -21,7 +21,6 @@
 
 namespace api\v4\Query;
 
-use Civi\Api4\Contact;
 use Civi\Api4\Email;
 use api\v4\UnitTestCase;
 
@@ -38,40 +37,11 @@ class SelectQueryMultiJoinTest extends 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');
index 4f3c34a3792671ca50e544352ac7d912175fe797..791062641ab6117f69acb4aa4cd313ff49eba6a2 100644 (file)
@@ -67,8 +67,9 @@ class SpecFormatterTest extends UnitTestCase {
       '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 */
index 3ac72066cab1c11be657a7fc700b30b7909585e7..d5295ff4557c05bb75489b2a7e6707500094b456 100644 (file)
@@ -41,7 +41,7 @@
     <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>
index 0db65072278a4ceee2e3ebe86aba76c2326fa530..0fc76b8dc1f38994fc23cc1948ebabfbb84346bd 100644 (file)
     <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>
index 8bacee1aa718fe0a41965e4247d0d8827f39b590..14d224f98c98ffbe535e770f10ea00a1348135ad 100644 (file)
@@ -62,6 +62,7 @@
     <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>
index f2a7496abebe05e8444f951e908183b4f93ce91e..a51b0e0be809f3ec5be0cca43e120d312d77d1c7 100644 (file)
@@ -119,6 +119,7 @@ INSERT INTO civicrm_state_province (id, country_id, abbreviation, name) VALUES
 (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"),